feat(refractor): use misskey userinfo key as scope

...and UI tweaks 🎨
This commit is contained in:
Nya Candy 2023-01-29 14:23:49 +08:00
parent 6aa0db6d0e
commit a99045a034
No known key found for this signature in database
GPG Key ID: 8B1BE5E86F2E66AE
14 changed files with 208 additions and 252 deletions

View File

@ -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
)

View File

@ -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
)

View File

@ -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.")

View File

@ -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)

View File

@ -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()

View File

@ -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)
}

View File

@ -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) {

View File

@ -1,11 +1,58 @@
{{ define "consent.tmpl" }}
<html>
<html lang="zh">
<head>
<title>授权确认</title>
{{ template "head.tmpl" }}
<style>
ul.scopes,
ul.app-terms {
list-style: none;
padding-left: 0;
}
ul.scopes {
text-align: left;
width: 100%;
}
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 {
display: flex;
justify-content: center;
}
p.remember, p.remember > * {
cursor: pointer;
@ -59,7 +106,7 @@
background: #15803d;
}
#app-name, #user-name {
.app-name, .user-name {
color: #62b6e7;
}
</style>
@ -67,25 +114,65 @@
<body>
<form id="main" action="/consent" method="POST">
<input type="hidden" name="_csrf" value="{{ .csrf }}" />
<input type="hidden" name="challenge" value="{{ .challenge }}" />
{{ if .logo }}
<img src="{{ .logo }}" alt="{{ .clientName }}" width="120" height="120" />
{{ 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>
{{ end }}
<div class="logo">
{{ if .logo }}
<img src="{{ .logo }}" alt="{{ .clientName }}" width="120" height="120" />
{{ else }}
<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 }}
</div>
<p>
应用程序
<span id="app-name">{{ .clientName }}</span>
正请求读取
<span id="user-name">{{ .user.Name }}</span>
的信息
</p>
<div>
<p>
应用程序
<span class="app-name">{{ .clientName }}</span>
正请求
<br />
读取
<span class="user-name">{{ .user.name }}</span>
的这些信息:
</p>
<p class="remember">
<input type="checkbox" id="remember" name="remember" value="true" />
<label for="remember">记住我的选择</label>
</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">使用政策</a></li>
{{ end }}
{{ if .clientTos }}
<li><a href="{{ .clientTos }}" target="_blank" referrerpolicy="no-referrer">服务条款</a></li>
{{ end }}
</ul>
{{ end }}
</div>
<div class="consent-notice">
<p>是否接受该请求?</p>
<p class="remember">
<input type="checkbox" id="remember" name="remember" value="true" />
<label for="remember">记住我的选择</label>
</p>
</div>
<p class="buttons">
<button type="submit" name="action" value="reject" class="reject">拒绝</button>

View File

@ -1,14 +1,30 @@
{{ define "error.tmpl" }}
<html>
<html lang="zh">
<head>
<title>出错了</title>
{{ template "head.tmpl" }}
<style>
.error {
width: 100%;
padding: 20px 0;
border-radius: 6px;
background-color: #282c34;
user-select: all;
}
</style>
</head>
<body>
<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">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 48 48">
<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>发生了一些错误</h1>
<p>{{ .error }}</p>
<p class="error">{{ .error }}</p>
</div>
</body>
</html>

View File

@ -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;
}
</style>

View File

@ -1,5 +1,5 @@
{{ define "index.tmpl" }}
<html>
<html lang="zh">
<head>
<title>MiSSO</title>
{{ template "head.tmpl" }}
@ -24,7 +24,15 @@
</head>
<body>
<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">
<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>欢迎来到 MiSSO</h1>
<p>
直接访问这里好像不太对哦

View File

@ -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

View File

@ -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

View File

@ -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)