diff --git a/lang/en.json b/lang/en.json
index 2009c867..e99bf4ad 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -484,5 +484,6 @@
"Matches media objects referenced by a specific object type": "Matches media objects referenced by a specific object type",
"Matching the ${this._('Change E-mail')}
${this.renderChangeEmail()}
-
- ${this._('Change password')}
- ${this.renderChangePw()}
+ ${this._localAuthDisabled
+ ? ''
+ : html`
+ ${this._('Change password')}
+ ${this.renderChangePw()}
+ `}
${supportsPersistentAccessTokens(this.appState?.dbInfo)
? html`
${this._('Access tokens')}
@@ -137,6 +145,7 @@ export class GrampsjsViewSettingsUser extends GrampsjsView {
`
: ''}
@@ -594,6 +606,27 @@ export class GrampsjsViewSettingsUser extends GrampsjsView { this._userInfo = data.data } } + + // the identity provider may overwrite full name and e-mail at each sign-in + _idpHint() { + return this._hasLinkedAccount + ? this._( + 'Your full name and e-mail address may be updated whenever you sign in with a linked account.' + ) + : '' + } + + _handleOidcAccountsChanged(e) { + this._hasLinkedAccount = e.detail.count > 0 + } + + // users without local auth have no password to change + async _fetchAuthConfig() { + const config = await apiGetOIDCConfig() + this._localAuthDisabled = Boolean( + config?.enabled && config.disable_local_auth + ) + } } window.customElements.define( diff --git a/test/unit/settingsUser.test.js b/test/unit/settingsUser.test.js index 3a4cbd9f..4b4c97cb 100644 --- a/test/unit/settingsUser.test.js +++ b/test/unit/settingsUser.test.js @@ -1,4 +1,6 @@ -import {describe, it, expect, vi} from 'vitest' +import {describe, it, expect, vi, afterEach} from 'vitest' +import * as api from '../../src/api.js' +import {GrampsjsOidcAccounts} from '../../src/components/GrampsjsOidcAccounts.js' import {GrampsjsViewSettingsUser} from '../../src/views/GrampsjsViewSettingsUser.js' import {SYMBOL_SET_DEFAULT, SYMBOL_SET_TEXT} from '../../src/symbols.js' @@ -129,6 +131,68 @@ describe('user profile settings', () => { }) }) +describe('password form visibility', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + const localAuthDisabledFor = async config => { + vi.spyOn(api, 'apiGetOIDCConfig').mockResolvedValue(config) + const element = new GrampsjsViewSettingsUser() + await element._fetchAuthConfig() + return element._localAuthDisabled + } + + it('hides the form when OIDC disables local auth', async () => { + expect( + await localAuthDisabledFor({enabled: true, disable_local_auth: true}) + ).toBe(true) + }) + + it('shows the form when local auth is enabled', async () => { + expect( + await localAuthDisabledFor({enabled: true, disable_local_auth: false}) + ).toBe(false) + }) + + it('shows the form when OIDC is off', async () => { + expect( + await localAuthDisabledFor({enabled: false, disable_local_auth: true}) + ).toBe(false) + }) + + it('shows the form when the config request fails', async () => { + expect(await localAuthDisabledFor({error: 'Not Found'})).toBe(false) + }) +}) + +describe('identity provider hint', () => { + it('reports the number of linked accounts', () => { + const accounts = new GrampsjsOidcAccounts() + vi.spyOn(accounts, 'dispatchEvent') + accounts._accounts = [{id: 'a'}, {id: 'b'}] + + accounts.updated(new Map([['_accounts', []]])) + + expect(accounts.dispatchEvent).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'oidc-accounts:changed', + detail: {count: 2}, + }) + ) + }) + + it('shows the hint while an account is linked', () => { + const element = new GrampsjsViewSettingsUser() + + element._handleOidcAccountsChanged({detail: {count: 1}}) + expect(element._idpHint()).not.toBe('') + + element._handleOidcAccountsChanged({detail: {count: 0}}) + expect(element._idpHint()).toBe('') + }) +}) + describe('genealogical symbols setting', () => { it('saves the selected symbol set', () => { const element = new GrampsjsViewSettingsUser()