Skip to content

Commit

Permalink
feat: implment issue passport
Browse files Browse the repository at this point in the history
  • Loading branch information
skgndi12 committed Jan 11, 2024
1 parent d80eed0 commit b4eb854
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 29 deletions.
46 changes: 40 additions & 6 deletions api/generate/openapi.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,25 @@
"Test"
],
"summary": "Authorization header required",
"security": [
{
"jwt": []
}
],
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {}
"schema": {
"$ref": "#/components/schemas/AuthRequiredResponse"
}
}
}
},
"default": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HttpErrorResponse"
}
}
}
}
Expand Down Expand Up @@ -143,6 +150,33 @@
"additionalProperties": {
"type": "string"
}
},
"AuthRequiredResponse": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"message"
]
},
"HttpErrorResponse": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
"required": [
"messages"
]
}
},
"securitySchemes": {
Expand Down
53 changes: 50 additions & 3 deletions api/src/controller/http/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import 'express-async-errors';
import { HttpError as ValidationError } from 'express-openapi-validator/dist/framework/types';
import { Logger } from 'winston';

import { CustomError, HttpErrorCode } from '@src/error/errors';
import { JwtHandler } from '@src/core/ports/jwt.handler';
import { AppErrorCode, CustomError, HttpErrorCode } from '@src/error/errors';

import { HttpErrorResponse } from '@controller/http/response';
import { idTokenCookieName } from '@controller/http/types';

export class Middleware {
constructor(public logger: Logger) {}
constructor(
private readonly logger: Logger,
private readonly jwtHandler: JwtHandler
) {}

public accessLog = (req: Request, res: Response, next: NextFunction) => {
const start = new Date().getTime();
Expand All @@ -33,7 +38,7 @@ export class Middleware {
};

public handleError = (
err: Error,
err: unknown,
req: Request,
res: Response<HttpErrorResponse>,
next: NextFunction
Expand Down Expand Up @@ -70,6 +75,17 @@ export class Middleware {
);
};

public issuePassport = (req: Request, res: Response, next: NextFunction) => {
let passport = this.issuePassportFromHeader(req);

if (passport === null) {
passport = this.issuePassportFromCookie(req);
}

res.locals.passport = passport;
next();
};

private convertValidationErrorToCustomError = (
err: ValidationError
): CustomError => {
Expand Down Expand Up @@ -171,4 +187,35 @@ export class Middleware {
messages: err.messages
});
};

private issuePassportFromHeader = (req: Request) => {
const authHeader = req.get('Authorization');
if (!authHeader) {
return null;
}

const splittedAuthHeader = authHeader.split(' ', 2);
if (splittedAuthHeader.length !== 2 || splittedAuthHeader[0] !== 'Bearer') {
throw new CustomError({
code: AppErrorCode.UNAUTHENTICATED,
message: 'Authorization header must be in Bearer format',
context: { splittedAuthHeader }
});
}

return this.jwtHandler.verifyAppIdToken(splittedAuthHeader[1]);
};

private issuePassportFromCookie = (req: Request) => {
const cookie = req.cookies[idTokenCookieName];
if (!cookie) {
throw new CustomError({
code: AppErrorCode.UNAUTHENTICATED,
message: 'cookie does not exist',
context: { cookie }
});
}

return this.jwtHandler.verifyAppIdToken(cookie);
};
}
14 changes: 11 additions & 3 deletions api/src/controller/http/server.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cookieParser from 'cookie-parser';
import express from 'express';
import { middleware as OpenApiValidatorMiddleware } from 'express-openapi-validator';
import { Express } from 'express-serve-static-core';
Expand All @@ -10,6 +11,7 @@ import { Logger } from 'winston';
import apiSpecification from '@root/generate/openapi.json';
import { name, version } from '@root/package.json';

import { JwtHandler } from '@src/core/ports/jwt.handler';
import { AuthService } from '@src/core/services/auth/auth.service';

import { AuthV1Controller } from '@controller/http/auth/auth.v1.controller';
Expand All @@ -26,9 +28,10 @@ export class HttpServer {
constructor(
private readonly logger: Logger,
private readonly config: HttpConfig,
private readonly authService: AuthService
private readonly authService: AuthService,
private readonly jwtHandler: JwtHandler
) {
this.middleware = new Middleware(this.logger);
this.middleware = new Middleware(this.logger, this.jwtHandler);
}

public start = async (): Promise<void> => {
Expand All @@ -37,7 +40,9 @@ export class HttpServer {
this.app.set('trust proxy', 0);
this.app.use(express.json());
await this.buildApiDocument();
this.app.use('/api', cookieParser());
this.app.use('/api', this.middleware.accessLog);
this.app.use('/api/v1/dev', this.middleware.issuePassport);
this.app.use(
OpenApiValidatorMiddleware({
apiSpec: path.join(__dirname, '../../../generate/openapi.json'),
Expand Down Expand Up @@ -71,7 +76,10 @@ export class HttpServer {
private getApiRouters = (): express.Router[] => {
const routers = [
new DevV1Controller().routes(),
new AuthV1Controller(this.authService).routes()
new AuthV1Controller(
this.authService,
this.config.cookieExpirationHours
).routes()
];
return routers;
};
Expand Down
15 changes: 12 additions & 3 deletions api/test/controller/http/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ import { Logger } from 'winston';

import { methodNotAllowed } from '@src/controller/http/handler';
import { Middleware } from '@src/controller/http/middleware';
import { JwtHandler } from '@src/core/ports/jwt.handler';

// TODO: https://github.com/MovieReviewComment/Mr.C/issues/49
class TestHttpServer {
middleware: Middleware;
server!: http.Server;
app!: Express;

constructor(private readonly logger: Logger) {
this.middleware = new Middleware(this.logger);
constructor(
private readonly logger: Logger,
private readonly jwtHandler: JwtHandler
) {
this.middleware = new Middleware(this.logger, this.jwtHandler);
}

public start = (): Promise<void> => {
Expand Down Expand Up @@ -70,12 +74,17 @@ class TestController {

describe('Test handler', () => {
let mockLogger: Partial<Logger>;
let mockJwtHandler: Partial<JwtHandler>;
let testHttpServer: TestHttpServer;
let baseUrl: string;

beforeAll(async () => {
mockLogger = {};
testHttpServer = new TestHttpServer(mockLogger as Logger);
mockJwtHandler = {};
testHttpServer = new TestHttpServer(
mockLogger as Logger,
mockJwtHandler as JwtHandler
);
baseUrl = '/api/v1/dev';
await testHttpServer.start();
});
Expand Down
Loading

0 comments on commit b4eb854

Please sign in to comment.