support unlinking external authentication
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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() != ""
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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<UserExternalAuthInfoResponse[]> => {
|
||||
return axios.get<ApiResponse<UserExternalAuthInfoResponse[]>>('v1/users/external_auth/list.json');
|
||||
},
|
||||
unlinkExternalAuth: (req: UserExternalAuthUnlinkRequest): ApiResponsePromise<boolean> => {
|
||||
return axios.post<ApiResponse<boolean>>('v1/users/external_auth/unlink.json', req);
|
||||
},
|
||||
getTokens: (): ApiResponsePromise<TokenInfoResponse[]> => {
|
||||
return axios.get<ApiResponse<TokenInfoResponse[]>>('v1/tokens/list.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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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é",
|
||||
|
||||
@@ -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à",
|
||||
|
||||
@@ -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": "最後のアクティビティ時間",
|
||||
|
||||
@@ -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": "마지막 활동 시간",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Время последней активности",
|
||||
|
||||
@@ -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": "เวลาทำกิจกรรมล่าสุด",
|
||||
|
||||
@@ -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": "Час останньої активності",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "最后活跃时间",
|
||||
|
||||
@@ -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": "最後活動時間",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<UserExternalAuthInfoResponse[]> {
|
||||
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<boolean> {
|
||||
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
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<v-dialog width="640" :persistent="true" v-model="showState">
|
||||
<v-card class="pa-2 pa-sm-4 pa-md-4">
|
||||
<template #title>
|
||||
<div class="d-flex align-center justify-center">
|
||||
<h4 class="text-h4 text-error text-wrap">{{ tt('Are you sure you want to unlink this login method?') }}</h4>
|
||||
</div>
|
||||
</template>
|
||||
<v-card-text class="mb-md-4 w-100 d-flex justify-center">
|
||||
<div class="w-100">
|
||||
<v-text-field
|
||||
autocomplete="current-password"
|
||||
type="password"
|
||||
variant="underlined"
|
||||
color="error"
|
||||
:disabled="unlinking"
|
||||
:placeholder="tt('Current Password')"
|
||||
v-model="currentPassword"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-text class="overflow-y-visible">
|
||||
<div class="w-100 d-flex justify-center gap-4">
|
||||
<v-btn color="error" :disabled="!currentPassword || unlinking" @click="confirm">
|
||||
{{ tt('Confirm') }}
|
||||
<v-progress-circular indeterminate size="22" class="ms-2" v-if="unlinking"></v-progress-circular>
|
||||
</v-btn>
|
||||
<v-btn color="secondary" variant="tonal" :disabled="unlinking" @click="cancel">
|
||||
{{ tt('Cancel') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<snack-bar ref="snackbar" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
|
||||
import { useUserExternalAuthStore } from '@/stores/userExternalAuth.ts';
|
||||
|
||||
type SnackBarType = InstanceType<typeof SnackBar>;
|
||||
|
||||
const { tt } = useI18n();
|
||||
|
||||
const userExternalAuthStore = useUserExternalAuthStore();
|
||||
|
||||
let resolveFunc: (() => void) | null = null;
|
||||
let rejectFunc: ((reason?: unknown) => void) | null = null;
|
||||
|
||||
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||
|
||||
const showState = ref<boolean>(false);
|
||||
const unlinking = ref<boolean>(false);
|
||||
const currentPassword = ref<string>('');
|
||||
const currentExternalAuthType = ref<string | undefined>(undefined);
|
||||
|
||||
function open(externalAuthType: string): Promise<void> {
|
||||
showState.value = true;
|
||||
unlinking.value = false;
|
||||
currentPassword.value = '';
|
||||
currentExternalAuthType.value = externalAuthType;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
resolveFunc = resolve;
|
||||
rejectFunc = reject;
|
||||
});
|
||||
}
|
||||
|
||||
function confirm(): void {
|
||||
if (!currentExternalAuthType.value || !currentPassword.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
unlinking.value = true;
|
||||
|
||||
userExternalAuthStore.unlinkExternalAuth({
|
||||
externalAuthType: currentExternalAuthType.value,
|
||||
password: currentPassword.value
|
||||
}).then(() => {
|
||||
unlinking.value = false;
|
||||
|
||||
resolveFunc?.();
|
||||
showState.value = false;
|
||||
}).catch(error => {
|
||||
unlinking.value = false;
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
rejectFunc?.();
|
||||
showState.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
@@ -63,6 +63,61 @@
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-card :class="{ 'disabled': loadingExternalAuth }">
|
||||
<template #title>
|
||||
<div class="d-flex align-center">
|
||||
<span>{{ tt('Third-Party Logins') }}</span>
|
||||
<v-btn density="compact" color="default" variant="text" size="24"
|
||||
class="ms-2" :icon="true" :loading="loadingExternalAuth" @click="reloadExternalAuth(false)">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20"/>
|
||||
</template>
|
||||
<v-icon :icon="mdiRefresh" size="24" />
|
||||
<v-tooltip activator="parent">{{ tt('Refresh') }}</v-tooltip>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-table class="table-striped text-no-wrap" :hover="!loadingExternalAuth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ tt('Type') }}</th>
|
||||
<th>{{ tt('Username') }}</th>
|
||||
<th>{{ tt('Linked Time') }}</th>
|
||||
<th class="text-right">{{ tt('Operation') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :key="itemIdx"
|
||||
v-for="itemIdx in (loadingExternalAuth && (!externalAuths || externalAuths.length < 1) ? [ 1 ] : [])">
|
||||
<td class="px-0" colspan="4">
|
||||
<v-skeleton-loader type="text" :loading="true"></v-skeleton-loader>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr :key="thirdPartyLogin.externalAuthType"
|
||||
v-for="thirdPartyLogin in thirdPartyLogins">
|
||||
<td class="text-sm">
|
||||
<v-icon start :icon="thirdPartyLogin.icon"/>
|
||||
{{ thirdPartyLogin.displayName }}
|
||||
</td>
|
||||
<td class="text-sm">{{ thirdPartyLogin.externalUsername }}</td>
|
||||
<td class="text-sm">{{ thirdPartyLogin.createdAt }}</td>
|
||||
<td class="text-sm text-right">
|
||||
<v-btn density="comfortable" color="error" variant="tonal"
|
||||
:disabled="loadingExternalAuth"
|
||||
@click="unlinkExternalAuth(thirdPartyLogin)"
|
||||
v-if="thirdPartyLogin.linked">
|
||||
{{ tt('Unlink') }}
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-card :class="{ 'disabled': loadingSession }">
|
||||
<template #title>
|
||||
@@ -126,6 +181,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<unlink-third-party-login-dialog ref="unlinkThirdPartyLoginDialog" />
|
||||
<user-generate-m-c-p-token-dialog ref="generateMCPTokenDialog" />
|
||||
<confirm-dialog ref="confirmDialog"/>
|
||||
<snack-bar ref="snackbar" />
|
||||
@@ -133,6 +189,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VTextField } from 'vuetify/components/VTextField';
|
||||
import UnlinkThirdPartyLoginDialog from '@/views/desktop/user/settings/dialogs/UnlinkThirdPartyLoginDialog.vue';
|
||||
import UserGenerateMCPTokenDialog from '@/views/desktop/user/settings/dialogs/UserGenerateMCPTokenDialog.vue';
|
||||
import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue';
|
||||
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||
@@ -143,17 +200,21 @@ import { useI18n } from '@/locales/helpers.ts';
|
||||
|
||||
import { useRootStore } from '@/stores/index.ts';
|
||||
import { useSettingsStore } from '@/stores/setting.ts';
|
||||
import { useUserExternalAuthStore } from '@/stores/userExternalAuth.ts';
|
||||
import { useTokensStore } from '@/stores/token.ts';
|
||||
|
||||
import { itemAndIndex, reversedItemAndIndex } from '@/core/base.ts';
|
||||
import { type UserExternalAuthInfoResponse } from '@/models/user_external_auth.ts';
|
||||
import { type TokenInfoResponse, SessionInfo } from '@/models/token.ts';
|
||||
|
||||
import { isEquals } from '@/lib/common.ts';
|
||||
import { parseSessionInfo } from '@/lib/session.ts';
|
||||
import { isMCPServerEnabled } from '@/lib/server_settings.ts';
|
||||
import { getOIDCCustomDisplayNames, isMCPServerEnabled } from '@/lib/server_settings.ts';
|
||||
|
||||
import {
|
||||
mdiRefresh,
|
||||
mdiLinkVariant,
|
||||
mdiGithub,
|
||||
mdiCellphone,
|
||||
mdiTablet,
|
||||
mdiWatch,
|
||||
@@ -163,40 +224,111 @@ import {
|
||||
mdiDevices
|
||||
} from '@mdi/js';
|
||||
|
||||
class DesktopPageLinkedThirdPartyLogin {
|
||||
public readonly externalAuthType: string;
|
||||
public readonly icon: string;
|
||||
public readonly displayName: string;
|
||||
public readonly linked: boolean;
|
||||
public readonly externalUsername: string;
|
||||
public readonly createdAt: string;
|
||||
|
||||
public constructor(externalAuthInfoResponse: UserExternalAuthInfoResponse) {
|
||||
this.externalAuthType = externalAuthInfoResponse.externalAuthType;
|
||||
this.linked = externalAuthInfoResponse.linked;
|
||||
this.externalUsername = externalAuthInfoResponse.externalUsername ? externalAuthInfoResponse.externalUsername : '-';
|
||||
this.createdAt = externalAuthInfoResponse.createdAt ? formatUnixTimeToLongDateTime(externalAuthInfoResponse.createdAt) : '-';
|
||||
|
||||
if (externalAuthInfoResponse.externalAuthCategory === 'oauth2') {
|
||||
this.displayName = getLocalizedOAuth2ProviderName(externalAuthInfoResponse.externalAuthType, getOIDCCustomDisplayNames());
|
||||
|
||||
if (externalAuthInfoResponse.externalAuthType === 'github') {
|
||||
this.icon = mdiGithub;
|
||||
} else {
|
||||
this.icon = mdiLinkVariant;
|
||||
}
|
||||
} else {
|
||||
this.displayName = externalAuthInfoResponse.externalAuthType;
|
||||
this.icon = mdiLinkVariant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopPageSessionInfo extends SessionInfo {
|
||||
public readonly icon: string;
|
||||
public readonly lastSeenDateTime: string;
|
||||
|
||||
public constructor(sessionInfo: SessionInfo) {
|
||||
super(sessionInfo.tokenId, sessionInfo.isCurrent, sessionInfo.deviceType, sessionInfo.deviceInfo, sessionInfo.createdByCli, sessionInfo.lastSeen);
|
||||
this.icon = getTokenIcon(sessionInfo.deviceType);
|
||||
this.icon = this.getTokenIcon(sessionInfo.deviceType);
|
||||
this.lastSeenDateTime = sessionInfo.lastSeen ? formatUnixTimeToLongDateTime(sessionInfo.lastSeen) : '-';
|
||||
}
|
||||
|
||||
private getTokenIcon(deviceType: string): string {
|
||||
if (deviceType === 'phone') {
|
||||
return mdiCellphone;
|
||||
} else if (deviceType === 'wearable') {
|
||||
return mdiWatch;
|
||||
} else if (deviceType === 'tablet') {
|
||||
return mdiTablet;
|
||||
} else if (deviceType === 'tv') {
|
||||
return mdiTelevision;
|
||||
} else if (deviceType === 'mcp') {
|
||||
return mdiCreationOutline;
|
||||
} else if (deviceType === 'cli') {
|
||||
return mdiConsole;
|
||||
} else {
|
||||
return mdiDevices;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type UnlinkThirdPartyLoginDialogType = InstanceType<typeof UnlinkThirdPartyLoginDialog>;
|
||||
type UserGenerateMCPTokenDialogType = InstanceType<typeof UserGenerateMCPTokenDialog>;
|
||||
type ConfirmDialogType = InstanceType<typeof ConfirmDialog>;
|
||||
type SnackBarType = InstanceType<typeof SnackBar>;
|
||||
|
||||
const { tt, formatUnixTimeToLongDateTime, setLanguage } = useI18n();
|
||||
const {
|
||||
tt,
|
||||
formatUnixTimeToLongDateTime,
|
||||
getLocalizedOAuth2ProviderName,
|
||||
setLanguage
|
||||
} = useI18n();
|
||||
|
||||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const userExternalAuthStore = useUserExternalAuthStore();
|
||||
const tokensStore = useTokensStore();
|
||||
|
||||
const newPasswordInput = useTemplateRef<VTextField>('newPasswordInput');
|
||||
const confirmPasswordInput = useTemplateRef<VTextField>('confirmPasswordInput');
|
||||
const unlinkThirdPartyLoginDialog = useTemplateRef<UnlinkThirdPartyLoginDialogType>('unlinkThirdPartyLoginDialog');
|
||||
const generateMCPTokenDialog = useTemplateRef<UserGenerateMCPTokenDialogType>('generateMCPTokenDialog');
|
||||
const confirmDialog = useTemplateRef<ConfirmDialogType>('confirmDialog');
|
||||
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||
|
||||
const externalAuths = ref<UserExternalAuthInfoResponse[]>([]);
|
||||
const tokens = ref<TokenInfoResponse[]>([]);
|
||||
const currentPassword = ref<string>('');
|
||||
const newPassword = ref<string>('');
|
||||
const confirmPassword = ref<string>('');
|
||||
const updatingPassword = ref<boolean>(false);
|
||||
const loadingExternalAuth = ref<boolean>(true);
|
||||
const loadingSession = ref<boolean>(true);
|
||||
|
||||
const thirdPartyLogins = computed<DesktopPageLinkedThirdPartyLogin[]>(() => {
|
||||
const logins: DesktopPageLinkedThirdPartyLogin[] = [];
|
||||
|
||||
if (!externalAuths.value) {
|
||||
return logins;
|
||||
}
|
||||
|
||||
for (const externalAuth of externalAuths.value) {
|
||||
logins.push(new DesktopPageLinkedThirdPartyLogin(externalAuth));
|
||||
}
|
||||
|
||||
return logins;
|
||||
});
|
||||
|
||||
const sessions = computed<DesktopPageSessionInfo[]>(() => {
|
||||
const sessions: DesktopPageSessionInfo[] = [];
|
||||
|
||||
@@ -226,37 +358,11 @@ const inputProblemMessage = computed<string | null>(() => {
|
||||
}
|
||||
});
|
||||
|
||||
function getTokenIcon(deviceType: string): string {
|
||||
if (deviceType === 'phone') {
|
||||
return mdiCellphone;
|
||||
} else if (deviceType === 'wearable') {
|
||||
return mdiWatch;
|
||||
} else if (deviceType === 'tablet') {
|
||||
return mdiTablet;
|
||||
} else if (deviceType === 'tv') {
|
||||
return mdiTelevision;
|
||||
} else if (deviceType === 'mcp') {
|
||||
return mdiCreationOutline;
|
||||
} else if (deviceType === 'cli') {
|
||||
return mdiConsole;
|
||||
} else {
|
||||
return mdiDevices;
|
||||
}
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
loadingExternalAuth.value = true;
|
||||
loadingSession.value = true;
|
||||
|
||||
tokensStore.getAllTokens().then(response => {
|
||||
tokens.value = response;
|
||||
loadingSession.value = false;
|
||||
}).catch(error => {
|
||||
loadingSession.value = false;
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
reloadExternalAuth(true);
|
||||
reloadSessions(true);
|
||||
}
|
||||
|
||||
function updatePassword(): void {
|
||||
@@ -296,6 +402,35 @@ function updatePassword(): void {
|
||||
});
|
||||
}
|
||||
|
||||
function reloadExternalAuth(silent?: boolean): void {
|
||||
loadingExternalAuth.value = true;
|
||||
|
||||
userExternalAuthStore.getExternalAuths().then(response => {
|
||||
if (!silent) {
|
||||
if (isEquals(externalAuths.value, response)) {
|
||||
snackbar.value?.showMessage('Third-party logins list is up to date');
|
||||
} else {
|
||||
snackbar.value?.showMessage('Third-party logins list has been updated');
|
||||
}
|
||||
}
|
||||
|
||||
externalAuths.value = response;
|
||||
loadingExternalAuth.value = false;
|
||||
}).catch(error => {
|
||||
loadingExternalAuth.value = false;
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function unlinkExternalAuth(thirdPartyLogin: DesktopPageLinkedThirdPartyLogin): void {
|
||||
unlinkThirdPartyLoginDialog.value?.open(thirdPartyLogin.externalAuthType).then(() => {
|
||||
reloadExternalAuth(true);
|
||||
});
|
||||
}
|
||||
|
||||
function generateMCPToken(): void {
|
||||
generateMCPTokenDialog.value?.open().then(() => {
|
||||
reloadSessions(true);
|
||||
|
||||
Reference in New Issue
Block a user