fix: config service precedence

This commit is contained in:
Mir Arif Hasan
2023-11-28 13:34:26 +06:00
parent ef8412544a
commit 9f62ed6c90
13 changed files with 105 additions and 42 deletions

View File

@@ -80,7 +80,7 @@ import { loadInfraConfiguration } from './infra-config/helper';
], ],
}), }),
UserModule, UserModule,
AuthModule, AuthModule.register(),
AdminModule, AdminModule,
UserSettingsModule, UserSettingsModule,
UserEnvironmentsModule, UserEnvironmentsModule,

View File

@@ -2,7 +2,6 @@ import {
Body, Body,
Controller, Controller,
Get, Get,
InternalServerErrorException,
Post, Post,
Query, Query,
Request, Request,
@@ -31,11 +30,15 @@ import { MicrosoftSSOGuard } from './guards/microsoft-sso-.guard';
import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard'; import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard';
import { SkipThrottle } from '@nestjs/throttler'; import { SkipThrottle } from '@nestjs/throttler';
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors'; import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
import { ConfigService } from '@nestjs/config';
@UseGuards(ThrottlerBehindProxyGuard) @UseGuards(ThrottlerBehindProxyGuard)
@Controller({ path: 'auth', version: '1' }) @Controller({ path: 'auth', version: '1' })
export class AuthController { export class AuthController {
constructor(private authService: AuthService) {} constructor(
private authService: AuthService,
private configService: ConfigService,
) {}
/** /**
** Route to initiate magic-link auth for a users email ** Route to initiate magic-link auth for a users email
@@ -45,8 +48,14 @@ export class AuthController {
@Body() authData: SignInMagicDto, @Body() authData: SignInMagicDto,
@Query('origin') origin: string, @Query('origin') origin: string,
) { ) {
if (!authProviderCheck(AuthProvider.EMAIL)) if (
!authProviderCheck(
AuthProvider.EMAIL,
this.configService.get('INFRA.VITE_ALLOWED_AUTH_PROVIDERS'),
)
) {
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 }); throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
}
const deviceIdToken = await this.authService.signInMagicLink( const deviceIdToken = await this.authService.signInMagicLink(
authData.email, authData.email,

View File

@@ -13,6 +13,7 @@ import { GithubStrategy } from './strategies/github.strategy';
import { MicrosoftStrategy } from './strategies/microsoft.strategy'; import { MicrosoftStrategy } from './strategies/microsoft.strategy';
import { AuthProvider, authProviderCheck } from './helper'; import { AuthProvider, authProviderCheck } from './helper';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { loadInfraConfiguration } from 'src/infra-config/helper';
@Module({ @Module({
imports: [ imports: [
@@ -28,14 +29,29 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
}), }),
}), }),
], ],
providers: [ providers: [AuthService, JwtStrategy, RTJwtStrategy],
AuthService,
JwtStrategy,
RTJwtStrategy,
...(authProviderCheck(AuthProvider.GOOGLE) ? [GoogleStrategy] : []),
...(authProviderCheck(AuthProvider.GITHUB) ? [GithubStrategy] : []),
...(authProviderCheck(AuthProvider.MICROSOFT) ? [MicrosoftStrategy] : []),
],
controllers: [AuthController], controllers: [AuthController],
}) })
export class AuthModule {} export class AuthModule {
static async register() {
const env = await loadInfraConfiguration();
const allowedAuthProviders = env.INFRA.VITE_ALLOWED_AUTH_PROVIDERS;
const providers = [
...(authProviderCheck(AuthProvider.GOOGLE, allowedAuthProviders)
? [GoogleStrategy]
: []),
...(authProviderCheck(AuthProvider.GITHUB, allowedAuthProviders)
? [GithubStrategy]
: []),
...(authProviderCheck(AuthProvider.MICROSOFT, allowedAuthProviders)
? [MicrosoftStrategy]
: []),
];
return {
module: AuthModule,
providers,
};
}
}

View File

@@ -3,14 +3,25 @@ import { AuthGuard } from '@nestjs/passport';
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper'; import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors'; import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class GithubSSOGuard extends AuthGuard('github') implements CanActivate { export class GithubSSOGuard extends AuthGuard('github') implements CanActivate {
constructor(private readonly configService: ConfigService) {
super();
}
canActivate( canActivate(
context: ExecutionContext, context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> { ): boolean | Promise<boolean> | Observable<boolean> {
if (!authProviderCheck(AuthProvider.GITHUB)) if (
!authProviderCheck(
AuthProvider.GITHUB,
this.configService.get('INFRA.VITE_ALLOWED_AUTH_PROVIDERS'),
)
) {
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 }); throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
}
return super.canActivate(context); return super.canActivate(context);
} }

View File

@@ -3,14 +3,25 @@ import { AuthGuard } from '@nestjs/passport';
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper'; import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors'; import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class GoogleSSOGuard extends AuthGuard('google') implements CanActivate { export class GoogleSSOGuard extends AuthGuard('google') implements CanActivate {
constructor(private readonly configService: ConfigService) {
super();
}
canActivate( canActivate(
context: ExecutionContext, context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> { ): boolean | Promise<boolean> | Observable<boolean> {
if (!authProviderCheck(AuthProvider.GOOGLE)) if (
!authProviderCheck(
AuthProvider.GOOGLE,
this.configService.get('INFRA.VITE_ALLOWED_AUTH_PROVIDERS'),
)
) {
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 }); throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
}
return super.canActivate(context); return super.canActivate(context);
} }

View File

@@ -3,20 +3,31 @@ import { AuthGuard } from '@nestjs/passport';
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper'; import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors'; import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class MicrosoftSSOGuard export class MicrosoftSSOGuard
extends AuthGuard('microsoft') extends AuthGuard('microsoft')
implements CanActivate implements CanActivate
{ {
constructor(private readonly configService: ConfigService) {
super();
}
canActivate( canActivate(
context: ExecutionContext, context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> { ): boolean | Promise<boolean> | Observable<boolean> {
if (!authProviderCheck(AuthProvider.MICROSOFT)) if (
!authProviderCheck(
AuthProvider.MICROSOFT,
this.configService.get('INFRA.VITE_ALLOWED_AUTH_PROVIDERS'),
)
) {
throwHTTPErr({ throwHTTPErr({
message: AUTH_PROVIDER_NOT_SPECIFIED, message: AUTH_PROVIDER_NOT_SPECIFIED,
statusCode: 404, statusCode: 404,
}); });
}
return super.canActivate(context); return super.canActivate(context);
} }

View File

@@ -7,6 +7,7 @@ import * as cookie from 'cookie';
import { AUTH_PROVIDER_NOT_SPECIFIED, COOKIES_NOT_FOUND } from 'src/errors'; import { AUTH_PROVIDER_NOT_SPECIFIED, COOKIES_NOT_FOUND } from 'src/errors';
import { throwErr } from 'src/utils'; import { throwErr } from 'src/utils';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { loadInfraConfiguration } from 'src/infra-config/helper';
enum AuthTokenType { enum AuthTokenType {
ACCESS_TOKEN = 'access_token', ACCESS_TOKEN = 'access_token',
@@ -117,18 +118,18 @@ export const subscriptionContextCookieParser = (rawCookies: string) => {
* @param provider Provider we want to check the presence of * @param provider Provider we want to check the presence of
* @returns Boolean if provider specified is present or not * @returns Boolean if provider specified is present or not
*/ */
export function authProviderCheck(provider: string) { export function authProviderCheck(
const configService = new ConfigService(); provider: string,
VITE_ALLOWED_AUTH_PROVIDERS: string,
) {
if (!provider) { if (!provider) {
throwErr(AUTH_PROVIDER_NOT_SPECIFIED); throwErr(AUTH_PROVIDER_NOT_SPECIFIED);
} }
const envVariables = configService.get('VITE_ALLOWED_AUTH_PROVIDERS') const envVariables = VITE_ALLOWED_AUTH_PROVIDERS
? configService ? VITE_ALLOWED_AUTH_PROVIDERS.split(',').map((provider) =>
.get('VITE_ALLOWED_AUTH_PROVIDERS') provider.trim().toUpperCase(),
.split(',') )
.map((provider) => provider.trim().toUpperCase())
: []; : [];
if (!envVariables.includes(provider.toUpperCase())) return false; if (!envVariables.includes(provider.toUpperCase())) return false;

View File

@@ -15,8 +15,8 @@ export class GithubStrategy extends PassportStrategy(Strategy) {
private configService: ConfigService, private configService: ConfigService,
) { ) {
super({ super({
clientID: configService.get('GITHUB_CLIENT_ID'), clientID: configService.get('INFRA.GITHUB_CLIENT_ID'),
clientSecret: configService.get('GITHUB_CLIENT_SECRET'), clientSecret: configService.get('INFRA.GITHUB_CLIENT_SECRET'),
callbackURL: configService.get('GITHUB_CALLBACK_URL'), callbackURL: configService.get('GITHUB_CALLBACK_URL'),
scope: [configService.get('GITHUB_SCOPE')], scope: [configService.get('GITHUB_SCOPE')],
store: true, store: true,

View File

@@ -15,8 +15,8 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
private configService: ConfigService, private configService: ConfigService,
) { ) {
super({ super({
clientID: configService.get('GOOGLE_CLIENT_ID'), clientID: configService.get('INFRA.GOOGLE_CLIENT_ID'),
clientSecret: configService.get('GOOGLE_CLIENT_SECRET'), clientSecret: configService.get('INFRA.GOOGLE_CLIENT_SECRET'),
callbackURL: configService.get('GOOGLE_CALLBACK_URL'), callbackURL: configService.get('GOOGLE_CALLBACK_URL'),
scope: configService.get('GOOGLE_SCOPE').split(','), scope: configService.get('GOOGLE_SCOPE').split(','),
passReqToCallback: true, passReqToCallback: true,

View File

@@ -15,8 +15,8 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
private configService: ConfigService, private configService: ConfigService,
) { ) {
super({ super({
clientID: configService.get('MICROSOFT_CLIENT_ID'), clientID: configService.get('INFRA.MICROSOFT_CLIENT_ID'),
clientSecret: configService.get('MICROSOFT_CLIENT_SECRET'), clientSecret: configService.get('INFRA.MICROSOFT_CLIENT_SECRET'),
callbackURL: configService.get('MICROSOFT_CALLBACK_URL'), callbackURL: configService.get('MICROSOFT_CALLBACK_URL'),
scope: [configService.get('MICROSOFT_SCOPE')], scope: [configService.get('MICROSOFT_SCOPE')],
tenant: configService.get('MICROSOFT_TENANT'), tenant: configService.get('MICROSOFT_TENANT'),

View File

@@ -16,7 +16,7 @@ export async function loadInfraConfiguration() {
environmentObject[infraConfig.name] = infraConfig.value; environmentObject[infraConfig.name] = infraConfig.value;
}); });
return environmentObject; return { INFRA: environmentObject };
} }
/** /**
@@ -24,7 +24,10 @@ export async function loadInfraConfiguration() {
* (Docker will re-start the app) * (Docker will re-start the app)
*/ */
export function stopApp() { export function stopApp() {
console.log('Stopping app in 5 seconds...');
setTimeout(() => { setTimeout(() => {
console.log('Stopping app now...');
process.exit(); process.exit();
}, 5000); }, 5000);
} }

View File

@@ -16,7 +16,9 @@ async function bootstrap() {
console.log(`Running in production: ${configService.get('PRODUCTION')}`); console.log(`Running in production: ${configService.get('PRODUCTION')}`);
console.log(`Port: ${configService.get('PORT')}`); console.log(`Port: ${configService.get('PORT')}`);
checkEnvironmentAuthProvider(); checkEnvironmentAuthProvider(
configService.get('VITE_ALLOWED_AUTH_PROVIDERS'),
);
app.use( app.use(
session({ session({

View File

@@ -162,24 +162,23 @@ export function isValidLength(title: string, length: number) {
/** /**
* This function is called by bootstrap() in main.ts * This function is called by bootstrap() in main.ts
* It checks if the "VITE_ALLOWED_AUTH_PROVIDERS" environment variable is properly set or not. * It checks if the "VITE_ALLOWED_AUTH_PROVIDERS" environment variable is properly set or not.
* If not, it throws an error. * If not, it throws an error.
*/ */
export function checkEnvironmentAuthProvider() { export function checkEnvironmentAuthProvider(
const configService = new ConfigService(); VITE_ALLOWED_AUTH_PROVIDERS: string,
) {
if (!configService.get('VITE_ALLOWED_AUTH_PROVIDERS')) { if (!VITE_ALLOWED_AUTH_PROVIDERS) {
throw new Error(ENV_NOT_FOUND_KEY_AUTH_PROVIDERS); throw new Error(ENV_NOT_FOUND_KEY_AUTH_PROVIDERS);
} }
if (configService.get('VITE_ALLOWED_AUTH_PROVIDERS') === '') { if (VITE_ALLOWED_AUTH_PROVIDERS === '') {
throw new Error(ENV_EMPTY_AUTH_PROVIDERS); throw new Error(ENV_EMPTY_AUTH_PROVIDERS);
} }
const givenAuthProviders = configService const givenAuthProviders = VITE_ALLOWED_AUTH_PROVIDERS.split(',').map(
.get('VITE_ALLOWED_AUTH_PROVIDERS') (provider) => provider.toLocaleUpperCase(),
.split(',') );
.map((provider) => provider.toLocaleUpperCase());
const supportedAuthProviders = Object.values(AuthProvider).map( const supportedAuthProviders = Object.values(AuthProvider).map(
(provider: string) => provider.toLocaleUpperCase(), (provider: string) => provider.toLocaleUpperCase(),
); );