diff --git a/cmd/webserver.go b/cmd/webserver.go index a6394fbe..b3e10527 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -336,6 +336,10 @@ func startWebServer(c *core.CliContext) error { apiV1Route.POST("/users/verify_email/resend.json", bindApi(api.Users.UserSendVerifyEmailByLoginedUserHandler)) } + // External Authentications + apiV1Route.GET("/users/external_auth/list.json", bindApi(api.UserExternalAuths.ExternalAuthListHanlder)) + apiV1Route.POST("/users/external_auth/unlink.json", bindApi(api.UserExternalAuths.UnlinkExternalAuthHandler)) + // Application Cloud Settings apiV1Route.GET("/users/settings/cloud/get.json", bindApi(api.UserApplicationCloudSettings.ApplicationSettingsGetHandler)) apiV1Route.POST("/users/settings/cloud/update.json", bindApi(api.UserApplicationCloudSettings.ApplicationSettingsUpdateHandler)) diff --git a/pkg/api/user_external_auths.go b/pkg/api/user_external_auths.go new file mode 100644 index 00000000..e8486771 --- /dev/null +++ b/pkg/api/user_external_auths.go @@ -0,0 +1,104 @@ +package api + +import ( + "sort" + + "github.com/mayswind/ezbookkeeping/pkg/auth/oauth2" + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/errs" + "github.com/mayswind/ezbookkeeping/pkg/log" + "github.com/mayswind/ezbookkeeping/pkg/models" + "github.com/mayswind/ezbookkeeping/pkg/services" +) + +// UserExternalAuthsApi represents user external auth api +type UserExternalAuthsApi struct { + users *services.UserService + userExternalAuths *services.UserExternalAuthService +} + +// Initialize a user external auth api singleton instance +var ( + UserExternalAuths = &UserExternalAuthsApi{ + users: services.Users, + userExternalAuths: services.UserExternalAuths, + } +) + +// ExternalAuthListHanlder returns external authentications list of current user +func (a *UserExternalAuthsApi) ExternalAuthListHanlder(c *core.WebContext) (any, *errs.Error) { + uid := c.GetCurrentUid() + userExternalAuths, err := a.userExternalAuths.GetUserAllExternalAuthsByUid(c, uid) + + if err != nil { + log.Errorf(c, "[user_external_auths.ExternalAuthListHanlder] failed to get all external authentications for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + userExternalAuthResps := make(models.UserExternalAuthInfoResponsesSlice, 0, len(userExternalAuths)+1) + currentExternalAuthType := oauth2.GetExternalUserAuthType() + hasCurrentExternalAuth := false + + for i := 0; i < len(userExternalAuths); i++ { + userExternalAuth := userExternalAuths[i] + + if userExternalAuth.ExternalAuthType == currentExternalAuthType { + hasCurrentExternalAuth = true + } + + userExternalAuthResps = append(userExternalAuthResps, userExternalAuth.ToUserExternalAuthInfoResponse()) + } + + if !hasCurrentExternalAuth { + userExternalAuthResps = append(userExternalAuthResps, &models.UserExternalAuthInfoResponse{ + ExternalAuthCategory: currentExternalAuthType.GetCategory(), + ExternalAuthType: currentExternalAuthType, + Linked: false, + }) + } + + sort.Sort(userExternalAuthResps) + + return userExternalAuthResps, nil +} + +// UnlinkExternalAuthHandler unlinks external authentication for current user +func (a *UserExternalAuthsApi) UnlinkExternalAuthHandler(c *core.WebContext) (any, *errs.Error) { + var externalAuthLinkReq models.UserExternalAuthUnlinkRequest + err := c.ShouldBindJSON(&externalAuthLinkReq) + + if err != nil { + log.Warnf(c, "[user_external_auths.UnlinkExternalAuthHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + user, err := a.users.GetUserById(c, uid) + + if err != nil { + if !errs.IsCustomError(err) { + log.Warnf(c, "[user_external_auths.UnlinkExternalAuthHandler] failed to get user for user \"uid:%d\", because %s", uid, err.Error()) + } + + return nil, errs.ErrUserNotFound + } + + if !a.users.IsPasswordEqualsUserPassword(externalAuthLinkReq.Password, user) { + return nil, errs.ErrUserPasswordWrong + } + + externalAuthType := core.UserExternalAuthType(externalAuthLinkReq.ExternalAuthType) + + if !externalAuthType.IsValid() { + return nil, errs.ErrUserExternalAuthNotFound + } + + err = a.userExternalAuths.DeleteUserExternalAuth(c, uid, externalAuthType) + + if err != nil { + log.Errorf(c, "[user_external_auths.UnlinkExternalAuthHandler] failed to unlink external authentication \"%s\" for user \"uid:%d\", because %s", externalAuthLinkReq.ExternalAuthType, uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + return true, nil +} diff --git a/pkg/core/user_external_auth_type.go b/pkg/core/user_external_auth_type.go index 9c7057d9..a0162cc8 100644 --- a/pkg/core/user_external_auth_type.go +++ b/pkg/core/user_external_auth_type.go @@ -1,5 +1,7 @@ package core +const USER_EXTERNAL_AUTH_TYPE_CATEOGRY_OAUTH2 = "oauth2" + // UserExternalAuthType represents the type of user external authentication type UserExternalAuthType string @@ -11,14 +13,19 @@ const ( USER_EXTERNAL_AUTH_TYPE_OAUTH2_GITHUB UserExternalAuthType = "github" ) -// IsValid checks if the UserExternalAuthType is valid -func (t UserExternalAuthType) IsValid() bool { +// GetCategory returns the category of the UserExternalAuthType +func (t UserExternalAuthType) GetCategory() string { switch t { case USER_EXTERNAL_AUTH_TYPE_OAUTH2_OIDC, USER_EXTERNAL_AUTH_TYPE_OAUTH2_NEXTCLOUD, USER_EXTERNAL_AUTH_TYPE_OAUTH2_GITEA, USER_EXTERNAL_AUTH_TYPE_OAUTH2_GITHUB: - return true + return USER_EXTERNAL_AUTH_TYPE_CATEOGRY_OAUTH2 } - return false + return "" +} + +// IsValid checks if the UserExternalAuthType is valid +func (t UserExternalAuthType) IsValid() bool { + return t.GetCategory() != "" } diff --git a/pkg/errs/external_auth.go b/pkg/errs/external_auth.go index cebc5b66..368bc671 100644 --- a/pkg/errs/external_auth.go +++ b/pkg/errs/external_auth.go @@ -8,4 +8,5 @@ import ( var ( ErrUserExternalAuthNotFound = NewNormalError(NormalSubcategoryUserExternalAuth, 0, http.StatusBadRequest, "user external auth is not found") ErrUserExternalAuthAlreadyExists = NewNormalError(NormalSubcategoryUserExternalAuth, 1, http.StatusBadRequest, "user external auth already exists") + ErrUserExternalAuthTypeInvalid = NewNormalError(NormalSubcategoryUserExternalAuth, 2, http.StatusBadRequest, "user external auth type invalid") ) diff --git a/pkg/models/user_external_auth.go b/pkg/models/user_external_auth.go index 49fcc406..836ffdf6 100644 --- a/pkg/models/user_external_auth.go +++ b/pkg/models/user_external_auth.go @@ -11,7 +11,54 @@ type UserExternalAuth struct { CreatedUnixTime int64 } -// UserExternalAuthRevokeRequest represents all parameters of user external auth revoke request -type UserExternalAuthRevokeRequest struct { - ExternalAuthType core.UserExternalAuthType `json:"externalAuthType" binding:"required,notBlank"` +// UserExternalAuthUnlinkRequest represents all parameters of user external auth unlink request +type UserExternalAuthUnlinkRequest struct { + ExternalAuthType string `json:"externalAuthType" binding:"required,notBlank"` + Password string `json:"password" binding:"required,min=6,max=128"` +} + +// UserExternalAuthInfoResponse represents a view-object of user external auth +type UserExternalAuthInfoResponse struct { + ExternalAuthCategory string `json:"externalAuthCategory"` + ExternalAuthType core.UserExternalAuthType `json:"externalAuthType"` + Linked bool `json:"linked"` + ExternalUsername string `json:"externalUsername,omitempty"` + CreatedAt int64 `json:"createdAt,omitempty"` +} + +// ToUserExternalAuthInfoResponse returns a view-object according to database model +func (a *UserExternalAuth) ToUserExternalAuthInfoResponse() *UserExternalAuthInfoResponse { + return &UserExternalAuthInfoResponse{ + ExternalAuthCategory: a.ExternalAuthType.GetCategory(), + ExternalAuthType: a.ExternalAuthType, + Linked: true, + ExternalUsername: a.ExternalUsername, + CreatedAt: a.CreatedUnixTime, + } +} + +// UserExternalAuthInfoResponsesSlice represents the slice data structure of UserExternalAuthInfoResponse +type UserExternalAuthInfoResponsesSlice []*UserExternalAuthInfoResponse + +// Len returns the count of items +func (a UserExternalAuthInfoResponsesSlice) Len() int { + return len(a) +} + +// Swap swaps two items +func (a UserExternalAuthInfoResponsesSlice) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +// Less reports whether the first item is less than the second one +func (a UserExternalAuthInfoResponsesSlice) Less(i, j int) bool { + if a[i].Linked && !a[j].Linked { + return true + } else if !a[i].Linked && a[j].Linked { + return false + } else if !a[i].Linked && !a[j].Linked { + return a[i].ExternalAuthType < a[j].ExternalAuthType + } + + return a[i].CreatedAt > a[j].CreatedAt } diff --git a/pkg/models/user_external_auth_test.go b/pkg/models/user_external_auth_test.go new file mode 100644 index 00000000..4e6b29c0 --- /dev/null +++ b/pkg/models/user_external_auth_test.go @@ -0,0 +1,41 @@ +package models + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/mayswind/ezbookkeeping/pkg/core" +) + +func TestUserExternalAuthInfoResponsesSliceLess(t *testing.T) { + var userExternalAuthInfoResponsesSlice UserExternalAuthInfoResponsesSlice + userExternalAuthInfoResponsesSlice = append(userExternalAuthInfoResponsesSlice, &UserExternalAuthInfoResponse{ + ExternalAuthType: core.USER_EXTERNAL_AUTH_TYPE_OAUTH2_OIDC, + Linked: true, + ExternalUsername: "test", + CreatedAt: int64(1), + }) + userExternalAuthInfoResponsesSlice = append(userExternalAuthInfoResponsesSlice, &UserExternalAuthInfoResponse{ + ExternalAuthType: core.USER_EXTERNAL_AUTH_TYPE_OAUTH2_NEXTCLOUD, + Linked: false, + }) + userExternalAuthInfoResponsesSlice = append(userExternalAuthInfoResponsesSlice, &UserExternalAuthInfoResponse{ + ExternalAuthType: core.USER_EXTERNAL_AUTH_TYPE_OAUTH2_GITEA, + Linked: false, + }) + userExternalAuthInfoResponsesSlice = append(userExternalAuthInfoResponsesSlice, &UserExternalAuthInfoResponse{ + ExternalAuthType: core.USER_EXTERNAL_AUTH_TYPE_OAUTH2_GITHUB, + Linked: true, + ExternalUsername: "test4", + CreatedAt: int64(2), + }) + + sort.Sort(userExternalAuthInfoResponsesSlice) + + assert.Equal(t, core.USER_EXTERNAL_AUTH_TYPE_OAUTH2_GITHUB, userExternalAuthInfoResponsesSlice[0].ExternalAuthType) + assert.Equal(t, core.USER_EXTERNAL_AUTH_TYPE_OAUTH2_OIDC, userExternalAuthInfoResponsesSlice[1].ExternalAuthType) + assert.Equal(t, core.USER_EXTERNAL_AUTH_TYPE_OAUTH2_GITEA, userExternalAuthInfoResponsesSlice[2].ExternalAuthType) + assert.Equal(t, core.USER_EXTERNAL_AUTH_TYPE_OAUTH2_NEXTCLOUD, userExternalAuthInfoResponsesSlice[3].ExternalAuthType) +} diff --git a/src/lib/services.ts b/src/lib/services.ts index c5304f53..471ec347 100644 --- a/src/lib/services.ts +++ b/src/lib/services.ts @@ -135,6 +135,10 @@ import type { UserProfileUpdateRequest, UserProfileUpdateResponse } from '@/models/user.ts'; +import type { + UserExternalAuthUnlinkRequest, + UserExternalAuthInfoResponse +} from '@/models/user_external_auth.ts'; import type { OAuth2CallbackLoginRequest } from '@/models/oauth2.ts'; @@ -323,6 +327,12 @@ export default { }); }); }, + getExternalAuths: (): ApiResponsePromise => { + return axios.get>('v1/users/external_auth/list.json'); + }, + unlinkExternalAuth: (req: UserExternalAuthUnlinkRequest): ApiResponsePromise => { + return axios.post>('v1/users/external_auth/unlink.json', req); + }, getTokens: (): ApiResponsePromise => { return axios.get>('v1/tokens/list.json'); }, diff --git a/src/locales/de.json b/src/locales/de.json index a56bae71..11172bef 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "Alle Benutzerdaten wurden gelöscht", "Unable to clear user data": "Benutzerdaten können nicht gelöscht werden", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Geräte & Sitzungen", "Device Info": "Geräteinformationen", "Last Activity Time": "Letzte Aktivitätszeit", diff --git a/src/locales/en.json b/src/locales/en.json index b3e6f089..3788e1b4 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "All user data has been cleared", "Unable to clear user data": "Unable to clear user data", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Device & Sessions", "Device Info": "Device Info", "Last Activity Time": "Last Activity Time", diff --git a/src/locales/es.json b/src/locales/es.json index 9ac88f8b..8aef13b2 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "Todos los datos del usuario han sido borrados.", "Unable to clear user data": "No se pueden borrar los datos del usuario", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Dispositivo y sesiones", "Device Info": "Información del dispositivo", "Last Activity Time": "Hora de la última actividad", diff --git a/src/locales/fr.json b/src/locales/fr.json index 4f090853..59a9974d 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "Aucune information de transaction détectée", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "Toutes les transactions ont été effacées", "All user data has been cleared": "Toutes les données utilisateur ont été effacées", "Unable to clear user data": "Impossible d'effacer les données utilisateur", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Appareils et sessions", "Device Info": "Informations sur l'appareil", "Last Activity Time": "Heure de dernière activité", diff --git a/src/locales/it.json b/src/locales/it.json index 1cb47b64..2a9a0074 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "Tutti i dati utente sono stati cancellati", "Unable to clear user data": "Impossibile cancellare i dati utente", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Dispositivo e sessioni", "Device Info": "Info dispositivo", "Last Activity Time": "Ora ultima attività", diff --git a/src/locales/ja.json b/src/locales/ja.json index 5aa6c76b..8d2e9333 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "ユーザーデータがすべてクリアされました", "Unable to clear user data": "ユーザーデータをクリアできません", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "デバイスとセッション", "Device Info": "デバイス情報", "Last Activity Time": "最後のアクティビティ時間", diff --git a/src/locales/ko.json b/src/locales/ko.json index 2e2be30d..dfe07018 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "거래 정보가 감지되지 않았습니다.", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "모든 거래가 지워졌습니다.", "All user data has been cleared": "모든 사용자 데이터가 지워졌습니다.", "Unable to clear user data": "사용자 데이터를 지울 수 없습니다.", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "장치 및 세션", "Device Info": "장치 정보", "Last Activity Time": "마지막 활동 시간", diff --git a/src/locales/nl.json b/src/locales/nl.json index 8eae769f..6fbea4b8 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "Alle gebruikersgegevens zijn gewist", "Unable to clear user data": "Kan gebruikersgegevens niet wissen", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Apparaten & Sessies", "Device Info": "Apparaatgegevens", "Last Activity Time": "Laatste activiteit", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 802b6fd3..9d6b78d0 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "Todos os dados de usuário foram apagados", "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", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Dispositivo e Sessões", "Device Info": "Informações do Dispositivo", "Last Activity Time": "Última Hora de Atividade", diff --git a/src/locales/ru.json b/src/locales/ru.json index dacf29ed..4baea46a 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "Все данные пользователя были очищены", "Unable to clear user data": "Не удалось очистить данные пользователя", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Устройства и сессии", "Device Info": "Информация об устройстве", "Last Activity Time": "Время последней активности", diff --git a/src/locales/th.json b/src/locales/th.json index bf79a8cc..9aa7d08a 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "ไม่พบข้อมูลธุรกรรม", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "รายการทั้งหมดถูกลบเรียบร้อยแล้ว", "All user data has been cleared": "ข้อมูลผู้ใช้ทั้งหมดถูกลบเรียบร้อยแล้ว", "Unable to clear user data": "ไม่สามารถลบข้อมูลผู้ใช้ได้", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "อุปกรณ์ & เซสชัน", "Device Info": "ข้อมูลอุปกรณ์", "Last Activity Time": "เวลาทำกิจกรรมล่าสุด", diff --git a/src/locales/uk.json b/src/locales/uk.json index b6900759..95ab8aba 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "Усі дані користувача очищено", "Unable to clear user data": "Не вдалося очистити дані", + "Third-Party Logins": "Third-Party Logins", + "Linked Time": "Linked Time", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Пристрої та сесії", "Device Info": "Інформація про пристрій", "Last Activity Time": "Час останньої активності", diff --git a/src/locales/vi.json b/src/locales/vi.json index 9242bc11..22b78e7f 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "No transaction information detected", "user external auth is not found": "User external authentication data not found", "user external auth already exists": "User external authentication data already exists, please unlink it first", + "user external auth type invalid": "User external authentication type is invalid", "oauth2 not enabled": "OAuth 2.0 is not enabled", "oauth2 auto registration not enabled": "OAuth 2.0 auto registration is not enabled", "invalid oauth2 login request": "Invalid OAuth 2.0 login request", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "All transactions has been cleared", "All user data has been cleared": "Tất cả dữ liệu người dùng đã bị xóa", "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", + "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", + "Third-party logins list is up to date": "Third-party logins list is up to date", + "Third-party logins list has been updated": "Third-party logins list has been updated", + "Unable to unlink third-party login": "Unable to unlink third-party login", "Device & Sessions": "Thiết bị & Phiên", "Device Info": "Thông tin thiết bị", "Last Activity Time": "Thời gian hoạt động gần nhất", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index 3a18053b..2cca7b6d 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "没有检测到交易信息", "user external auth is not found": "找不到用户外部认证数据", "user external auth already exists": "用户外部认证数据已存在,请先解绑", + "user external auth type invalid": "用户外部认证类型无效", "oauth2 not enabled": "OAuth 2.0 没有启用", "oauth2 auto registration not enabled": "OAuth 2.0 自动注册没有启用", "invalid oauth2 login request": "无效的 OAuth 2.0 登录请求", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "所有交易已经清空", "All user data has been cleared": "用户所有数据已经清空", "Unable to clear user data": "无法清除用户数据", + "Third-Party Logins": "第三方登录", + "Linked Time": "关联时间", + "Unlink": "取消关联", + "Are you sure you want to unlink this login method?": "您确定要取消关联该登录方式?", + "Unable to retrieve third-party logins list": "无法获取第三方登录列表", + "Third-party logins list is up to date": "第三方登录列表已是最新", + "Third-party logins list has been updated": "关联账户列表已更新", + "Unable to unlink third-party login": "无法取消关联第三方登录", "Device & Sessions": "设备和会话", "Device Info": "设备信息", "Last Activity Time": "最后活跃时间", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index 751c1e58..3398d713 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -1245,6 +1245,7 @@ "no transaction information detected": "沒有檢測到交易資訊", "user external auth is not found": "找不到使用者外部驗證資料", "user external auth already exists": "使用者外部驗證資料已存在,請先解除連結", + "user external auth type invalid": "使用者外部驗證類型無效", "oauth2 not enabled": "OAuth 2.0 未啟用", "oauth2 auto registration not enabled": "OAuth 2.0 自動註冊未啟用", "invalid oauth2 login request": "無效的 OAuth 2.0 登入請求", @@ -2144,6 +2145,14 @@ "All transactions has been cleared": "所有交易已經清空", "All user data has been cleared": "使用者所有資料已經清空", "Unable to clear user data": "無法清除使用者資料", + "Third-Party Logins": "第三方登入", + "Linked Time": "連結時間", + "Unlink": "取消連結", + "Are you sure you want to unlink this login method?": "您確定要取消連結這個登入方式?", + "Unable to retrieve third-party logins list": "無法取得第三方登入清單", + "Third-party logins list is up to date": "第三方登入清單已是最新", + "Third-party logins list has been updated": "連結的帳戶清單已更新", + "Unable to unlink third-party login": "無法取消連結第三方登入", "Device & Sessions": "裝置和會話", "Device Info": "裝置資訊", "Last Activity Time": "最後活動時間", diff --git a/src/models/user_external_auth.ts b/src/models/user_external_auth.ts new file mode 100644 index 00000000..6206f38d --- /dev/null +++ b/src/models/user_external_auth.ts @@ -0,0 +1,12 @@ +export interface UserExternalAuthUnlinkRequest { + readonly externalAuthType: string; + readonly password: string; +} + +export interface UserExternalAuthInfoResponse { + readonly externalAuthCategory: string; + readonly externalAuthType: string; + readonly linked: boolean; + readonly externalUsername?: string; + readonly createdAt?: number; +} diff --git a/src/stores/userExternalAuth.ts b/src/stores/userExternalAuth.ts new file mode 100644 index 00000000..b7880243 --- /dev/null +++ b/src/stores/userExternalAuth.ts @@ -0,0 +1,64 @@ +import { defineStore } from 'pinia'; + +import type {UserExternalAuthInfoResponse, UserExternalAuthUnlinkRequest} from '@/models/user_external_auth.ts'; + +import logger from '@/lib/logger.ts'; +import services from '@/lib/services.ts'; + +export const useUserExternalAuthStore = defineStore('userExternalAUth', () => { + function getExternalAuths(): Promise { + return new Promise((resolve, reject) => { + services.getExternalAuths().then(response => { + const data = response.data; + + if (!data || !data.success || !data.result) { + reject({ message: 'Unable to retrieve third-party logins list' }); + return; + } + + resolve(data.result); + }).catch(error => { + logger.error('failed to load third-party logins list', error); + + if (error.response && error.response.data && error.response.data.errorMessage) { + reject({ error: error.response.data }); + } else if (!error.processed) { + reject({ message: 'Unable to retrieve third-party logins list' }); + } else { + reject(error); + } + }); + }); + } + + function unlinkExternalAuth(req: UserExternalAuthUnlinkRequest): Promise { + return new Promise((resolve, reject) => { + services.unlinkExternalAuth(req).then(response => { + const data = response.data; + + if (!data || !data.success || !data.result) { + reject({ message: 'Unable to unlink third-party login' }); + return; + } + + resolve(data.result); + }).catch(error => { + logger.error('failed to revoke token', error); + + if (error.response && error.response.data && error.response.data.errorMessage) { + reject({ error: error.response.data }); + } else if (!error.processed) { + reject({ message: 'Unable to unlink third-party login' }); + } else { + reject(error); + } + }); + }); + } + + return { + // functions + getExternalAuths, + unlinkExternalAuth + }; +}); diff --git a/src/views/desktop/user/settings/dialogs/UnlinkThirdPartyLoginDialog.vue b/src/views/desktop/user/settings/dialogs/UnlinkThirdPartyLoginDialog.vue new file mode 100644 index 00000000..9f64ede0 --- /dev/null +++ b/src/views/desktop/user/settings/dialogs/UnlinkThirdPartyLoginDialog.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/views/desktop/user/settings/tabs/UserSecuritySettingTab.vue b/src/views/desktop/user/settings/tabs/UserSecuritySettingTab.vue index b22455a5..116918bf 100644 --- a/src/views/desktop/user/settings/tabs/UserSecuritySettingTab.vue +++ b/src/views/desktop/user/settings/tabs/UserSecuritySettingTab.vue @@ -63,6 +63,61 @@ + + + + + + + + {{ tt('Type') }} + {{ tt('Username') }} + {{ tt('Linked Time') }} + {{ tt('Operation') }} + + + + + + + + + + + + + {{ thirdPartyLogin.displayName }} + + {{ thirdPartyLogin.externalUsername }} + {{ thirdPartyLogin.createdAt }} + + + {{ tt('Unlink') }} + + + + + + + +