summaryrefslogtreecommitdiff
path: root/server/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/refresh_tokens.controller.ts32
-rw-r--r--server/controllers/sessions.controller.ts67
-rw-r--r--server/controllers/users.controller.ts65
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);
}
}
}