feat: refactoring auth & add email password login (#318)

* feat: wip

* fix: issues

* feat: clean controllers and services

* fix: test

* Fix auth

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2023-06-17 13:42:02 +02:00
committed by GitHub
parent d13ceb98fa
commit 299ca293a8
215 changed files with 1668 additions and 680 deletions

View File

@ -0,0 +1,30 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
import { AuthService } from '../services/auth.service';
import { TokenService } from '../services/token.service';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {},
},
{
provide: TokenService,
useValue: {},
},
],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,23 @@
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from '../services/auth.service';
import { VerifyInput } from '../dto/verify.input';
import { VerifyEntity } from '../dto/verify.entity';
import { TokenService } from '../services/token.service';
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly tokenService: TokenService,
) {}
@Post('verify')
async verify(@Body() verifyInput: VerifyInput): Promise<VerifyEntity> {
const email = await this.tokenService.verifyLoginToken(
verifyInput.loginToken,
);
const result = await this.authService.verify(email);
return result;
}
}

View File

@ -0,0 +1,54 @@
import {
Controller,
Get,
InternalServerErrorException,
Req,
Res,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Response } from 'express';
import { GoogleRequest } from '../strategies/google.auth.strategy';
import { UserService } from '../../user/user.service';
import { assertNotNull } from 'src/utils/assert';
import { TokenService } from '../services/token.service';
@Controller('auth/google')
export class GoogleAuthController {
constructor(
private readonly tokenService: TokenService,
private readonly userService: UserService,
) {}
@Get()
@UseGuards(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'))
async googleAuthRedirect(@Req() req: GoogleRequest, @Res() res: Response) {
const { firstName, lastName, email } = req.user;
const displayName = [firstName, lastName].filter(assertNotNull).join(' ');
const user = await this.userService.createUser({
data: {
email,
displayName,
locale: 'en',
},
});
if (!user) {
throw new InternalServerErrorException(
'User email domain does not match an existing workspace',
);
}
const loginToken = await this.tokenService.generateLoginToken(user.email);
return res.redirect(this.tokenService.computeRedirectURI(loginToken.token));
}
}

View File

@ -0,0 +1,30 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PasswordAuthController } from './password-auth.controller';
import { AuthService } from '../services/auth.service';
import { TokenService } from '../services/token.service';
describe('PasswordAuthController', () => {
let controller: PasswordAuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PasswordAuthController],
providers: [
{
provide: AuthService,
useValue: {},
},
{
provide: TokenService,
useValue: {},
},
],
}).compile();
controller = module.get<PasswordAuthController>(PasswordAuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,23 @@
import { Body, Controller, Post } from '@nestjs/common';
import { ChallengeInput } from '../dto/challenge.input';
import { AuthService } from '../services/auth.service';
import { LoginTokenEntity } from '../dto/login-token.entity';
import { TokenService } from '../services/token.service';
@Controller('auth/password')
export class PasswordAuthController {
constructor(
private readonly authService: AuthService,
private readonly tokenService: TokenService,
) {}
@Post()
async challenge(
@Body() challengeInput: ChallengeInput,
): Promise<LoginTokenEntity> {
const user = await this.authService.challenge(challengeInput);
const loginToken = await this.tokenService.generateLoginToken(user.email);
return { loginToken };
}
}

View File

@ -0,0 +1,21 @@
import { BadRequestException, Body, Controller, Post } from '@nestjs/common';
import { RefreshTokenInput } from '../dto/refresh-token.input';
import { TokenService } from '../services/token.service';
@Controller('auth/token')
export class TokenController {
constructor(private tokenService: TokenService) {}
@Post()
async generateAccessToken(@Body() body: RefreshTokenInput) {
if (!body.refreshToken) {
throw new BadRequestException('Refresh token is mendatory');
}
const tokens = await this.tokenService.generateTokensFromRefreshToken(
body.refreshToken,
);
return { tokens: tokens };
}
}