support linking OAuth 2.0 user to logged-in users
This commit is contained in:
@@ -488,11 +488,33 @@ func (a *AuthorizationsApi) OAuth2CallbackAuthorizeHandler(c *core.WebContext) (
|
||||
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to revoke temporary token \"utid:%s\" for user \"uid:%d\", because %s", oldTokenClaims.UserTokenId, user.Uid, err.Error())
|
||||
}
|
||||
|
||||
token, claims, err := a.tokens.CreateToken(c, user)
|
||||
var token string
|
||||
var claims *core.UserTokenClaims
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrTokenGenerating
|
||||
if credential.Token != "" {
|
||||
_, claims, _, err = a.tokens.ParseToken(c, credential.Token)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to parse token, because %s", err.Error())
|
||||
return nil, errs.ErrInvalidToken
|
||||
}
|
||||
|
||||
if claims.Uid != user.Uid {
|
||||
log.Warnf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] oauth 2.0 user \"uid:%d\" does not match current user \"uid:%d\"", user.Uid, claims.Uid)
|
||||
token = ""
|
||||
claims = nil
|
||||
} else {
|
||||
token = credential.Token
|
||||
}
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
token, claims, err = a.tokens.CreateToken(c, user)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[authorizations.OAuth2CallbackAuthorizeHandler] failed to create token for user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
return nil, errs.ErrTokenGenerating
|
||||
}
|
||||
}
|
||||
|
||||
c.SetTextualToken(token)
|
||||
|
||||
@@ -73,6 +73,29 @@ func (a *OAuth2AuthenticationApi) LoginHandler(c *core.WebContext) (string, *err
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrRepeatedRequest)
|
||||
}
|
||||
|
||||
uid := int64(0)
|
||||
|
||||
if oauth2LoginReq.Token != "" {
|
||||
_, claims, _, err := a.tokens.ParseToken(c, oauth2LoginReq.Token)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[oauth2_authentications.LoginHandler] failed to parse token, because %s", err.Error())
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrInvalidToken)
|
||||
}
|
||||
|
||||
uid = claims.Uid
|
||||
user, err := a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil && !errors.Is(err, errs.ErrUserNotFound) {
|
||||
log.Errorf(c, "[oauth2_authentications.LoginHandler] failed to get user by id %d, because %s", uid, err.Error())
|
||||
return a.redirectToFailedCallbackPage(c, errs.Or(err, errs.ErrOperationFailed))
|
||||
}
|
||||
|
||||
if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_OAUTH2_LOGIN) {
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrNotPermittedToPerformThisAction)
|
||||
}
|
||||
}
|
||||
|
||||
verifier, err := utils.GetRandomNumberOrLowercaseLetter(64)
|
||||
|
||||
if err != nil {
|
||||
@@ -80,7 +103,7 @@ func (a *OAuth2AuthenticationApi) LoginHandler(c *core.WebContext) (string, *err
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrSystemError)
|
||||
}
|
||||
|
||||
remark = fmt.Sprintf("%s|%s|%s", oauth2LoginReq.Platform, oauth2LoginReq.ClientSessionId, verifier)
|
||||
remark = fmt.Sprintf("%s|%s|%d|%s", oauth2LoginReq.Platform, oauth2LoginReq.ClientSessionId, uid, verifier)
|
||||
state := fmt.Sprintf("%s|%s|%s", oauth2LoginReq.Platform, oauth2LoginReq.ClientSessionId, utils.MD5EncodeToString([]byte(remark)))
|
||||
|
||||
redirectUrl, err := oauth2.GetOAuth2AuthUrl(c, state, verifier)
|
||||
@@ -123,7 +146,7 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
||||
|
||||
stateParts := strings.Split(oauth2CallbackReq.State, "|")
|
||||
|
||||
if len(stateParts) >= 2 {
|
||||
if len(stateParts) == 3 {
|
||||
platform = stateParts[0]
|
||||
clientSessionId = stateParts[1]
|
||||
} else {
|
||||
@@ -143,14 +166,21 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
||||
|
||||
remarkParts := strings.Split(remark, "|")
|
||||
|
||||
if len(remarkParts) != 3 || remarkParts[0] != platform || remarkParts[1] != clientSessionId {
|
||||
if len(remarkParts) != 4 || remarkParts[0] != platform || remarkParts[1] != clientSessionId {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] invalid oauth 2.0 state \"%s\" in duplicate checker for client session id \"%s\"", remark, clientSessionId)
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrInvalidOAuth2State)
|
||||
}
|
||||
|
||||
verifier := remarkParts[2]
|
||||
expectedState := fmt.Sprintf("%s|%s|%s", platform, clientSessionId, verifier)
|
||||
expectedState = fmt.Sprintf("%s|%s|%s", platform, clientSessionId, utils.MD5EncodeToString([]byte(expectedState)))
|
||||
uid, err := utils.StringToInt64(remarkParts[2])
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] invalid uid \"%s\" in oauth 2.0 state \"%s\"", remarkParts[2], remark)
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrInvalidOAuth2State)
|
||||
}
|
||||
|
||||
verifier := remarkParts[3]
|
||||
expectedRemark := fmt.Sprintf("%s|%s|%d|%s", platform, clientSessionId, uid, verifier)
|
||||
expectedState := fmt.Sprintf("%s|%s|%s", platform, clientSessionId, utils.MD5EncodeToString([]byte(expectedRemark)))
|
||||
|
||||
if oauth2CallbackReq.State != expectedState {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] mismatched random string in oauth 2.0 state, expected \"%s\", got \"%s\"", expectedState, oauth2CallbackReq.State)
|
||||
@@ -199,6 +229,11 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
||||
return a.redirectToFailedCallbackPage(c, errs.Or(err, errs.ErrOperationFailed))
|
||||
}
|
||||
|
||||
if uid != 0 && userExternalAuth != nil && userExternalAuth.Uid != uid {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] oauth 2.0 external auth has been bound to another user \"uid:%d\", current user \"uid:%d\"", userExternalAuth.Uid, uid)
|
||||
return a.redirectToFailedCallbackPage(c, errs.ErrOAuth2UserAlreadyBoundToAnotherUser)
|
||||
}
|
||||
|
||||
var user *models.User
|
||||
|
||||
if err == nil { // user already bound to external auth, redirect to success page
|
||||
@@ -209,17 +244,26 @@ func (a *OAuth2AuthenticationApi) CallbackHandler(c *core.WebContext) (string, *
|
||||
return a.redirectToFailedCallbackPage(c, errs.Or(err, errs.ErrOperationFailed))
|
||||
}
|
||||
} else { // errors.Is(err, errs.ErrUserExternalAuthNotFound) // user not bound to external auth, try to bind or register new user
|
||||
if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierEmail {
|
||||
user, err = a.users.GetUserByEmail(c, oauth2UserInfo.Email)
|
||||
} else if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername {
|
||||
user, err = a.users.GetUserByUsername(c, oauth2UserInfo.UserName)
|
||||
} else {
|
||||
user, err = a.users.GetUserByEmail(c, oauth2UserInfo.Email)
|
||||
}
|
||||
if uid != 0 {
|
||||
user, err = a.users.GetUserById(c, uid)
|
||||
|
||||
if err != nil && !errors.Is(err, errs.ErrUserNotFound) {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] failed to get user, because %s", err.Error())
|
||||
return a.redirectToFailedCallbackPage(c, errs.Or(err, errs.ErrOperationFailed))
|
||||
if err != nil && !errors.Is(err, errs.ErrUserNotFound) {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] failed to get user by id %d, because %s", uid, err.Error())
|
||||
return a.redirectToFailedCallbackPage(c, errs.Or(err, errs.ErrOperationFailed))
|
||||
}
|
||||
} else {
|
||||
if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierEmail {
|
||||
user, err = a.users.GetUserByEmail(c, oauth2UserInfo.Email)
|
||||
} else if a.CurrentConfig().OAuth2UserIdentifier == settings.OAuth2UserIdentifierUsername {
|
||||
user, err = a.users.GetUserByUsername(c, oauth2UserInfo.UserName)
|
||||
} else {
|
||||
user, err = a.users.GetUserByEmail(c, oauth2UserInfo.Email)
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, errs.ErrUserNotFound) {
|
||||
log.Errorf(c, "[oauth2_authentications.CallbackHandler] failed to get user, because %s", err.Error())
|
||||
return a.redirectToFailedCallbackPage(c, errs.Or(err, errs.ErrOperationFailed))
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil && a.CurrentConfig().EnableUserRegister && a.CurrentConfig().OAuth2AutoRegister {
|
||||
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
|
||||
// Error codes related to oauth 2.0
|
||||
var (
|
||||
ErrOAuth2NotEnabled = NewNormalError(NormalSubcategoryOAuth2, 0, http.StatusBadRequest, "oauth2 not enabled")
|
||||
ErrOAuth2AutoRegistrationNotEnabled = NewNormalError(NormalSubcategoryOAuth2, 1, http.StatusBadRequest, "oauth2 auto registration not enabled")
|
||||
ErrInvalidOAuth2LoginRequest = NewNormalError(NormalSubcategoryOAuth2, 2, http.StatusBadRequest, "invalid oauth2 login request")
|
||||
ErrInvalidOAuth2Callback = NewNormalError(NormalSubcategoryOAuth2, 3, http.StatusBadRequest, "invalid oauth2 callback")
|
||||
ErrMissingOAuth2State = NewNormalError(NormalSubcategoryOAuth2, 4, http.StatusBadRequest, "missing state in oauth2 callback")
|
||||
ErrMissingOAuth2Code = NewNormalError(NormalSubcategoryOAuth2, 5, http.StatusBadRequest, "missing code in oauth2 callback")
|
||||
ErrInvalidOAuth2State = NewNormalError(NormalSubcategoryOAuth2, 6, http.StatusBadRequest, "invalid state in oauth2 callback")
|
||||
ErrCannotRetrieveOAuth2Token = NewNormalError(NormalSubcategoryOAuth2, 7, http.StatusBadRequest, "cannot retrieve oauth2 token")
|
||||
ErrInvalidOAuth2Token = NewNormalError(NormalSubcategoryOAuth2, 8, http.StatusBadRequest, "invalid oauth2 token")
|
||||
ErrCannotRetrieveUserInfo = NewNormalError(NormalSubcategoryOAuth2, 9, http.StatusBadRequest, "cannot retrieve user info from oauth2 provider")
|
||||
ErrOAuth2NotEnabled = NewNormalError(NormalSubcategoryOAuth2, 0, http.StatusBadRequest, "oauth2 not enabled")
|
||||
ErrOAuth2AutoRegistrationNotEnabled = NewNormalError(NormalSubcategoryOAuth2, 1, http.StatusBadRequest, "oauth2 auto registration not enabled")
|
||||
ErrInvalidOAuth2LoginRequest = NewNormalError(NormalSubcategoryOAuth2, 2, http.StatusBadRequest, "invalid oauth2 login request")
|
||||
ErrInvalidOAuth2Callback = NewNormalError(NormalSubcategoryOAuth2, 3, http.StatusBadRequest, "invalid oauth2 callback")
|
||||
ErrMissingOAuth2State = NewNormalError(NormalSubcategoryOAuth2, 4, http.StatusBadRequest, "missing state in oauth2 callback")
|
||||
ErrMissingOAuth2Code = NewNormalError(NormalSubcategoryOAuth2, 5, http.StatusBadRequest, "missing code in oauth2 callback")
|
||||
ErrInvalidOAuth2State = NewNormalError(NormalSubcategoryOAuth2, 6, http.StatusBadRequest, "invalid state in oauth2 callback")
|
||||
ErrCannotRetrieveOAuth2Token = NewNormalError(NormalSubcategoryOAuth2, 7, http.StatusBadRequest, "cannot retrieve oauth2 token")
|
||||
ErrInvalidOAuth2Token = NewNormalError(NormalSubcategoryOAuth2, 8, http.StatusBadRequest, "invalid oauth2 token")
|
||||
ErrCannotRetrieveUserInfo = NewNormalError(NormalSubcategoryOAuth2, 9, http.StatusBadRequest, "cannot retrieve user info from oauth2 provider")
|
||||
ErrOAuth2UserAlreadyBoundToAnotherUser = NewNormalError(NormalSubcategoryOAuth2, 10, http.StatusBadRequest, "oauth2 user already bound to another user")
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ package models
|
||||
type OAuth2LoginRequest struct {
|
||||
Platform string `form:"platform" binding:"required"`
|
||||
ClientSessionId string `form:"client_session_id" binding:"required"`
|
||||
Token string `form:"token"`
|
||||
}
|
||||
|
||||
// OAuth2CallbackRequest represents all parameters of OAuth 2.0 callback request
|
||||
@@ -18,4 +19,5 @@ type OAuth2CallbackRequest struct {
|
||||
type OAuth2CallbackLoginRequest struct {
|
||||
Password string `json:"password" binding:"omitempty,min=6,max=128"`
|
||||
Passcode string `json:"passcode" binding:"omitempty,notBlank,len=6"`
|
||||
Token string `json:"token" binding:"omitempty"`
|
||||
}
|
||||
|
||||
@@ -258,26 +258,35 @@ export default {
|
||||
return axios.post<ApiResponse<AuthResponse>>('2fa/authorize.json', {
|
||||
passcode: passcode
|
||||
}, {
|
||||
noAuth: true,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
} as ApiRequestConfig);
|
||||
},
|
||||
authorize2FAByBackupCode: ({ recoveryCode, token }: { recoveryCode: string, token: string }): ApiResponsePromise<AuthResponse> => {
|
||||
return axios.post<ApiResponse<AuthResponse>>('2fa/recovery.json', {
|
||||
recoveryCode: recoveryCode
|
||||
}, {
|
||||
noAuth: true,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
} as ApiRequestConfig);
|
||||
},
|
||||
authorizeOAuth2: ({ req, token }: { req: OAuth2CallbackLoginRequest, token: string }): ApiResponsePromise<AuthResponse> => {
|
||||
authorizeOAuth2: ({ password, passcode, callbackToken }: { password?: string, passcode?: string, callbackToken: string }): ApiResponsePromise<AuthResponse> => {
|
||||
const req: OAuth2CallbackLoginRequest = {
|
||||
password,
|
||||
passcode,
|
||||
token: getCurrentToken() || undefined
|
||||
};
|
||||
|
||||
return axios.post<ApiResponse<AuthResponse>>('oauth2/authorize.json', req, {
|
||||
noAuth: true,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
Authorization: `Bearer ${callbackToken}`
|
||||
}
|
||||
});
|
||||
} as ApiRequestConfig);
|
||||
},
|
||||
register: (req: UserRegisterRequest): ApiResponsePromise<RegisterResponse> => {
|
||||
return axios.post<ApiResponse<RegisterResponse>>('register.json', req);
|
||||
@@ -718,6 +727,9 @@ export default {
|
||||
generateOAuth2LoginUrl: (platform: 'mobile' | 'desktop', clientSessionId: string): string => {
|
||||
return `${getBasePath()}/oauth2/login?platform=${platform}&client_session_id=${clientSessionId}`;
|
||||
},
|
||||
generateOAuth2LinkUrl: (platform: 'mobile' | 'desktop', clientSessionId: string): string => {
|
||||
return `${getBasePath()}/oauth2/login?platform=${platform}&client_session_id=${clientSessionId}&token=${getCurrentToken()}`;
|
||||
},
|
||||
generateQrCodeUrl: (qrCodeName: string): string => {
|
||||
return `${getBasePath()}${BASE_QRCODE_PATH}/${qrCodeName}.png`;
|
||||
},
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "Abfrageelemente dürfen nicht leer sein",
|
||||
"query items too much": "Zu viele Abfrageelemente",
|
||||
"query items have invalid item": "Ungültiges Element in Abfrageelementen",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Benutzerdaten können nicht gelöscht werden",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "There are no query items",
|
||||
"query items too much": "There are too many query items",
|
||||
"query items have invalid item": "There is invalid item in query items",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Unable to clear user data",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "--",
|
||||
"query items too much": "--",
|
||||
"query items have invalid item": "Hay un elemento no válido en los elementos de consulta",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "No se pueden borrar los datos del usuario",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "Il n'y a pas d'éléments de requête",
|
||||
"query items too much": "Il y a trop d'éléments de requête",
|
||||
"query items have invalid item": "Il y a un élément invalide dans les éléments de requête",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Impossible d'effacer les données utilisateur",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "Non ci sono elementi di query",
|
||||
"query items too much": "Ci sono troppi elementi di query",
|
||||
"query items have invalid item": "C'è un elemento non valido negli elementi di query",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Impossibile cancellare i dati utente",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "クエリ項目がありません",
|
||||
"query items too much": "クエリ項目が多すぎます",
|
||||
"query items have invalid item": "クエリ項目に無効な項目があります",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "ユーザーデータをクリアできません",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "쿼리 항목이 비어 있을 수 없습니다.",
|
||||
"query items too much": "쿼리 항목이 너무 많습니다.",
|
||||
"query items have invalid item": "쿼리 항목에 유효하지 않은 항목이 있습니다.",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "사용자 데이터를 지울 수 없습니다.",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "Geen zoekitems opgegeven",
|
||||
"query items too much": "Te veel zoekitems",
|
||||
"query items have invalid item": "Ongeldig item in zoekitems",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Kan gebruikersgegevens niet wissen",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "Não há itens de consulta",
|
||||
"query items too much": "Há muitos itens de consulta",
|
||||
"query items have invalid item": "Há item inválido nos itens de consulta",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Não foi possível limpar os dados de usuário",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "Нет элементов запроса",
|
||||
"query items too much": "Слишком много элементов запроса",
|
||||
"query items have invalid item": "В элементах запроса присутствует недопустимый элемент",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Не удалось очистить данные пользователя",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "ไม่มีรายการสำหรับค้นหา",
|
||||
"query items too much": "รายการค้นหามากเกินไป",
|
||||
"query items have invalid item": "มีรายการไม่ถูกต้องในรายการค้นหา",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "ไม่สามารถลบข้อมูลผู้ใช้ได้",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "Елементи запиту не можуть бути порожніми",
|
||||
"query items too much": "Занадто багато елементів запиту",
|
||||
"query items have invalid item": "Запит містить недійсний елемент",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Не вдалося очистити дані",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "Cannot retrieve OAuth 2.0 token",
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"query items cannot be blank": "Không có mục truy vấn",
|
||||
"query items too much": "Có quá nhiều mục truy vấn",
|
||||
"query items have invalid item": "Có mục không hợp lệ trong các mục truy vấn",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "Không thể xóa dữ liệu người dùng",
|
||||
"Third-Party Logins": "Third-Party Logins",
|
||||
"Linked Time": "Linked Time",
|
||||
"Link": "Link",
|
||||
"Unlink": "Unlink",
|
||||
"Are you sure you want to unlink this login method?": "Are you sure you want to unlink this login method?",
|
||||
"Unable to retrieve third-party logins list": "Unable to retrieve third-party logins list",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "无法获取 OAuth 2.0 令牌",
|
||||
"invalid oauth2 token": "无效的 OAuth 2.0 令牌",
|
||||
"cannot retrieve user info from oauth2 provider": "无法从 OAuth 2.0 提供者获取用户信息",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 用户已经绑定到另一个用户",
|
||||
"query items cannot be blank": "请求项目不能为空",
|
||||
"query items too much": "请求项目过多",
|
||||
"query items have invalid item": "请求项目中有非法项目",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "无法清除用户数据",
|
||||
"Third-Party Logins": "第三方登录",
|
||||
"Linked Time": "关联时间",
|
||||
"Link": "关联",
|
||||
"Unlink": "取消关联",
|
||||
"Are you sure you want to unlink this login method?": "您确定要取消关联该登录方式?",
|
||||
"Unable to retrieve third-party logins list": "无法获取第三方登录列表",
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
"cannot retrieve oauth2 token": "無法獲取 OAuth 2.0 令牌",
|
||||
"invalid oauth2 token": "無效的 OAuth 2.0 令牌",
|
||||
"cannot retrieve user info from oauth2 provider": "無法從 OAuth 2.0 提供者獲取使用者資訊",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 使用者已綁定到另一個使用者",
|
||||
"query items cannot be blank": "查詢項目不能為空",
|
||||
"query items too much": "查詢項目過多",
|
||||
"query items have invalid item": "查詢項目中有非法項目",
|
||||
@@ -2154,6 +2155,7 @@
|
||||
"Unable to clear user data": "無法清除使用者資料",
|
||||
"Third-Party Logins": "第三方登入",
|
||||
"Linked Time": "連結時間",
|
||||
"Link": "連結",
|
||||
"Unlink": "取消連結",
|
||||
"Are you sure you want to unlink this login method?": "您確定要取消連結這個登入方式?",
|
||||
"Unable to retrieve third-party logins list": "無法取得第三方登入清單",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface OAuth2CallbackLoginRequest {
|
||||
readonly password?: string;
|
||||
readonly passcode?: string;
|
||||
readonly token?: string;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,10 @@ export const useRootStore = defineStore('root', () => {
|
||||
return services.generateOAuth2LoginUrl(platform, clientSessionId);
|
||||
}
|
||||
|
||||
function generateOAuth2LinkUrl(platform: 'mobile' | 'desktop', clientSessionId: string): string {
|
||||
return services.generateOAuth2LinkUrl(platform, clientSessionId);
|
||||
}
|
||||
|
||||
function authorize(req: UserLoginRequest): Promise<AuthResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.authorize(req).then(response => {
|
||||
@@ -191,14 +195,12 @@ export const useRootStore = defineStore('root', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function authorizeOAuth2({ password, passcode, token }: { password?: string, passcode?: string, token: string }): Promise<AuthResponse> {
|
||||
function authorizeOAuth2({ password, passcode, callbackToken }: { password?: string, passcode?: string, callbackToken: string }): Promise<AuthResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.authorizeOAuth2({
|
||||
req: {
|
||||
password,
|
||||
passcode
|
||||
},
|
||||
token
|
||||
password,
|
||||
passcode,
|
||||
callbackToken
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
@@ -643,6 +645,7 @@ export const useRootStore = defineStore('root', () => {
|
||||
// functions
|
||||
setNotificationContent,
|
||||
generateOAuth2LoginUrl,
|
||||
generateOAuth2LinkUrl,
|
||||
authorize,
|
||||
authorize2FA,
|
||||
authorizeOAuth2,
|
||||
|
||||
@@ -212,7 +212,7 @@ function verifyAndLogin(): void {
|
||||
rootStore.authorizeOAuth2({
|
||||
password: password.value,
|
||||
passcode: passcode.value,
|
||||
token: props.token || ''
|
||||
callbackToken: props.token || ''
|
||||
}).then(authResponse => {
|
||||
loggingInByOAuth2.value = false;
|
||||
doAfterLogin(authResponse);
|
||||
@@ -238,7 +238,7 @@ if (!error.value && props.platform && props.token && !props.userName) {
|
||||
loggingInByOAuth2.value = true;
|
||||
|
||||
rootStore.authorizeOAuth2({
|
||||
token: props.token
|
||||
callbackToken: props.token
|
||||
}).then(authResponse => {
|
||||
loggingInByOAuth2.value = false;
|
||||
doAfterLogin(authResponse);
|
||||
|
||||
@@ -105,6 +105,14 @@
|
||||
<td class="text-sm">{{ thirdPartyLogin.externalUsername }}</td>
|
||||
<td class="text-sm">{{ thirdPartyLogin.createdAt }}</td>
|
||||
<td class="text-sm text-right">
|
||||
<v-btn density="comfortable" variant="tonal"
|
||||
:disabled="loggingInByOAuth2"
|
||||
:href="oauth2LinkUrl"
|
||||
@click="loggingInByOAuth2 = true"
|
||||
v-if="!thirdPartyLogin.linked && isOAuth2Enabled() && getOAuth2Provider() === thirdPartyLogin.externalAuthType">
|
||||
{{ tt('Link') }}
|
||||
<v-progress-circular indeterminate size="22" class="ms-2" v-if="loggingInByOAuth2"></v-progress-circular>
|
||||
</v-btn>
|
||||
<v-btn density="comfortable" color="error" variant="tonal"
|
||||
:disabled="loadingExternalAuth"
|
||||
@click="unlinkExternalAuth(thirdPartyLogin)"
|
||||
@@ -209,7 +217,8 @@ import { type TokenInfoResponse, SessionInfo } from '@/models/token.ts';
|
||||
|
||||
import { isEquals } from '@/lib/common.ts';
|
||||
import { parseSessionInfo } from '@/lib/session.ts';
|
||||
import { isOAuth2Enabled, getOIDCCustomDisplayNames, isMCPServerEnabled } from '@/lib/server_settings.ts';
|
||||
import { isOAuth2Enabled, getOAuth2Provider, getOIDCCustomDisplayNames, isMCPServerEnabled } from '@/lib/server_settings.ts';
|
||||
import { generateRandomUUID } from '@/lib/misc.ts';
|
||||
|
||||
import {
|
||||
mdiRefresh,
|
||||
@@ -314,6 +323,10 @@ const confirmPassword = ref<string>('');
|
||||
const updatingPassword = ref<boolean>(false);
|
||||
const loadingExternalAuth = ref<boolean>(true);
|
||||
const loadingSession = ref<boolean>(true);
|
||||
const loggingInByOAuth2 = ref<boolean>(false);
|
||||
const oauth2ClientSessionId = ref<string>(generateRandomUUID());
|
||||
|
||||
const oauth2LinkUrl = computed<string>(() => rootStore.generateOAuth2LinkUrl('desktop', oauth2ClientSessionId.value));
|
||||
|
||||
const thirdPartyLogins = computed<DesktopPageLinkedThirdPartyLogin[]>(() => {
|
||||
const logins: DesktopPageLinkedThirdPartyLogin[] = [];
|
||||
|
||||
Reference in New Issue
Block a user