发生了一些错误
-{{ .error }}
+An error happened
+{{ .error }}
diff --git a/.config/misso.yml.example b/.config/misso.yml.example index dd581b3..79ef846 100644 --- a/.config/misso.yml.example +++ b/.config/misso.yml.example @@ -7,3 +7,8 @@ misskey: secret: "" hydra: admin_url: "http://localhost:4445" +time: + request_valid: 3600 + login_remember: 600 + consent_remember: 0 + userinfo_cache: 3600 diff --git a/.gitignore b/.gitignore index 820f081..8a9782b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ db/ redis/ + +config.yml +misso diff --git a/consts/redis_key.go b/consts/redis_key.go index 1b82715..e7fb534 100644 --- a/consts/redis_key.go +++ b/consts/redis_key.go @@ -1,8 +1,8 @@ package consts const ( - 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_SHARE_ACCESS_TOKEN = "misso:share:at:%s" // Subject, access token as value - REDIS_KEY_SHARE_USER_INFO = "misso:share:ui:%s" // Subject, user info 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_USER_ACCESS_TOKEN = "misso:user:token:%s" // Subject, access token as value + REDIS_KEY_USER_INFO = "misso:user:info:%s" // Subject, user info as value ) diff --git a/consts/time.go b/consts/time.go index 25dbb44..dda9e94 100644 --- a/consts/time.go +++ b/consts/time.go @@ -1,12 +1,10 @@ package consts -import "time" - const ( - TIME_REQUEST_VALID = 1 * time.Hour + TIME_DEFAULT_REQUEST_VALID = 3600 // 1 Hour - TIME_LOGIN_REMEMBER = 10 * time.Minute - TIME_CONSENT_REMEMBER = 0 // Forever + TIME_DEFAULT_LOGIN_REMEMBER = 600 // 10 Minute + TIME_DEFAULT_CONSENT_REMEMBER = 0 // Forever - TIME_USERINFO_CACHE = 10 * time.Minute + TIME_DEFAULT_USERINFO_CACHE = 3600 // 1 Hour ) diff --git a/handlers/consent/consent_check.go b/handlers/consent/consent_check.go index 7503f44..0b5bd09 100644 --- a/handlers/consent/consent_check.go +++ b/handlers/consent/consent_check.go @@ -5,10 +5,12 @@ import ( "fmt" "github.com/gin-gonic/gin" client "github.com/ory/hydra-client-go/v2" + "misso/config" "misso/consts" "misso/global" "misso/utils" "net/http" + "time" ) func ConsentCheck(ctx *gin.Context) { @@ -56,8 +58,8 @@ func ConsentCheck(ctx *gin.Context) { // Generate CSRF token global.Logger.Debugf("Generating CSRF token...") csrf := utils.RandString(32) - sessKey := fmt.Sprintf(consts.REDIS_KEY_CONSENT_CSRF, csrf) - err := global.Redis.Set(context.Background(), sessKey, oauth2challenge, consts.TIME_REQUEST_VALID).Err() + sessKey := fmt.Sprintf(consts.REDIS_KEY_CONSENT_CSRF, oauth2challenge) + err := global.Redis.Set(context.Background(), sessKey, csrf, time.Duration(config.Config.Time.RequestValid)*time.Second).Err() if err != nil { global.Logger.Errorf("Failed to save csrf into redis with error: %v", err) ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{ @@ -69,7 +71,7 @@ func ConsentCheck(ctx *gin.Context) { // Retrieve context global.Logger.Debugf("Retrieving context...") - userinfoCtx, err := utils.GetUserinfo(*consentReq.Subject) + userinfo, err := utils.GetUserinfo(*consentReq.Subject) if err != nil { global.Logger.Errorf("Failed to retrieve userinfo with error: %v", err) ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{ @@ -81,9 +83,10 @@ func ConsentCheck(ctx *gin.Context) { // Show the consent UI global.Logger.Debugf("Rendering consent UI...") templateFields := gin.H{ - "user": *userinfoCtx, + "user": *userinfo, "challenge": oauth2challenge, "csrf": csrf, + "scopes": consentReq.RequestedScope, } if consentReq.Client.LogoUri != nil && *consentReq.Client.LogoUri != "" { @@ -94,6 +97,12 @@ func ConsentCheck(ctx *gin.Context) { } else { 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) global.Logger.Debugf("User should now see Consent UI.") diff --git a/handlers/consent/consent_confirm.go b/handlers/consent/consent_confirm.go index d93055d..7591eb8 100644 --- a/handlers/consent/consent_confirm.go +++ b/handlers/consent/consent_confirm.go @@ -5,16 +5,17 @@ import ( "fmt" "github.com/gin-gonic/gin" client "github.com/ory/hydra-client-go/v2" + "misso/config" "misso/consts" "misso/global" "net/http" - "time" ) type ConsentConfirmRequest struct { - CSRF string `form:"_csrf"` - Remember bool `form:"remember"` - Action string `form:"action"` + CSRF string `form:"_csrf"` + Challenge string `form:"challenge"` + Remember bool `form:"remember"` + Action string `form:"action"` } func ConsentConfirm(ctx *gin.Context) { @@ -32,16 +33,23 @@ func ConsentConfirm(ctx *gin.Context) { // Validate CSRF global.Logger.Debugf("Validating CSRF...") - sessKey := fmt.Sprintf(consts.REDIS_KEY_CONSENT_CSRF, req.CSRF) - oauth2challenge, err := global.Redis.Get(context.Background(), sessKey).Result() + sessKey := fmt.Sprintf(consts.REDIS_KEY_CONSENT_CSRF, req.Challenge) + csrfSession, err := global.Redis.Get(context.Background(), sessKey).Result() if err != nil { global.Logger.Errorf("Failed to get csrf from redis with error: %v", err) ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{ "error": "Failed to get csrf", }) 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 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("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{ - GrantScope: consentReq.RequestedScope, // TODO: Specify scopes + GrantScope: consentReq.RequestedScope, GrantAccessTokenAudience: consentReq.RequestedAccessTokenAudience, Remember: &req.Remember, RememberFor: &rememberFor, diff --git a/handlers/login/login.go b/handlers/login/login.go index a5bac0f..68756f9 100644 --- a/handlers/login/login.go +++ b/handlers/login/login.go @@ -5,10 +5,12 @@ import ( "fmt" "github.com/gin-gonic/gin" client "github.com/ory/hydra-client-go/v2" + "misso/config" "misso/consts" "misso/global" "misso/misskey" "net/http" + "time" ) 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) 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 { global.Logger.Errorf("Failed to save session into redis with error: %v", err) ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{ diff --git a/handlers/login/misskey_auth_callback.go b/handlers/login/misskey_auth_callback.go index bdfc35a..a9c0b32 100644 --- a/handlers/login/misskey_auth_callback.go +++ b/handlers/login/misskey_auth_callback.go @@ -9,9 +9,7 @@ import ( "misso/consts" "misso/global" "misso/misskey" - "misso/utils" "net/http" - "time" ) func MisskeyAuthCallback(ctx *gin.Context) { @@ -62,9 +60,10 @@ func MisskeyAuthCallback(ctx *gin.Context) { 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() if err != nil { 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 } - // 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...") - remember := true - rememberFor := int64(consts.TIME_LOGIN_REMEMBER / time.Second) - acceptReq, _, err := global.Hydra.Admin.OAuth2Api.AcceptOAuth2LoginRequest(context.Background()).LoginChallenge(oauth2challenge).AcceptOAuth2LoginRequest(client.AcceptOAuth2LoginRequest{ - Subject: userid, - Remember: &remember, - RememberFor: &rememberFor, - }).Execute() + acceptReq := client.AcceptOAuth2LoginRequest{ + Subject: userIdentifier, + } + if config.Config.Time.LoginRemember > 0 { + remember := true + acceptReq.Remember = &remember + acceptReq.RememberFor = &config.Config.Time.LoginRemember + } + acceptRes, _, err := global.Hydra.Admin.OAuth2Api.AcceptOAuth2LoginRequest(context.Background()).LoginChallenge(oauth2challenge).AcceptOAuth2LoginRequest(acceptReq).Execute() if err != nil { global.Logger.Errorf("Failed to accept login request with error: %v", err) ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{ @@ -101,7 +92,7 @@ func MisskeyAuthCallback(ctx *gin.Context) { } // 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.") diff --git a/handlers/user/info.go b/handlers/user/info.go index a318dc4..4ef420c 100644 --- a/handlers/user/info.go +++ b/handlers/user/info.go @@ -4,17 +4,11 @@ import ( "context" "github.com/gin-gonic/gin" "misso/global" - "misso/types" "misso/utils" "net/http" "strings" ) -type UserinfoResponse struct { - types.MisskeyUser - EMail string `json:"email"` -} - func UserInfo(ctx *gin.Context) { // Get token from header accessToken := strings.Replace(ctx.GetHeader("Authorization"), "Bearer ", "", 1) @@ -45,7 +39,7 @@ func UserInfo(ctx *gin.Context) { // Return user info global.Logger.Debugf("Retrieving context...") - userinfoCtx, err := utils.GetUserinfo(*tokenInfo.Sub) + userinfo, err := utils.GetUserinfo(*tokenInfo.Sub) if err != nil { global.Logger.Errorf("Failed to retrieve userinfo with error: %v", err) ctx.HTML(http.StatusInternalServerError, "error.tmpl", gin.H{ @@ -54,9 +48,26 @@ func UserInfo(ctx *gin.Context) { return } - ctx.JSON(http.StatusOK, UserinfoResponse{ - MisskeyUser: *userinfoCtx, - EMail: *tokenInfo.Sub, - }) + userinfoRes := gin.H{} // map[string]interface{} + + // 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) } diff --git a/inits/config.go b/inits/config.go index f7b74d7..a63c42a 100644 --- a/inits/config.go +++ b/inits/config.go @@ -3,6 +3,7 @@ package inits import ( "gopkg.in/yaml.v3" "misso/config" + "misso/consts" "os" ) @@ -23,5 +24,21 @@ func Config() error { 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 } diff --git a/misskey/base.go b/misskey/base.go index 1651a51..f3bfdf5 100644 --- a/misskey/base.go +++ b/misskey/base.go @@ -22,7 +22,7 @@ func PostAPIRequest[T I_Response | AuthSessionGenerate_Response | AuthSessionUse apiEndpointPath string, reqBody any, ) (*T, error) { // 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) if err != nil { diff --git a/misskey/generate_auth_session.go b/misskey/generate_auth_session.go index 4bb33c4..07ecb03 100644 --- a/misskey/generate_auth_session.go +++ b/misskey/generate_auth_session.go @@ -13,7 +13,7 @@ type AuthSessionGenerate_Response struct { 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, }) diff --git a/misskey/get_userinfo.go b/misskey/get_userinfo.go index 07120dd..c97b9e6 100644 --- a/misskey/get_userinfo.go +++ b/misskey/get_userinfo.go @@ -9,7 +9,7 @@ type I_Request struct { type I_Response = types.MisskeyUser func GetUserinfo(accessToken string) (*I_Response, error) { - return PostAPIRequest[I_Response]("/api/i", &I_Request{ + return PostAPIRequest[I_Response]("i", &I_Request{ I: accessToken, }) } diff --git a/misskey/get_userkey.go b/misskey/get_userkey.go index a1befd1..2f16e1b 100644 --- a/misskey/get_userkey.go +++ b/misskey/get_userkey.go @@ -11,13 +11,13 @@ type AuthSessionUserkey_Request struct { } type AuthSessionUserkey_Response struct { - AccessToken string `json:"accessToken"` - User types.MisskeyUser `json:"user"` + AccessToken string `json:"accessToken"` + User types.MisskeyUserBase `json:"user"` } 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, Token: token, }) diff --git a/templates/consent.tmpl b/templates/consent.tmpl index 6e6c878..9e0e0d5 100644 --- a/templates/consent.tmpl +++ b/templates/consent.tmpl @@ -1,11 +1,60 @@ {{ define "consent.tmpl" }} - +
-{{ .error }}
+{{ .error }}
- 直接访问这里好像不太对哦 + You should not access this page directly.
- 查看文档 + Documentation