diff --git a/pkg/api/authorizations.go b/pkg/api/authorizations.go index 9717f51d..0438f104 100644 --- a/pkg/api/authorizations.go +++ b/pkg/api/authorizations.go @@ -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) diff --git a/pkg/api/oauth2_authentications.go b/pkg/api/oauth2_authentications.go index 461acec9..df5511a3 100644 --- a/pkg/api/oauth2_authentications.go +++ b/pkg/api/oauth2_authentications.go @@ -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 { diff --git a/pkg/errs/oauth2.go b/pkg/errs/oauth2.go index 88736648..8f1b274b 100644 --- a/pkg/errs/oauth2.go +++ b/pkg/errs/oauth2.go @@ -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") ) diff --git a/pkg/models/oauth2.go b/pkg/models/oauth2.go index f1667154..fd045f8c 100644 --- a/pkg/models/oauth2.go +++ b/pkg/models/oauth2.go @@ -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"` } diff --git a/src/lib/services.ts b/src/lib/services.ts index 471ec347..52bb5541 100644 --- a/src/lib/services.ts +++ b/src/lib/services.ts @@ -258,26 +258,35 @@ export default { return axios.post>('2fa/authorize.json', { passcode: passcode }, { + noAuth: true, headers: { Authorization: `Bearer ${token}` } - }); + } as ApiRequestConfig); }, authorize2FAByBackupCode: ({ recoveryCode, token }: { recoveryCode: string, token: string }): ApiResponsePromise => { return axios.post>('2fa/recovery.json', { recoveryCode: recoveryCode }, { + noAuth: true, headers: { Authorization: `Bearer ${token}` } - }); + } as ApiRequestConfig); }, - authorizeOAuth2: ({ req, token }: { req: OAuth2CallbackLoginRequest, token: string }): ApiResponsePromise => { + authorizeOAuth2: ({ password, passcode, callbackToken }: { password?: string, passcode?: string, callbackToken: string }): ApiResponsePromise => { + const req: OAuth2CallbackLoginRequest = { + password, + passcode, + token: getCurrentToken() || undefined + }; + return axios.post>('oauth2/authorize.json', req, { + noAuth: true, headers: { - Authorization: `Bearer ${token}` + Authorization: `Bearer ${callbackToken}` } - }); + } as ApiRequestConfig); }, register: (req: UserRegisterRequest): ApiResponsePromise => { return axios.post>('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`; }, diff --git a/src/locales/de.json b/src/locales/de.json index 83ece860..31fb12f1 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -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", diff --git a/src/locales/en.json b/src/locales/en.json index d39a81d0..d397038c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -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", diff --git a/src/locales/es.json b/src/locales/es.json index 40dddd8f..9b2b6e5c 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -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", diff --git a/src/locales/fr.json b/src/locales/fr.json index 4f5b70f5..d90698fd 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -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", diff --git a/src/locales/it.json b/src/locales/it.json index f8746afb..a93848cd 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -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", diff --git a/src/locales/ja.json b/src/locales/ja.json index 589e738d..6149f5f6 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -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", diff --git a/src/locales/ko.json b/src/locales/ko.json index 2ec50a59..176028e7 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -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", diff --git a/src/locales/nl.json b/src/locales/nl.json index f359105d..00fd25cd 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -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", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 18fe0210..27ba4579 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -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", diff --git a/src/locales/ru.json b/src/locales/ru.json index b6a6d2bd..944464c9 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -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", diff --git a/src/locales/th.json b/src/locales/th.json index a2f346dd..b5d9139d 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -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", diff --git a/src/locales/uk.json b/src/locales/uk.json index 45362009..4003882a 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -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", diff --git a/src/locales/vi.json b/src/locales/vi.json index 7bd70aaf..ada7f906 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -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", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index b07fecab..5dbe0004 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -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": "无法获取第三方登录列表", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index ac5d0031..7b7ffb7e 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -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": "無法取得第三方登入清單", diff --git a/src/models/oauth2.ts b/src/models/oauth2.ts index ddb65684..9cc9903f 100644 --- a/src/models/oauth2.ts +++ b/src/models/oauth2.ts @@ -1,4 +1,5 @@ export interface OAuth2CallbackLoginRequest { readonly password?: string; readonly passcode?: string; + readonly token?: string; } diff --git a/src/stores/index.ts b/src/stores/index.ts index 06d856bd..9d962093 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -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 { 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 { + function authorizeOAuth2({ password, passcode, callbackToken }: { password?: string, passcode?: string, callbackToken: string }): Promise { 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, diff --git a/src/views/desktop/OAuth2CallbackPage.vue b/src/views/desktop/OAuth2CallbackPage.vue index 7c640355..c28ca666 100644 --- a/src/views/desktop/OAuth2CallbackPage.vue +++ b/src/views/desktop/OAuth2CallbackPage.vue @@ -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); diff --git a/src/views/desktop/user/settings/tabs/UserSecuritySettingTab.vue b/src/views/desktop/user/settings/tabs/UserSecuritySettingTab.vue index b2fc99c9..5217cb0e 100644 --- a/src/views/desktop/user/settings/tabs/UserSecuritySettingTab.vue +++ b/src/views/desktop/user/settings/tabs/UserSecuritySettingTab.vue @@ -105,6 +105,14 @@ {{ thirdPartyLogin.externalUsername }} {{ thirdPartyLogin.createdAt }} + + {{ tt('Link') }} + + (''); const updatingPassword = ref(false); const loadingExternalAuth = ref(true); const loadingSession = ref(true); +const loggingInByOAuth2 = ref(false); +const oauth2ClientSessionId = ref(generateRandomUUID()); + +const oauth2LinkUrl = computed(() => rootStore.generateOAuth2LinkUrl('desktop', oauth2ClientSessionId.value)); const thirdPartyLogins = computed(() => { const logins: DesktopPageLinkedThirdPartyLogin[] = [];