Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/doc_developers/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('E2E Spec', () => {
let app: INestApplication;
beforeAll(async () => {
// Création de l'app
app = (await Test.createTestingModule({ imports: [AppModule] }).compile()).createNestApplication();
app = (await Test.createTestingModule({ imports: [AppModule.register()] }).compile()).createNestApplication();
// ... Reste de l'initialisation de l'app.
});
GetUserFromIdE2ESpec(() => app);
Expand Down
1 change: 0 additions & 1 deletion migration/etuutt_old/modules/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export async function migrateUsers(
prisma.user.create({
data: {
login: user.login,
hash: '', // it is not possible to migrate the password, it's not possible to hash a password to an empty string
studentId: user.studentId,
firstName: user.firstName,
lastName: user.lastName,
Expand Down
17 changes: 8 additions & 9 deletions prisma/models/user.prisma
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
model User {
id String @id @default(uuid())
login String @unique @db.VarChar(50)
hash String?
id String @id @default(uuid())
login String @unique @db.VarChar(50)
// TODO : maybe a field accountType (that is either CAS or login-password), but this may be implemented in the centralized authentication
studentId Int?
firstName String
lastName String
rgpdId String @unique
preferenceId String @unique
infosId String @unique
mailsPhonesId String @unique
socialNetworkId String @unique
privacyId String @unique
rgpdId String @unique
preferenceId String @unique
infosId String @unique
mailsPhonesId String @unique
socialNetworkId String @unique
privacyId String @unique

socialNetwork UserSocialNetwork @relation(fields: [socialNetworkId], references: [id])
rgpd UserRGPD @relation(fields: [rgpdId], references: [id])
Expand Down
4 changes: 0 additions & 4 deletions prisma/seed/modules/user.seed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { PrismaClient, Sex, RawUser } from '../../../src/prisma/types';
import { faker } from '@faker-js/faker';
import * as bcrypt from 'bcryptjs';
import { DEFAULT_APPLICATION } from '../utils';
import { AuthService } from '../../../src/auth/auth.service';

Expand All @@ -9,8 +8,6 @@ const FAKER_ROUNDS = 100;
export async function userSeed(prisma: PrismaClient): Promise<RawUser[]> {
console.log('Seeding users...');
const users: Promise<RawUser>[] = [];
const saltRounds = Number.parseInt(process.env.SALT_ROUNDS);
const hash = await bcrypt.hash('etuutt', saltRounds);
for (let i = 0; i < FAKER_ROUNDS; i++) {
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
Expand All @@ -22,7 +19,6 @@ export async function userSeed(prisma: PrismaClient): Promise<RawUser[]> {
studentId: faker.number.int({ max: 99999 }),
login: i === 0 ? 'student' : faker.internet.username(),
userType: 'STUDENT',
hash,
apiKeys: {
create: {
token: AuthService.generateToken(),
Expand Down
2 changes: 1 addition & 1 deletion scripts/dependency_graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SpelunkerModule } from 'nestjs-spelunker';
import * as fs from 'fs';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create(AppModule.register());
await generateDependencyGraph(app);
}
bootstrap();
Expand Down
95 changes: 56 additions & 39 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { DynamicModule, INestApplication, VersioningType } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { ProfileModule } from './profile/profile.module';
Expand All @@ -17,42 +17,59 @@ import { TranslationInterceptor } from './app.interceptor';
import { SemesterModule } from './semester/semester.module';
import { ImageMediaModule } from './media/image/imagemedia.module';
import { MailModule } from './mail/mail.module';
import { AppValidationPipe } from './app.pipe';
import { NotFoundFilter } from './exceptions';

@Module({
imports: [
ConfigModule,
HttpModule,
PrismaModule,
ImageMediaModule,
MailModule,
SemesterModule,
AuthModule,
ProfileModule,
UsersModule,
UeModule,
TimetableModule,
BranchModule,
AssosModule,
],
// The providers below are used for all the routes of the api.
// For example, the JwtGuard is used for all the routes and checks whether the user is authenticated.
providers: [
{
provide: APP_GUARD,
useClass: JwtGuard,
},
{
provide: APP_GUARD,
useClass: PermissionGuard,
},
{
provide: APP_GUARD,
useClass: RoleGuard,
},
{
provide: APP_INTERCEPTOR,
useClass: TranslationInterceptor,
},
],
})
export class AppModule {}
export class AppModule {
static register(): DynamicModule {
return {
module: AppModule,
imports: [
ConfigModule,
HttpModule,
PrismaModule,
ImageMediaModule,
MailModule,
SemesterModule,
AuthModule.register(),
ProfileModule,
UsersModule,
UeModule,
TimetableModule,
BranchModule,
AssosModule,
],
// The providers below are used for all the routes of the api.
// For example, the JwtGuard is used for all the routes and checks whether the user is authenticated.
providers: [
{
provide: APP_GUARD,
useClass: JwtGuard,
},
{
provide: APP_GUARD,
useClass: PermissionGuard,
},
{
provide: APP_GUARD,
useClass: RoleGuard,
},
{
provide: APP_INTERCEPTOR,
useClass: TranslationInterceptor,
},
],
}
}
static initApp(app: INestApplication) {
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
});
// This env variable is not set in ConfigModule because we use it before modules are loaded
app.setGlobalPrefix(process.env.API_PREFIX);
app.useGlobalPipes(new AppValidationPipe());
app.useGlobalFilters(new NotFoundFilter())
app.enableCors({ origin: '*' });
}
}
3 changes: 1 addition & 2 deletions src/app.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import {
ValidationPipe,
ParseIntPipe,
} from '@nestjs/common';
import { AppException, ERROR_CODE } from './exceptions';
import { validationExceptionFactory } from './validation';
import { AppException, ERROR_CODE, validationExceptionFactory } from './exceptions';

/**
* A validating pipe for regex.
Expand Down
4 changes: 4 additions & 0 deletions src/auth/application/application.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ export default class ApplicationService {
});
return this.authService.signAuthenticationToken(updatedApiKey.token, tokenExpiresIn);
}

formatRedirectUrl(application: Application, validationToken: string): string {
return `${application.redirectUrl}?${new URLSearchParams({ token: validationToken }).toString()}`;
}
}
72 changes: 72 additions & 0 deletions src/auth/auth-debug.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { IsPublic } from './decorator';
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiOkResponse, ApiOperation } from '@nestjs/swagger';
import { AppException, ERROR_CODE } from '../exceptions';
import { ApiAppErrorResponse } from '../app.dto';
import AuthSignUpDebugReqDto from './dto/req/auth-sign-up-debug-req.dto';
import { GetApplication } from './decorator/get-application.decorator';
import { Application } from './application/interfaces/application.interface';
import AuthSignInDebugResDto from './dto/res/auth-sign-in-debug-res.dto';
import AuthSignInDebugReqDto from './dto/req/auth-sign-in-debug-req.dto';
import { AuthService } from './auth.service';
import { ConfigService } from '../config/config.service';
import ApplicationService from './application/application.service';

@Controller({ path: 'auth', version: 'dev' })
export class AuthDebugController {
constructor(private authService: AuthService, private config: ConfigService, private applicationService: ApplicationService) {
}

@IsPublic()
@Post('signup')
@ApiOperation({
description: 'Signs up the user, and returns an authentication token. This token should be used as a Bearer token.',
})
@ApiCreatedResponse({
description: 'The account was created successfully, the user is now authenticated and the token is returned.',
type: AuthSignInDebugResDto,
})
@ApiAppErrorResponse(
ERROR_CODE.CREDENTIALS_ALREADY_TAKEN,
'Login, email address or any field that should be unique is already taken',
)
async debugSignUp(@Body() dto: AuthSignUpDebugReqDto, @GetApplication() application: Application): Promise<AuthSignInDebugResDto> {
const token = await this.authService.signUp(dto, application.id, false, dto.tokenExpiresIn);
const redirectUrl = `${application.redirectUrl}/${token}`;
return { signedIn: true, token, redirectUrl };
}

@HttpCode(HttpStatus.OK)
@IsPublic()
@Post('signin')
@ApiOperation({
description: 'Signs in the user, and returns an authentication token. This token should be used as a Bearer token.',
})
@ApiOkResponse({
description: 'The user was successfully authenticated, the token is returned.',
type: AuthSignInDebugResDto,
})
@ApiAppErrorResponse(ERROR_CODE.INVALID_CREDENTIALS, 'Either the login or the password is incorrect')
async debugSignIn(@Body() dto: AuthSignInDebugReqDto, @GetApplication() application: Application): Promise<AuthSignInDebugResDto> {
const res = await this.authService.signInFromLogin(dto.login, application.id);
if (!res) throw new AppException(ERROR_CODE.INVALID_CREDENTIALS);
if (!res.apiKey)
return {
signedIn: false,
token: await this.authService.signRegisterApiKeyToken(res.userId, application.id, dto.tokenExpiresIn),
redirectUrl: null,
};
if (application.id === this.config.ETUUTT_WEBSITE_APPLICATION_ID)
return {
signedIn: true,
token: await this.authService.signApiKey(res.apiKey.id, dto.tokenExpiresIn),
redirectUrl: null,
};
const token = await this.authService.signValidationToken(res.apiKey.id, application.id, dto.tokenExpiresIn);
return {
signedIn: true,
token: null,
redirectUrl: this.applicationService.formatRedirectUrl(application, token),
};
}
}
Loading
Loading