diff options
Diffstat (limited to 'server/controllers')
-rw-r--r-- | server/controllers/refresh_tokens.controller.ts | 32 | ||||
-rw-r--r-- | server/controllers/sessions.controller.ts | 67 | ||||
-rw-r--r-- | server/controllers/users.controller.ts | 65 |
3 files changed, 100 insertions, 64 deletions
diff --git a/server/controllers/refresh_tokens.controller.ts b/server/controllers/refresh_tokens.controller.ts new file mode 100644 index 0000000..2a24abe --- /dev/null +++ b/server/controllers/refresh_tokens.controller.ts @@ -0,0 +1,32 @@ +import { Body, Controller, Get, HttpException, Req } from '@nestjs/common'; +import { Request } from 'express'; +import { UsersService } from 'server/providers/services/users.service'; +import { SignInDto } from 'server/dto/sign_in.dto'; +import { RefreshTokenBody } from 'server/dto/refresh_token_body.dto'; +import { JwtService } from 'server/providers/services/jwt.service'; + +// this is kind of a misnomer because we are doing token based auth +// instead of session based auth +@Controller() +export class RefreshTokensController { + constructor(private usersService: UsersService, private jwtService: JwtService) {} + + @Get('/refresh_token') + async get(@Body() body: SignInDto, @Req() req: Request) { + const refreshToken: string = req.cookies['_refresh_token']; + if (!refreshToken) { + throw new HttpException('No refresh token present', 401); + } + + const tokenBody = this.jwtService.parseRefreshToken(refreshToken) as RefreshTokenBody; + + const user = await this.usersService.find(tokenBody.userId, ['refreshTokens']); + const userRefreshToken = user.refreshTokens.find((t) => t.id === tokenBody.id); + if (!userRefreshToken) { + throw new HttpException('User refresh token not found', 401); + } + + const token = this.jwtService.issueToken({ userId: user.id }); + return { token }; + } +} diff --git a/server/controllers/sessions.controller.ts b/server/controllers/sessions.controller.ts index 90b8e78..9ae647b 100644 --- a/server/controllers/sessions.controller.ts +++ b/server/controllers/sessions.controller.ts @@ -1,56 +1,53 @@ -import { - Body, - Controller, - Delete, - HttpException, - HttpStatus, - Post, - Redirect, - Res, -} from '@nestjs/common'; +import { Body, Controller, Delete, HttpException, HttpStatus, Post, Res } from '@nestjs/common'; import { Response } from 'express'; -import * as jwt from 'jsonwebtoken'; import { UsersService } from 'server/providers/services/users.service'; import { SignInDto } from 'server/dto/sign_in.dto'; - +import { JwtService } from 'server/providers/services/jwt.service'; +import { RefreshTokensService } from 'server/providers/services/refresh_tokens.service'; +import { RefreshToken } from 'server/entities/refresh_token.entity'; // this is kind of a misnomer because we are doing token based auth // instead of session based auth @Controller() export class SessionsController { - constructor(private usersService: UsersService) {} + constructor( + private usersService: UsersService, + private jwtService: JwtService, + private refreshTokenService: RefreshTokensService, + ) {} @Post('/sessions') - async create( - @Body() body: SignInDto, - @Res({ passthrough: true }) res: Response, - ) { - const { verified, user } = await this.usersService.verify( - body.email, - body.password, - ); + async create(@Body() body: SignInDto, @Res({ passthrough: true }) res: Response) { + const { verified, user } = await this.usersService.verify(body.email, body.password); if (!verified) { - throw new HttpException( - 'Invalid email or password.', - HttpStatus.BAD_REQUEST, - ); + throw new HttpException('Invalid email or password.', HttpStatus.BAD_REQUEST); + } + + let refreshToken = user.refreshTokens[0]; + if (!refreshToken) { + const newRefreshToken = new RefreshToken(); + newRefreshToken.user = user; + refreshToken = await this.refreshTokenService.create(newRefreshToken); + // generate new refresh token } - // Write JWT to cookie and send with response. - const token = jwt.sign( - { - user_id: user.id, - }, - process.env.ENCRYPTION_KEY, - { expiresIn: '1h' }, - ); - res.cookie('_token', token); + + // JWT gets sent with response + const token = this.jwtService.issueToken({ userId: user.id }); + + const refreshJwtToken = this.jwtService.issueRefreshToken({ id: refreshToken.id, userId: user.id }); + + // only refresh token should go in the cookie + res.cookie('_refresh_token', refreshJwtToken, { + httpOnly: true, // prevents javascript code from accessing cookie (helps protect against XSS attacks) + }); + return { token }; } @Delete('/sessions') async destroy(@Res({ passthrough: true }) res: Response) { - res.clearCookie('_token'); + res.clearCookie('_refresh_token'); return { success: true }; } } diff --git a/server/controllers/users.controller.ts b/server/controllers/users.controller.ts index 120a2b3..f9aba90 100644 --- a/server/controllers/users.controller.ts +++ b/server/controllers/users.controller.ts @@ -1,50 +1,57 @@ -import { - Body, - Controller, - HttpException, - HttpStatus, - Post, - Res, -} from '@nestjs/common'; +import { Body, Controller, Get, HttpException, HttpStatus, Post, Res, UseGuards } from '@nestjs/common'; import * as bcrypt from 'bcrypt'; import { Response } from 'express'; -import * as jwt from 'jsonwebtoken'; +import { JwtBody } from 'server/decorators/jwt_body.decorator'; import { CreateUserDto } from 'server/dto/create_user.dto'; +import { JwtBodyDto } from 'server/dto/jwt_body.dto'; +import { RefreshToken } from 'server/entities/refresh_token.entity'; import { User } from 'server/entities/user.entity'; +import { AuthGuard } from 'server/providers/guards/auth.guard'; +import { JwtService } from 'server/providers/services/jwt.service'; +import { RefreshTokensService } from 'server/providers/services/refresh_tokens.service'; import { UsersService } from 'server/providers/services/users.service'; @Controller() export class UsersController { - constructor(private usersService: UsersService) {} + constructor( + private usersService: UsersService, + private jwtService: JwtService, + private refreshTokenService: RefreshTokensService, + ) {} + + @Get('/users/me') + @UseGuards(AuthGuard) + async getCurrentUser(@JwtBody() jwtBody: JwtBodyDto) { + const user = await this.usersService.find(jwtBody.userId); + return { user }; + } @Post('/users') - async create( - @Body() userPayload: CreateUserDto, - @Res({ passthrough: true }) res: Response, - ) { + async create(@Body() userPayload: CreateUserDto, @Res({ passthrough: true }) res: Response) { const newUser = new User(); newUser.email = userPayload.email; newUser.name = userPayload.name; - newUser.password_hash = await bcrypt.hash(userPayload.password, 10); + newUser.passwordHash = await bcrypt.hash(userPayload.password, 10); try { const user = await this.usersService.create(newUser); - // assume signup and write cookie - // Write JWT to cookie and send with response. - const token = jwt.sign( - { - user_id: user.id, - }, - process.env.ENCRYPTION_KEY, - { expiresIn: '1h' }, - ); - res.cookie('_token', token); + // create refresh token in database for user + const newRefreshToken = new RefreshToken(); + newRefreshToken.user = user; + const refreshToken = await this.refreshTokenService.create(newRefreshToken); + + // issue jwt and refreshJwtToken + const token = this.jwtService.issueToken({ userId: user.id }); + const refreshJwtToken = this.jwtService.issueRefreshToken({ id: refreshToken.id, userId: user.id }); + + // only refresh token should go in the cookie + res.cookie('_refresh_token', refreshJwtToken, { + httpOnly: true, // prevents javascript code from accessing cookie (helps protect against XSS attacks) + }); + return { user, token }; } catch (e) { - throw new HttpException( - `User creation failed. ${e.message}`, - HttpStatus.BAD_REQUEST, - ); + throw new HttpException(`User creation failed. ${e.message}`, HttpStatus.BAD_REQUEST); } } } |