diff --git a/client/components/modals/AccountModal.vue b/client/components/modals/AccountModal.vue index bdb8711d78..ed8660a06f 100644 --- a/client/components/modals/AccountModal.vue +++ b/client/components/modals/AccountModal.vue @@ -111,7 +111,8 @@
- {{ $strings.ButtonChangeRootPassword }} + Unlink OpenID + {{ $strings.ButtonChangeRootPassword }}
{{ $strings.ButtonSubmit }}
@@ -136,7 +137,8 @@ export default { newUser: {}, isNew: true, tags: [], - loadingTags: false + loadingTags: false, + unlinkingFromOpenID: false } }, watch: { @@ -180,7 +182,7 @@ export default { return this.isNew ? this.$strings.HeaderNewAccount : this.$strings.HeaderUpdateAccount }, isEditingRoot() { - return this.account && this.account.type === 'root' + return this.account?.type === 'root' }, libraries() { return this.$store.state.libraries.libraries @@ -198,6 +200,9 @@ export default { }, tagsSelectionText() { return this.newUser.permissions.selectedTagsNotAccessible ? this.$strings.LabelTagsNotAccessibleToUser : this.$strings.LabelTagsAccessibleToUser + }, + hasOpenIDLink() { + return !!this.account?.hasOpenIDLink } }, methods: { @@ -205,6 +210,31 @@ export default { // Force close when navigating - used in UsersTable if (this.$refs.modal) this.$refs.modal.setHide() }, + unlinkOpenID() { + const payload = { + message: 'Are you sure you want to unlink this user from OpenID?', + callback: (confirmed) => { + if (confirmed) { + this.unlinkingFromOpenID = true + this.$axios + .$patch(`/api/users/${this.account.id}/openid-unlink`) + .then(() => { + this.$toast.success('User unlinked from OpenID') + this.show = false + }) + .catch((error) => { + console.error('Failed to unlink user from OpenID', error) + this.$toast.error('Failed to unlink user from OpenID') + }) + .finally(() => { + this.unlinkingFromOpenID = false + }) + } + }, + type: 'yesNo' + } + this.$store.commit('globals/setConfirmPrompt', payload) + }, accessAllTagsToggled(val) { if (val) { if (this.newUser.itemTagsSelected?.length) { diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js index 86d2c78e59..726777516d 100644 --- a/server/controllers/UserController.js +++ b/server/controllers/UserController.js @@ -194,6 +194,23 @@ class UserController { }) } + /** + * PATCH: /api/users/:id/openid-unlink + * + * @param {import('express').Request} req + * @param {import('express').Response} res + */ + async unlinkFromOpenID(req, res) { + Logger.debug(`[UserController] Unlinking user "${req.reqUser.username}" from OpenID with sub "${req.reqUser.authOpenIDSub}"`) + req.reqUser.authOpenIDSub = null + if (await Database.userModel.updateFromOld(req.reqUser)) { + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.reqUser.toJSONForBrowser()) + res.sendStatus(200) + } else { + res.sendStatus(500) + } + } + // GET: api/users/:id/listening-sessions async getListeningSessions(req, res) { var listeningSessions = await this.getUserListeningSessionsHelper(req.params.id) diff --git a/server/objects/user/User.js b/server/objects/user/User.js index b503872d64..d926e8be0b 100644 --- a/server/objects/user/User.js +++ b/server/objects/user/User.js @@ -117,7 +117,8 @@ class User { createdAt: this.createdAt, permissions: this.permissions, librariesAccessible: [...this.librariesAccessible], - itemTagsSelected: [...this.itemTagsSelected] + itemTagsSelected: [...this.itemTagsSelected], + hasOpenIDLink: !!this.authOpenIDSub } if (minimal) { delete json.mediaProgress diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 3deb403002..d18d93e657 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -130,7 +130,7 @@ class ApiRouter { this.router.get('/users/:id', UserController.middleware.bind(this), UserController.findOne.bind(this)) this.router.patch('/users/:id', UserController.middleware.bind(this), UserController.update.bind(this)) this.router.delete('/users/:id', UserController.middleware.bind(this), UserController.delete.bind(this)) - + this.router.patch('/users/:id/openid-unlink', UserController.middleware.bind(this), UserController.unlinkFromOpenID.bind(this)) this.router.get('/users/:id/listening-sessions', UserController.middleware.bind(this), UserController.getListeningSessions.bind(this)) this.router.get('/users/:id/listening-stats', UserController.middleware.bind(this), UserController.getListeningStats.bind(this))