beavuck-time 2.3.6 → 2.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/build/api.js +20 -0
  2. package/build/config/corsOptions.js +60 -0
  3. package/build/config/logger.js +36 -0
  4. package/build/controllers/nowController.js +30 -0
  5. package/build/errors/BeavuckTimeClientError.js +13 -0
  6. package/build/errors/BeavuckTimeServerError.js +16 -0
  7. package/build/errors/CorsError.js +13 -0
  8. package/build/errors/base/BeavuckTimeError.js +14 -0
  9. package/build/middlewares/corsMiddleware.js +34 -0
  10. package/build/middlewares/errorHandler.js +47 -0
  11. package/build/middlewares/notFoundHandler.js +9 -0
  12. package/build/middlewares/rateLimiter.js +16 -0
  13. package/build/models/now.js +14 -0
  14. package/build/routes/routes.js +54 -0
  15. package/build/server.js +63 -0
  16. package/build/services/nowService.js +11 -0
  17. package/build/types/isoTimestamp.js +4 -0
  18. package/build/utils/urlUtil.js +31 -0
  19. package/openapi/swagger.json +1 -1
  20. package/package.json +13 -3
  21. package/.env +0 -21
  22. package/.env.test +0 -30
  23. package/.gitlab/issue_templates/bug.md +0 -53
  24. package/.gitlab/issue_templates/enhancement.md +0 -53
  25. package/.gitlab/merge_request_templates/bug_fix.md +0 -9
  26. package/.gitlab/merge_request_templates/enhancement.md +0 -9
  27. package/.gitlab-ci.yml +0 -158
  28. package/.nvmrc +0 -1
  29. package/.sonarlint/connectedMode.json +0 -4
  30. package/DOCKERHUB_OVERVIEW.md +0 -185
  31. package/Dockerfile +0 -25
  32. package/HELP.md +0 -53
  33. package/api-tests/bruno/bruno.json +0 -9
  34. package/api-tests/bruno/collection.bru +0 -7
  35. package/api-tests/bruno/environments/local.bru +0 -8
  36. package/api-tests/bruno/now/nok/nok_no_origin.bru +0 -19
  37. package/api-tests/bruno/now/nok/nok_non_recognized_referrer.bru +0 -23
  38. package/api-tests/bruno/now/nok/nok_non_trusted_origin.bru +0 -23
  39. package/api-tests/bruno/now/nok/nok_non_truted_origin_nor_referrer.bru +0 -24
  40. package/api-tests/bruno/now/ok/ok_same_origin.bru +0 -23
  41. package/api-tests/bruno/now/ok/ok_same_referrer.bru +0 -23
  42. package/api-tests/bruno/now/ok/ok_trusted_origin.bru +0 -23
  43. package/eslint.config.mjs +0 -28
  44. package/sonar-project.properties +0 -9
  45. package/src/__tests__/api.test.ts +0 -98
  46. package/src/__tests__/middlewares/errorHandler.test.ts +0 -93
  47. package/src/__tests__/utils/urlUtil.test.ts +0 -53
  48. package/src/api.ts +0 -19
  49. package/src/config/corsOptions.ts +0 -63
  50. package/src/config/logger.ts +0 -38
  51. package/src/controllers/nowController.ts +0 -19
  52. package/src/errors/BeavuckTimeClientError.ts +0 -12
  53. package/src/errors/BeavuckTimeServerError.ts +0 -18
  54. package/src/errors/CorsError.ts +0 -12
  55. package/src/errors/base/BeavuckTimeError.ts +0 -12
  56. package/src/middlewares/corsMiddleware.ts +0 -32
  57. package/src/middlewares/errorHandler.ts +0 -57
  58. package/src/middlewares/notFoundHandler.ts +0 -8
  59. package/src/middlewares/rateLimiter.ts +0 -15
  60. package/src/models/now.ts +0 -14
  61. package/src/routes/routes.ts +0 -84
  62. package/src/server.ts +0 -83
  63. package/src/services/nowService.ts +0 -9
  64. package/src/types/isoTimestamp.ts +0 -8
  65. package/src/utils/urlUtil.ts +0 -29
  66. package/tsconfig.json +0 -38
  67. package/tsoa.json +0 -12
@@ -1,23 +0,0 @@
1
- meta {
2
- name: ok_same_origin
3
- type: http
4
- seq: 2
5
- }
6
-
7
- get {
8
- url: {{BASE_URL}}/now
9
- body: none
10
- auth: inherit
11
- }
12
-
13
- headers {
14
- Origin: {{BASE_URL}}
15
- }
16
-
17
- assert {
18
- res.status: eq 200
19
- res.body: isDefined
20
- res.body.now: isDefined
21
- res.body.now: matches {{RFC_3339_REGEX}}
22
- res.responseTime: lt 200
23
- }
@@ -1,23 +0,0 @@
1
- meta {
2
- name: ok_same_referrer
3
- type: http
4
- seq: 3
5
- }
6
-
7
- get {
8
- url: {{BASE_URL}}/now
9
- body: none
10
- auth: inherit
11
- }
12
-
13
- headers {
14
- Referrer: {{BASE_URL}}
15
- }
16
-
17
- assert {
18
- res.status: eq 200
19
- res.body: isDefined
20
- res.body.now: isDefined
21
- res.body.now: matches {{RFC_3339_REGEX}}
22
- res.responseTime: lt 200
23
- }
@@ -1,23 +0,0 @@
1
- meta {
2
- name: ok_trusted_origin
3
- type: http
4
- seq: 1
5
- }
6
-
7
- get {
8
- url: {{BASE_URL}}/now
9
- body: none
10
- auth: inherit
11
- }
12
-
13
- headers {
14
- Origin: {{TRUSTED_ORIGIN}}
15
- }
16
-
17
- assert {
18
- res.status: eq 200
19
- res.body: isDefined
20
- res.body.now: isDefined
21
- res.body.now: matches {{RFC_3339_REGEX}}
22
- res.responseTime: lt 200
23
- }
package/eslint.config.mjs DELETED
@@ -1,28 +0,0 @@
1
- // eslint.config.mjs
2
-
3
- import globals from 'globals'
4
- import pluginJs from '@eslint/js'
5
- import tseslint from 'typescript-eslint'
6
-
7
- /** @type {import('eslint').Linter.Config[]} */
8
- export default [
9
- {files: ['**/*.{js,mjs,cjs,ts}']},
10
- {languageOptions: {globals: globals.browser}},
11
- pluginJs.configs.recommended,
12
- ...tseslint.configs.recommended,
13
- {
14
- rules: {
15
- '@typescript-eslint/no-explicit-any': 'off',
16
- 'no-restricted-syntax': [
17
- 'error', {
18
- selector: 'CallExpression[callee.object.name="console"]',
19
- message: 'Don\'t use console -- use the configured logger instead',
20
- },
21
- 'error', {
22
- selector: ':not(BinaryExpression:matches([operator="!=="], [operator="==="])) > Literal[value="null"]',
23
- message: 'Usage of "null" is deprecated except when received from legacy APIs. Use "undefined" instead',
24
- },
25
- ],
26
- },
27
- },
28
- ]
@@ -1,9 +0,0 @@
1
- sonar.projectKey=beavuck-services_time
2
- sonar.organization=beavuck-services
3
- sonar.sources=.
4
- sonar.exclusions=**/node_modules/**, **/coverage/**, **/build/**, **/build/**, **/api-tests/**, *package*.json, src/routes/routes.ts
5
- sonar.coverage.exclusions=src/__tests__/utils/urlUtil.test.ts, src/__tests__/middlewares/errorHandler.test.ts, src/__tests__/config/corsOptions.test.ts, src/__tests__/api.test.ts, src/config/logger.ts, src/server.ts, src/middlewares/rateLimiter.ts, src/routes/routes.ts
6
- sonar.sourceEncoding=UTF-8
7
- sonar.typescript.tsconfigPaths=tsconfig.json
8
- sonar.coverage.inclusions=src/**/*.*
9
- sonar.javascript.lcov.reportPaths=coverage/lcov.info
@@ -1,98 +0,0 @@
1
- import request from 'supertest'
2
- import {api} from '../api'
3
- import {StatusCodes} from 'http-status-codes'
4
- import {RFC_3339_FORMAT} from '../types/isoTimestamp'
5
-
6
- let callsMadeSoFar = 0
7
-
8
- function getSomeTrustedOrigin(): string {
9
- return process.env.BEAVUCK_TIME_TRUSTED_ORIGINS?.split(',')[0] ?? ''
10
- }
11
-
12
- /**
13
- * Increments the callsMadeSoFar counter and returns the response and trusted origin
14
- */
15
- async function makeSuccessfulCallToNowEndpoint() {
16
- const trustedOrigin = getSomeTrustedOrigin()
17
- const res = await request(api).get('/now').set('Origin', trustedOrigin)
18
- callsMadeSoFar++
19
- return {res, trustedOrigin}
20
- }
21
-
22
- describe('App Initialization', () => {
23
- it('should disable x-powered-by header', async () => {
24
- const res = await request(api).get('/now')
25
- expect(res.header['x-powered-by']).toBeUndefined()
26
- })
27
-
28
- it('should allow requests from trusted origins', async () => {
29
- const {res, trustedOrigin} = await makeSuccessfulCallToNowEndpoint()
30
- expect(res.header['access-control-allow-origin']).toBe(trustedOrigin)
31
- expect(res.status).toBe(StatusCodes.OK)
32
- })
33
-
34
- it('should block requests from non-trusted origins', async () => {
35
- const res = await request(api).get('/now').set('Origin', 'https://non-trusted.com')
36
- expect(res.header['access-control-allow-origin']).toBeUndefined()
37
- expect(res.status).toBe(StatusCodes.FORBIDDEN)
38
- })
39
-
40
- it('should block requests with no origin header', async () => {
41
- const res = await request(api).get('/now')
42
- expect(res.header['access-control-allow-origin']).toBeUndefined()
43
- expect(res.status).toBe(StatusCodes.FORBIDDEN)
44
- })
45
-
46
- it('should allow requests with no origin header but a referrer header identical to server url', async () => {
47
- const REFERRER: string = process.env.BEAVUCK_TIME_HOST_URL!
48
- const res = await request(api).get('/now').set('Referrer', REFERRER)
49
- expect(res.header['access-control-allow-origin']).toBeUndefined()
50
- expect(res.status).toBe(StatusCodes.OK)
51
- callsMadeSoFar++
52
- })
53
-
54
- it('should allow requests with no origin header but a referer (sic) header identical to server url', async () => {
55
- const REFERER: string = process.env.BEAVUCK_TIME_HOST_URL!
56
- const res = await request(api).get('/now').set('Referer', REFERER)
57
- expect(res.header['access-control-allow-origin']).toBeUndefined()
58
- expect(res.status).toBe(StatusCodes.OK)
59
- callsMadeSoFar++
60
- })
61
-
62
- it('should handle errors using errorHandler middleware', async () => {
63
- const res = await request(api).get('/nope').set('Origin', getSomeTrustedOrigin())
64
- expect(res.status).toBe(StatusCodes.NOT_FOUND)
65
- })
66
-
67
- it('should return the current time in ISO format', async () => {
68
- const {res} = await makeSuccessfulCallToNowEndpoint()
69
- expect(res.status).toBe(StatusCodes.OK)
70
- expect(res.body).toHaveProperty('now')
71
- expect(res.body.now).toMatch(RFC_3339_FORMAT)
72
- })
73
-
74
- it('should return 500 if HOST_URL is not set', async () => {
75
- const originalHostUrl = process.env.BEAVUCK_TIME_HOST_URL
76
- delete process.env.BEAVUCK_TIME_HOST_URL
77
-
78
- const res = await request(api).get('/now')
79
- expect(res.status).toBe(StatusCodes.INTERNAL_SERVER_ERROR)
80
-
81
- process.env.BEAVUCK_TIME_HOST_URL = originalHostUrl
82
- })
83
-
84
- it('should block requests with non-trusted referrer header if origin is not OK', async () => {
85
- const res = await request(api).get('/now').set('Referrer', 'https://non-trusted.com')
86
- expect(res.status).toBe(StatusCodes.FORBIDDEN)
87
- })
88
-
89
- it('should block requests that exceed the rate limit', async () => {
90
- const RATE_LIMIT = parseInt(process.env.BEAVUCK_TIME_RATE_LIMIT!, 10)
91
- while (callsMadeSoFar <= RATE_LIMIT) {
92
- const {res} = await makeSuccessfulCallToNowEndpoint()
93
- if (callsMadeSoFar > RATE_LIMIT) {
94
- expect(res.status).toBe(StatusCodes.TOO_MANY_REQUESTS)
95
- }
96
- }
97
- })
98
- })
@@ -1,93 +0,0 @@
1
- // src/__tests__/middlewares/errorHandler.test.ts
2
-
3
- import express from 'express'
4
- import {errorHandler} from '../../middlewares/errorHandler'
5
- import {BeavuckTimeClientError} from '../../errors/BeavuckTimeClientError'
6
- import {BeavuckTimeServerError} from '../../errors/BeavuckTimeServerError'
7
- import {CorsError} from '../../errors/CorsError'
8
- import {StatusCodes} from 'http-status-codes'
9
- import {logger} from '../../config/logger'
10
- import {ValidateError} from 'tsoa'
11
-
12
- jest.mock('../../config/logger')
13
-
14
- describe('errorHandler', () => {
15
- let req: express.Request
16
- let res: express.Response
17
- let next: express.NextFunction
18
-
19
- beforeEach(() => {
20
- req = {} as express.Request
21
- res = {
22
- status: jest.fn().mockReturnThis(),
23
- json: jest.fn().mockReturnThis(),
24
- } as unknown as express.Response
25
- next = jest.fn() as express.NextFunction
26
- })
27
-
28
- it('should handle BeavuckTimeClientError', () => {
29
- const err = new BeavuckTimeClientError('Some message')
30
- errorHandler(err, req, res, next)
31
- expect(logger.warn).toHaveBeenCalledWith(err)
32
- expect(res.status).toHaveBeenCalledWith(err.code)
33
- expect(res.json).toHaveBeenCalledWith({message: `${err.message}`})
34
- })
35
-
36
- it('should handle CorsError', () => {
37
- const err = new CorsError('Some message')
38
- errorHandler(err, req, res, next)
39
- expect(logger.warn).toHaveBeenCalledWith(err)
40
- expect(res.status).toHaveBeenCalledWith(err.code)
41
- expect(res.json).toHaveBeenCalledWith({message: `${err.message}`})
42
- })
43
-
44
- it('should handle BeavuckTimeServerError', () => {
45
- const err = new BeavuckTimeServerError('Some message')
46
- errorHandler(err, req, res, next)
47
- expect(logger.error).toHaveBeenCalledWith(err)
48
- expect(res.status).toHaveBeenCalledWith(err.code)
49
- expect(res.json).toHaveBeenCalledWith({message: `${err.message}`})
50
- })
51
-
52
- it('should handle non-specific error', () => {
53
- const err = new Error('Some message')
54
- errorHandler(err, req, res, next)
55
- const beavuckError = BeavuckTimeServerError.fromError(err)
56
- expect(logger.error).toHaveBeenCalledWith(beavuckError)
57
- expect(res.status).toHaveBeenCalledWith(StatusCodes.INTERNAL_SERVER_ERROR)
58
- expect(res.json).toHaveBeenCalledWith({
59
- message: `${beavuckError.message}`,
60
- })
61
- })
62
-
63
- it('should call next if no error', () => {
64
- errorHandler(undefined, req, res, next)
65
- expect(next).toHaveBeenCalled()
66
- })
67
-
68
- it('should handle ValidateError', () => {
69
- const err = new ValidateError({}, 'Some message')
70
- errorHandler(err, req, res, next)
71
- expect(logger.error).toHaveBeenCalled()
72
- expect(res.status).toHaveBeenCalledWith(StatusCodes.UNPROCESSABLE_ENTITY)
73
- expect(res.json).toHaveBeenCalledWith({
74
- message: 'Validation Error',
75
- details: err.fields,
76
- })
77
- })
78
-
79
- it('should handle unknown error type', () => {
80
- const err = {some: 'unknown error'}
81
- errorHandler(err, req, res, next)
82
- expect(logger.error).toHaveBeenCalled()
83
- expect(res.status).toHaveBeenCalledWith(StatusCodes.INTERNAL_SERVER_ERROR)
84
- expect(res.json).toHaveBeenCalledWith({
85
- message: 'Internal Server Error',
86
- })
87
- })
88
-
89
- it('should handle null error', () => {
90
- errorHandler(undefined, req, res, next)
91
- expect(next).toHaveBeenCalled()
92
- })
93
- })
@@ -1,53 +0,0 @@
1
- // src/__tests__/utils/urlUtil.test.ts
2
-
3
- import {isSameOrigin, tryParseUrl} from '../../utils/urlUtil'
4
- import {URL} from 'url'
5
-
6
- const A_VALID_URL_STRING = 'https://example.com:1234'
7
- const SAME_VALID_URL_DIFFERENT_PORT = 'https://example.com:81'
8
- const SOME_OTHER_VALID_URL_STRING = 'https://different.com:1234'
9
- const AN_INVALID_URL_STRING = 'invalid'
10
-
11
- describe('urlUtil', () => {
12
- describe('isSameOrigin', () => {
13
- it('returns false when either URL is undefined', () => {
14
- const url = new URL(A_VALID_URL_STRING)
15
- expect(isSameOrigin(url)).toBe(false)
16
- expect(isSameOrigin(undefined, url)).toBe(false)
17
- })
18
-
19
- it('returns true when URLs have the same origin', () => {
20
- const url1 = new URL(A_VALID_URL_STRING)
21
- const url2 = new URL(A_VALID_URL_STRING)
22
- expect(isSameOrigin(url1, url2)).toBe(true)
23
- })
24
-
25
- it('returns false when URLs have the same origin but different ports', () => {
26
- const url1 = new URL(A_VALID_URL_STRING)
27
- const url2 = new URL(SAME_VALID_URL_DIFFERENT_PORT)
28
- expect(isSameOrigin(url1, url2)).toBe(false)
29
- })
30
-
31
- it('returns false when URLs have different origins', () => {
32
- const url1 = new URL(A_VALID_URL_STRING)
33
- const url2 = new URL(SOME_OTHER_VALID_URL_STRING)
34
- expect(isSameOrigin(url1, url2)).toBe(false)
35
- })
36
- })
37
-
38
- describe('tryParseUrl', () => {
39
- it('returns undefined when URL string is undefined', () => {
40
- expect(tryParseUrl()).toBeUndefined()
41
- })
42
-
43
- it('returns a URL object when URL string is valid', () => {
44
- const url = tryParseUrl(A_VALID_URL_STRING)
45
- expect(url).toBeInstanceOf(URL)
46
- expect(url?.origin).toBe(A_VALID_URL_STRING)
47
- })
48
-
49
- it('returns undefined when URL string is invalid', () => {
50
- expect(tryParseUrl(AN_INVALID_URL_STRING)).toBeUndefined()
51
- })
52
- })
53
- })
package/src/api.ts DELETED
@@ -1,19 +0,0 @@
1
- // src/api.ts
2
-
3
- import express from 'express'
4
- import {corsMiddleware} from './middlewares/corsMiddleware'
5
- import {errorHandler} from './middlewares/errorHandler'
6
- import {rateLimiter} from './middlewares/rateLimiter'
7
- import {notFoundHandler} from './middlewares/notFoundHandler'
8
- import {RegisterRoutes} from './routes/routes'
9
-
10
- export const api = express()
11
- api.disable('x-powered-by')
12
-
13
- api.use(corsMiddleware)
14
- api.use(errorHandler)
15
- api.use(rateLimiter)
16
-
17
- RegisterRoutes(api)
18
-
19
- api.use(notFoundHandler)
@@ -1,63 +0,0 @@
1
- // src/config/corsOptions.ts
2
-
3
- import {CorsOptions} from 'cors'
4
- import {CorsError} from '../errors/CorsError'
5
- import {isSameOrigin, tryParseUrl} from '../utils/urlUtil'
6
- import {StatusCodes} from 'http-status-codes'
7
- import dotenvx from '@dotenvx/dotenvx'
8
-
9
- dotenvx.config()
10
-
11
- export const corsOptions: CorsOptions = {
12
- methods: ['GET', 'OPTIONS'],
13
- optionsSuccessStatus: StatusCodes.OK,
14
- origin: (
15
- origin: string | undefined,
16
- callback: (err: Error | null, allow?: boolean) => void,
17
- ): void => {
18
- if (
19
- isAllTrusted() ||
20
- isSameOrigin(getHostUrl(), tryParseUrl(origin)) ||
21
- isOriginAbsentOrTrusted(origin)
22
- ) {
23
- // eslint-disable-next-line no-restricted-syntax
24
- callback(null, true)
25
- } else {
26
- callback(new CorsError(origin), false)
27
- }
28
- },
29
- }
30
-
31
- let HOST_URL: URL
32
- let TRUSTED_ORIGINS: string[]
33
-
34
- export function getHostUrl(): URL | undefined {
35
- if (!HOST_URL) {
36
- initHostUrl()
37
- }
38
- return HOST_URL
39
- }
40
-
41
- export function initHostUrl(): void {
42
- HOST_URL = new URL(process.env.BEAVUCK_TIME_HOST_URL!)
43
- }
44
-
45
- export function getTrustedOrigins(): string[] {
46
- if (!TRUSTED_ORIGINS || TRUSTED_ORIGINS.length === 0) {
47
- initTrustedOrigins()
48
- }
49
- return TRUSTED_ORIGINS
50
- }
51
-
52
- export function initTrustedOrigins(): void {
53
- const trustedOrigins: string = process.env.BEAVUCK_TIME_TRUSTED_ORIGINS!
54
- TRUSTED_ORIGINS = trustedOrigins.split(',')
55
- }
56
-
57
- export function isAllTrusted(): boolean {
58
- return getTrustedOrigins().includes('*')
59
- }
60
-
61
- export function isOriginAbsentOrTrusted(origin?: string): boolean {
62
- return !origin || getTrustedOrigins().includes(origin)
63
- }
@@ -1,38 +0,0 @@
1
- // src/config/logger.ts
2
-
3
- import {createLogger, format, transports} from 'winston'
4
- import 'winston-daily-rotate-file'
5
- import dotenvx from '@dotenvx/dotenvx'
6
-
7
- dotenvx.config()
8
-
9
- const LOGS_DIR = 'logs'
10
- const LOG_LEVEL: string = process.env.BEAVUCK_TIME_LOG_LEVEL ?? 'info'
11
- const MAX_LOG_FILES: string | number = process.env.BEAVUCK_TIME_MAX_LOG_FILES ?? 64
12
- const MAX_SIZE_LOG_FILES: string = process.env.BEAVUCK_TIME_MAX_SIZE_LOG_FILES ?? '1m'
13
-
14
- const dailyRotateFileTransport = new transports.DailyRotateFile({
15
- filename: `${LOGS_DIR}/%DATE%-combined.log`,
16
- datePattern: 'YYYY-MM-DD',
17
- zippedArchive: true,
18
- maxSize: MAX_SIZE_LOG_FILES,
19
- maxFiles: MAX_LOG_FILES,
20
- })
21
-
22
- export const logger = createLogger({
23
- level: LOG_LEVEL,
24
- format: format.combine(
25
- format.timestamp(),
26
- format.printf(({timestamp, level, message}) => {
27
- return `${timestamp} [${level.toUpperCase()}]: ${message}`
28
- }),
29
- ),
30
- transports: [
31
- new transports.Console(),
32
- new transports.File({
33
- filename: `${LOGS_DIR}/error.log`,
34
- level: 'error',
35
- }),
36
- dailyRotateFileTransport,
37
- ],
38
- })
@@ -1,19 +0,0 @@
1
- // src/controllers/nowController.ts
2
-
3
- import {Controller, Get, OperationId, Route, Tags} from 'tsoa'
4
- import {Now} from '../models/now'
5
- import {NowService} from '../services/nowService'
6
-
7
- @Route('now')
8
- export class NowController extends Controller {
9
- /**
10
- * Get the current time in ISO format, in UTC timezone
11
- * @summary Get current time
12
- */
13
- @Tags('now')
14
- @Get()
15
- @OperationId('getNow')
16
- public async getNow(): Promise<Now> {
17
- return new NowService().get()
18
- }
19
- }
@@ -1,12 +0,0 @@
1
- // src/errors/BeavuckTimeClientError.ts
2
-
3
- import {BeavuckTimeError} from './base/BeavuckTimeError'
4
- import {StatusCodes} from 'http-status-codes'
5
-
6
- export class BeavuckTimeClientError extends BeavuckTimeError {
7
- static readonly baseMessage: string = 'Client Error'
8
-
9
- constructor(message: string, code: number = StatusCodes.BAD_REQUEST) {
10
- super(`${BeavuckTimeClientError.baseMessage}: ${message}`, code)
11
- }
12
- }
@@ -1,18 +0,0 @@
1
- // src/errors/BeavuckTimeServerError.ts
2
-
3
- import {BeavuckTimeError} from './base/BeavuckTimeError'
4
- import {StatusCodes} from 'http-status-codes'
5
-
6
- export class BeavuckTimeServerError extends BeavuckTimeError {
7
- static readonly baseMessage: string = 'Internal Server Error'
8
-
9
- constructor(message: string, code: number = StatusCodes.INTERNAL_SERVER_ERROR) {
10
- super(`${BeavuckTimeServerError.baseMessage}: ${message}`, code)
11
- }
12
-
13
- static fromError(error: Error): BeavuckTimeServerError {
14
- return error instanceof BeavuckTimeServerError ? error : (
15
- new BeavuckTimeServerError(error.message)
16
- )
17
- }
18
- }
@@ -1,12 +0,0 @@
1
- // src/errors/CorsError.ts
2
-
3
- import {StatusCodes} from 'http-status-codes'
4
- import {BeavuckTimeClientError} from './BeavuckTimeClientError'
5
-
6
- export class CorsError extends BeavuckTimeClientError {
7
- static readonly baseMessage: string = 'Origin forbidden by CORS'
8
-
9
- constructor(origin?: string) {
10
- super(`${CorsError.baseMessage}: ${origin}`, StatusCodes.FORBIDDEN)
11
- }
12
- }
@@ -1,12 +0,0 @@
1
- // src/errors/base/BeavuckTimeError.ts
2
-
3
- export abstract class BeavuckTimeError extends Error {
4
- code: number
5
-
6
- protected constructor(message: string, code: number) {
7
- super(message)
8
- this.name = this.constructor.name
9
- this.code = code
10
- Error.captureStackTrace(this, this.constructor)
11
- }
12
- }
@@ -1,32 +0,0 @@
1
- // src/middlewares/corsMiddleware.ts
2
-
3
- import cors from 'cors'
4
- import {isSameOrigin, tryParseUrl} from '../utils/urlUtil'
5
- import {CorsError} from '../errors/CorsError'
6
- import express from 'express'
7
- import {logger} from '../config/logger'
8
- import {BeavuckTimeServerError} from '../errors/BeavuckTimeServerError'
9
- import {corsOptions} from '../config/corsOptions'
10
-
11
- export const corsMiddleware = (
12
- req: express.Request,
13
- res: express.Response,
14
- next: express.NextFunction,
15
- ) => {
16
- const HOST_URL = tryParseUrl(process.env.BEAVUCK_TIME_HOST_URL ?? '')
17
- // 'referer' is a misspelling that was kept for compatibility: https://en.wikipedia.org/wiki/HTTP_referer
18
- const referrerHeader: string | undefined =
19
- (req.headers.referrer as string) || req.headers.referer
20
- if (req.headers.origin) {
21
- logger.debug(`CORS request from ${req.headers.origin}`)
22
- cors(corsOptions)(req, res, next)
23
- } else if (!HOST_URL) {
24
- const noHostUrl = 'Host URL not set'
25
- next(new BeavuckTimeServerError(noHostUrl))
26
- } else if (isSameOrigin(HOST_URL, tryParseUrl(referrerHeader ?? ''))) {
27
- next()
28
- } else {
29
- const badOriginAndOrReferrer = `origin: ${req.headers.origin}, referrer: ${referrerHeader}`
30
- next(new CorsError(badOriginAndOrReferrer))
31
- }
32
- }
@@ -1,57 +0,0 @@
1
- // src/middlewares/errorHandler.ts
2
-
3
- import express from 'express'
4
- import {logger} from '../config/logger'
5
- import {BeavuckTimeClientError} from '../errors/BeavuckTimeClientError'
6
- import {BeavuckTimeServerError} from '../errors/BeavuckTimeServerError'
7
- import {StatusCodes} from 'http-status-codes'
8
- import {ValidateError} from 'tsoa'
9
- import {BeavuckTimeError} from '../errors/base/BeavuckTimeError'
10
-
11
- export const errorHandler = (
12
- err: unknown,
13
- req: express.Request,
14
- res: express.Response,
15
- next: express.NextFunction,
16
- ) => {
17
- if (err instanceof ValidateError) {
18
- const logMsg = `Validation error on ${req.path}: ${err.fields}`
19
- const resStatus = StatusCodes.UNPROCESSABLE_ENTITY
20
- const resMsgObj = {message: 'Validation Error', details: err.fields}
21
- handleOtherError(res, logMsg, resStatus, resMsgObj)
22
- } else if (err instanceof BeavuckTimeClientError) {
23
- handleBeavuckClientError(res, err)
24
- } else if (err instanceof BeavuckTimeServerError || err instanceof Error) {
25
- handleBeavuckServerError(res, BeavuckTimeServerError.fromError(err))
26
- } else if (err) {
27
- handleOtherError(res)
28
- } else {
29
- next()
30
- }
31
- }
32
-
33
- function sendErrorResponse(res: express.Response, err: BeavuckTimeError) {
34
- res.status(err.code).json({message: err.message})
35
- }
36
-
37
- function handleBeavuckClientError(res: express.Response, err: BeavuckTimeClientError) {
38
- logger.warn(err)
39
- sendErrorResponse(res, err)
40
- }
41
-
42
- function handleBeavuckServerError(res: express.Response, err: BeavuckTimeServerError) {
43
- logger.error(err)
44
- sendErrorResponse(res, err)
45
- }
46
-
47
- function handleOtherError(
48
- res: express.Response,
49
- logMsg: string = `Unknown error`,
50
- resStatus: StatusCodes = StatusCodes.INTERNAL_SERVER_ERROR,
51
- resMsg: {message: string; details?: unknown} = {
52
- message: BeavuckTimeServerError.baseMessage,
53
- },
54
- ) {
55
- logger.error(`${logMsg}: ${JSON.stringify(resMsg)}`)
56
- res.status(resStatus).json(resMsg)
57
- }
@@ -1,8 +0,0 @@
1
- // src/middlewares/notFoundHandler.ts
2
-
3
- import {Request as ExpressRequest, Response as ExpressResponse} from 'express'
4
- import {StatusCodes} from 'http-status-codes'
5
-
6
- export const notFoundHandler = (_req: ExpressRequest, res: ExpressResponse) => {
7
- res.status(StatusCodes.NOT_FOUND).send({message: 'Not Found'})
8
- }
@@ -1,15 +0,0 @@
1
- // src/middlewares/rateLimiter.ts
2
-
3
- import rateLimit from 'express-rate-limit'
4
-
5
- const NO_RATE_LIMIT = '-1'
6
- const RATE_LIMIT = parseInt(process.env.BEAVUCK_TIME_RATE_LIMIT ?? NO_RATE_LIMIT, 10)
7
-
8
- const SECONDS_IN_ONE_MINUTE = 60
9
- const MILLISECONDS_IN_ONE_SECOND = 1000
10
- const ONE_MINUTE = SECONDS_IN_ONE_MINUTE * MILLISECONDS_IN_ONE_SECOND
11
-
12
- export const rateLimiter =
13
- RATE_LIMIT > 0 ?
14
- rateLimit({windowMs: ONE_MINUTE, limit: RATE_LIMIT})
15
- : (_req: any, _res: any, next: any) => next()