feat: upload module (#486)

* feat: wip upload module

* feat: local storage and serve local images

* feat: protect against injections

* feat: server local and s3 files

* fix: use storage location when serving local files

* feat: cross field env validation
This commit is contained in:
Jérémy M
2023-07-04 16:02:44 +02:00
committed by GitHub
parent 820ef184d3
commit 5e1fc1ad11
52 changed files with 2632 additions and 64 deletions

View File

@ -1,6 +1,5 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
import { AuthService } from './services/auth.service';
import { GoogleAuthController } from './controllers/google-auth.controller';
@ -8,25 +7,24 @@ import { GoogleStrategy } from './strategies/google.auth.strategy';
import { PrismaService } from 'src/database/prisma.service';
import { UserModule } from '../user/user.module';
import { VerifyAuthController } from './controllers/verify-auth.controller';
import { TokenService } from './services/token.service';
import { AuthResolver } from './auth.resolver';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
const jwtModule = JwtModule.registerAsync({
useFactory: async (configService: ConfigService) => {
useFactory: async (environmentService: EnvironmentService) => {
return {
secret: configService.get<string>('ACCESS_TOKEN_SECRET'),
secret: environmentService.getAccessTokenSecret(),
signOptions: {
expiresIn: configService.get<string>('ACCESS_TOKEN_EXPIRES_IN'),
expiresIn: environmentService.getAccessTokenExpiresIn(),
},
};
},
imports: [ConfigModule.forRoot({})],
inject: [ConfigService],
inject: [EnvironmentService],
});
@Module({
imports: [jwtModule, ConfigModule.forRoot({}), UserModule],
imports: [jwtModule, UserModule],
controllers: [GoogleAuthController, VerifyAuthController],
providers: [
AuthService,

View File

@ -3,7 +3,7 @@ import { TokenService } from './token.service';
import { PrismaService } from 'src/database/prisma.service';
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
describe('TokenService', () => {
let service: TokenService;
@ -17,7 +17,7 @@ describe('TokenService', () => {
useValue: {},
},
{
provide: ConfigService,
provide: EnvironmentService,
useValue: {},
},
{

View File

@ -8,24 +8,24 @@ import {
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { JwtPayload } from '../strategies/jwt.auth.strategy';
import { ConfigService } from '@nestjs/config';
import { PrismaService } from 'src/database/prisma.service';
import { assert } from 'src/utils/assert';
import { addMilliseconds } from 'date-fns';
import ms from 'ms';
import { AuthToken } from '../dto/token.entity';
import { TokenExpiredError } from 'jsonwebtoken';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
@Injectable()
export class TokenService {
constructor(
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
private readonly environmentService: EnvironmentService,
private readonly prismaService: PrismaService,
) {}
async generateAccessToken(userId: string): Promise<AuthToken> {
const expiresIn = this.configService.get<string>('ACCESS_TOKEN_EXPIRES_IN');
const expiresIn = this.environmentService.getAccessTokenExpiresIn();
assert(expiresIn, '', InternalServerErrorException);
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
@ -56,10 +56,8 @@ export class TokenService {
}
async generateRefreshToken(userId: string): Promise<AuthToken> {
const secret = this.configService.get('REFRESH_TOKEN_SECRET');
const expiresIn = this.configService.get<string>(
'REFRESH_TOKEN_EXPIRES_IN',
);
const secret = this.environmentService.getRefreshTokenSecret();
const expiresIn = this.environmentService.getRefreshTokenExpiresIn();
assert(expiresIn, '', InternalServerErrorException);
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
@ -87,8 +85,8 @@ export class TokenService {
}
async generateLoginToken(email: string): Promise<AuthToken> {
const secret = this.configService.get('LOGIN_TOKEN_SECRET');
const expiresIn = this.configService.get<string>('LOGIN_TOKEN_EXPIRES_IN');
const secret = this.environmentService.getLoginTokenSecret();
const expiresIn = this.environmentService.getLoginTokenExpiresIn();
assert(expiresIn, '', InternalServerErrorException);
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
const jwtPayload = {
@ -105,7 +103,7 @@ export class TokenService {
}
async verifyLoginToken(loginToken: string): Promise<string> {
const loginTokenSecret = this.configService.get('LOGIN_TOKEN_SECRET');
const loginTokenSecret = this.environmentService.getLoginTokenSecret();
const payload = await this.verifyJwt(loginToken, loginTokenSecret);
@ -113,7 +111,7 @@ export class TokenService {
}
async verifyRefreshToken(refreshToken: string) {
const secret = this.configService.get('REFRESH_TOKEN_SECRET');
const secret = this.environmentService.getRefreshTokenSecret();
const jwtPayload = await this.verifyJwt(refreshToken, secret);
assert(
@ -191,9 +189,7 @@ export class TokenService {
}
computeRedirectURI(loginToken: string): string {
return `${this.configService.get<string>(
'FRONT_AUTH_CALLBACK_URL',
)}?loginToken=${loginToken}`;
return `${this.environmentService.getFrontAuthCallbackUrl()}?loginToken=${loginToken}`;
}
async verifyJwt(token: string, secret?: string) {

View File

@ -2,8 +2,8 @@ import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
export type GoogleRequest = Request & {
user: {
@ -15,11 +15,11 @@ export type GoogleRequest = Request & {
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(configService: ConfigService) {
constructor(environmentService: EnvironmentService) {
super({
clientID: configService.get<string>('AUTH_GOOGLE_CLIENT_ID'),
clientSecret: configService.get<string>('AUTH_GOOGLE_CLIENT_SECRET'),
callbackURL: configService.get<string>('AUTH_GOOGLE_CALLBACK_URL'),
clientID: environmentService.getAuthGoogleClientId(),
clientSecret: environmentService.getAuthGoogleClientSecret(),
callbackURL: environmentService.getAuthGoogleCallbackUrl(),
scope: ['email', 'profile'],
});
}

View File

@ -1,9 +1,9 @@
import { Strategy, ExtractJwt } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaService } from 'src/database/prisma.service';
import { User, Workspace } from '@prisma/client';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
export type JwtPayload = { sub: string; workspaceId: string };
export type PassportUser = { user: User; workspace: Workspace };
@ -11,13 +11,13 @@ export type PassportUser = { user: User; workspace: Workspace };
@Injectable()
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
private readonly configService: ConfigService,
private readonly environmentService: EnvironmentService,
private readonly prismaService: PrismaService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('ACCESS_TOKEN_SECRET'),
secretOrKey: environmentService.getAccessTokenSecret(),
});
}