enhancement(auth/oidc): allow manual defining end_session_endpoint URL

For non-OIDC compliant OAuth2 providers it was only possible to define
the authorize, token and userinfo URLs but not the end_session_endpoint.
This commit adds that functionality.

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2024-09-13 16:41:44 +02:00
parent e69da38b71
commit 78a8f9dab7
No known key found for this signature in database
GPG key ID: DB99ADDDC5C0AF82
5 changed files with 53 additions and 2 deletions

View file

@ -524,7 +524,7 @@ describe('authConfig', () => {
});
});
describe('odic', () => {
describe('oidc', () => {
const oidcNames = ['gitlab'];
const providerName = 'Gitlab oAuth2';
const issuer = 'https://gitlab.example.org';
@ -534,7 +534,8 @@ describe('authConfig', () => {
const authorizeUrl = 'https://example.org/auth';
const tokenUrl = 'https://example.org/token';
const userinfoUrl = 'https://example.org/user';
const scope = 'some scopr';
const endSessionUrl = 'https://example.org/end';
const scope = 'some scope';
const defaultScope = 'openid profile email';
const userIdField = 'login';
const defaultUserIdField = 'sub';
@ -556,6 +557,7 @@ describe('authConfig', () => {
HD_AUTH_OIDC_GITLAB_AUTHORIZE_URL: authorizeUrl,
HD_AUTH_OIDC_GITLAB_TOKEN_URL: tokenUrl,
HD_AUTH_OIDC_GITLAB_USERINFO_URL: userinfoUrl,
HD_AUTH_OIDC_GITLAB_END_SESSION_URL: endSessionUrl,
HD_AUTH_OIDC_GITLAB_SCOPE: scope,
HD_AUTH_OIDC_GITLAB_USER_ID_FIELD: userIdField,
HD_AUTH_OIDC_GITLAB_USER_NAME_FIELD: userNameField,
@ -587,6 +589,7 @@ describe('authConfig', () => {
expect(firstOidc.theme).toEqual(theme);
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.userIdField).toEqual(userIdField);
@ -620,6 +623,7 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
@ -652,6 +656,7 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toBeUndefined();
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
@ -684,6 +689,7 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toBeUndefined();
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
@ -716,6 +722,40 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toBeUndefined();
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField);
restore();
});
it('when HD_AUTH_OIDC_GITLAB_END_SESSION_URL is not set', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_END_SESSION_URL: undefined,
/* eslint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
},
);
const config = authConfig();
expect(config.oidc).toHaveLength(1);
const firstOidc = config.oidc[0];
expect(firstOidc.identifier).toEqual(oidcNames[0]);
expect(firstOidc.issuer).toEqual(issuer);
expect(firstOidc.clientID).toEqual(clientId);
expect(firstOidc.clientSecret).toEqual(clientSecret);
expect(firstOidc.theme).toEqual(theme);
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toBeUndefined();
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
@ -748,6 +788,7 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(defaultScope);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
@ -781,6 +822,7 @@ describe('authConfig', () => {
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.userIdField).toEqual(defaultUserIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(displayNameField);
@ -813,6 +855,7 @@ describe('authConfig', () => {
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(defaultDisplayNameField);
@ -845,6 +888,7 @@ describe('authConfig', () => {
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(displayNameField);
@ -879,6 +923,7 @@ describe('authConfig', () => {
expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(displayNameField);

View file

@ -43,6 +43,7 @@ export interface OidcConfig extends InternalIdentifier {
authorizeUrl?: string;
tokenUrl?: string;
userinfoUrl?: string;
endSessionUrl?: string;
scope: string;
userNameField: string;
userIdField: string;
@ -139,6 +140,7 @@ const authSchema = Joi.object({
authorizeUrl: Joi.string().optional(),
tokenUrl: Joi.string().optional(),
userinfoUrl: Joi.string().optional(),
endSessionUrl: Joi.string().optional(),
scope: Joi.string().default('openid profile email').optional(),
userIdField: Joi.string().default('sub').optional(),
userNameField: Joi.string().default('preferred_username').optional(),
@ -206,6 +208,7 @@ export default registerAs('authConfig', () => {
authorizeUrl: process.env[`HD_AUTH_OIDC_${oidcName}_AUTHORIZE_URL`],
tokenUrl: process.env[`HD_AUTH_OIDC_${oidcName}_TOKEN_URL`],
userinfoUrl: process.env[`HD_AUTH_OIDC_${oidcName}_USERINFO_URL`],
endSessionUrl: process.env[`HD_AUTH_OIDC_${oidcName}_END_SESSION_URL`],
scope: process.env[`HD_AUTH_OIDC_${oidcName}_SCOPE`],
userIdField: process.env[`HD_AUTH_OIDC_${oidcName}_USER_ID_FIELD`],
userNameField: process.env[`HD_AUTH_OIDC_${oidcName}_USER_NAME_FIELD`],

View file

@ -84,6 +84,7 @@ export function replaceAuthErrorsWithEnvironmentVariables(
newMessage = newMessage.replace('.authorizeUrl', '_AUTHORIZE_URL');
newMessage = newMessage.replace('.tokenUrl', '_TOKEN_URL');
newMessage = newMessage.replace('.userinfoUrl', '_USERINFO_URL');
newMessage = newMessage.replace('.endSessionUrl', '_END_SESSION_URL');
newMessage = newMessage.replace('.scope', '_SCOPE');
newMessage = newMessage.replace('.tlsCaCerts', '_TLS_CERT_PATHS');
newMessage = newMessage.replace('.issuer', '_ISSUER');

View file

@ -89,6 +89,7 @@ export class OidcService {
authorization_endpoint: oidcConfig.authorizeUrl,
token_endpoint: oidcConfig.tokenUrl,
userinfo_endpoint: oidcConfig.userinfoUrl,
end_session_endpoint: oidcConfig.endSessionUrl,
/* eslint-enable @typescript-eslint/naming-convention */
});

View file

@ -31,6 +31,7 @@ no OIDC (e.g., GitHub or Discord). In this case, you need the following addition
| `HD_AUTH_OIDC_$NAME_AUTHORIZE_URL` | - | `https://auth.example.com/oauth2/auth` | The URL to which the user should be redirected to start the OAuth2 flow. |
| `HD_AUTH_OIDC_$NAME_TOKEN_URL` | - | `https://auth.example.com/oauth2/token` | The URL to which the user should be redirected to exchange the code for an access token. |
| `HD_AUTH_OIDC_$NAME_USERINFO_URL` | - | `https://auth.example.com/oauth2/userinfo` | The URL to which the user should be redirected to get the user information. |
| `HD_AUTH_OIDC_$NAME_END_SESSION_URL` | - | `https://auth.example.com/oauth2/logout` | The URL to which the user should be redirected to end the session. |
| `HD_AUTH_OIDC_$NAME_SCOPE` | - | `profile` | The scope that should be requested to get the user information. |
| `HD_AUTH_OIDC_$NAME_USER_ID_FIELD` | `sub` | `sub`, `id` | The unique identifier that is returned for the user from the OAuth2 provider. |
| `HD_AUTH_OIDC_$NAME_USER_ID_FIELD` | `sub` | `sub`, `id` | The unique identifier that is returned for the user from the OAuth2 provider. |