Compare commits
10 commits
6aa0db6d0e
...
f4be6e135a
Author | SHA1 | Date | |
---|---|---|---|
Peter Cai | f4be6e135a | ||
Peter Cai | 950f521002 | ||
Peter Cai | 20ad51f214 | ||
Peter Cai | 7de5f11bcc | ||
fec344dc45 | |||
efe707fd16 | |||
22d44480c9 | |||
369db32569 | |||
a04c5b062e | |||
a99045a034 |
|
@ -7,3 +7,8 @@ misskey:
|
||||||
secret: ""
|
secret: ""
|
||||||
hydra:
|
hydra:
|
||||||
admin_url: "http://localhost:4445"
|
admin_url: "http://localhost:4445"
|
||||||
|
time:
|
||||||
|
request_valid: 3600
|
||||||
|
login_remember: 600
|
||||||
|
consent_remember: 0
|
||||||
|
userinfo_cache: 3600
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,3 +4,6 @@
|
||||||
|
|
||||||
db/
|
db/
|
||||||
redis/
|
redis/
|
||||||
|
|
||||||
|
config.yml
|
||||||
|
misso
|
||||||
|
|
|
@ -3,6 +3,6 @@ package consts
|
||||||
const (
|
const (
|
||||||
REDIS_KEY_LOGIN_SESSION = "misso:login:%s" // Username, token as value
|
REDIS_KEY_LOGIN_SESSION = "misso:login:%s" // Username, token as value
|
||||||
REDIS_KEY_CONSENT_CSRF = "misso:consent:%s" // Random string, consent challenge as value
|
REDIS_KEY_CONSENT_CSRF = "misso:consent:%s" // Random string, consent challenge as value
|
||||||
REDIS_KEY_SHARE_ACCESS_TOKEN = "misso:share:at:%s" // Subject, access token as value
|
REDIS_KEY_USER_ACCESS_TOKEN = "misso:user:token:%s" // Subject, access token as value
|
||||||
REDIS_KEY_SHARE_USER_INFO = "misso:share:ui:%s" // Subject, user info as value
|
REDIS_KEY_USER_INFO = "misso:user:info:%s" // Subject, user info as value
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package consts
|
package consts
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TIME_REQUEST_VALID = 1 * time.Hour
|
TIME_DEFAULT_REQUEST_VALID = 3600 // 1 Hour
|
||||||
|
|
||||||
TIME_LOGIN_REMEMBER = 10 * time.Minute
|
TIME_DEFAULT_LOGIN_REMEMBER = 600 // 10 Minute
|
||||||
TIME_CONSENT_REMEMBER = 0 // Forever
|
TIME_DEFAULT_CONSENT_REMEMBER = 0 // Forever
|
||||||
|
|
||||||
TIME_USERINFO_CACHE = 10 * time.Minute
|
TIME_DEFAULT_USERINFO_CACHE = 3600 // 1 Hour
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,10 +5,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
client "github.com/ory/hydra-client-go/v2"
|
client "github.com/ory/hydra-client-go/v2"
|
||||||
|
"misso/config"
|
||||||
"misso/consts"
|
"misso/consts"
|
||||||
"misso/global"
|
"misso/global"
|
||||||
"misso/utils"
|
"misso/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConsentCheck(ctx *gin.Context) {
|
func ConsentCheck(ctx *gin.Context) {
|
||||||
|
@ -56,8 +58,8 @@ func ConsentCheck(ctx *gin.Context) {
|
||||||
// Generate CSRF token
|
// Generate CSRF token
|
||||||
global.Logger.Debugf("Generating CSRF token...")
|
global.Logger.Debugf("Generating CSRF token...")
|
||||||
csrf := utils.RandString(32)
|
csrf := utils.RandString(32)
|
||||||
sessKey := fmt.Sprintf(consts.REDIS_KEY_CONSENT_CSRF, csrf)
|
sessKey := fmt.Sprintf(consts.REDIS_KEY_CONSENT_CSRF, oauth2challenge)
|
||||||
err := global.Redis.Set(context.Background(), sessKey, oauth2challenge, consts.TIME_REQUEST_VALID).Err()
|
err := global.Redis.Set(context.Background(), sessKey, csrf, time.Duration(config.Config.Time.RequestValid)*time.Second).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to save csrf into redis with error: %v", err)
|
global.Logger.Errorf("Failed to save csrf into redis with error: %v", err)
|
||||||
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
||||||
|
@ -69,7 +71,7 @@ func ConsentCheck(ctx *gin.Context) {
|
||||||
// Retrieve context
|
// Retrieve context
|
||||||
global.Logger.Debugf("Retrieving context...")
|
global.Logger.Debugf("Retrieving context...")
|
||||||
|
|
||||||
userinfoCtx, err := utils.GetUserinfo(*consentReq.Subject)
|
userinfo, err := utils.GetUserinfo(*consentReq.Subject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to retrieve userinfo with error: %v", err)
|
global.Logger.Errorf("Failed to retrieve userinfo with error: %v", err)
|
||||||
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
||||||
|
@ -81,9 +83,10 @@ func ConsentCheck(ctx *gin.Context) {
|
||||||
// Show the consent UI
|
// Show the consent UI
|
||||||
global.Logger.Debugf("Rendering consent UI...")
|
global.Logger.Debugf("Rendering consent UI...")
|
||||||
templateFields := gin.H{
|
templateFields := gin.H{
|
||||||
"user": *userinfoCtx,
|
"user": *userinfo,
|
||||||
"challenge": oauth2challenge,
|
"challenge": oauth2challenge,
|
||||||
"csrf": csrf,
|
"csrf": csrf,
|
||||||
|
"scopes": consentReq.RequestedScope,
|
||||||
}
|
}
|
||||||
|
|
||||||
if consentReq.Client.LogoUri != nil && *consentReq.Client.LogoUri != "" {
|
if consentReq.Client.LogoUri != nil && *consentReq.Client.LogoUri != "" {
|
||||||
|
@ -94,6 +97,12 @@ func ConsentCheck(ctx *gin.Context) {
|
||||||
} else {
|
} else {
|
||||||
templateFields["clientName"] = *consentReq.Client.ClientId
|
templateFields["clientName"] = *consentReq.Client.ClientId
|
||||||
}
|
}
|
||||||
|
if consentReq.Client.PolicyUri != nil && *consentReq.Client.PolicyUri != "" {
|
||||||
|
templateFields["clientPolicy"] = *consentReq.Client.PolicyUri
|
||||||
|
}
|
||||||
|
if consentReq.Client.TosUri != nil && *consentReq.Client.TosUri != "" {
|
||||||
|
templateFields["clientTos"] = *consentReq.Client.TosUri
|
||||||
|
}
|
||||||
ctx.HTML(http.StatusOK, "consent.tmpl", templateFields)
|
ctx.HTML(http.StatusOK, "consent.tmpl", templateFields)
|
||||||
|
|
||||||
global.Logger.Debugf("User should now see Consent UI.")
|
global.Logger.Debugf("User should now see Consent UI.")
|
||||||
|
|
|
@ -5,14 +5,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
client "github.com/ory/hydra-client-go/v2"
|
client "github.com/ory/hydra-client-go/v2"
|
||||||
|
"misso/config"
|
||||||
"misso/consts"
|
"misso/consts"
|
||||||
"misso/global"
|
"misso/global"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConsentConfirmRequest struct {
|
type ConsentConfirmRequest struct {
|
||||||
CSRF string `form:"_csrf"`
|
CSRF string `form:"_csrf"`
|
||||||
|
Challenge string `form:"challenge"`
|
||||||
Remember bool `form:"remember"`
|
Remember bool `form:"remember"`
|
||||||
Action string `form:"action"`
|
Action string `form:"action"`
|
||||||
}
|
}
|
||||||
|
@ -32,16 +33,23 @@ func ConsentConfirm(ctx *gin.Context) {
|
||||||
|
|
||||||
// Validate CSRF
|
// Validate CSRF
|
||||||
global.Logger.Debugf("Validating CSRF...")
|
global.Logger.Debugf("Validating CSRF...")
|
||||||
sessKey := fmt.Sprintf(consts.REDIS_KEY_CONSENT_CSRF, req.CSRF)
|
sessKey := fmt.Sprintf(consts.REDIS_KEY_CONSENT_CSRF, req.Challenge)
|
||||||
oauth2challenge, err := global.Redis.Get(context.Background(), sessKey).Result()
|
csrfSession, err := global.Redis.Get(context.Background(), sessKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to get csrf from redis with error: %v", err)
|
global.Logger.Errorf("Failed to get csrf from redis with error: %v", err)
|
||||||
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
||||||
"error": "Failed to get csrf",
|
"error": "Failed to get csrf",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
} else if csrfSession != req.CSRF {
|
||||||
|
ctx.HTML(http.StatusForbidden, "error.tmpl", gin.H{
|
||||||
|
"error": "CSRF not match",
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oauth2challenge := req.Challenge
|
||||||
|
|
||||||
// Delete used challenge
|
// Delete used challenge
|
||||||
global.Redis.Del(context.Background(), sessKey)
|
global.Redis.Del(context.Background(), sessKey)
|
||||||
|
|
||||||
|
@ -80,9 +88,9 @@ func ConsentConfirm(ctx *gin.Context) {
|
||||||
global.Logger.Debugf("User accepted the request, reporting back to hydra...")
|
global.Logger.Debugf("User accepted the request, reporting back to hydra...")
|
||||||
|
|
||||||
global.Logger.Debugf("Initializing ID Token...")
|
global.Logger.Debugf("Initializing ID Token...")
|
||||||
rememberFor := int64(consts.TIME_CONSENT_REMEMBER / time.Second) // Remember forever
|
rememberFor := config.Config.Time.ConsentRemember // Remember forever
|
||||||
acceptReq, _, err := global.Hydra.Admin.OAuth2Api.AcceptOAuth2ConsentRequest(context.Background()).ConsentChallenge(oauth2challenge).AcceptOAuth2ConsentRequest(client.AcceptOAuth2ConsentRequest{
|
acceptReq, _, err := global.Hydra.Admin.OAuth2Api.AcceptOAuth2ConsentRequest(context.Background()).ConsentChallenge(oauth2challenge).AcceptOAuth2ConsentRequest(client.AcceptOAuth2ConsentRequest{
|
||||||
GrantScope: consentReq.RequestedScope, // TODO: Specify scopes
|
GrantScope: consentReq.RequestedScope,
|
||||||
GrantAccessTokenAudience: consentReq.RequestedAccessTokenAudience,
|
GrantAccessTokenAudience: consentReq.RequestedAccessTokenAudience,
|
||||||
Remember: &req.Remember,
|
Remember: &req.Remember,
|
||||||
RememberFor: &rememberFor,
|
RememberFor: &rememberFor,
|
||||||
|
|
|
@ -5,10 +5,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
client "github.com/ory/hydra-client-go/v2"
|
client "github.com/ory/hydra-client-go/v2"
|
||||||
|
"misso/config"
|
||||||
"misso/consts"
|
"misso/consts"
|
||||||
"misso/global"
|
"misso/global"
|
||||||
"misso/misskey"
|
"misso/misskey"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Login(ctx *gin.Context) {
|
func Login(ctx *gin.Context) {
|
||||||
|
@ -69,7 +71,7 @@ func Login(ctx *gin.Context) {
|
||||||
|
|
||||||
// Save login challenge state into redis (misskey cannot keep state info)
|
// Save login challenge state into redis (misskey cannot keep state info)
|
||||||
sessKey := fmt.Sprintf(consts.REDIS_KEY_LOGIN_SESSION, authSess.Token)
|
sessKey := fmt.Sprintf(consts.REDIS_KEY_LOGIN_SESSION, authSess.Token)
|
||||||
err = global.Redis.Set(context.Background(), sessKey, oauth2challenge, consts.TIME_REQUEST_VALID).Err()
|
err = global.Redis.Set(context.Background(), sessKey, oauth2challenge, time.Duration(config.Config.Time.RequestValid)*time.Second).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to save session into redis with error: %v", err)
|
global.Logger.Errorf("Failed to save session into redis with error: %v", err)
|
||||||
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
||||||
|
|
|
@ -9,9 +9,7 @@ import (
|
||||||
"misso/consts"
|
"misso/consts"
|
||||||
"misso/global"
|
"misso/global"
|
||||||
"misso/misskey"
|
"misso/misskey"
|
||||||
"misso/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func MisskeyAuthCallback(ctx *gin.Context) {
|
func MisskeyAuthCallback(ctx *gin.Context) {
|
||||||
|
@ -62,9 +60,10 @@ func MisskeyAuthCallback(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userid := fmt.Sprintf("%s@%s", usermeta.User.Username, config.Config.Misskey.Instance)
|
// Save user key
|
||||||
|
userIdentifier := fmt.Sprintf("%s@%s", usermeta.User.Username, config.Config.Misskey.Instance)
|
||||||
|
|
||||||
sessAccessTokenKey := fmt.Sprintf(consts.REDIS_KEY_SHARE_ACCESS_TOKEN, userid)
|
sessAccessTokenKey := fmt.Sprintf(consts.REDIS_KEY_USER_ACCESS_TOKEN, userIdentifier)
|
||||||
err = global.Redis.Set(context.Background(), sessAccessTokenKey, usermeta.AccessToken, 0).Err()
|
err = global.Redis.Set(context.Background(), sessAccessTokenKey, usermeta.AccessToken, 0).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to save session access token into redis with error: %v", err)
|
global.Logger.Errorf("Failed to save session access token into redis with error: %v", err)
|
||||||
|
@ -74,24 +73,16 @@ func MisskeyAuthCallback(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save context into redis
|
|
||||||
err = utils.SaveUserinfo(userid, &usermeta.User)
|
|
||||||
if err != nil {
|
|
||||||
global.Logger.Errorf("Failed to save session user info into redis with error: %v", err)
|
|
||||||
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
|
||||||
"error": "Failed to save userinfo",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
global.Logger.Debugf("User accepted the request, reporting back to hydra...")
|
global.Logger.Debugf("User accepted the request, reporting back to hydra...")
|
||||||
|
acceptReq := client.AcceptOAuth2LoginRequest{
|
||||||
|
Subject: userIdentifier,
|
||||||
|
}
|
||||||
|
if config.Config.Time.LoginRemember > 0 {
|
||||||
remember := true
|
remember := true
|
||||||
rememberFor := int64(consts.TIME_LOGIN_REMEMBER / time.Second)
|
acceptReq.Remember = &remember
|
||||||
acceptReq, _, err := global.Hydra.Admin.OAuth2Api.AcceptOAuth2LoginRequest(context.Background()).LoginChallenge(oauth2challenge).AcceptOAuth2LoginRequest(client.AcceptOAuth2LoginRequest{
|
acceptReq.RememberFor = &config.Config.Time.LoginRemember
|
||||||
Subject: userid,
|
}
|
||||||
Remember: &remember,
|
acceptRes, _, err := global.Hydra.Admin.OAuth2Api.AcceptOAuth2LoginRequest(context.Background()).LoginChallenge(oauth2challenge).AcceptOAuth2LoginRequest(acceptReq).Execute()
|
||||||
RememberFor: &rememberFor,
|
|
||||||
}).Execute()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to accept login request with error: %v", err)
|
global.Logger.Errorf("Failed to accept login request with error: %v", err)
|
||||||
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
||||||
|
@ -101,7 +92,7 @@ func MisskeyAuthCallback(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to target uri
|
// Redirect to target uri
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, acceptReq.RedirectTo)
|
ctx.Redirect(http.StatusTemporaryRedirect, acceptRes.RedirectTo)
|
||||||
|
|
||||||
global.Logger.Debugf("User should now be redirecting to target URI.")
|
global.Logger.Debugf("User should now be redirecting to target URI.")
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"misso/global"
|
"misso/global"
|
||||||
"misso/types"
|
|
||||||
"misso/utils"
|
"misso/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserinfoResponse struct {
|
|
||||||
types.MisskeyUser
|
|
||||||
EMail string `json:"email"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func UserInfo(ctx *gin.Context) {
|
func UserInfo(ctx *gin.Context) {
|
||||||
// Get token from header
|
// Get token from header
|
||||||
accessToken := strings.Replace(ctx.GetHeader("Authorization"), "Bearer ", "", 1)
|
accessToken := strings.Replace(ctx.GetHeader("Authorization"), "Bearer ", "", 1)
|
||||||
|
@ -45,7 +39,7 @@ func UserInfo(ctx *gin.Context) {
|
||||||
|
|
||||||
// Return user info
|
// Return user info
|
||||||
global.Logger.Debugf("Retrieving context...")
|
global.Logger.Debugf("Retrieving context...")
|
||||||
userinfoCtx, err := utils.GetUserinfo(*tokenInfo.Sub)
|
userinfo, err := utils.GetUserinfo(*tokenInfo.Sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to retrieve userinfo with error: %v", err)
|
global.Logger.Errorf("Failed to retrieve userinfo with error: %v", err)
|
||||||
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{
|
||||||
|
@ -54,9 +48,26 @@ func UserInfo(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, UserinfoResponse{
|
userinfoRes := gin.H{} // map[string]interface{}
|
||||||
MisskeyUser: *userinfoCtx,
|
|
||||||
EMail: *tokenInfo.Sub,
|
// Get scopes
|
||||||
})
|
if tokenInfo.Scope != nil && *tokenInfo.Scope != "" {
|
||||||
|
// Has scopes
|
||||||
|
scopes := strings.Split(*tokenInfo.Scope, " ")
|
||||||
|
for _, s := range scopes {
|
||||||
|
if value, ok := (*userinfo)[s]; ok {
|
||||||
|
userinfoRes[s] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "openid" {
|
||||||
|
userinfoRes["sub"] = *tokenInfo.Sub
|
||||||
|
} else if s == "username" {
|
||||||
|
// Add "nickname" field for OIDC compatibility
|
||||||
|
userinfoRes["nickname"] = userinfoRes[s]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, userinfoRes)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package inits
|
||||||
import (
|
import (
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"misso/config"
|
"misso/config"
|
||||||
|
"misso/consts"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,5 +24,21 @@ func Config() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate time
|
||||||
|
if config.Config.Time.RequestValid <= 0 {
|
||||||
|
config.Config.Time.RequestValid = consts.TIME_DEFAULT_REQUEST_VALID
|
||||||
|
}
|
||||||
|
if config.Config.Time.LoginRemember < 0 {
|
||||||
|
// 0 means don't remember (in extreme account switch situations)
|
||||||
|
config.Config.Time.LoginRemember = consts.TIME_DEFAULT_LOGIN_REMEMBER
|
||||||
|
}
|
||||||
|
if config.Config.Time.ConsentRemember < 0 {
|
||||||
|
// 0 means remember forever (default behavior)
|
||||||
|
config.Config.Time.ConsentRemember = consts.TIME_DEFAULT_CONSENT_REMEMBER
|
||||||
|
}
|
||||||
|
if config.Config.Time.UserinfoCache <= 0 {
|
||||||
|
config.Config.Time.UserinfoCache = consts.TIME_DEFAULT_USERINFO_CACHE
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ func PostAPIRequest[T I_Response | AuthSessionGenerate_Response | AuthSessionUse
|
||||||
apiEndpointPath string, reqBody any,
|
apiEndpointPath string, reqBody any,
|
||||||
) (*T, error) {
|
) (*T, error) {
|
||||||
// Prepare request
|
// Prepare request
|
||||||
apiEndpoint := fmt.Sprintf("https://%s%s", config.Config.Misskey.Instance, apiEndpointPath)
|
apiEndpoint := fmt.Sprintf("https://%s/api/%s", config.Config.Misskey.Instance, apiEndpointPath)
|
||||||
|
|
||||||
reqBodyBytes, err := json.Marshal(reqBody)
|
reqBodyBytes, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -13,7 +13,7 @@ type AuthSessionGenerate_Response struct {
|
||||||
|
|
||||||
func GenerateAuthSession() (*AuthSessionGenerate_Response, error) {
|
func GenerateAuthSession() (*AuthSessionGenerate_Response, error) {
|
||||||
|
|
||||||
return PostAPIRequest[AuthSessionGenerate_Response]("/api/auth/session/generate", &AuthSessionGenerate_Request{
|
return PostAPIRequest[AuthSessionGenerate_Response]("auth/session/generate", &AuthSessionGenerate_Request{
|
||||||
AppSecret: config.Config.Misskey.Application.Secret,
|
AppSecret: config.Config.Misskey.Application.Secret,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ type I_Request struct {
|
||||||
type I_Response = types.MisskeyUser
|
type I_Response = types.MisskeyUser
|
||||||
|
|
||||||
func GetUserinfo(accessToken string) (*I_Response, error) {
|
func GetUserinfo(accessToken string) (*I_Response, error) {
|
||||||
return PostAPIRequest[I_Response]("/api/i", &I_Request{
|
return PostAPIRequest[I_Response]("i", &I_Request{
|
||||||
I: accessToken,
|
I: accessToken,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@ type AuthSessionUserkey_Request struct {
|
||||||
|
|
||||||
type AuthSessionUserkey_Response struct {
|
type AuthSessionUserkey_Response struct {
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
User types.MisskeyUser `json:"user"`
|
User types.MisskeyUserBase `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserkey(token string) (*AuthSessionUserkey_Response, error) {
|
func GetUserkey(token string) (*AuthSessionUserkey_Response, error) {
|
||||||
|
|
||||||
return PostAPIRequest[AuthSessionUserkey_Response]("/api/auth/session/userkey", &AuthSessionUserkey_Request{
|
return PostAPIRequest[AuthSessionUserkey_Response]("auth/session/userkey", &AuthSessionUserkey_Request{
|
||||||
AppSecret: config.Config.Misskey.Application.Secret,
|
AppSecret: config.Config.Misskey.Application.Secret,
|
||||||
Token: token,
|
Token: token,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,60 @@
|
||||||
{{ define "consent.tmpl" }}
|
{{ define "consent.tmpl" }}
|
||||||
<html>
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<title>授权确认</title>
|
<title>Confirm authorization</title>
|
||||||
{{ template "head.tmpl" }}
|
{{ template "head.tmpl" }}
|
||||||
<style>
|
<style>
|
||||||
|
ul.scopes,
|
||||||
|
ul.app-terms {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.scopes {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 30vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.scopes > li {
|
||||||
|
padding: 6px 12px;
|
||||||
|
margin: 6px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #282c34;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.scopes > li > * {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.app-terms {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.app-terms > li {
|
||||||
|
padding: 0 6px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.app-terms > li:not(:last-child) {
|
||||||
|
border-right: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.app-terms > li > a {
|
||||||
|
color: #62b6e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consent-notice {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #282c34;
|
||||||
|
}
|
||||||
|
|
||||||
p.remember {
|
p.remember {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
p.remember, p.remember > * {
|
p.remember, p.remember > * {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -59,7 +108,7 @@
|
||||||
background: #15803d;
|
background: #15803d;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app-name, #user-name {
|
.app-name, .user-name {
|
||||||
color: #62b6e7;
|
color: #62b6e7;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -67,29 +116,69 @@
|
||||||
<body>
|
<body>
|
||||||
<form id="main" action="/consent" method="POST">
|
<form id="main" action="/consent" method="POST">
|
||||||
<input type="hidden" name="_csrf" value="{{ .csrf }}" />
|
<input type="hidden" name="_csrf" value="{{ .csrf }}" />
|
||||||
|
<input type="hidden" name="challenge" value="{{ .challenge }}" />
|
||||||
|
|
||||||
|
<div class="logo">
|
||||||
{{ if .logo }}
|
{{ if .logo }}
|
||||||
<img src="{{ .logo }}" alt="{{ .clientName }}" width="120" height="120" />
|
<img src="{{ .logo }}" alt="{{ .clientName }}" width="120" height="120" />
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 48 48"><title>c-warning</title><g><path fill="#EFD358" d="M24,1C11.31787,1,1,11.31787,1,24s10.31787,23,23,23s23-10.31787,23-23S36.68213,1,24,1z"></path> <path fill="#FFFFFF" d="M24,28c0.55225,0,1-0.44775,1-1V14c0-0.55225-0.44775-1-1-1s-1,0.44775-1,1v13 C23,27.55225,23.44775,28,24,28z"></path> <circle fill="#FFFFFF" cx="24" cy="33" r="2"></circle></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 48 48">
|
||||||
|
<g>
|
||||||
|
<circle fill="#EFD358" cx="24" cy="24" r="24"></circle>
|
||||||
|
<path fill="#FFFFFF" d="M24,28c0.55225,0,1-0.44775,1-1V14c0-0.55225-0.44775-1-1-1s-1,0.44775-1,1v13 C23,27.55225,23.44775,28,24,28z"></path>
|
||||||
|
<circle fill="#FFFFFF" cx="24" cy="33" r="2"></circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
<p>
|
<p>
|
||||||
应用程序
|
Application
|
||||||
<span id="app-name">{{ .clientName }}</span>
|
<span class="app-name">{{ .clientName }}</span>
|
||||||
正请求读取
|
is requesting
|
||||||
<span id="user-name">{{ .user.Name }}</span>
|
<br />
|
||||||
的信息
|
access to account
|
||||||
|
<span class="user-name">{{ .user.name }}</span>
|
||||||
|
with the following scopes:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<ul class="scopes">
|
||||||
|
{{ $user := .user }}
|
||||||
|
{{ range $scope := .scopes }}
|
||||||
|
<li>
|
||||||
|
<details>
|
||||||
|
<summary>{{ $scope }}</summary>
|
||||||
|
<pre>{{ index $user $scope }}</pre>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{ if or .clientPolicy .clientTos }}
|
||||||
|
<ul class="app-terms">
|
||||||
|
{{ if .clientPolicy }}
|
||||||
|
<li><a href="{{ .clientPolicy }}" target="_blank" referrerpolicy="no-referrer">Client Usage Policy</a></li>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .clientTos }}
|
||||||
|
<li><a href="{{ .clientTos }}" target="_blank" referrerpolicy="no-referrer">Terms of Service</a></li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="consent-notice">
|
||||||
|
<p>Accept the request?</p>
|
||||||
|
|
||||||
<p class="remember">
|
<p class="remember">
|
||||||
<input type="checkbox" id="remember" name="remember" value="true" />
|
<input type="checkbox" id="remember" name="remember" value="true" />
|
||||||
<label for="remember">记住我的选择</label>
|
<label for="remember">Remember my choice</label>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="buttons">
|
<p class="buttons">
|
||||||
<button type="submit" name="action" value="reject" class="reject">拒绝</button>
|
<button type="submit" name="action" value="reject" class="reject">Reject</button>
|
||||||
<button type="submit" name="action" value="accept" class="accept">接受</button>
|
<button type="submit" name="action" value="accept" class="accept">Accept</button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,14 +1,30 @@
|
||||||
{{ define "error.tmpl" }}
|
{{ define "error.tmpl" }}
|
||||||
<html>
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<title>出错了</title>
|
<title>Error</title>
|
||||||
{{ template "head.tmpl" }}
|
{{ template "head.tmpl" }}
|
||||||
|
<style>
|
||||||
|
.error {
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #282c34;
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 48 48"><title>c-remove</title><g><path fill="#E86C60" d="M24,47C11.31787,47,1,36.68262,1,24S11.31787,1,24,1s23,10.31738,23,23S36.68213,47,24,47z"></path> <path fill="#FFFFFF" d="M25.41406,24l7.29297-7.29297c0.39062-0.39062,0.39062-1.02344,0-1.41406s-1.02344-0.39062-1.41406,0 L24,22.58594l-7.29297-7.29297c-0.39062-0.39062-1.02344-0.39062-1.41406,0s-0.39062,1.02344,0,1.41406L22.58594,24 l-7.29297,7.29297c-0.39062,0.39062-0.39062,1.02344,0,1.41406C15.48828,32.90234,15.74414,33,16,33 s0.51172-0.09766,0.70703-0.29297L24,25.41406l7.29297,7.29297C31.48828,32.90234,31.74414,33,32,33 s0.51172-0.09766,0.70703-0.29297c0.39062-0.39062,0.39062-1.02344,0-1.41406L25.41406,24z"></path></g></svg>
|
<div class="logo">
|
||||||
<h1>发生了一些错误</h1>
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 48 48">
|
||||||
<p>{{ .error }}</p>
|
<g>
|
||||||
|
<circle fill="#E86C60" cx="24" cy="24" r="24"></circle>
|
||||||
|
<path fill="#FFFFFF" d="M25.41406,24l7.29297-7.29297c0.39062-0.39062,0.39062-1.02344,0-1.41406s-1.02344-0.39062-1.41406,0 L24,22.58594l-7.29297-7.29297c-0.39062-0.39062-1.02344-0.39062-1.41406,0s-0.39062,1.02344,0,1.41406L22.58594,24 l-7.29297,7.29297c-0.39062,0.39062-0.39062,1.02344,0,1.41406C15.48828,32.90234,15.74414,33,16,33 s0.51172-0.09766,0.70703-0.29297L24,25.41406l7.29297,7.29297C31.48828,32.90234,31.74414,33,32,33 s0.51172-0.09766,0.70703-0.29297c0.39062-0.39062,0.39062-1.02344,0-1.41406L25.41406,24z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1>An error happened</h1>
|
||||||
|
<p class="error">{{ .error }}</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||||
<style>
|
<style>
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #282c34;
|
background-color: #282c34;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main {
|
#main {
|
||||||
|
@ -13,12 +19,23 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 40px;
|
padding: 80px 40px 40px 40px;
|
||||||
background-color: #21252b;
|
background-color: #21252b;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
margin: 0 40px;
|
margin: 0 40px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
position: absolute;
|
||||||
|
border: 10px solid #282c34;
|
||||||
|
top: -70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo, .logo > * {
|
||||||
|
border-radius: 120px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{ define "index.tmpl" }}
|
{{ define "index.tmpl" }}
|
||||||
<html>
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<title>MiSSO</title>
|
<title>MiSSO</title>
|
||||||
{{ template "head.tmpl" }}
|
{{ template "head.tmpl" }}
|
||||||
|
@ -24,13 +24,21 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 48 48"><title>access-key</title><rect data-element="frame" x="0" y="0" width="48" height="48" rx="48" ry="48" stroke="none" fill="#62b6e7"></rect><g transform="translate(9.600000000000001 9.600000000000001) scale(0.6)"><path d="M38,23a1,1,0,0,1-.707-.293l-6-6a1,1,0,0,1,0-1.414l8-8a1,1,0,0,1,1.414,0l6,6a1,1,0,0,1,0,1.414l-2,2a1,1,0,0,1-1.414,0L41,14.414,38.414,17l2.293,2.293a1,1,0,0,1,0,1.414l-2,2A1,1,0,0,1,38,23Z" fill="#eba40a"></path><path d="M44.061,3.939a1.5,1.5,0,0,0-2.122,0L17.923,27.956a10.027,10.027,0,1,0,2.121,2.121L44.061,6.061A1.5,1.5,0,0,0,44.061,3.939ZM12,43a7,7,0,1,1,4.914-11.978c.011.012.014.027.025.039s.027.014.039.025A6.995,6.995,0,0,1,12,43Z" fill="#ffd764"></path></g></svg>
|
<div class="logo">
|
||||||
<h1>欢迎来到 MiSSO</h1>
|
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 48 48">
|
||||||
|
<circle fill="#62b6e7" cx="24" cy="24" r="24"></circle>
|
||||||
|
<g transform="translate(9.600000000000001 9.600000000000001) scale(0.6)">
|
||||||
|
<path d="M38,23a1,1,0,0,1-.707-.293l-6-6a1,1,0,0,1,0-1.414l8-8a1,1,0,0,1,1.414,0l6,6a1,1,0,0,1,0,1.414l-2,2a1,1,0,0,1-1.414,0L41,14.414,38.414,17l2.293,2.293a1,1,0,0,1,0,1.414l-2,2A1,1,0,0,1,38,23Z" fill="#eba40a"></path>
|
||||||
|
<path d="M44.061,3.939a1.5,1.5,0,0,0-2.122,0L17.923,27.956a10.027,10.027,0,1,0,2.121,2.121L44.061,6.061A1.5,1.5,0,0,0,44.061,3.939ZM12,43a7,7,0,1,1,4.914-11.978c.011.012.014.027.025.039s.027.014.039.025A6.995,6.995,0,0,1,12,43Z" fill="#ffd764"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1>Welcome to MiSSO</h1>
|
||||||
<p>
|
<p>
|
||||||
直接访问这里好像不太对哦
|
You should not access this page directly.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="primary" href="https://docs.nya.one/peripheral/misso/">查看文档</a>
|
<a class="primary" href="https://docs.nya.one/peripheral/misso/">Documentation</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -14,4 +14,10 @@ type Config struct {
|
||||||
Hydra struct {
|
Hydra struct {
|
||||||
AdminUrl string `yaml:"admin_url"`
|
AdminUrl string `yaml:"admin_url"`
|
||||||
} `yaml:"hydra"`
|
} `yaml:"hydra"`
|
||||||
|
Time struct {
|
||||||
|
RequestValid int64 `yaml:"request_valid"`
|
||||||
|
LoginRemember int64 `yaml:"login_remember"`
|
||||||
|
ConsentRemember int64 `yaml:"consent_remember"`
|
||||||
|
UserinfoCache int64 `yaml:"userinfo_cache"`
|
||||||
|
} `yaml:"time"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,186 +1,8 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import "time"
|
type MisskeyUserBase struct {
|
||||||
|
|
||||||
type MisskeyUser struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Host interface{} `json:"host"`
|
// Ignore other fields
|
||||||
AvatarUrl string `json:"avatarUrl"`
|
|
||||||
AvatarBlurhash string `json:"avatarBlurhash"`
|
|
||||||
IsBot bool `json:"isBot"`
|
|
||||||
IsCat bool `json:"isCat"`
|
|
||||||
OnlineStatus string `json:"onlineStatus"`
|
|
||||||
Url interface{} `json:"url"`
|
|
||||||
Uri interface{} `json:"uri"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
|
||||||
LastFetchedAt interface{} `json:"lastFetchedAt"`
|
|
||||||
BannerUrl string `json:"bannerUrl"`
|
|
||||||
BannerBlurhash string `json:"bannerBlurhash"`
|
|
||||||
IsLocked bool `json:"isLocked"`
|
|
||||||
IsSilenced bool `json:"isSilenced"`
|
|
||||||
IsSuspended bool `json:"isSuspended"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Location string `json:"location"`
|
|
||||||
Birthday string `json:"birthday"`
|
|
||||||
Lang string `json:"lang"`
|
|
||||||
Fields []struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
} `json:"fields"`
|
|
||||||
FollowersCount int `json:"followersCount"`
|
|
||||||
FollowingCount int `json:"followingCount"`
|
|
||||||
NotesCount int `json:"notesCount"`
|
|
||||||
PinnedNoteIds []string `json:"pinnedNoteIds"`
|
|
||||||
PinnedNotes []struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
UserId string `json:"userId"`
|
|
||||||
User struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Host interface{} `json:"host"`
|
|
||||||
AvatarUrl string `json:"avatarUrl"`
|
|
||||||
AvatarBlurhash string `json:"avatarBlurhash"`
|
|
||||||
IsBot bool `json:"isBot"`
|
|
||||||
IsCat bool `json:"isCat"`
|
|
||||||
OnlineStatus string `json:"onlineStatus"`
|
|
||||||
} `json:"user"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
Cw *string `json:"cw"`
|
|
||||||
Visibility string `json:"visibility"`
|
|
||||||
LocalOnly bool `json:"localOnly"`
|
|
||||||
RenoteCount int `json:"renoteCount"`
|
|
||||||
RepliesCount int `json:"repliesCount"`
|
|
||||||
Reactions map[string]int `json:"reactions"`
|
|
||||||
FileIds []string `json:"fileIds"`
|
|
||||||
Files []struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Md5 string `json:"md5"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
IsSensitive bool `json:"isSensitive"`
|
|
||||||
Blurhash string `json:"blurhash"`
|
|
||||||
Properties struct {
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
} `json:"properties"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
ThumbnailUrl string `json:"thumbnailUrl"`
|
|
||||||
Comment interface{} `json:"comment"`
|
|
||||||
FolderId interface{} `json:"folderId"`
|
|
||||||
Folder interface{} `json:"folder"`
|
|
||||||
UserId interface{} `json:"userId"`
|
|
||||||
User interface{} `json:"user"`
|
|
||||||
} `json:"files"`
|
|
||||||
ReplyId interface{} `json:"replyId"`
|
|
||||||
RenoteId interface{} `json:"renoteId"`
|
|
||||||
MyReaction string `json:"myReaction,omitempty"`
|
|
||||||
} `json:"pinnedNotes"`
|
|
||||||
PinnedPageId string `json:"pinnedPageId"`
|
|
||||||
PinnedPage struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
|
||||||
UserId string `json:"userId"`
|
|
||||||
User struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Host interface{} `json:"host"`
|
|
||||||
AvatarUrl string `json:"avatarUrl"`
|
|
||||||
AvatarBlurhash string `json:"avatarBlurhash"`
|
|
||||||
IsBot bool `json:"isBot"`
|
|
||||||
IsCat bool `json:"isCat"`
|
|
||||||
OnlineStatus string `json:"onlineStatus"`
|
|
||||||
} `json:"user"`
|
|
||||||
Content []struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Text string `json:"text,omitempty"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
} `json:"content"`
|
|
||||||
Variables []interface{} `json:"variables"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Summary string `json:"summary"`
|
|
||||||
HideTitleWhenPinned bool `json:"hideTitleWhenPinned"`
|
|
||||||
AlignCenter bool `json:"alignCenter"`
|
|
||||||
Font string `json:"font"`
|
|
||||||
Script string `json:"script"`
|
|
||||||
EyeCatchingImageId string `json:"eyeCatchingImageId"`
|
|
||||||
EyeCatchingImage struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Md5 string `json:"md5"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
IsSensitive bool `json:"isSensitive"`
|
|
||||||
Blurhash string `json:"blurhash"`
|
|
||||||
Properties struct {
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
} `json:"properties"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
ThumbnailUrl string `json:"thumbnailUrl"`
|
|
||||||
Comment interface{} `json:"comment"`
|
|
||||||
FolderId interface{} `json:"folderId"`
|
|
||||||
Folder interface{} `json:"folder"`
|
|
||||||
UserId interface{} `json:"userId"`
|
|
||||||
User interface{} `json:"user"`
|
|
||||||
} `json:"eyeCatchingImage"`
|
|
||||||
AttachedFiles []interface{} `json:"attachedFiles"`
|
|
||||||
LikedCount int `json:"likedCount"`
|
|
||||||
IsLiked bool `json:"isLiked"`
|
|
||||||
} `json:"pinnedPage"`
|
|
||||||
PublicReactions bool `json:"publicReactions"`
|
|
||||||
FfVisibility string `json:"ffVisibility"`
|
|
||||||
TwoFactorEnabled bool `json:"twoFactorEnabled"`
|
|
||||||
UsePasswordLessLogin bool `json:"usePasswordLessLogin"`
|
|
||||||
SecurityKeys bool `json:"securityKeys"`
|
|
||||||
Roles []struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Color string `json:"color"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
IsModerator bool `json:"isModerator"`
|
|
||||||
IsAdministrator bool `json:"isAdministrator"`
|
|
||||||
} `json:"roles"`
|
|
||||||
AvatarId string `json:"avatarId"`
|
|
||||||
BannerId string `json:"bannerId"`
|
|
||||||
IsModerator bool `json:"isModerator"`
|
|
||||||
IsAdmin bool `json:"isAdmin"`
|
|
||||||
InjectFeaturedNote bool `json:"injectFeaturedNote"`
|
|
||||||
ReceiveAnnouncementEmail bool `json:"receiveAnnouncementEmail"`
|
|
||||||
AlwaysMarkNsfw bool `json:"alwaysMarkNsfw"`
|
|
||||||
AutoSensitive bool `json:"autoSensitive"`
|
|
||||||
CarefulBot bool `json:"carefulBot"`
|
|
||||||
AutoAcceptFollowed bool `json:"autoAcceptFollowed"`
|
|
||||||
NoCrawle bool `json:"noCrawle"`
|
|
||||||
IsExplorable bool `json:"isExplorable"`
|
|
||||||
IsDeleted bool `json:"isDeleted"`
|
|
||||||
HideOnlineStatus bool `json:"hideOnlineStatus"`
|
|
||||||
HasUnreadSpecifiedNotes bool `json:"hasUnreadSpecifiedNotes"`
|
|
||||||
HasUnreadMentions bool `json:"hasUnreadMentions"`
|
|
||||||
HasUnreadAnnouncement bool `json:"hasUnreadAnnouncement"`
|
|
||||||
HasUnreadAntenna bool `json:"hasUnreadAntenna"`
|
|
||||||
HasUnreadChannel bool `json:"hasUnreadChannel"`
|
|
||||||
HasUnreadMessagingMessage bool `json:"hasUnreadMessagingMessage"`
|
|
||||||
HasUnreadNotification bool `json:"hasUnreadNotification"`
|
|
||||||
HasPendingReceivedFollowRequest bool `json:"hasPendingReceivedFollowRequest"`
|
|
||||||
Integrations struct {
|
|
||||||
} `json:"integrations"`
|
|
||||||
MutedWords [][]string `json:"mutedWords"`
|
|
||||||
MutedInstances []string `json:"mutedInstances"`
|
|
||||||
MutingNotificationTypes []string `json:"mutingNotificationTypes"`
|
|
||||||
EmailNotificationTypes []string `json:"emailNotificationTypes"`
|
|
||||||
ShowTimelineReplies bool `json:"showTimelineReplies"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Find a better way to split necessary fields and additional fields
|
type MisskeyUser = map[string]any // Just raw json map
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
func GetUserinfo(subject string) (*types.MisskeyUser, error) {
|
func GetUserinfo(subject string) (*types.MisskeyUser, error) {
|
||||||
// Check cache key
|
// Check cache key
|
||||||
global.Logger.Debugf("Checking userinfo cache...")
|
global.Logger.Debugf("Checking userinfo cache...")
|
||||||
userinfoCacheKey := fmt.Sprintf(consts.REDIS_KEY_SHARE_USER_INFO, subject)
|
userinfoCacheKey := fmt.Sprintf(consts.REDIS_KEY_USER_INFO, subject)
|
||||||
exist, err := global.Redis.Exists(context.Background(), userinfoCacheKey).Result()
|
exist, err := global.Redis.Exists(context.Background(), userinfoCacheKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to check userinfo exist status with error: %v", err)
|
global.Logger.Errorf("Failed to check userinfo exist status with error: %v", err)
|
||||||
|
@ -38,7 +38,7 @@ func GetUserinfo(subject string) (*types.MisskeyUser, error) {
|
||||||
|
|
||||||
// Fallback to get info directly, we need user's access token.
|
// Fallback to get info directly, we need user's access token.
|
||||||
global.Logger.Debugf("No cached userinfo found (or valid), trying to get latest response.")
|
global.Logger.Debugf("No cached userinfo found (or valid), trying to get latest response.")
|
||||||
accessTokenCacheKey := fmt.Sprintf(consts.REDIS_KEY_SHARE_ACCESS_TOKEN, subject)
|
accessTokenCacheKey := fmt.Sprintf(consts.REDIS_KEY_USER_ACCESS_TOKEN, subject)
|
||||||
accessToken, err := global.Redis.Get(context.Background(), accessTokenCacheKey).Result()
|
accessToken, err := global.Redis.Get(context.Background(), accessTokenCacheKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to get user access token with error: %v", err)
|
global.Logger.Errorf("Failed to get user access token with error: %v", err)
|
||||||
|
@ -52,6 +52,9 @@ func GetUserinfo(subject string) (*types.MisskeyUser, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append subject as email to userinfo
|
||||||
|
(*userinfo)["email"] = subject
|
||||||
|
|
||||||
// Save userinfo into redis
|
// Save userinfo into redis
|
||||||
_ = SaveUserinfo(subject, userinfo) // Ignore errors
|
_ = SaveUserinfo(subject, userinfo) // Ignore errors
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"misso/config"
|
||||||
"misso/consts"
|
"misso/consts"
|
||||||
"misso/global"
|
"misso/global"
|
||||||
"misso/types"
|
"misso/types"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SaveUserinfo(subject string, userinfo *types.MisskeyUser) error {
|
func SaveUserinfo(subject string, userinfo *types.MisskeyUser) error {
|
||||||
|
@ -15,8 +17,8 @@ func SaveUserinfo(subject string, userinfo *types.MisskeyUser) error {
|
||||||
global.Logger.Errorf("Failed to parse accept context with error: %v", err)
|
global.Logger.Errorf("Failed to parse accept context with error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sessUserInfoKey := fmt.Sprintf(consts.REDIS_KEY_SHARE_USER_INFO, subject)
|
sessUserInfoKey := fmt.Sprintf(consts.REDIS_KEY_USER_INFO, subject)
|
||||||
err = global.Redis.Set(context.Background(), sessUserInfoKey, userinfoBytes, consts.TIME_USERINFO_CACHE).Err()
|
err = global.Redis.Set(context.Background(), sessUserInfoKey, userinfoBytes, time.Duration(config.Config.Time.UserinfoCache)*time.Second).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Errorf("Failed to save session user info into redis with error: %v", err)
|
global.Logger.Errorf("Failed to save session user info into redis with error: %v", err)
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in a new issue