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..309757e 100644 --- a/consts/time.go +++ b/consts/time.go @@ -5,8 +5,8 @@ import "time" const ( TIME_REQUEST_VALID = 1 * time.Hour - TIME_LOGIN_REMEMBER = 10 * time.Minute + TIME_LOGIN_REMEMBER = 10 * time.Minute TIME_CONSENT_REMEMBER = 0 // Forever - TIME_USERINFO_CACHE = 10 * time.Minute + TIME_USERINFO_CACHE = 1 * time.Hour ) diff --git a/handlers/consent/consent_check.go b/handlers/consent/consent_check.go index 7503f44..6b8c027 100644 --- a/handlers/consent/consent_check.go +++ b/handlers/consent/consent_check.go @@ -56,8 +56,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, consts.TIME_REQUEST_VALID).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 +69,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 +81,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 +95,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..63e06d8 100644 --- a/handlers/consent/consent_confirm.go +++ b/handlers/consent/consent_confirm.go @@ -12,9 +12,10 @@ import ( ) 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) diff --git a/handlers/login/misskey_auth_callback.go b/handlers/login/misskey_auth_callback.go index bdfc35a..05635b5 100644 --- a/handlers/login/misskey_auth_callback.go +++ b/handlers/login/misskey_auth_callback.go @@ -9,7 +9,6 @@ import ( "misso/consts" "misso/global" "misso/misskey" - "misso/utils" "net/http" "time" ) @@ -62,9 +61,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,21 +74,11 @@ 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, + Subject: userIdentifier, Remember: &remember, RememberFor: &rememberFor, }).Execute() diff --git a/handlers/user/info.go b/handlers/user/info.go index a318dc4..a74c96f 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,19 @@ 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 + } + } + } + + ctx.JSON(http.StatusOK, userinfoRes) } diff --git a/misskey/get_userkey.go b/misskey/get_userkey.go index a1befd1..3e00f84 100644 --- a/misskey/get_userkey.go +++ b/misskey/get_userkey.go @@ -11,8 +11,8 @@ 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) { diff --git a/templates/consent.tmpl b/templates/consent.tmpl index 6e6c878..c7c9349 100644 --- a/templates/consent.tmpl +++ b/templates/consent.tmpl @@ -1,11 +1,58 @@ {{ define "consent.tmpl" }} - + 授权确认 {{ template "head.tmpl" }} @@ -67,25 +114,65 @@
+ - {{ if .logo }} - {{ .clientName }} - {{ else }} - c-warning - {{ end }} + -

- 应用程序 - {{ .clientName }} - 正请求读取 - {{ .user.Name }} - 的信息 -

+
+

+ 应用程序 + {{ .clientName }} + 正请求 +
+ 读取 + {{ .user.name }} + 的这些信息: +

-

- - -

+ + + {{ if or .clientPolicy .clientTos }} + + {{ end }} +
+ +

diff --git a/templates/error.tmpl b/templates/error.tmpl index a5c80ef..b6e4d30 100644 --- a/templates/error.tmpl +++ b/templates/error.tmpl @@ -1,14 +1,30 @@ {{ define "error.tmpl" }} - + 出错了 {{ template "head.tmpl" }} +

- c-remove +

发生了一些错误

-

{{ .error }}

+

{{ .error }}

diff --git a/templates/head.tmpl b/templates/head.tmpl index ae72b1d..9654f5b 100644 --- a/templates/head.tmpl +++ b/templates/head.tmpl @@ -4,7 +4,7 @@ display: flex; align-items: center; justify-content: center; - background: #282c34; + background-color: #282c34; } #main { @@ -13,12 +13,23 @@ text-align: center; align-items: center; color: white; - padding: 40px; + padding: 80px 40px 40px 40px; background-color: #21252b; border-radius: 12px; margin: 0 40px; width: 100%; max-width: 480px; word-break: break-word; + position: relative; + } + + .logo { + position: absolute; + border: 10px solid #282c34; + top: -70px; + } + + .logo, .logo > * { + border-radius: 120px; } diff --git a/templates/index.tmpl b/templates/index.tmpl index 93e7b19..5438b73 100644 --- a/templates/index.tmpl +++ b/templates/index.tmpl @@ -1,5 +1,5 @@ {{ define "index.tmpl" }} - + MiSSO {{ template "head.tmpl" }} @@ -24,7 +24,15 @@
- access-key +

欢迎来到 MiSSO

直接访问这里好像不太对哦 diff --git a/types/MisskeyUser.go b/types/MisskeyUser.go index 25a7ca2..d089ac0 100644 --- a/types/MisskeyUser.go +++ b/types/MisskeyUser.go @@ -1,186 +1,8 @@ package types -import "time" - -type MisskeyUser 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"` - 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"` +type MisskeyUserBase struct { + Username string `json:"username"` + // Ignore other fields } -// TODO: Find a better way to split necessary fields and additional fields +type MisskeyUser = map[string]interface{} // Just raw json map diff --git a/utils/get_userinfo.go b/utils/get_userinfo.go index 5d269d1..7c3700c 100644 --- a/utils/get_userinfo.go +++ b/utils/get_userinfo.go @@ -13,7 +13,7 @@ import ( func GetUserinfo(subject string) (*types.MisskeyUser, error) { // Check cache key 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() if err != nil { 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. 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() if err != nil { 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 } + // Append subject as email to userinfo + (*userinfo)["email"] = subject + // Save userinfo into redis _ = SaveUserinfo(subject, userinfo) // Ignore errors diff --git a/utils/save_userinfo.go b/utils/save_userinfo.go index d77819c..4b309d6 100644 --- a/utils/save_userinfo.go +++ b/utils/save_userinfo.go @@ -15,7 +15,7 @@ func SaveUserinfo(subject string, userinfo *types.MisskeyUser) error { global.Logger.Errorf("Failed to parse accept context with error: %v", 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() if err != nil { global.Logger.Errorf("Failed to save session user info into redis with error: %v", err)