mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat(server): add OIDC logout URL override option (#27389)
* feat(server): add OIDC logout URL override option - Added toggle and field consistent with existing mobile redirect URI override. - Existing auto-discovery remains default. - Update tests and docs for new feature. * fix(server): changes from review for OIDC logout URL override - Rename 'logoutUri' to 'endSessionEndpoint' - Remove toggle, just use override if provided - Moved field in settings UI
This commit is contained in:
@@ -104,6 +104,7 @@ export type SystemConfig = {
|
||||
defaultStorageQuota: number | null;
|
||||
enabled: boolean;
|
||||
issuerUrl: string;
|
||||
endSessionEndpoint: string;
|
||||
mobileOverrideEnabled: boolean;
|
||||
mobileRedirectUri: string;
|
||||
prompt: string;
|
||||
@@ -297,6 +298,7 @@ export const defaults = Object.freeze<SystemConfig>({
|
||||
defaultStorageQuota: null,
|
||||
enabled: false,
|
||||
issuerUrl: '',
|
||||
endSessionEndpoint: '',
|
||||
mobileOverrideEnabled: false,
|
||||
mobileRedirectUri: '',
|
||||
prompt: '',
|
||||
|
||||
@@ -190,6 +190,12 @@ const SystemConfigOAuthSchema = z
|
||||
.describe('Issuer URL'),
|
||||
scope: z.string().describe('Scope'),
|
||||
prompt: z.string().describe('OAuth prompt parameter (e.g. select_account, login, consent)'),
|
||||
endSessionEndpoint: z
|
||||
.string()
|
||||
.refine((url) => url.length === 0 || z.url().safeParse(url).success, {
|
||||
error: 'endSessionEndpoint must be an empty string or a valid URL',
|
||||
})
|
||||
.describe('End session endpoint'),
|
||||
signingAlgorithm: z.string().describe('Signing algorithm'),
|
||||
profileSigningAlgorithm: z.string().describe('Profile signing algorithm'),
|
||||
storageLabelClaim: z.string().describe('Storage label claim'),
|
||||
|
||||
@@ -22,6 +22,7 @@ export type OAuthConfig = {
|
||||
clientId: string;
|
||||
clientSecret?: string;
|
||||
issuerUrl: string;
|
||||
endSessionEndpoint: string;
|
||||
mobileOverrideEnabled: boolean;
|
||||
mobileRedirectUri: string;
|
||||
profileSigningAlgorithm: string;
|
||||
|
||||
@@ -164,6 +164,32 @@ describe(AuthService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the custom end session endpoint if provided', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue({
|
||||
oauth: { enabled: true, endSessionEndpoint: 'http://custom-logout-url' },
|
||||
});
|
||||
|
||||
await expect(sut.logout(auth, AuthType.OAuth)).resolves.toEqual({
|
||||
successful: true,
|
||||
redirectUri: 'http://custom-logout-url',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the auto-discovered end session endpoint if custom endpoint is not provided', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue({
|
||||
oauth: { enabled: true, endSessionEndpoint: '' },
|
||||
});
|
||||
|
||||
await expect(sut.logout(auth, AuthType.OAuth)).resolves.toEqual({
|
||||
successful: true,
|
||||
redirectUri: 'http://end-session-endpoint',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the default redirect', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
|
||||
|
||||
@@ -455,6 +455,10 @@ export class AuthService extends BaseService {
|
||||
return LOGIN_URL;
|
||||
}
|
||||
|
||||
if (config.oauth.endSessionEndpoint) {
|
||||
return config.oauth.endSessionEndpoint;
|
||||
}
|
||||
|
||||
return (await this.oauthRepository.getLogoutEndpoint(config.oauth)) || LOGIN_URL;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
|
||||
defaultStorageQuota: null,
|
||||
enabled: false,
|
||||
issuerUrl: '',
|
||||
endSessionEndpoint: '',
|
||||
mobileOverrideEnabled: false,
|
||||
mobileRedirectUri: '',
|
||||
prompt: '',
|
||||
|
||||
Reference in New Issue
Block a user