fastify-txstate 3.5.0 → 3.5.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/lib/analytics.js CHANGED
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.analyticsPlugin = exports.ElasticAnalyticsClient = exports.LoggingAnalyticsClient = exports.AnalyticsClient = void 0;
6
+ exports.ElasticAnalyticsClient = exports.LoggingAnalyticsClient = exports.AnalyticsClient = void 0;
7
+ exports.analyticsPlugin = analyticsPlugin;
7
8
  const elasticsearch_1 = __importDefault(require("@elastic/elasticsearch"));
8
9
  const fastify_shared_1 = require("@txstate-mws/fastify-shared");
9
10
  const txstate_utils_1 = require("txstate-utils");
@@ -114,4 +115,3 @@ function analyticsPlugin(fastify, opts, done) {
114
115
  });
115
116
  done();
116
117
  }
117
- exports.analyticsPlugin = analyticsPlugin;
package/lib/error.d.ts CHANGED
@@ -15,8 +15,8 @@ export declare class FailedValidationError extends HttpError {
15
15
  }
16
16
  export declare class ValidationError extends HttpError {
17
17
  path?: string | undefined;
18
- type?: "error" | "success" | "warning" | "system" | undefined;
19
- constructor(message: string, path?: string | undefined, type?: "error" | "success" | "warning" | "system" | undefined);
18
+ type?: ValidationMessage["type"] | undefined;
19
+ constructor(message: string, path?: string | undefined, type?: ValidationMessage["type"] | undefined);
20
20
  }
21
21
  export declare class ValidationErrors extends HttpError {
22
22
  errors: ValidationMessage[];
package/lib/error.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fstValidationToMessage = exports.ValidationErrors = exports.ValidationError = exports.FailedValidationError = exports.HttpError = void 0;
3
+ exports.ValidationErrors = exports.ValidationError = exports.FailedValidationError = exports.HttpError = void 0;
4
+ exports.fstValidationToMessage = fstValidationToMessage;
4
5
  const http_status_codes_1 = require("http-status-codes");
5
6
  class HttpError extends Error {
6
7
  statusCode;
@@ -54,4 +55,3 @@ function fstValidationToMessage(v) {
54
55
  const instancePath = v.keyword === 'required' ? v.instancePath + '/' + v.params.missingProperty : v.instancePath;
55
56
  return { message: v.message, path: instancePath.substring(1).replace(/\//g, '.'), type: 'error' };
56
57
  }
57
- exports.fstValidationToMessage = fstValidationToMessage;
package/lib/index.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  import { type FastifyDynamicSwaggerOptions } from '@fastify/swagger';
4
2
  import { type FastifySwaggerUiOptions } from '@fastify/swagger-ui';
5
3
  import type { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts';
@@ -45,6 +43,14 @@ export interface FastifyTxStateAuthInfo {
45
43
  * this field can help log requests that are authenticated with the other application's token.
46
44
  */
47
45
  clientId?: string;
46
+ /**
47
+ * A string that designates the current session as one that has limited authorization. The application will
48
+ * be responsible for checking this field and restricting appropriately.
49
+ *
50
+ * For example, a user who authenticated via non-standard mechanism might be given a scope of 'altlogin' and
51
+ * only a portion of the application's functionality would be available to them.
52
+ */
53
+ scope?: string;
48
54
  }
49
55
  export interface FastifyTxStateOptions extends Partial<FastifyServerOptions> {
50
56
  https?: http2.SecureServerOptions;
@@ -112,7 +118,7 @@ export declare const devLogger: {
112
118
  (message?: any, ...optionalParams: any[]): void;
113
119
  };
114
120
  silent: (msg: any) => void;
115
- child(bindings: any, options?: any): any;
121
+ child(bindings: any, options?: any): /*elided*/ any;
116
122
  };
117
123
  export declare const prodLogger: FastifyLoggerOptions;
118
124
  export type FastifyInstanceTyped = FastifyInstance<RawServerDefault, http.IncomingMessage, http.ServerResponse<http.IncomingMessage>, FastifyBaseLogger, JsonSchemaToTsProvider>;
package/lib/index.js CHANGED
@@ -292,12 +292,12 @@ class Server {
292
292
  });
293
293
  this.app.get('/health', { logLevel: 'warn' }, async (req, res) => {
294
294
  if (this.shuttingDown) {
295
- res.log.info('Returning 503 on /health because we are shutting down/restarting.');
295
+ res.log.warn('Returning 503 on /health because we are shutting down/restarting.');
296
296
  void res.status(503);
297
297
  return 'MAINTENANCE';
298
298
  }
299
299
  else if (this.healthMessage) {
300
- res.log.info(this.healthMessage);
300
+ res.log.error(this.healthMessage);
301
301
  void res.status(500);
302
302
  return this.healthMessage;
303
303
  }
@@ -305,7 +305,7 @@ class Server {
305
305
  const resp = await this.healthCallback();
306
306
  const [status, msg] = typeof resp === 'string' ? [500, resp] : [resp?.status, resp?.message];
307
307
  if (!!msg || !!status) {
308
- res.log.info(resp, 'Health check callback failed.');
308
+ res.log.error(resp, 'Health check callback failed.');
309
309
  void res.status(status ?? 500);
310
310
  return msg ?? 'FAIL';
311
311
  }
@@ -394,9 +394,9 @@ this is log into this application and use dev tools to pull your token from the
394
394
  }
395
395
  await this.app.register(swagger_1.default, {
396
396
  openapi,
397
- transform({ schema, url, route, swaggerObject, openapiObject }) {
398
- const newSchema = findRefs((0, txstate_utils_1.clone)(schema));
399
- return { schema: newSchema, url, route, swaggerObject, openapiObject };
397
+ transform(transformArgs) {
398
+ const newSchema = findRefs((0, txstate_utils_1.clone)(transformArgs.schema));
399
+ return { ...transformArgs, schema: newSchema };
400
400
  }
401
401
  });
402
402
  this.swaggerEndpoint = opts?.path ?? opts?.ui?.routePrefix ?? '/docs';
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerUaCookieRoutes = exports.requireCookieAuth = exports.unifiedAuthenticateAll = exports.unifiedAuthenticate = exports.uaCookieName = void 0;
3
+ exports.uaCookieName = void 0;
4
+ exports.unifiedAuthenticate = unifiedAuthenticate;
5
+ exports.unifiedAuthenticateAll = unifiedAuthenticateAll;
6
+ exports.requireCookieAuth = requireCookieAuth;
7
+ exports.registerUaCookieRoutes = registerUaCookieRoutes;
4
8
  const crypto_1 = require("crypto");
5
9
  const jose_1 = require("jose");
6
10
  const txstate_utils_1 = require("txstate-utils");
@@ -65,7 +69,7 @@ function remoteJWKSet(jwkUrl) {
65
69
  function processIssuerConfig(config) {
66
70
  if (config.iss === 'unified-auth') {
67
71
  config.validateUrl = new URL(config.url ?? '');
68
- config.validateUrl.pathname = '/validateToken';
72
+ config.validateUrl.pathname = [...config.validateUrl.pathname.split('/').slice(0, -1), 'validateToken'].join('/');
69
73
  }
70
74
  return config;
71
75
  }
@@ -112,17 +116,16 @@ async function unifiedAuthenticate(req) {
112
116
  username: payload.sub,
113
117
  sessionId: payload.sub + '-' + payload.iat,
114
118
  clientId: payload.client_id,
115
- impersonatedBy: payload.act?.sub
119
+ impersonatedBy: payload.act?.sub,
120
+ scope: payload.scope
116
121
  };
117
122
  }
118
- exports.unifiedAuthenticate = unifiedAuthenticate;
119
123
  async function unifiedAuthenticateAll(req) {
120
124
  const auth = await unifiedAuthenticate(req);
121
125
  if (!auth?.username.length)
122
126
  throw new Error('All requests require authentication.');
123
127
  return auth;
124
128
  }
125
- exports.unifiedAuthenticateAll = unifiedAuthenticateAll;
126
129
  async function requireCookieAuth(req, res) {
127
130
  if (req.auth === undefined || req.auth.username.length === 0) {
128
131
  await res
@@ -134,7 +137,6 @@ async function requireCookieAuth(req, res) {
134
137
  return false;
135
138
  }
136
139
  }
137
- exports.requireCookieAuth = requireCookieAuth;
138
140
  function registerUaCookieRoutes(app) {
139
141
  app.get('/.uaLogout', {
140
142
  schema: {
@@ -182,4 +184,3 @@ function registerUaCookieRoutes(app) {
182
184
  .redirect(decodeURIComponent(m[1]));
183
185
  });
184
186
  }
185
- exports.registerUaCookieRoutes = registerUaCookieRoutes;
@@ -0,0 +1,155 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { type FastifyDynamicSwaggerOptions } from '@fastify/swagger';
4
+ import { type FastifySwaggerUiOptions } from '@fastify/swagger-ui';
5
+ import type { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts';
6
+ import { type FastifyInstance, type FastifyRequest, type FastifyReply, type FastifyServerOptions, type FastifyLoggerOptions, type FastifyBaseLogger, type RawServerDefault } from 'fastify';
7
+ import http from 'node:http';
8
+ import type http2 from 'node:http2';
9
+ type ErrorHandler = (error: Error, req: FastifyRequest, res: FastifyReply) => Promise<void>;
10
+ export interface FastifyTxStateAuthInfo {
11
+ /**
12
+ * The primary identifier for the user that is making the request, after processing
13
+ * their session token / JWT.
14
+ */
15
+ username: string;
16
+ /**
17
+ * This should be an identifier for the particular session, so that the same user
18
+ * on different devices/browsers/tabs can be distinguished from one another.
19
+ *
20
+ * It should NOT be usable as a cookie or bearer token, as it will appear in logs. If you
21
+ * use JSON Web Tokens, an easy thing is to combine the username with the `iat` issued
22
+ * date to create something unique but not useful to attackers.
23
+ *
24
+ * For lookup tokens, you can do the same `${username}-${createdAt}` after looking up
25
+ * the session in your database.
26
+ *
27
+ * If all else fails, you can sha256 the session token with a salt.
28
+ */
29
+ sessionId: string;
30
+ /**
31
+ * Some authentication systems allow administrators to impersonate regular users, so that
32
+ * they can see what that user sees and troubleshoot issues. We still want to log the administrator
33
+ * with any actions they take while impersonating someone, for auditing purposes, so you should
34
+ * fill this field when applicable.
35
+ *
36
+ * This will also be available at `req.auth.impersonatedBy`, so it is possible for your API
37
+ * to implement complicated authorization rules based on whether a user is being impersonated.
38
+ * It sort of defeats the purpose of impersonation, but used sparingly it could prevent administrators
39
+ * from making mistakes.
40
+ */
41
+ impersonatedBy?: string;
42
+ /**
43
+ * If your API may be accessed by a different client application, such that the user is actually logged
44
+ * into that application instead of yours, but you accept that application's session tokens, filling
45
+ * this field can help log requests that are authenticated with the other application's token.
46
+ */
47
+ clientId?: string;
48
+ }
49
+ export interface FastifyTxStateOptions extends Partial<FastifyServerOptions> {
50
+ https?: http2.SecureServerOptions;
51
+ validOrigins?: string[];
52
+ validOriginHosts?: string[];
53
+ validOriginSuffixes?: string[];
54
+ skipOriginCheck?: boolean;
55
+ checkOrigin?: (req: FastifyRequest) => boolean;
56
+ /**
57
+ * Run an asynchronous function to check the health of the service.
58
+ *
59
+ * Return a non-empty error message to trigger unhealthy status.
60
+ *
61
+ * Setting a health message with setUnhealthy will override this and prevent it from being executed.
62
+ */
63
+ checkHealth?: () => Promise<string | {
64
+ status?: number;
65
+ message?: string;
66
+ } | undefined>;
67
+ /**
68
+ * Run an async function to get authentication information out of the request
69
+ * object. Should return an object with at least a username and sessionid (see FastifyTxStateAuthInfo
70
+ * for further detail).
71
+ *
72
+ * The return object will be added to the request object as `req.auth` for later
73
+ * use in your route handlers. It will also be added to the logs in production.
74
+ *
75
+ * IMPORTANT: It is not advisable to return excessive amounts of data here, nor anything
76
+ * particularly sensitive, since it will all be included in every log entry.
77
+ *
78
+ * If this function throws, the client will receive a 401 response.
79
+ */
80
+ authenticate?: (req: FastifyRequest) => Promise<FastifyTxStateAuthInfo | undefined>;
81
+ }
82
+ declare module 'fastify' {
83
+ interface FastifyRequest {
84
+ auth?: FastifyTxStateAuthInfo;
85
+ }
86
+ interface FastifyReply {
87
+ extraLogInfo: any;
88
+ }
89
+ }
90
+ export declare const devLogger: {
91
+ level: string;
92
+ info: (msg: any) => void;
93
+ error: {
94
+ (...data: any[]): void;
95
+ (message?: any, ...optionalParams: any[]): void;
96
+ };
97
+ debug: {
98
+ (...data: any[]): void;
99
+ (message?: any, ...optionalParams: any[]): void;
100
+ };
101
+ fatal: {
102
+ (...data: any[]): void;
103
+ (message?: any, ...optionalParams: any[]): void;
104
+ };
105
+ warn: {
106
+ (...data: any[]): void;
107
+ (message?: any, ...optionalParams: any[]): void;
108
+ };
109
+ trace: {
110
+ (...data: any[]): void;
111
+ (message?: any, ...optionalParams: any[]): void;
112
+ };
113
+ silent: (msg: any) => void;
114
+ child(bindings: any, options?: any): any;
115
+ };
116
+ export declare const prodLogger: FastifyLoggerOptions;
117
+ export type FastifyInstanceTyped = FastifyInstance<RawServerDefault, http.IncomingMessage, http.ServerResponse<http.IncomingMessage>, FastifyBaseLogger, JsonSchemaToTsProvider>;
118
+ export type TxServer = Server;
119
+ export default class Server {
120
+ protected config: FastifyTxStateOptions & {
121
+ http2?: true;
122
+ };
123
+ protected https: boolean;
124
+ protected errorHandlers: ErrorHandler[];
125
+ protected healthMessage?: string;
126
+ protected healthCallback?: () => Promise<string | {
127
+ status?: number;
128
+ message?: string;
129
+ } | undefined>;
130
+ protected shuttingDown: boolean;
131
+ protected sigHandler: (signal: any) => void;
132
+ protected validOrigins: Record<string, boolean>;
133
+ protected validOriginHosts: Record<string, boolean>;
134
+ protected validOriginSuffixes: Set<string>;
135
+ app: FastifyInstanceTyped;
136
+ constructor(config?: FastifyTxStateOptions & {
137
+ http2?: true;
138
+ });
139
+ start(port?: number): Promise<void>;
140
+ addErrorHandler(handler: ErrorHandler): void;
141
+ setUnhealthy(message: string): void;
142
+ setHealthy(): void;
143
+ setValidOrigins(origins: string[]): void;
144
+ setValidOriginHosts(hosts: string[]): void;
145
+ setValidOriginSuffixes(suffixes: string[]): void;
146
+ swagger(opts?: {
147
+ path?: string;
148
+ openapi?: FastifyDynamicSwaggerOptions['openapi'];
149
+ ui?: FastifySwaggerUiOptions;
150
+ }): Promise<void>;
151
+ close(softSeconds?: number): Promise<void>;
152
+ }
153
+ export * from './analytics';
154
+ export * from './error';
155
+ export * from './unified-auth';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify-txstate",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
4
4
  "description": "A small wrapper for fastify providing a set of common conventions & utility functions we use.",
5
5
  "exports": {
6
6
  ".": {
@@ -36,7 +36,7 @@
36
36
  "devDependencies": {
37
37
  "@types/chai": "^4.2.14",
38
38
  "@types/mocha": "^10.0.0",
39
- "@types/node": "^20.0.0",
39
+ "@types/node": "^22.0.0",
40
40
  "axios": "^1.6.8",
41
41
  "chai": "^4.2.0",
42
42
  "eslint-config-standard-with-typescript": "^43.0.0",