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 committed by Philip Molares
parent 53409825d4
commit 603ad8088c
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 oidcNames = ['gitlab'];
const providerName = 'Gitlab oAuth2'; const providerName = 'Gitlab oAuth2';
const issuer = 'https://gitlab.example.org'; const issuer = 'https://gitlab.example.org';
@ -534,7 +534,8 @@ describe('authConfig', () => {
const authorizeUrl = 'https://example.org/auth'; const authorizeUrl = 'https://example.org/auth';
const tokenUrl = 'https://example.org/token'; const tokenUrl = 'https://example.org/token';
const userinfoUrl = 'https://example.org/user'; 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 defaultScope = 'openid profile email';
const userIdField = 'login'; const userIdField = 'login';
const defaultUserIdField = 'sub'; const defaultUserIdField = 'sub';
@ -556,6 +557,7 @@ describe('authConfig', () => {
HD_AUTH_OIDC_GITLAB_AUTHORIZE_URL: authorizeUrl, HD_AUTH_OIDC_GITLAB_AUTHORIZE_URL: authorizeUrl,
HD_AUTH_OIDC_GITLAB_TOKEN_URL: tokenUrl, HD_AUTH_OIDC_GITLAB_TOKEN_URL: tokenUrl,
HD_AUTH_OIDC_GITLAB_USERINFO_URL: userinfoUrl, 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_SCOPE: scope,
HD_AUTH_OIDC_GITLAB_USER_ID_FIELD: userIdField, HD_AUTH_OIDC_GITLAB_USER_ID_FIELD: userIdField,
HD_AUTH_OIDC_GITLAB_USER_NAME_FIELD: userNameField, HD_AUTH_OIDC_GITLAB_USER_NAME_FIELD: userNameField,
@ -587,6 +589,7 @@ describe('authConfig', () => {
expect(firstOidc.theme).toEqual(theme); expect(firstOidc.theme).toEqual(theme);
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl); expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope); expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
@ -620,6 +623,7 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl); expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope); expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
@ -652,6 +656,7 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toBeUndefined(); expect(firstOidc.authorizeUrl).toBeUndefined();
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope); expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
@ -684,6 +689,7 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl); expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toBeUndefined(); expect(firstOidc.tokenUrl).toBeUndefined();
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(scope); expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
@ -716,6 +722,40 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl); expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toBeUndefined(); 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.scope).toEqual(scope);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
@ -748,6 +788,7 @@ describe('authConfig', () => {
expect(firstOidc.authorizeUrl).toEqual(authorizeUrl); expect(firstOidc.authorizeUrl).toEqual(authorizeUrl);
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.scope).toEqual(defaultScope); expect(firstOidc.scope).toEqual(defaultScope);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
@ -781,6 +822,7 @@ describe('authConfig', () => {
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.scope).toEqual(scope); expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.userIdField).toEqual(defaultUserIdField); expect(firstOidc.userIdField).toEqual(defaultUserIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
@ -813,6 +855,7 @@ describe('authConfig', () => {
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.scope).toEqual(scope); expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(defaultDisplayNameField); expect(firstOidc.displayNameField).toEqual(defaultDisplayNameField);
@ -845,6 +888,7 @@ describe('authConfig', () => {
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.scope).toEqual(scope); expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
@ -879,6 +923,7 @@ describe('authConfig', () => {
expect(firstOidc.tokenUrl).toEqual(tokenUrl); expect(firstOidc.tokenUrl).toEqual(tokenUrl);
expect(firstOidc.scope).toEqual(scope); expect(firstOidc.scope).toEqual(scope);
expect(firstOidc.userinfoUrl).toEqual(userinfoUrl); expect(firstOidc.userinfoUrl).toEqual(userinfoUrl);
expect(firstOidc.endSessionUrl).toEqual(endSessionUrl);
expect(firstOidc.userIdField).toEqual(userIdField); expect(firstOidc.userIdField).toEqual(userIdField);
expect(firstOidc.userNameField).toEqual(userNameField); expect(firstOidc.userNameField).toEqual(userNameField);
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);

View file

@ -43,6 +43,7 @@ export interface OidcConfig extends InternalIdentifier {
authorizeUrl?: string; authorizeUrl?: string;
tokenUrl?: string; tokenUrl?: string;
userinfoUrl?: string; userinfoUrl?: string;
endSessionUrl?: string;
scope: string; scope: string;
userNameField: string; userNameField: string;
userIdField: string; userIdField: string;
@ -139,6 +140,7 @@ const authSchema = Joi.object({
authorizeUrl: Joi.string().optional(), authorizeUrl: Joi.string().optional(),
tokenUrl: Joi.string().optional(), tokenUrl: Joi.string().optional(),
userinfoUrl: Joi.string().optional(), userinfoUrl: Joi.string().optional(),
endSessionUrl: Joi.string().optional(),
scope: Joi.string().default('openid profile email').optional(), scope: Joi.string().default('openid profile email').optional(),
userIdField: Joi.string().default('sub').optional(), userIdField: Joi.string().default('sub').optional(),
userNameField: Joi.string().default('preferred_username').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`], authorizeUrl: process.env[`HD_AUTH_OIDC_${oidcName}_AUTHORIZE_URL`],
tokenUrl: process.env[`HD_AUTH_OIDC_${oidcName}_TOKEN_URL`], tokenUrl: process.env[`HD_AUTH_OIDC_${oidcName}_TOKEN_URL`],
userinfoUrl: process.env[`HD_AUTH_OIDC_${oidcName}_USERINFO_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`], scope: process.env[`HD_AUTH_OIDC_${oidcName}_SCOPE`],
userIdField: process.env[`HD_AUTH_OIDC_${oidcName}_USER_ID_FIELD`], userIdField: process.env[`HD_AUTH_OIDC_${oidcName}_USER_ID_FIELD`],
userNameField: process.env[`HD_AUTH_OIDC_${oidcName}_USER_NAME_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('.authorizeUrl', '_AUTHORIZE_URL');
newMessage = newMessage.replace('.tokenUrl', '_TOKEN_URL'); newMessage = newMessage.replace('.tokenUrl', '_TOKEN_URL');
newMessage = newMessage.replace('.userinfoUrl', '_USERINFO_URL'); newMessage = newMessage.replace('.userinfoUrl', '_USERINFO_URL');
newMessage = newMessage.replace('.endSessionUrl', '_END_SESSION_URL');
newMessage = newMessage.replace('.scope', '_SCOPE'); newMessage = newMessage.replace('.scope', '_SCOPE');
newMessage = newMessage.replace('.tlsCaCerts', '_TLS_CERT_PATHS'); newMessage = newMessage.replace('.tlsCaCerts', '_TLS_CERT_PATHS');
newMessage = newMessage.replace('.issuer', '_ISSUER'); newMessage = newMessage.replace('.issuer', '_ISSUER');

View file

@ -89,6 +89,7 @@ export class OidcService {
authorization_endpoint: oidcConfig.authorizeUrl, authorization_endpoint: oidcConfig.authorizeUrl,
token_endpoint: oidcConfig.tokenUrl, token_endpoint: oidcConfig.tokenUrl,
userinfo_endpoint: oidcConfig.userinfoUrl, userinfo_endpoint: oidcConfig.userinfoUrl,
end_session_endpoint: oidcConfig.endSessionUrl,
/* eslint-enable @typescript-eslint/naming-convention */ /* 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_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_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_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_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. |
| `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. |