@devmoods/express-extras 0.20.0 → 0.20.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.
package/dist/auth.d.ts CHANGED
@@ -24,6 +24,14 @@ export declare function createAuth<UserModel extends Record<string, any>>({ getU
24
24
  * of `req`.
25
25
  */
26
26
  export declare function parseAuthorizationHeader(req: Request): string | null;
27
+ /**
28
+ * Extract an access token from the `Request`-object.
29
+ *
30
+ * Checks multiple places and uses the first match. Priority:
31
+ * 1. Request header `Authorization: `
32
+ * 2. Cookie named `access_token`
33
+ */
34
+ export declare function extractAccessTokenFromRequest(req: Request): string | null;
27
35
  export declare function setAccessTokenCookie(res: Response, token: string | null): void;
28
36
  export declare function checkIfPasswordsMatch(realPassword: string, attemptedPassword: string): Promise<boolean>;
29
37
  export declare function hashPassword(password: string): Promise<string>;
package/dist/auth.js CHANGED
@@ -16,6 +16,7 @@ export function createAuthToken(payload, secret = process.env.APP_JWT_SECRET) {
16
16
  }
17
17
  export function createAuth({ getUserByToken, methods: { emailAndPassword }, filterUser = (x) => x, redis, tokenExpiry = TOKEN_EXPIRES_IN, onResetRequested, onPasswordChanged, }) {
18
18
  const useCurrentUser = () => useContext('currentUser');
19
+ const setCurrentUser = (user) => setContext('currentUser', user);
19
20
  const createRouter = () => {
20
21
  const router = Router();
21
22
  router.get('/me', middleware(), route(() => {
@@ -36,6 +37,8 @@ export function createAuth({ getUserByToken, methods: { emailAndPassword }, filt
36
37
  const token = createAuthToken(filterUser(user));
37
38
  await redis.set(token, '1', { EX: tokenExpiry });
38
39
  setAccessTokenCookie(res, token);
40
+ setCurrentUser(user);
41
+ logger.info('access token created');
39
42
  return [201, { token }];
40
43
  }
41
44
  catch (error) {
@@ -75,12 +78,14 @@ export function createAuth({ getUserByToken, methods: { emailAndPassword }, filt
75
78
  const { token } = req.params;
76
79
  const email = await redis.get(token);
77
80
  if (!email) {
81
+ logger.info('invalid reset token', { token });
78
82
  throw BadRequest('invalid reset token');
79
83
  }
80
84
  const user = await emailAndPassword(email, skipPasswordCheck);
85
+ setCurrentUser(user);
81
86
  await onPasswordChanged?.(user, await hashPassword(password));
82
87
  await redis.del(token);
83
- logger.info('user changed password', { email });
88
+ logger.info('user changed password');
84
89
  return 200;
85
90
  }));
86
91
  }
@@ -88,6 +93,7 @@ export function createAuth({ getUserByToken, methods: { emailAndPassword }, filt
88
93
  const token = req.params.token;
89
94
  const email = await redis.get(token);
90
95
  if (!email) {
96
+ logger.info('invalid reset token', { token });
91
97
  throw NotFound();
92
98
  }
93
99
  return 200;
@@ -110,6 +116,7 @@ export function createAuth({ getUserByToken, methods: { emailAndPassword }, filt
110
116
  EX: TEMP_TOKEN_EXIRES_IN,
111
117
  });
112
118
  await onResetRequested?.(user, temporaryToken);
119
+ logger.info('password reset request', { email });
113
120
  }
114
121
  catch (error) {
115
122
  logger.error(error, 'auth reset failed');
@@ -118,7 +125,6 @@ export function createAuth({ getUserByToken, methods: { emailAndPassword }, filt
118
125
  }));
119
126
  router.get('/token/refresh', middleware(false), route(async (req, res) => {
120
127
  const user = useCurrentUser();
121
- logger.info('refreshed token', { user });
122
128
  if (!user) {
123
129
  throw Unauthorized();
124
130
  }
@@ -126,6 +132,7 @@ export function createAuth({ getUserByToken, methods: { emailAndPassword }, filt
126
132
  const token = createAuthToken(rest);
127
133
  await redis.set(token, '1', { EX: tokenExpiry });
128
134
  setAccessTokenCookie(res, token);
135
+ logger.info('refreshed token', { user });
129
136
  return { token };
130
137
  }));
131
138
  return router;
@@ -143,6 +150,7 @@ export function createAuth({ getUserByToken, methods: { emailAndPassword }, filt
143
150
  const valid = await redis.get(token);
144
151
  if (!valid) {
145
152
  if (!authorize) {
153
+ logger.warn('request with invalid access token');
146
154
  return next();
147
155
  }
148
156
  throw new InvalidToken('token expired');
@@ -152,7 +160,7 @@ export function createAuth({ getUserByToken, methods: { emailAndPassword }, filt
152
160
  throw new Error('Failed to decode token');
153
161
  }
154
162
  const user = await getUserByToken({ ...decoded });
155
- setContext('currentUser', user);
163
+ setCurrentUser(user);
156
164
  next();
157
165
  }
158
166
  catch (error) {
@@ -191,7 +199,14 @@ export function parseAuthorizationHeader(req) {
191
199
  }
192
200
  return token;
193
201
  }
194
- function extractAccessTokenFromRequest(req) {
202
+ /**
203
+ * Extract an access token from the `Request`-object.
204
+ *
205
+ * Checks multiple places and uses the first match. Priority:
206
+ * 1. Request header `Authorization: `
207
+ * 2. Cookie named `access_token`
208
+ */
209
+ export function extractAccessTokenFromRequest(req) {
195
210
  const authorizationHeader = parseAuthorizationHeader(req);
196
211
  if (authorizationHeader) {
197
212
  return authorizationHeader;
@@ -227,7 +242,7 @@ export async function checkIfPasswordsMatch(realPassword, attemptedPassword) {
227
242
  }
228
243
  const validPassword = await bcrypt.compare(attemptedPassword, realPassword);
229
244
  if (!validPassword) {
230
- throw new Error(`Passwords does not match`);
245
+ throw new Error(`Passwords do not match`);
231
246
  }
232
247
  return true;
233
248
  }
package/dist/email.js CHANGED
@@ -15,7 +15,7 @@ export function createEmail({ host, port, user, pass, senderName, senderAddress,
15
15
  text: message,
16
16
  ...rest,
17
17
  });
18
- getLogger('core').info('email-sent', { email: result });
18
+ getLogger('email').info('email-sent', { email: result });
19
19
  };
20
20
  return { send, transporter };
21
21
  }
package/dist/logger.d.ts CHANGED
@@ -18,29 +18,17 @@ export declare function attachSentryClientToLoggers(sentryClient: CreateSentry['
18
18
  export declare function setGlobalLoggerContext(getContext: () => LoggerContext): void;
19
19
  export type Logger = ReturnType<typeof createLogger>;
20
20
  export declare function createLogger({ name, getContext, silent, }: LoggerOptions): {
21
- debug(message: string, context?: {
22
- [key: string]: any;
23
- }): void;
24
- info(message: string, context?: {
25
- [key: string]: any;
26
- }): void;
27
- warn(message: string, context?: {
28
- [key: string]: any;
29
- }): void;
21
+ debug(message: string, context?: LoggerContext): void;
22
+ info(message: string, context?: LoggerContext): void;
23
+ warn(message: string, context?: LoggerContext): void;
30
24
  error(error: Error | null, message?: string): void;
31
25
  fatal(error: Error | null, message?: string): void;
32
26
  };
33
27
  export declare function getLogger(name?: string): Logger;
34
28
  export declare function getLoggers(): Map<string, {
35
- debug(message: string, context?: {
36
- [key: string]: any;
37
- }): void;
38
- info(message: string, context?: {
39
- [key: string]: any;
40
- }): void;
41
- warn(message: string, context?: {
42
- [key: string]: any;
43
- }): void;
29
+ debug(message: string, context?: LoggerContext): void;
30
+ info(message: string, context?: LoggerContext): void;
31
+ warn(message: string, context?: LoggerContext): void;
44
32
  error(error: Error | null, message?: string | undefined): void;
45
33
  fatal(error: Error | null, message?: string | undefined): void;
46
34
  }>;
package/dist/logger.js CHANGED
@@ -1,5 +1,14 @@
1
- import bunyan from 'bunyan';
1
+ import * as os from 'node:os';
2
2
  import { useRequestId } from './context.js';
3
+ const LEVELS = {
4
+ TRACE: 10,
5
+ DEBUG: 20,
6
+ INFO: 30,
7
+ WARN: 40,
8
+ ERROR: 50,
9
+ FATAL: 60,
10
+ };
11
+ const LEVELS_TO_NAME = Object.fromEntries(Object.entries(LEVELS).map(([name, level]) => [level, name.toLowerCase()]));
3
12
  const _loggers = new Map();
4
13
  let _sentryClient;
5
14
  export function attachSentryClientToLoggers(sentryClient) {
@@ -19,14 +28,9 @@ export function createLogger({ name, getContext, silent = () => process.env.NODE
19
28
  if (_loggers.has(name)) {
20
29
  throw new Error(`A logger named ${name} already exists`);
21
30
  }
22
- const logger = bunyan.createLogger({
23
- name,
24
- serializers: bunyan.stdSerializers,
25
- });
26
- _loggers.set(name, logger);
27
- if (silent()) {
28
- logger.level(bunyan.FATAL + 1);
29
- }
31
+ const hostname = os.hostname();
32
+ const pid = process.pid;
33
+ const logLevel = silent() ? LEVELS.FATAL + 1 : LEVELS.INFO;
30
34
  function getFinalContext(context) {
31
35
  return {
32
36
  ...defaultGetContext(),
@@ -34,24 +38,38 @@ export function createLogger({ name, getContext, silent = () => process.env.NODE
34
38
  ...context,
35
39
  };
36
40
  }
37
- return {
41
+ function log(level, message, context) {
42
+ const payload = JSON.stringify({
43
+ level: LEVELS_TO_NAME[level],
44
+ hostname,
45
+ pid,
46
+ message,
47
+ ...context,
48
+ time: new Date().toISOString(),
49
+ v: 1,
50
+ });
51
+ if (level >= logLevel) {
52
+ console.log(payload);
53
+ }
54
+ }
55
+ const api = {
38
56
  debug(message, context = {}) {
39
- return logger.debug(getFinalContext(context), message);
57
+ return log(LEVELS.DEBUG, message, getFinalContext(context));
40
58
  },
41
59
  info(message, context = {}) {
42
- return logger.info(getFinalContext(context), message);
60
+ return log(LEVELS.INFO, message, getFinalContext(context));
43
61
  },
44
62
  warn(message, context = {}) {
45
- return logger.warn(getFinalContext(context), message);
63
+ return log(LEVELS.WARN, message, getFinalContext(context));
46
64
  },
47
65
  error(error, message) {
48
- const context = getFinalContext({ err: error });
66
+ const context = getFinalContext({ err: error?.stack });
49
67
  if (_sentryClient != null) {
50
68
  _sentryClient.captureException(error, {
51
69
  tags: context,
52
70
  });
53
71
  }
54
- return logger.error(context, message || error?.message);
72
+ return log(LEVELS.ERROR, message || error?.message || '', context);
55
73
  },
56
74
  fatal(error, message) {
57
75
  const context = getFinalContext({ err: error });
@@ -60,9 +78,11 @@ export function createLogger({ name, getContext, silent = () => process.env.NODE
60
78
  tags: context,
61
79
  });
62
80
  }
63
- return logger.fatal(context, message);
81
+ return log(LEVELS.FATAL, message || error?.message || '', context);
64
82
  },
65
83
  };
84
+ _loggers.set(name, api);
85
+ return api;
66
86
  }
67
87
  export function getLogger(name = 'default') {
68
88
  const logger = _loggers.get(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devmoods/express-extras",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "description": "Kubernetes-ready utilities for rapid Node.js application development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -8,6 +8,17 @@
8
8
  "bin": {
9
9
  "migrate": "bin/migrate"
10
10
  },
11
+ "scripts": {
12
+ "start": "ts-node --esm example/index.ts",
13
+ "clean": "rm -rf dist",
14
+ "lint": "eslint src",
15
+ "test": "NODE_ENV=test vitest",
16
+ "build": "pnpm run clean && tsc --project tsconfig.build.json",
17
+ "prepublishOnly": "pnpm run clean && pnpm run build",
18
+ "prettier": "prettier --write '**/*.{js,css,md,ts,tsx}'",
19
+ "import-sort": "import-sort --write '**/*.{js,jsx,ts,tsx}'",
20
+ "format": "pnpm run import-sort && pnpm run prettier"
21
+ },
11
22
  "author": {
12
23
  "name": "hanse",
13
24
  "email": "hanse@koren.im"
@@ -51,7 +62,6 @@
51
62
  "@sentry/node": "^7.60.1",
52
63
  "@types/bcrypt": "^5.0.0",
53
64
  "@types/body-parser": "^1.19.2",
54
- "@types/bunyan": "^1.8.8",
55
65
  "@types/cookie-parser": "^1.4.3",
56
66
  "@types/express": "^4.17.17",
57
67
  "@types/jsonwebtoken": "^9.0.2",
@@ -61,7 +71,6 @@
61
71
  "ajv": "^8.12.0",
62
72
  "bcrypt": "^5.1.0",
63
73
  "body-parser": "^1.20.2",
64
- "bunyan": "^1.8.15",
65
74
  "compose-middleware": "^5.0.1",
66
75
  "cookie-parser": "^1.4.6",
67
76
  "dotenv": "^16.3.1",
@@ -90,15 +99,5 @@
90
99
  "@typescript-eslint/no-shadow": "off",
91
100
  "react-hooks/rules-of-hooks": "off"
92
101
  }
93
- },
94
- "scripts": {
95
- "start": "ts-node --esm example/index.ts",
96
- "clean": "rm -rf dist",
97
- "lint": "eslint src",
98
- "test": "NODE_ENV=test vitest",
99
- "build": "pnpm run clean && tsc --project tsconfig.build.json",
100
- "prettier": "prettier --write '**/*.{js,css,md,ts,tsx}'",
101
- "import-sort": "import-sort --write '**/*.{js,jsx,ts,tsx}'",
102
- "format": "pnpm run import-sort && pnpm run prettier"
103
102
  }
104
- }
103
+ }