Browse Source

Fix: Use envalid to check OIDC mandatory variables

pull/5981/head
Germán Martín 2 days ago
parent
commit
48c800659b
  1. 145
      apps/api/src/app/auth/auth.module.ts
  2. 15
      apps/api/src/services/configuration/configuration.service.ts
  3. 6
      apps/api/src/services/interfaces/environment.interface.ts

145
apps/api/src/app/auth/auth.module.ts

@ -19,89 +19,6 @@ import { GoogleStrategy } from './google.strategy';
import { JwtStrategy } from './jwt.strategy';
import { OidcStrategy } from './oidc.strategy';
// ANSI color codes
const colors = {
blue: '\x1b[34m',
reset: '\x1b[0m',
white: '\x1b[37m',
yellow: '\x1b[33m'
};
function validateOidcConfiguration(
configurationService: ConfigurationService
): void {
const missingVariables: string[] = [];
// Common required variables for both configurations
const clientId = configurationService.get('OIDC_CLIENT_ID');
const clientSecret = configurationService.get('OIDC_CLIENT_SECRET');
const rootUrl = configurationService.get('ROOT_URL');
if (!clientId) {
missingVariables.push('OIDC_CLIENT_ID');
}
if (!clientSecret) {
missingVariables.push('OIDC_CLIENT_SECRET');
}
if (!rootUrl) {
missingVariables.push('ROOT_URL');
}
// Check for automatic or manual configuration
const authorizationUrl = configurationService.get('OIDC_AUTHORIZATION_URL');
const issuer = configurationService.get('OIDC_ISSUER');
const tokenUrl = configurationService.get('OIDC_TOKEN_URL');
const userInfoUrl = configurationService.get('OIDC_USER_INFO_URL');
const hasAutomaticConfig = !!issuer;
const hasManualConfig = authorizationUrl || tokenUrl || userInfoUrl;
if (!hasAutomaticConfig && !hasManualConfig) {
missingVariables.push(
'OIDC_ISSUER (for automatic configuration) or OIDC_AUTHORIZATION_URL, OIDC_TOKEN_URL, OIDC_USER_INFO_URL (for manual configuration)'
);
} else if (!hasAutomaticConfig && hasManualConfig) {
// Manual configuration: all three URLs are required
if (!authorizationUrl) {
missingVariables.push('OIDC_AUTHORIZATION_URL');
}
if (!tokenUrl) {
missingVariables.push('OIDC_TOKEN_URL');
}
if (!userInfoUrl) {
missingVariables.push('OIDC_USER_INFO_URL');
}
}
if (missingVariables.length > 0) {
const formattedVariables = missingVariables
.map(
(variable) =>
` ${colors.blue}${variable}:${colors.white} undefined${colors.reset}`
)
.join('\n');
const errorMessage = `
================================
${colors.yellow}Missing${colors.white} OIDC environment variables:${colors.reset}
${formattedVariables}
${colors.white}Configuration options:${colors.reset}
1. Automatic: Set ${colors.blue}OIDC_ISSUER${colors.reset} (endpoints discovered automatically)
2. Manual: Set ${colors.blue}OIDC_AUTHORIZATION_URL${colors.reset}, ${colors.blue}OIDC_TOKEN_URL${colors.reset}, ${colors.blue}OIDC_USER_INFO_URL${colors.reset}
Both options require: ${colors.blue}ROOT_URL${colors.reset}, ${colors.blue}OIDC_CLIENT_ID${colors.reset}, ${colors.blue}OIDC_CLIENT_SECRET${colors.reset}
================================
`;
Logger.error(errorMessage, 'OidcStrategy');
process.exit(1);
}
}
@Module({
controllers: [AuthController],
imports: [
@ -137,8 +54,6 @@ ${formattedVariables}
return null;
}
validateOidcConfiguration(configurationService);
const issuer = configurationService.get('OIDC_ISSUER');
const scope = configurationService.get('OIDC_SCOPE');
@ -146,9 +61,24 @@ ${formattedVariables}
configurationService.get('OIDC_CALLBACK_URL') ||
`${configurationService.get('ROOT_URL')}/api/auth/oidc/callback`;
let options: StrategyOptions;
if (issuer) {
// Check for manual URL overrides
const manualAuthorizationUrl = configurationService.get(
'OIDC_AUTHORIZATION_URL'
);
const manualTokenUrl = configurationService.get('OIDC_TOKEN_URL');
const manualUserInfoUrl =
configurationService.get('OIDC_USER_INFO_URL');
let authorizationURL: string;
let tokenURL: string;
let userInfoURL: string;
// If all manual URLs are provided, use them; otherwise fetch from discovery
if (manualAuthorizationUrl && manualTokenUrl && manualUserInfoUrl) {
authorizationURL = manualAuthorizationUrl;
tokenURL = manualTokenUrl;
userInfoURL = manualUserInfoUrl;
} else {
try {
const response = await fetch(
`${issuer}/.well-known/openid-configuration`
@ -160,35 +90,28 @@ ${formattedVariables}
userinfo_endpoint: string;
};
options = {
issuer,
scope,
authorizationURL: config.authorization_endpoint,
callbackURL: callbackUrl,
clientID: configurationService.get('OIDC_CLIENT_ID'),
clientSecret: configurationService.get('OIDC_CLIENT_SECRET'),
tokenURL: config.token_endpoint,
userInfoURL: config.userinfo_endpoint
};
// Manual URLs take priority over discovered ones
authorizationURL =
manualAuthorizationUrl || config.authorization_endpoint;
tokenURL = manualTokenUrl || config.token_endpoint;
userInfoURL = manualUserInfoUrl || config.userinfo_endpoint;
} catch (error) {
Logger.error(error, 'OidcStrategy');
throw new Error('Failed to fetch OIDC configuration from issuer');
}
} else {
options = {
scope,
authorizationURL: configurationService.get(
'OIDC_AUTHORIZATION_URL'
),
callbackURL: callbackUrl,
clientID: configurationService.get('OIDC_CLIENT_ID'),
clientSecret: configurationService.get('OIDC_CLIENT_SECRET'),
issuer: configurationService.get('OIDC_ISSUER'),
tokenURL: configurationService.get('OIDC_TOKEN_URL'),
userInfoURL: configurationService.get('OIDC_USER_INFO_URL')
};
}
const options: StrategyOptions = {
issuer,
scope,
authorizationURL,
callbackURL: callbackUrl,
clientID: configurationService.get('OIDC_CLIENT_ID'),
clientSecret: configurationService.get('OIDC_CLIENT_SECRET'),
tokenURL,
userInfoURL
};
return new OidcStrategy(authService, options);
}
},

15
apps/api/src/services/configuration/configuration.service.ts

@ -60,9 +60,18 @@ export class ConfigurationService {
MAX_CHART_ITEMS: num({ default: 365 }),
OIDC_AUTHORIZATION_URL: str({ default: '' }),
OIDC_CALLBACK_URL: str({ default: '' }),
OIDC_CLIENT_ID: str({ default: '' }),
OIDC_CLIENT_SECRET: str({ default: '' }),
OIDC_ISSUER: str({ default: '' }),
OIDC_CLIENT_ID: str({
default: undefined,
requiredWhen: (env) => env.ENABLE_FEATURE_AUTH_OIDC === true
}),
OIDC_CLIENT_SECRET: str({
default: undefined,
requiredWhen: (env) => env.ENABLE_FEATURE_AUTH_OIDC === true
}),
OIDC_ISSUER: str({
default: undefined,
requiredWhen: (env) => env.ENABLE_FEATURE_AUTH_OIDC === true
}),
OIDC_SCOPE: json({ default: ['openid'] }),
OIDC_TOKEN_URL: str({ default: '' }),
OIDC_USER_INFO_URL: str({ default: '' }),

6
apps/api/src/services/interfaces/environment.interface.ts

@ -35,9 +35,9 @@ export interface Environment extends CleanedEnvAccessors {
MAX_CHART_ITEMS: number;
OIDC_AUTHORIZATION_URL: string;
OIDC_CALLBACK_URL: string;
OIDC_CLIENT_ID: string;
OIDC_CLIENT_SECRET: string;
OIDC_ISSUER: string;
OIDC_CLIENT_ID: string | undefined;
OIDC_CLIENT_SECRET: string | undefined;
OIDC_ISSUER: string | undefined;
OIDC_SCOPE: string[];
OIDC_TOKEN_URL: string;
OIDC_USER_INFO_URL: string;

Loading…
Cancel
Save