Make google auth optional on server side (#508)
* Make google auth optional on server side * fix lint * Fix according to review
This commit is contained in:
@ -1,6 +1,3 @@
|
||||
AUTH_GOOGLE_CLIENT_ID=REPLACE_ME
|
||||
AUTH_GOOGLE_CLIENT_SECRET=REPLACE_ME
|
||||
AUTH_GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/redirect
|
||||
ACCESS_TOKEN_SECRET=secret_jwt
|
||||
ACCESS_TOKEN_EXPIRES_IN=5m
|
||||
LOGIN_TOKEN_SECRET=secret_login_token
|
||||
@ -10,5 +7,4 @@ REFRESH_TOKEN_EXPIRES_IN=90d
|
||||
PG_DATABASE_URL=postgres://postgres:postgrespassword@postgres:5432/default?connection_limit=1
|
||||
FRONT_AUTH_CALLBACK_URL=http://localhost:3001/auth/callback
|
||||
STORAGE_TYPE=local
|
||||
STORAGE_REGION=eu-west-1
|
||||
STORAGE_LOCATION=.local-storage
|
||||
STORAGE_LOCAL_PATH=.local-storage
|
||||
|
||||
@ -11,6 +11,7 @@ import { Response } from 'express';
|
||||
import { GoogleRequest } from '../strategies/google.auth.strategy';
|
||||
import { UserService } from '../../user/user.service';
|
||||
import { TokenService } from '../services/token.service';
|
||||
import { GoogleProviderEnabledGuard } from '../guards/google-provider-enabled.guard';
|
||||
|
||||
@Controller('auth/google')
|
||||
export class GoogleAuthController {
|
||||
@ -20,14 +21,14 @@ export class GoogleAuthController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(AuthGuard('google'))
|
||||
@UseGuards(GoogleProviderEnabledGuard, AuthGuard('google'))
|
||||
async googleAuth() {
|
||||
// As this method is protected by Google Auth guard, it will trigger Google SSO flow
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('redirect')
|
||||
@UseGuards(AuthGuard('google'))
|
||||
@UseGuards(GoogleProviderEnabledGuard, AuthGuard('google'))
|
||||
async googleAuthRedirect(@Req() req: GoogleRequest, @Res() res: Response) {
|
||||
const { firstName, lastName, email } = req.user;
|
||||
|
||||
|
||||
14
server/src/core/auth/guards/google-provider-enabled.guard.ts
Normal file
14
server/src/core/auth/guards/google-provider-enabled.guard.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Injectable, CanActivate, HttpException } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleProviderEnabledGuard implements CanActivate {
|
||||
constructor(private readonly environmentService: EnvironmentService) {}
|
||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!this.environmentService.getAuthGoogleEnabled()) {
|
||||
throw new HttpException('Google auth is not enabled', 404);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -16,10 +16,17 @@ export type GoogleRequest = Request & {
|
||||
@Injectable()
|
||||
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
constructor(environmentService: EnvironmentService) {
|
||||
const isAuthGoogleEnabled = environmentService.getAuthGoogleEnabled();
|
||||
super({
|
||||
clientID: environmentService.getAuthGoogleClientId(),
|
||||
clientSecret: environmentService.getAuthGoogleClientSecret(),
|
||||
callbackURL: environmentService.getAuthGoogleCallbackUrl(),
|
||||
clientID: isAuthGoogleEnabled
|
||||
? environmentService.getAuthGoogleClientId()
|
||||
: 'disabled',
|
||||
clientSecret: isAuthGoogleEnabled
|
||||
? environmentService.getAuthGoogleClientSecret()
|
||||
: 'disabled',
|
||||
callbackURL: isAuthGoogleEnabled
|
||||
? environmentService.getAuthGoogleCallbackUrl()
|
||||
: 'disabled',
|
||||
scope: ['email', 'profile'],
|
||||
});
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ export class FileService {
|
||||
}
|
||||
|
||||
private async getLocalFileStream(folderPath: string, filename: string) {
|
||||
const storageLocation = this.environmentService.getStorageLocation();
|
||||
const storageLocation = this.environmentService.getStorageLocalPath();
|
||||
|
||||
const filePath = join(
|
||||
process.cwd(),
|
||||
|
||||
@ -40,6 +40,10 @@ export class EnvironmentService {
|
||||
return this.configService.get<string>('FRONT_AUTH_CALLBACK_URL')!;
|
||||
}
|
||||
|
||||
getAuthGoogleEnabled(): boolean | undefined {
|
||||
return this.configService.get<boolean>('AUTH_GOOGLE_ENABLED');
|
||||
}
|
||||
|
||||
getAuthGoogleClientId(): string | undefined {
|
||||
return this.configService.get<string>('AUTH_GOOGLE_CLIENT_ID');
|
||||
}
|
||||
@ -56,11 +60,15 @@ export class EnvironmentService {
|
||||
return this.configService.get<StorageType>('STORAGE_TYPE');
|
||||
}
|
||||
|
||||
getStorageRegion(): AwsRegion | undefined {
|
||||
return this.configService.get<AwsRegion>('STORAGE_REGION');
|
||||
getStorageS3Region(): AwsRegion | undefined {
|
||||
return this.configService.get<AwsRegion>('STORAGE_S3_REGION');
|
||||
}
|
||||
|
||||
getStorageLocation(): string {
|
||||
return this.configService.get<string>('STORAGE_LOCATION')!;
|
||||
getStorageS3Name(): string | undefined {
|
||||
return this.configService.get<AwsRegion>('STORAGE_S3_NAME');
|
||||
}
|
||||
|
||||
getStorageLocalPath(): string | undefined {
|
||||
return this.configService.get<string>('STORAGE_LOCAL_PATH')!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { plainToClass, Transform } from 'class-transformer';
|
||||
import {
|
||||
IsEnum,
|
||||
IsOptional,
|
||||
@ -6,6 +6,7 @@ import {
|
||||
IsUrl,
|
||||
ValidateIf,
|
||||
validateSync,
|
||||
IsBoolean,
|
||||
} from 'class-validator';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { IsDuration } from './decorators/is-duration.decorator';
|
||||
@ -38,16 +39,21 @@ export class EnvironmentVariables {
|
||||
@IsUrl({ require_tld: false })
|
||||
FRONT_AUTH_CALLBACK_URL: string;
|
||||
|
||||
@IsString()
|
||||
@Transform(({ value }) => envValueToBoolean(value))
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
AUTH_GOOGLE_ENABLED?: boolean;
|
||||
|
||||
@IsString()
|
||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
|
||||
AUTH_GOOGLE_CLIENT_ID?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
|
||||
AUTH_GOOGLE_CLIENT_SECRET?: string;
|
||||
|
||||
@IsUrl({ require_tld: false })
|
||||
@IsOptional()
|
||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
|
||||
AUTH_GOOGLE_CALLBACK_URL?: string;
|
||||
|
||||
// Storage
|
||||
@ -55,17 +61,22 @@ export class EnvironmentVariables {
|
||||
@IsOptional()
|
||||
STORAGE_TYPE?: StorageType;
|
||||
|
||||
@ValidateIf((_, value) => value === StorageType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageType.S3)
|
||||
@IsAWSRegion()
|
||||
STORAGE_REGION?: AwsRegion;
|
||||
STORAGE_S3_REGION?: AwsRegion;
|
||||
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageType.S3)
|
||||
@IsString()
|
||||
STORAGE_S3_NAME?: string;
|
||||
|
||||
@IsString()
|
||||
STORAGE_LOCATION: string;
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageType.Local)
|
||||
STORAGE_LOCAL_PATH?: string;
|
||||
}
|
||||
|
||||
export function validate(config: Record<string, unknown>) {
|
||||
const validatedConfig = plainToClass(EnvironmentVariables, config, {
|
||||
enableImplicitConversion: true,
|
||||
enableImplicitConversion: false,
|
||||
});
|
||||
|
||||
const errors = validateSync(validatedConfig, {
|
||||
@ -75,3 +86,16 @@ export function validate(config: Record<string, unknown>) {
|
||||
|
||||
return validatedConfig;
|
||||
}
|
||||
|
||||
const envValueToBoolean = (value: any) => {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
if (['true', 'on', 'yes', '1'].includes(value.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
if (['false', 'off', 'no', '0'].includes(value.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@ -6,7 +6,6 @@ import { LocalStorageModule } from './local-storage/local-storage.module';
|
||||
import { LocalStorageModuleOptions } from './local-storage/interfaces';
|
||||
import { EnvironmentModule } from './environment/environment.module';
|
||||
import { EnvironmentService } from './environment/environment.service';
|
||||
import { assert } from 'src/utils/assert';
|
||||
|
||||
/**
|
||||
* S3 Storage Module factory
|
||||
@ -16,23 +15,16 @@ import { assert } from 'src/utils/assert';
|
||||
const S3StorageModuleFactory = async (
|
||||
environmentService: EnvironmentService,
|
||||
): Promise<S3StorageModuleOptions> => {
|
||||
const fileSystem = environmentService.getStorageType();
|
||||
const bucketName = environmentService.getStorageLocation();
|
||||
const region = environmentService.getStorageRegion();
|
||||
|
||||
if (fileSystem === 'local') {
|
||||
return { bucketName };
|
||||
}
|
||||
|
||||
assert(region, 'S3 region is not defined');
|
||||
const bucketName = environmentService.getStorageS3Name();
|
||||
const region = environmentService.getStorageS3Region();
|
||||
|
||||
return {
|
||||
bucketName,
|
||||
bucketName: bucketName ?? '',
|
||||
credentials: fromNodeProviderChain({
|
||||
clientConfig: { region },
|
||||
}),
|
||||
forcePathStyle: true,
|
||||
region,
|
||||
region: region ?? '',
|
||||
};
|
||||
};
|
||||
|
||||
@ -44,10 +36,10 @@ const S3StorageModuleFactory = async (
|
||||
const localStorageModuleFactory = async (
|
||||
environmentService: EnvironmentService,
|
||||
): Promise<LocalStorageModuleOptions> => {
|
||||
const folderName = environmentService.getStorageLocation();
|
||||
const storagePath = environmentService.getStorageLocalPath();
|
||||
|
||||
return {
|
||||
storagePath: process.cwd() + '/' + folderName,
|
||||
storagePath: process.cwd() + '/' + storagePath,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -2,4 +2,5 @@ import { S3ClientConfig } from '@aws-sdk/client-s3';
|
||||
|
||||
export interface S3StorageModuleOptions extends S3ClientConfig {
|
||||
bucketName: string;
|
||||
region: string;
|
||||
}
|
||||
|
||||
@ -23,9 +23,13 @@ export class S3StorageService {
|
||||
@Inject(MODULE_OPTIONS_TOKEN)
|
||||
private readonly options: S3StorageModuleOptions,
|
||||
) {
|
||||
const { bucketName, ...s3Options } = options;
|
||||
const { bucketName, region, ...s3Options } = options;
|
||||
|
||||
this.s3Client = new S3(s3Options);
|
||||
if (!bucketName || !region) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.s3Client = new S3({ ...s3Options, region });
|
||||
this.bucketName = bucketName;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user