Skip to content
This repository has been archived by the owner on Apr 8, 2023. It is now read-only.

Commit

Permalink
Add emailVerified to authentication tokens and emailVerified checks f…
Browse files Browse the repository at this point in the history
…or creating tips, opinions, reviews, tip votes and opinion votes (#64)
  • Loading branch information
shawnkoh authored Sep 26, 2019
1 parent d79ab46 commit ce6d96d
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 49 deletions.
12 changes: 6 additions & 6 deletions backend/src/controllers/TokensController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
ResetPasswordTokenSignedPayload
} from "../types/tokens";
import { getEntityArray } from "../utils/entities";
import { getAuthenticationTokens } from "../utils/users";

export async function login(request: Request, response: Response) {
if (!request.headers.authorization) {
Expand Down Expand Up @@ -43,7 +42,7 @@ export async function login(request: Request, response: Response) {
return;
}

const result = getAuthenticationTokens(user);
const result = user.createAuthenticationTokens();
response.status(200).json(result);
}

Expand All @@ -63,12 +62,10 @@ export async function refreshAuthentication(
return;
}

const result = getAuthenticationTokens(user);
const result = user.createAuthenticationTokens();
response.status(200).json(result);
}

// export async function resetPassword(request: Request, response: Response) {

export async function editOpinion(request: Request, response: Response) {
try {
const entityTokenSignedPayload = response.locals
Expand Down Expand Up @@ -242,7 +239,10 @@ export async function verifyEmail(request: Request, response: Response) {
if (result.affected === 0) {
throw new Error("Failed to update user");
}
response.status(204).send();

user.emailVerified = true;
const authenticationTokens = user.createAuthenticationTokens();
response.status(204).json(authenticationTokens);
} catch (error) {
response.sendStatus(400);
console.error(error);
Expand Down
3 changes: 1 addition & 2 deletions backend/src/controllers/UsersController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { NextFunction, Request, Response } from "express";
import { getRepository } from "typeorm";
import { User } from "../entities/User";
import { AccessTokenSignedPayload } from "../types/tokens";
import { getAuthenticationTokens } from "../utils/users";
import {
sendVerificationEmail,
sendResetPasswordEmail
Expand All @@ -23,7 +22,7 @@ export async function create(request: Request, response: Response) {

sendVerificationEmail(user);

const result = { ...user, ...getAuthenticationTokens(user) };
const result = { ...user, ...user.createAuthenticationTokens() };
delete result.password;
response.status(201).json(result);
} catch (error) {
Expand Down
37 changes: 36 additions & 1 deletion backend/src/entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ import {
IsString
} from "class-validator";
import { Column, Entity, OneToMany } from "typeorm";
import { sign } from "jsonwebtoken";
import { Discardable } from "./Discardable";
import { TipVote } from "./TipVote";
import { OpinionVote } from "./OpinionVote";
import { UserRole } from "../types/users";
import { BearerTokenType, EntityTokenPayload } from "../types/tokens";
import {
BearerTokenType,
EntityTokenPayload,
Credentials,
RefreshTokenPayload,
AccessTokenPayload
} from "../types/tokens";

@Entity()
export class User extends Discardable {
Expand Down Expand Up @@ -65,5 +72,33 @@ export class User extends Discardable {
username: this.username
});

getCredentials = (): Credentials => ({
id: this.id,
email: this.email,
emailVerified: this.emailVerified,
role: this.role,
username: this.username
});

createAuthenticationTokens = () => {
const credentials = this.getCredentials();
const accessTokenPayload: AccessTokenPayload = {
type: BearerTokenType.AccessToken,
...credentials
};
const accessToken = sign(accessTokenPayload, process.env.JWT_SECRET!, {
expiresIn: "15m"
});
const refreshTokenPayload: RefreshTokenPayload = {
type: BearerTokenType.RefreshToken,
...credentials
};
const refreshToken = sign(refreshTokenPayload, process.env.JWT_SECRET!, {
expiresIn: "7 days"
});

return { accessToken, refreshToken };
};

entityName = "User";
}
16 changes: 16 additions & 0 deletions backend/src/middlewares/checkEmailVerified.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Request, Response, NextFunction } from "express";
import { AccessTokenSignedPayload } from "../types/tokens";

export const checkEmailVerified = (
req: Request,
res: Response,
next: NextFunction
) => {
const payload = res.locals.payload as AccessTokenSignedPayload;

if (payload.emailVerified) {
next();
} else {
res.sendStatus(403);
}
};
2 changes: 2 additions & 0 deletions backend/src/routes/api/moduleSemesters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as OpinionsController from "../../controllers/OpinionsController";
import * as ReviewsController from "../../controllers/ReviewsController";
import * as TipsController from "../../controllers/TipsController";
import { checkBearerToken } from "../../middlewares/checkBearerToken";
import { checkEmailVerified } from "../../middlewares/checkEmailVerified";
import { BearerTokenType } from "../../types/tokens";

export const router = Router();
Expand All @@ -14,6 +15,7 @@ router.get("/:id/tips", ModuleSemestersController.tips);
router.get("/:id/reviews", ModuleSemestersController.reviews);

router.use(checkBearerToken(BearerTokenType.AccessToken));
router.use(checkEmailVerified);
router.post("/:id/opinions", OpinionsController.create);
router.post("/:id/tips", TipsController.create);
router.post("/:id/reviews", ReviewsController.create);
Expand Down
2 changes: 2 additions & 0 deletions backend/src/routes/api/opinions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { checkBearerToken } from "../../middlewares/checkBearerToken";
import { checkRole } from "../../middlewares/checkRole";
import { UserRole } from "../../types/users";
import { BearerTokenType } from "../../types/tokens";
import { checkEmailVerified } from "../../middlewares/checkEmailVerified";

export const router = Router();

router.get("/:id", OpinionsController.show);
router.get("/:id/votes", OpinionsController.votes);

router.use(checkBearerToken(BearerTokenType.AccessToken));
router.use(checkEmailVerified);
router.post("/:id/votes", OpinionVotesController.create);
router.use(checkRole([UserRole.Admin]));
router.delete("/:id", OpinionsController.discard);
Expand Down
2 changes: 2 additions & 0 deletions backend/src/routes/api/tips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { checkBearerToken } from "../../middlewares/checkBearerToken";
import { checkRole } from "../../middlewares/checkRole";
import { UserRole } from "../../types/users";
import { BearerTokenType } from "../../types/tokens";
import { checkEmailVerified } from "../../middlewares/checkEmailVerified";

export const router = Router();

router.get("/:id", TipsController.show);
router.get("/:id/votes", TipsController.votes);

router.use(checkBearerToken(BearerTokenType.AccessToken));
router.use(checkEmailVerified);
router.post("/:id/votes", TipVotesController.create);
router.use(checkRole([UserRole.Admin]));
router.delete("/:id", TipsController.discard);
Expand Down
4 changes: 3 additions & 1 deletion backend/src/types/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ type TokenLifespan = {
exp: number;
};

type Credentials = {
export type Credentials = {
id: number;
email: string;
emailVerified: boolean;
role: UserRole;
username: string;
};
Expand Down Expand Up @@ -65,6 +66,7 @@ function hasCredentials(payload: any) {
typeof payload.id === "number" &&
typeof payload.username === "string" &&
typeof payload.email === "string" &&
typeof payload.emailVerified === "boolean" &&
Object.values(UserRole).includes(payload.role)
);
}
Expand Down
5 changes: 1 addition & 4 deletions backend/src/utils/sendgrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ export function sendResetPasswordEmail(user: User) {

const payload: ResetPasswordTokenPayload = {
type: BearerTokenType.ResetPasswordToken,
id: user.id,
email: user.email,
role: user.role,
username: user.username
...user.getCredentials()
};

const token = jwt.sign(payload, process.env.JWT_SECRET!, {
Expand Down
35 changes: 0 additions & 35 deletions backend/src/utils/users.ts

This file was deleted.

0 comments on commit ce6d96d

Please sign in to comment.