@alanszp/express 6.0.4 → 7.0.1

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 (47) hide show
  1. package/.env.test +5 -0
  2. package/babel.config.js +7 -0
  3. package/dist/helpers/now.d.ts +1 -0
  4. package/dist/helpers/now.js +8 -0
  5. package/dist/helpers/now.js.map +1 -0
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/middlewares/authenticateUser.d.ts +25 -0
  10. package/dist/middlewares/authenticateUser.js +114 -0
  11. package/dist/middlewares/authenticateUser.js.map +1 -0
  12. package/dist/middlewares/authenticateUser.test.d.ts +1 -0
  13. package/dist/middlewares/authenticateUser.test.js +308 -0
  14. package/dist/middlewares/authenticateUser.test.js.map +1 -0
  15. package/dist/test/mocks/authOptionsMocks.d.ts +8 -0
  16. package/dist/test/mocks/authOptionsMocks.js +30 -0
  17. package/dist/test/mocks/authOptionsMocks.js.map +1 -0
  18. package/dist/test/mocks/expressMocks.d.ts +5 -0
  19. package/dist/test/mocks/expressMocks.js +20 -0
  20. package/dist/test/mocks/expressMocks.js.map +1 -0
  21. package/dist/test/mocks/jwtUserMocks.d.ts +3 -0
  22. package/dist/test/mocks/jwtUserMocks.js +18 -0
  23. package/dist/test/mocks/jwtUserMocks.js.map +1 -0
  24. package/dist/test/setup.d.ts +1 -0
  25. package/dist/test/setup.js +5 -0
  26. package/dist/test/setup.js.map +1 -0
  27. package/dist/test/setup.test.d.ts +1 -0
  28. package/dist/test/setup.test.js +14 -0
  29. package/dist/test/setup.test.js.map +1 -0
  30. package/dist/types/AuthMethod.d.ts +1 -1
  31. package/jest.config.js +10 -0
  32. package/package.json +12 -7
  33. package/src/helpers/now.ts +3 -0
  34. package/src/index.ts +1 -1
  35. package/src/middlewares/authenticateUser.test.ts +403 -0
  36. package/src/middlewares/authenticateUser.ts +170 -0
  37. package/src/test/mocks/authOptionsMocks.ts +35 -0
  38. package/src/test/mocks/expressMocks.ts +17 -0
  39. package/src/test/mocks/jwtUserMocks.ts +17 -0
  40. package/src/test/setup.test.ts +15 -0
  41. package/src/test/setup.ts +3 -0
  42. package/src/types/AuthMethod.ts +4 -1
  43. package/tsconfig.json +5 -2
  44. package/dist/middlewares/authWithJWT.d.ts +0 -4
  45. package/dist/middlewares/authWithJWT.js +0 -56
  46. package/dist/middlewares/authWithJWT.js.map +0 -1
  47. package/src/middlewares/authWithJWT.ts +0 -54
@@ -0,0 +1,170 @@
1
+ import { JWTUser, verifyJWT, VerifyOptions } from "@alanszp/jwt";
2
+ import { UnauthorizedError } from "@alanszp/errors";
3
+ import { errorView } from "../views/errorView";
4
+ import { NextFunction, Response } from "express";
5
+ import { getRequestLogger } from "../helpers/getRequestLogger";
6
+ import { GenericRequest } from "../types/GenericRequest";
7
+ import { ILogger } from "@alanszp/logger";
8
+ import { compact, isEmpty, omit } from "lodash";
9
+
10
+ function parseAuthorizationHeader(
11
+ authorization: string | undefined
12
+ ): string | undefined {
13
+ if (!authorization) return undefined;
14
+ const [bearer, jwt, ...other] = authorization.split(" ");
15
+
16
+ if (bearer !== "Bearer" || other.length > 0) return undefined;
17
+
18
+ return jwt;
19
+ }
20
+
21
+ export enum AuthMethods {
22
+ JWT = "JWT",
23
+ API_KEY = "API_KEY",
24
+ }
25
+
26
+ export interface JWTVerifyOptions extends Partial<VerifyOptions> {
27
+ publicKey: string;
28
+ }
29
+
30
+ export interface JWTOptions {
31
+ jwtVerifyOptions: JWTVerifyOptions;
32
+ types: [AuthMethods.JWT];
33
+ }
34
+
35
+ export interface ApiKeyOptions {
36
+ validApiKeys: string[];
37
+ types: [AuthMethods.API_KEY];
38
+ }
39
+
40
+ export interface BothMethodsOptions {
41
+ jwtVerifyOptions: JWTVerifyOptions;
42
+ validApiKeys: string[];
43
+ types:
44
+ | [AuthMethods.JWT, AuthMethods.API_KEY]
45
+ | [AuthMethods.API_KEY, AuthMethods.JWT];
46
+ }
47
+
48
+ export type AuthOptions = JWTOptions | ApiKeyOptions | BothMethodsOptions;
49
+
50
+ const middlewareGetterByAuthType: Record<
51
+ AuthMethods,
52
+ (
53
+ tokenOrJwt: string | null | undefined,
54
+ options: AuthOptions,
55
+ logger: ILogger
56
+ ) => Promise<JWTUser | null | undefined>
57
+ > = {
58
+ [AuthMethods.JWT]: async (
59
+ jwt: string | null | undefined,
60
+ options: Exclude<AuthOptions, ApiKeyOptions>,
61
+ logger: ILogger
62
+ ) => {
63
+ try {
64
+ if (!jwt) return undefined;
65
+ const jwtUser = await verifyJWT(
66
+ options.jwtVerifyOptions.publicKey,
67
+ jwt,
68
+ omit(options.jwtVerifyOptions, "publicKey")
69
+ );
70
+ logger.debug("auth.authWithJwt.authed", {
71
+ user: jwtUser.id,
72
+ org: jwtUser.organizationReference,
73
+ });
74
+ return jwtUser;
75
+ } catch (error: unknown) {
76
+ logger.info("auth.authWithJwt.invalidJwt", { jwt, error });
77
+ return null;
78
+ }
79
+ },
80
+ [AuthMethods.API_KEY]: async (
81
+ token: string | null | undefined,
82
+ options: Exclude<AuthOptions, JWTOptions>,
83
+ logger: ILogger
84
+ ): Promise<JWTUser | null | undefined> => {
85
+ try {
86
+ if (!token) return undefined;
87
+ if (options.validApiKeys.includes(token)) {
88
+ logger.debug("auth.authWithApiKey.authed", {
89
+ user: "0",
90
+ org: "lara",
91
+ });
92
+ return Promise.resolve({
93
+ id: "0",
94
+ employeeReference: "0",
95
+ organizationReference: "lara",
96
+ roles: [],
97
+ permissions: [],
98
+ });
99
+ } else {
100
+ return null;
101
+ }
102
+ } catch (error: unknown) {
103
+ logger.info("auth.authWithApiKey.invalidApiKey", { token, error });
104
+ return null;
105
+ }
106
+ },
107
+ };
108
+
109
+ export function createAuthContext<Options extends AuthOptions>(
110
+ options: Options
111
+ ) {
112
+ return function getMiddlewareForMethods(
113
+ authMethods: Options["types"][number][]
114
+ ) {
115
+ return async function authWithGivenMethods(
116
+ req: GenericRequest,
117
+ res: Response,
118
+ next: NextFunction
119
+ ): Promise<void> {
120
+ const logger = getRequestLogger(req);
121
+ const cookies = (req.cookies as Record<string, string | undefined>) || {};
122
+ const jwt =
123
+ cookies.jwt || parseAuthorizationHeader(req.headers.authorization);
124
+
125
+ try {
126
+ const authAttempts = await Promise.all(
127
+ authMethods.map((method) =>
128
+ middlewareGetterByAuthType[method](
129
+ method === AuthMethods.JWT ? jwt : req.headers.authorization,
130
+ options,
131
+ logger
132
+ )
133
+ )
134
+ );
135
+
136
+ const successfulAuthAttempts = compact(authAttempts);
137
+
138
+ if (isEmpty(successfulAuthAttempts)) {
139
+ res
140
+ .status(401)
141
+ .json(
142
+ errorView(
143
+ new UnauthorizedError([
144
+ authAttempts.includes(null)
145
+ ? `Token invalid for methods ${authMethods}`
146
+ : `Token not set for methods ${authMethods}`,
147
+ ])
148
+ )
149
+ );
150
+ return;
151
+ }
152
+
153
+ const jwtUser: JWTUser = successfulAuthAttempts[0];
154
+ req.context.jwtUser = jwtUser;
155
+ req.context.authenticated.push(
156
+ jwtUser.employeeReference !== "0" ? "jwt" : "api_key"
157
+ );
158
+ next();
159
+ } catch (error: unknown) {
160
+ logger.info("auth.authenticateUser.error", {
161
+ jwt,
162
+ token: req.headers.authorization,
163
+ methods: AuthMethods,
164
+ error,
165
+ });
166
+ res.status(401).json(errorView(new UnauthorizedError(authMethods)));
167
+ }
168
+ };
169
+ };
170
+ }
@@ -0,0 +1,35 @@
1
+ import {
2
+ ApiKeyOptions,
3
+ AuthMethods,
4
+ BothMethodsOptions,
5
+ JWTOptions,
6
+ } from "../../middlewares/authenticateUser";
7
+
8
+ export const jwtAuthOptions = {
9
+ jwtVerifyOptions: {
10
+ publicKey: "publicKey",
11
+ issuer: "issuer",
12
+ audience: "audience",
13
+ },
14
+ types: [AuthMethods.JWT],
15
+ } as any as JWTOptions;
16
+
17
+ export const verifyOptions = {
18
+ issuer: "issuer",
19
+ audience: "audience",
20
+ };
21
+
22
+ export const apiKeyAuthOptions: ApiKeyOptions = {
23
+ validApiKeys: ["token", "tooooken"],
24
+ types: [AuthMethods.API_KEY],
25
+ };
26
+
27
+ export const bothMethodsAuthOptions: BothMethodsOptions = {
28
+ jwtVerifyOptions: {
29
+ publicKey: "publicKey",
30
+ issuer: "issuer",
31
+ audience: "audience",
32
+ },
33
+ validApiKeys: ["token", "tooooken"],
34
+ types: [AuthMethods.API_KEY, AuthMethods.JWT],
35
+ };
@@ -0,0 +1,17 @@
1
+ import { GenericRequest } from "../../types/GenericRequest";
2
+
3
+ export const mockRequest = (authorization: string): GenericRequest => {
4
+ return {
5
+ headers: { authorization },
6
+ context: { jwtUser: undefined, authenticated: [] },
7
+ } as any as GenericRequest;
8
+ };
9
+
10
+ export const mockResponse = () => {
11
+ const res = {} as any;
12
+ res.status = jest.fn().mockReturnValue(res);
13
+ res.json = jest.fn().mockReturnValue(res);
14
+ return res;
15
+ };
16
+
17
+ export const mockNext = () => jest.fn();
@@ -0,0 +1,17 @@
1
+ import { JWTUser } from "@alanszp/jwt";
2
+
3
+ export const userJwtUserMock: JWTUser = {
4
+ id: "1",
5
+ employeeReference: "1",
6
+ organizationReference: "test",
7
+ roles: [],
8
+ permissions: [],
9
+ };
10
+
11
+ export const laraJwtUserMock: JWTUser = {
12
+ id: "0",
13
+ employeeReference: "0",
14
+ organizationReference: "lara",
15
+ roles: [],
16
+ permissions: [],
17
+ };
@@ -0,0 +1,15 @@
1
+ import { now } from "../helpers/now";
2
+
3
+ jest.mock("@/helpers/now");
4
+
5
+ describe("Timezones", () => {
6
+ it("should always be UTC", () => {
7
+ expect(new Date().getTimezoneOffset()).toBe(0);
8
+ });
9
+
10
+ it("now() is mocked", () => {
11
+ (now as jest.Mock).mockReturnValue(new Date("2021-01-01T12:30:43"));
12
+
13
+ expect(now()).toEqual(new Date("2021-01-01T12:30:43"));
14
+ });
15
+ });
@@ -0,0 +1,3 @@
1
+ import { config } from "dotenv";
2
+
3
+ config({ path: ".env.test" });
@@ -21,4 +21,7 @@ type OverridableStringUnion<
21
21
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
22
22
  export interface AuthMethodsOverride {}
23
23
 
24
- export type AuthMethod = OverridableStringUnion<"jwt", AuthMethodsOverride>;
24
+ export type AuthMethod = OverridableStringUnion<
25
+ "jwt" | "api_key",
26
+ AuthMethodsOverride
27
+ >;
package/tsconfig.json CHANGED
@@ -4,12 +4,15 @@
4
4
  "outDir": "dist",
5
5
  "module": "commonjs",
6
6
  "target": "es6",
7
- "types": ["node"],
7
+ "types": ["jest", "node"],
8
8
  "esModuleInterop": true,
9
9
  "sourceMap": true,
10
10
 
11
11
  "alwaysStrict": true,
12
12
  "strictNullChecks": true
13
13
  },
14
- "exclude": ["node_modules"]
14
+ "exclude": ["node_modules"],
15
+ "paths": {
16
+ "@/*": ["src/*"]
17
+ }
15
18
  }
@@ -1,4 +0,0 @@
1
- import { VerifyOptions } from "@alanszp/jwt";
2
- import { NextFunction, Response } from "express";
3
- import { GenericRequest } from "../types/GenericRequest";
4
- export declare function createAuthWithJWT(publicKey: string, options?: VerifyOptions): (req: GenericRequest, res: Response, next: NextFunction) => Promise<void>;
@@ -1,56 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.createAuthWithJWT = void 0;
13
- const jwt_1 = require("@alanszp/jwt");
14
- const errors_1 = require("@alanszp/errors");
15
- const errorView_1 = require("../views/errorView");
16
- const getRequestLogger_1 = require("../helpers/getRequestLogger");
17
- function parseAuthorizationHeader(authorization) {
18
- if (!authorization)
19
- return undefined;
20
- const [bearer, jwt, ...other] = authorization.split(" ");
21
- if (bearer !== "Bearer" || other.length > 0)
22
- return undefined;
23
- return jwt;
24
- }
25
- function createAuthWithJWT(publicKey, options) {
26
- return function authWithJwt(req, res, next) {
27
- return __awaiter(this, void 0, void 0, function* () {
28
- const logger = (0, getRequestLogger_1.getRequestLogger)(req);
29
- const cookies = req.cookies || {};
30
- const jwt = cookies.jwt || parseAuthorizationHeader(req.headers.authorization);
31
- if (!jwt) {
32
- logger.debug("auth.authWithJwt.error.notPresent", {
33
- headers: req.headers,
34
- });
35
- res.status(401).json((0, errorView_1.errorView)(new errors_1.UnauthorizedError(["jwt"])));
36
- return;
37
- }
38
- try {
39
- const jwtUser = yield (0, jwt_1.verifyJWT)(publicKey, jwt, options);
40
- logger.debug("auth.authWithJwt.authed", {
41
- user: jwtUser.id,
42
- org: jwtUser.organizationReference,
43
- });
44
- req.context.jwtUser = jwtUser;
45
- req.context.authenticated.push("jwt");
46
- next();
47
- }
48
- catch (error) {
49
- logger.info("auth.authWithJwt.invalidJwt", { jwt, error });
50
- res.status(401).json((0, errorView_1.errorView)(new errors_1.UnauthorizedError(["jwt"])));
51
- }
52
- });
53
- };
54
- }
55
- exports.createAuthWithJWT = createAuthWithJWT;
56
- //# sourceMappingURL=authWithJWT.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"authWithJWT.js","sourceRoot":"","sources":["../../src/middlewares/authWithJWT.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,sCAAwD;AACxD,4CAAoD;AACpD,kDAA+C;AAE/C,kEAA+D;AAG/D,SAAS,wBAAwB,CAC/B,aAAiC;IAEjC,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IACrC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEzD,IAAI,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAE9D,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAgB,iBAAiB,CAAC,SAAiB,EAAE,OAAuB;IAC1E,OAAO,SAAe,WAAW,CAC/B,GAAmB,EACnB,GAAa,EACb,IAAkB;;YAElB,MAAM,MAAM,GAAG,IAAA,mCAAgB,EAAC,GAAG,CAAC,CAAC;YACrC,MAAM,OAAO,GAAI,GAAG,CAAC,OAA8C,IAAI,EAAE,CAAC;YAC1E,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,IAAI,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YAErE,IAAI,CAAC,GAAG,EAAE;gBACR,MAAM,CAAC,KAAK,CAAC,mCAAmC,EAAE;oBAChD,OAAO,EAAE,GAAG,CAAC,OAAO;iBACrB,CAAC,CAAC;gBACH,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAA,qBAAS,EAAC,IAAI,0BAAiB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBAChE,OAAO;aACR;YAED,IAAI;gBACF,MAAM,OAAO,GAAG,MAAM,IAAA,eAAS,EAAC,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;gBACzD,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE;oBACtC,IAAI,EAAE,OAAO,CAAC,EAAE;oBAChB,GAAG,EAAE,OAAO,CAAC,qBAAqB;iBACnC,CAAC,CAAC;gBAEH,GAAG,CAAC,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC;gBAC9B,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAEtC,IAAI,EAAE,CAAC;aACR;YAAC,OAAO,KAAc,EAAE;gBACvB,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAA,qBAAS,EAAC,IAAI,0BAAiB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;aACjE;QACH,CAAC;KAAA,CAAC;AACJ,CAAC;AAnCD,8CAmCC"}
@@ -1,54 +0,0 @@
1
- import { verifyJWT, VerifyOptions } from "@alanszp/jwt";
2
- import { UnauthorizedError } from "@alanszp/errors";
3
- import { errorView } from "../views/errorView";
4
- import { NextFunction, Response } from "express";
5
- import { getRequestLogger } from "../helpers/getRequestLogger";
6
- import { GenericRequest } from "../types/GenericRequest";
7
-
8
- function parseAuthorizationHeader(
9
- authorization: string | undefined
10
- ): string | undefined {
11
- if (!authorization) return undefined;
12
- const [bearer, jwt, ...other] = authorization.split(" ");
13
-
14
- if (bearer !== "Bearer" || other.length > 0) return undefined;
15
-
16
- return jwt;
17
- }
18
-
19
- export function createAuthWithJWT(publicKey: string, options?: VerifyOptions) {
20
- return async function authWithJwt(
21
- req: GenericRequest,
22
- res: Response,
23
- next: NextFunction
24
- ): Promise<void> {
25
- const logger = getRequestLogger(req);
26
- const cookies = (req.cookies as Record<string, string | undefined>) || {};
27
- const jwt =
28
- cookies.jwt || parseAuthorizationHeader(req.headers.authorization);
29
-
30
- if (!jwt) {
31
- logger.debug("auth.authWithJwt.error.notPresent", {
32
- headers: req.headers,
33
- });
34
- res.status(401).json(errorView(new UnauthorizedError(["jwt"])));
35
- return;
36
- }
37
-
38
- try {
39
- const jwtUser = await verifyJWT(publicKey, jwt, options);
40
- logger.debug("auth.authWithJwt.authed", {
41
- user: jwtUser.id,
42
- org: jwtUser.organizationReference,
43
- });
44
-
45
- req.context.jwtUser = jwtUser;
46
- req.context.authenticated.push("jwt");
47
-
48
- next();
49
- } catch (error: unknown) {
50
- logger.info("auth.authWithJwt.invalidJwt", { jwt, error });
51
- res.status(401).json(errorView(new UnauthorizedError(["jwt"])));
52
- }
53
- };
54
- }