feat(server): userinfo signing (#10756)

* feat(server): userinfo signing

* chore: e2e tests
This commit is contained in:
Jason Rasmussen 2024-07-11 07:55:00 -04:00 committed by GitHub
parent 3cb42de931
commit 25a380d023
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1439 additions and 33 deletions

View file

@ -9,7 +9,7 @@ import { isNumber, isString } from 'class-validator';
import cookieParser from 'cookie';
import { DateTime } from 'luxon';
import { IncomingHttpHeaders } from 'node:http';
import { ClientMetadata, Issuer, UserinfoResponse, custom, generators } from 'openid-client';
import { Issuer, UserinfoResponse, custom, generators } from 'openid-client';
import { SystemConfig } from 'src/config';
import { AuthType, LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
import { SystemConfigCore } from 'src/cores/system-config.core';
@ -45,9 +45,7 @@ export interface LoginDetails {
deviceOS: string;
}
interface OAuthProfile extends UserinfoResponse {
email: string;
}
type OAuthProfile = UserinfoResponse;
interface ClaimOptions<T> {
key: string;
@ -192,34 +190,35 @@ export class AuthService {
async callback(dto: OAuthCallbackDto, loginDetails: LoginDetails) {
const config = await this.configCore.getConfig({ withCache: false });
const profile = await this.getOAuthProfile(config, dto.url);
const { autoRegister, defaultStorageQuota, storageLabelClaim, storageQuotaClaim } = config.oauth;
this.logger.debug(`Logging in with OAuth: ${JSON.stringify(profile)}`);
let user = await this.userRepository.getByOAuthId(profile.sub);
// link existing user
if (!user) {
// link by email
if (!user && profile.email) {
const emailUser = await this.userRepository.getByEmail(profile.email);
if (emailUser) {
if (emailUser.oauthId) {
throw new BadRequestException('User already exists, but is linked to another account.');
} else {
user = await this.userRepository.update(emailUser.id, { oauthId: profile.sub });
}
user = await this.userRepository.update(emailUser.id, { oauthId: profile.sub });
}
}
const { autoRegister, defaultStorageQuota, storageLabelClaim, storageQuotaClaim } = config.oauth;
// register new user
if (!user) {
if (!autoRegister) {
this.logger.warn(
`Unable to register ${profile.email}. To enable set OAuth Auto Register to true in admin settings.`,
`Unable to register ${profile.sub}/${profile.email || '(no email)'}. To enable set OAuth Auto Register to true in admin settings.`,
);
throw new BadRequestException(`User does not exist and auto registering is disabled.`);
}
this.logger.log(`Registering new user: ${profile.email}/${profile.sub}`);
this.logger.verbose(`OAuth Profile: ${JSON.stringify(profile)}`);
if (!profile.email) {
throw new BadRequestException('OAuth profile does not have an email address');
}
this.logger.log(`Registering new user: ${profile.sub}/${profile.email}`);
const storageLabel = this.getClaim(profile, {
key: storageLabelClaim,
@ -299,23 +298,21 @@ export class AuthService {
}
private async getOAuthClient(config: SystemConfig) {
const { enabled, clientId, clientSecret, issuerUrl, signingAlgorithm } = config.oauth;
const { enabled, clientId, clientSecret, issuerUrl, signingAlgorithm, profileSigningAlgorithm } = config.oauth;
if (!enabled) {
throw new BadRequestException('OAuth2 is not enabled');
}
const metadata: ClientMetadata = {
client_id: clientId,
client_secret: clientSecret,
response_types: ['code'],
};
try {
const issuer = await Issuer.discover(issuerUrl);
metadata.id_token_signed_response_alg = signingAlgorithm;
return new issuer.Client(metadata);
return new issuer.Client({
client_id: clientId,
client_secret: clientSecret,
response_types: ['code'],
userinfo_signed_response_alg: profileSigningAlgorithm === 'none' ? undefined : profileSigningAlgorithm,
id_token_signed_response_alg: signingAlgorithm,
});
} catch (error: any | AggregateError) {
this.logger.error(`Error in OAuth discovery: ${error}`, error?.stack, error?.errors);
throw new InternalServerErrorException(`Error in OAuth discovery: ${error}`, { cause: error });