diff --git a/src/server/database/entities/User.ts b/src/server/database/entities/User.ts index 7ee702c..0f40a9c 100644 --- a/src/server/database/entities/User.ts +++ b/src/server/database/entities/User.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, OneToMany, OneToOne, JoinColumn, CreateDateColumn } from "typeorm"; +import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, OneToMany, OneToOne, JoinColumn, CreateDateColumn, MoreThanOrEqual } from "typeorm"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { MovieTicket } from "./MovieTicket"; @@ -28,7 +28,7 @@ export class User extends BaseEntity @OneToOne(() => MovieQuota, { nullable: true }) @JoinColumn() - quota!: MovieQuota; + quota!: MovieQuota | null; @OneToMany(() => User, user => user.movieTickets) movieTickets!: MovieTicket[]; @@ -69,4 +69,32 @@ export class User extends BaseEntity } return await user.save(); } + + /** + * Determine the user's available quota + */ + public async availableQuota() { + let quota = await this.fetchQuota(); + if (quota === null) { + return null; + } + let oneWeekAgo = new Date(Date.now() - 1000*60*60*24*7); + let numTicketsThisWeek = await MovieTicket.count({ + user: this, + createdAt: MoreThanOrEqual(oneWeekAgo), + isCanceled: false + }); + return quota.moviesPerWeek - numTicketsThisWeek; + } + + /** + * Get the user's quota, fetching it if undefined + */ + public async fetchQuota() { + if (this.quota !== undefined) { + return this.quota; + } + let user = await User.findOne(this.id, { relations: ["quota"] }); + return user.quota; + } } diff --git a/src/server/services/WebServer/middleware/auth.ts b/src/server/services/WebServer/middleware/auth.ts index ffa9985..fbc193f 100644 --- a/src/server/services/WebServer/middleware/auth.ts +++ b/src/server/services/WebServer/middleware/auth.ts @@ -29,7 +29,7 @@ async function authenticateJwtToken(request: FastifyRequest, reply: FastifyReply let user: User; try { let decoded = jwt.verify(token, Application.instance().APP_KEY); - user = await User.findOneOrFail(decoded.id); + user = await User.findOneOrFail(decoded.id, { relations: [ "quota" ] }); } catch(e) { reply.status(401); reply.send({ status: "Unauthorized" }); diff --git a/src/server/services/WebServer/requests/Request.ts b/src/server/services/WebServer/requests/Request.ts index a2cf89e..8a5ef20 100644 --- a/src/server/services/WebServer/requests/Request.ts +++ b/src/server/services/WebServer/requests/Request.ts @@ -7,18 +7,18 @@ export default class Request * Handle the incoming request */ public async handle(request: MiddlewareRequest, reply: FastifyReply) { - if (!this.isAuthorized(request)) { - reply.status(403); - return { - status: "Forbidden" - }; - } if (!this.checkFormat(request)) { reply.status(400); return { status: "Bad request" }; } + if (!await this.isAuthorized(request)) { + reply.status(403); + return { + status: "Forbidden" + }; + } try { await this.validate(request); } catch(errors) { @@ -33,16 +33,16 @@ export default class Request // Overridable --------------------------------------------------------------------------------- /** - * Check if the user is authorized to make this request + * Check the format of the given request */ - public isAuthorized(request: MiddlewareRequest) { + public checkFormat(request: MiddlewareRequest) { return true; } /** - * Check the format of the given request + * Check if the user is authorized to make this request */ - public checkFormat(request: MiddlewareRequest) { + public async isAuthorized(request: MiddlewareRequest) { return true; } diff --git a/src/server/services/WebServer/requests/RequestMovieRequest.ts b/src/server/services/WebServer/requests/RequestMovieRequest.ts index 8a71691..ecb6a98 100644 --- a/src/server/services/WebServer/requests/RequestMovieRequest.ts +++ b/src/server/services/WebServer/requests/RequestMovieRequest.ts @@ -7,7 +7,9 @@ export default class RequestMovieRequest extend /** * Ensure the user is able to request movies */ - public isAuthorized(request: MiddlewareRequest) { - return true; + public async isAuthorized(request: MiddlewareRequest) { + let user = request.middlewareParams.auth.user; + let quota = await user.availableQuota(); + return quota === null || quota > 0; } }