feat(auth): allow to disable OIDC user registration

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2024-12-03 01:22:11 +01:00
parent b24f8b0a76
commit fd1795f941
4 changed files with 80 additions and 16 deletions

View file

@ -73,13 +73,18 @@ export class OidcController {
this.logger.log('No OIDC user identifier in callback', 'callback'); this.logger.log('No OIDC user identifier in callback', 'callback');
throw new UnauthorizedException('No OIDC user identifier found'); throw new UnauthorizedException('No OIDC user identifier found');
} }
request.session.authProviderType = ProviderType.OIDC;
const identity = await this.oidcService.getExistingOidcIdentity( const identity = await this.oidcService.getExistingOidcIdentity(
oidcIdentifier, oidcIdentifier,
oidcUserIdentifier, oidcUserIdentifier,
); );
request.session.authProviderType = ProviderType.OIDC;
const mayUpdate = this.identityService.mayUpdateIdentity(oidcIdentifier); const mayUpdate = this.identityService.mayUpdateIdentity(oidcIdentifier);
if (identity !== null) {
if (identity === null) {
request.session.newUserData = userInfo;
return { url: '/new-user' };
}
const user = await identity.user; const user = await identity.user;
if (mayUpdate) { if (mayUpdate) {
await this.usersService.updateUser( await this.usersService.updateUser(
@ -92,10 +97,6 @@ export class OidcController {
request.session.username = user.username; request.session.username = user.username;
return { url: '/' }; return { url: '/' };
} else {
request.session.newUserData = userInfo;
return { url: '/new-user' };
}
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { import {
ForbiddenException,
Inject, Inject,
Injectable, Injectable,
InternalServerErrorException, InternalServerErrorException,
@ -264,6 +265,11 @@ export class OidcService {
); );
} catch (e) { } catch (e) {
if (e instanceof NotInDBError) { if (e instanceof NotInDBError) {
if (!clientConfig.config.enableRegistration) {
throw new ForbiddenException(
'Registration is disabled for this OIDC provider',
);
}
return null; return null;
} else { } else {
throw e; throw e;

View file

@ -546,6 +546,7 @@ describe('authConfig', () => {
const defaultProfilePictureField = 'picture'; const defaultProfilePictureField = 'picture';
const emailField = 'a_email'; const emailField = 'a_email';
const defaultEmailField = 'email'; const defaultEmailField = 'email';
const enableRegistration = 'false';
const completeOidcConfig = { const completeOidcConfig = {
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
HD_AUTH_OIDC_SERVERS: oidcNames.join(','), HD_AUTH_OIDC_SERVERS: oidcNames.join(','),
@ -564,6 +565,7 @@ describe('authConfig', () => {
HD_AUTH_OIDC_GITLAB_DISPLAY_NAME_FIELD: displayNameField, HD_AUTH_OIDC_GITLAB_DISPLAY_NAME_FIELD: displayNameField,
HD_AUTH_OIDC_GITLAB_PROFILE_PICTURE_FIELD: profilePictureField, HD_AUTH_OIDC_GITLAB_PROFILE_PICTURE_FIELD: profilePictureField,
HD_AUTH_OIDC_GITLAB_EMAIL_FIELD: emailField, HD_AUTH_OIDC_GITLAB_EMAIL_FIELD: emailField,
HD_AUTH_OIDC_GITLAB_ENABLE_REGISTER: enableRegistration,
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
}; };
describe('is correctly parsed', () => { describe('is correctly parsed', () => {
@ -597,6 +599,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_THEME is not set', () => { it('when HD_AUTH_OIDC_GITLAB_THEME is not set', () => {
@ -630,6 +633,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_AUTHORIZE_URL is not set', () => { it('when HD_AUTH_OIDC_GITLAB_AUTHORIZE_URL is not set', () => {
@ -663,6 +667,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_TOKEN_URL is not set', () => { it('when HD_AUTH_OIDC_GITLAB_TOKEN_URL is not set', () => {
@ -696,6 +701,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_USERINFO_URL is not set', () => { it('when HD_AUTH_OIDC_GITLAB_USERINFO_URL is not set', () => {
@ -729,6 +735,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_END_SESSION_URL is not set', () => { it('when HD_AUTH_OIDC_GITLAB_END_SESSION_URL is not set', () => {
@ -762,6 +769,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_SCOPE is not set', () => { it('when HD_AUTH_OIDC_GITLAB_SCOPE is not set', () => {
@ -795,6 +803,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_USER_ID_FIELD is not set', () => { it('when HD_AUTH_OIDC_GITLAB_USER_ID_FIELD is not set', () => {
@ -828,6 +837,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_DISPLAY_NAME_FIELD is not set', () => { it('when HD_AUTH_OIDC_GITLAB_DISPLAY_NAME_FIELD is not set', () => {
@ -861,6 +871,7 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(defaultDisplayNameField); expect(firstOidc.displayNameField).toEqual(defaultDisplayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_PROFILE_PICTURE_FIELD is not set', () => { it('when HD_AUTH_OIDC_GITLAB_PROFILE_PICTURE_FIELD is not set', () => {
@ -896,6 +907,7 @@ describe('authConfig', () => {
defaultProfilePictureField, defaultProfilePictureField,
); );
expect(firstOidc.emailField).toEqual(emailField); expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore(); restore();
}); });
it('when HD_AUTH_OIDC_GITLAB_EMAIL_FIELD is not set', () => { it('when HD_AUTH_OIDC_GITLAB_EMAIL_FIELD is not set', () => {
@ -929,6 +941,41 @@ describe('authConfig', () => {
expect(firstOidc.displayNameField).toEqual(displayNameField); expect(firstOidc.displayNameField).toEqual(displayNameField);
expect(firstOidc.profilePictureField).toEqual(profilePictureField); expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(defaultEmailField); expect(firstOidc.emailField).toEqual(defaultEmailField);
expect(firstOidc.enableRegistration).toEqual(false);
restore();
});
it('when HD_AUTH_OIDC_GITLAB_ENABLE_REGISTER is not set', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
...neededAuthConfig,
...completeOidcConfig,
HD_AUTH_OIDC_GITLAB_ENABLE_REGISTER: 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.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);
expect(firstOidc.profilePictureField).toEqual(profilePictureField);
expect(firstOidc.emailField).toEqual(emailField);
expect(firstOidc.enableRegistration).toEqual(true);
restore(); restore();
}); });
}); });

View file

@ -11,6 +11,7 @@ import { Theme } from './theme.enum';
import { import {
buildErrorMessage, buildErrorMessage,
ensureNoDuplicatesExist, ensureNoDuplicatesExist,
parseOptionalBoolean,
parseOptionalNumber, parseOptionalNumber,
replaceAuthErrorsWithEnvironmentVariables, replaceAuthErrorsWithEnvironmentVariables,
toArrayConfig, toArrayConfig,
@ -50,6 +51,7 @@ export interface OidcConfig extends InternalIdentifier {
displayNameField: string; displayNameField: string;
profilePictureField: string; profilePictureField: string;
emailField: string; emailField: string;
enableRegistration?: boolean;
} }
export interface AuthConfig { export interface AuthConfig {
@ -147,6 +149,7 @@ const authSchema = Joi.object({
displayNameField: Joi.string().default('name').optional(), displayNameField: Joi.string().default('name').optional(),
profilePictureField: Joi.string().default('picture').optional(), profilePictureField: Joi.string().default('picture').optional(),
emailField: Joi.string().default('email').optional(), emailField: Joi.string().default('email').optional(),
enableRegistration: Joi.boolean().default(true).optional(),
}).optional(), }).optional(),
) )
.optional(), .optional(),
@ -217,6 +220,9 @@ export default registerAs('authConfig', () => {
profilePictureField: profilePictureField:
process.env[`HD_AUTH_OIDC_${oidcName}_PROFILE_PICTURE_FIELD`], process.env[`HD_AUTH_OIDC_${oidcName}_PROFILE_PICTURE_FIELD`],
emailField: process.env[`HD_AUTH_OIDC_${oidcName}_EMAIL_FIELD`], emailField: process.env[`HD_AUTH_OIDC_${oidcName}_EMAIL_FIELD`],
enableRegistration: parseOptionalBoolean(
process.env[`HD_AUTH_OIDC_${oidcName}_ENABLE_REGISTER`],
),
})); }));
let syncSource = process.env.HD_AUTH_SYNC_SOURCE; let syncSource = process.env.HD_AUTH_SYNC_SOURCE;
@ -236,8 +242,12 @@ export default registerAs('authConfig', () => {
lifetime: parseOptionalNumber(process.env.HD_SESSION_LIFETIME), lifetime: parseOptionalNumber(process.env.HD_SESSION_LIFETIME),
}, },
local: { local: {
enableLogin: process.env.HD_AUTH_LOCAL_ENABLE_LOGIN, enableLogin: parseOptionalBoolean(
enableRegister: process.env.HD_AUTH_LOCAL_ENABLE_REGISTER, process.env.HD_AUTH_LOCAL_ENABLE_LOGIN,
),
enableRegister: parseOptionalBoolean(
process.env.HD_AUTH_LOCAL_ENABLE_REGISTER,
),
minimalPasswordStrength: parseOptionalNumber( minimalPasswordStrength: parseOptionalNumber(
process.env.HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH, process.env.HD_AUTH_LOCAL_MINIMAL_PASSWORD_STRENGTH,
), ),