fastify-txstate 3.6.9 → 4.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.
package/lib/index.js CHANGED
@@ -1,422 +1,9 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- var __importDefault = (this && this.__importDefault) || function (mod) {
17
- return (mod && mod.__esModule) ? mod : { "default": mod };
18
- };
19
- Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.prodLogger = exports.devLogger = void 0;
21
- const ajv_1 = __importDefault(require("ajv"));
22
- const swagger_1 = __importDefault(require("@fastify/swagger"));
23
- const swagger_ui_1 = __importDefault(require("@fastify/swagger-ui"));
24
- const fastify_shared_1 = require("@txstate-mws/fastify-shared");
25
- const ajv_errors_1 = __importDefault(require("ajv-errors"));
26
- const ajv_formats_1 = __importDefault(require("ajv-formats"));
27
- const fastify_1 = require("fastify");
28
- const node_fs_1 = __importDefault(require("node:fs"));
29
- const node_http_1 = __importDefault(require("node:http"));
30
- const txstate_utils_1 = require("txstate-utils");
31
- const error_1 = require("./error");
32
- exports.devLogger = {
33
- level: 'info',
34
- info: (msg) => { console.info(msg.req ? `${msg.req.method} ${msg.req.url}` : msg.res ? `${msg.res.statusCode} - ${msg.responseTime}` : msg); },
35
- error: console.error,
36
- debug: console.debug,
37
- fatal: console.error,
38
- warn: console.warn,
39
- trace: console.trace,
40
- silent: (msg) => { },
41
- child(bindings, options) { return this; }
42
- };
43
- exports.prodLogger = {
44
- level: 'info',
45
- serializers: {
46
- req(req) {
47
- return {
48
- method: req.method,
49
- url: req.url.replace(/(token|unifiedJwt)=[\w.]+/i, '$1=redacted'),
50
- remoteAddress: req.ip,
51
- traceparent: req.headers.traceparent
52
- };
53
- },
54
- res(res) {
55
- return {
56
- statusCode: res.statusCode,
57
- url: res.request?.url.replace(/(token|unifiedJwt)=[\w.]+/i, '$1=redacted'),
58
- length: Number((0, txstate_utils_1.toArray)(res.getHeader?.('content-length'))[0]),
59
- ...res.extraLogInfo,
60
- auth: (0, txstate_utils_1.omit)(res.request?.auth ?? res.extraLogInfo?.auth ?? {}, 'token', 'issuerConfig')
61
- };
62
- }
63
- }
64
- };
65
- class Server {
66
- config;
67
- https = false;
68
- errorHandlers = [];
69
- healthMessage;
70
- healthCallback;
71
- shuttingDown = false;
72
- sigHandler;
73
- validOrigins = {};
74
- validOriginHosts = {};
75
- validOriginSuffixes = new Set();
76
- swaggerEndpoint;
77
- app;
78
- constructor(config = {}) {
79
- this.config = config;
80
- try {
81
- const key = node_fs_1.default.readFileSync('/securekeys/private.key');
82
- const cert = node_fs_1.default.readFileSync('/securekeys/cert.pem');
83
- config.https = {
84
- ...config.https,
85
- allowHTTP1: true,
86
- key,
87
- cert,
88
- minVersion: 'TLSv1.2'
89
- };
90
- config.http2 = true;
91
- this.https = true;
92
- }
93
- catch (e) {
94
- this.https = false;
95
- delete config.https;
96
- }
97
- if (typeof config.logger === 'undefined') {
98
- config.logger = process.env.NODE_ENV === 'development'
99
- ? exports.devLogger
100
- : exports.prodLogger;
101
- }
102
- if (process.env.TRUST_PROXY != null) {
103
- if (['true', '1'].includes(process.env.TRUST_PROXY))
104
- config.trustProxy = true;
105
- else
106
- config.trustProxy = process.env.TRUST_PROXY;
107
- }
108
- config.ajv = { ...config.ajv, plugins: [...(config.ajv?.plugins ?? []), ajv_errors_1.default, [ajv_formats_1.default, { mode: 'fast' }]], customOptions: { ...config.ajv?.customOptions, allErrors: true, strictSchema: false, coerceTypes: true } };
109
- this.healthCallback = config.checkHealth;
110
- this.app = (0, fastify_1.fastify)(config);
111
- this.app.addHook('onRoute', route => {
112
- if (!route.schema?.body)
113
- return;
114
- const missingResponse = route.schema?.response == null;
115
- const response400 = (0, txstate_utils_1.set)(fastify_shared_1.validatedResponse.properties.messages, 'description', 'Basic validation failure. This means that the UI provided input that failed validation as defined in the openapi specification published by the API. The UI is at fault and should be re-coded to avoid sending invalid data.');
116
- let newSchema = (0, txstate_utils_1.set)(route.schema ?? {}, 'response.400', response400);
117
- const response422 = (0, txstate_utils_1.set)(fastify_shared_1.validatedResponse, 'description', 'Validation failure. This means that the user provided an invalid object. The user should be shown their error so that they can correct it.');
118
- newSchema = (0, txstate_utils_1.set)(newSchema, 'response.422', response422);
119
- if (missingResponse) {
120
- newSchema.response['200'] = {
121
- description: 'Success. Return type has not been specified.',
122
- type: 'object'
123
- };
124
- }
125
- route.schema = newSchema;
126
- });
127
- this.app.addHook('preValidation', (req, res, done) => {
128
- if (req.body != null && req.routeOptions.schema?.body)
129
- (0, txstate_utils_1.destroyNulls)(req.body);
130
- done();
131
- });
132
- // use Ajv to validate responses instead of @fastify/json-fast-stringify since ajv does
133
- // a better job with recursive types and we don't want to have different behavior between
134
- // input and output validation
135
- const ajv = new ajv_1.default(config.ajv.customOptions);
136
- for (const pluginConfig of config.ajv.plugins ?? []) {
137
- const [plugin, opts] = (0, txstate_utils_1.toArray)(pluginConfig);
138
- plugin(ajv, opts);
139
- }
140
- this.app.setSerializerCompiler((route) => {
141
- const schema = route.schema;
142
- const validate = schema == null ? ajv.compile({ type: 'object' }) : ajv.compile(schema);
143
- return data => {
144
- /**
145
- * Ajv unfortunately treats optional properties as non-nullable, so they're allowed to
146
- * be undefined but not allowed to be null. Worse, with `coerceTypes`, null will be converted
147
- * to empty string or 0 or false. This is silly behavior, so we're converting all nulls to
148
- * undefined before we validate.
149
- */
150
- if (schema != null)
151
- (0, txstate_utils_1.destroyNulls)((0, txstate_utils_1.stringifyDates)(data));
152
- if (!validate(data))
153
- throw new Error('Output validation failed. ' + validate.errors?.[0].instancePath + ': ' + validate.errors?.[0].message);
154
- return JSON.stringify(data);
155
- };
156
- });
157
- this.app.addHook('onRequest', (req, res, done) => {
158
- res.extraLogInfo = {};
159
- done();
160
- });
161
- if (!config.skipOriginCheck && !process.env.SKIP_ORIGIN_CHECK) {
162
- this.setValidOrigins([...(config.validOrigins ?? []), ...(process.env.VALID_ORIGINS?.split(',') ?? [])]);
163
- this.setValidOriginHosts([...(config.validOriginHosts ?? []), ...(process.env.VALID_ORIGIN_HOSTS?.split(',') ?? [])]);
164
- this.setValidOriginSuffixes([...(config.validOriginSuffixes ?? []), ...(process.env.VALID_ORIGIN_SUFFIXES?.split(',') ?? [])]);
165
- this.app.addHook('onRequest', async (req, res) => {
166
- if (!req.headers.origin)
167
- return;
168
- let passed = this.validOrigins[req.headers.origin];
169
- if (!passed && req.headers.origin === 'null')
170
- passed = process.env.NODE_ENV === 'development';
171
- else if (!passed) {
172
- const parsedOrigin = new URL(req.headers.origin);
173
- if (req.hostname.replace(/:\d+$/, '') === parsedOrigin.hostname)
174
- passed = true;
175
- if (this.validOriginHosts[parsedOrigin.hostname])
176
- passed = true;
177
- if (!passed && this.validOriginSuffixes.size > 0) {
178
- const originParts = parsedOrigin.hostname.split('.');
179
- for (let i = 0; i < originParts.length; i++) {
180
- const suffix = originParts.slice(i).join('.');
181
- if (this.validOriginSuffixes.has(suffix))
182
- passed = true;
183
- }
184
- }
185
- }
186
- if (!passed && config.checkOrigin?.(req))
187
- passed = true;
188
- if (!passed) {
189
- await res.status(403).send('Origin check failed. Suspected XSRF attack.');
190
- return res;
191
- }
192
- else {
193
- void res.header('Access-Control-Allow-Origin', req.headers.origin);
194
- void res.header('Access-Control-Max-Age', '600'); // ask browser to skip pre-flights for 10 minutes after a yes
195
- if (req.headers['access-control-request-method'])
196
- void res.header('access-control-allow-methods', req.headers['access-control-request-method']);
197
- if (req.headers['access-control-request-headers'])
198
- void res.header('Access-Control-Allow-Headers', req.headers['access-control-request-headers']);
199
- }
200
- });
201
- this.app.options('*', async (req, res) => {
202
- await res.send();
203
- });
204
- }
205
- if (config.authenticate) {
206
- const authenticatedMethods = {
207
- GET: true,
208
- POST: true,
209
- PUT: true,
210
- PATCH: true,
211
- DELETE: true
212
- };
213
- this.app.addHook('onRequest', async (req, res) => {
214
- if (!authenticatedMethods[req.method] || (0, txstate_utils_1.isBlank)(req.routeOptions.url) || req.routeOptions.url === '/health' || req.routeOptions.url === '/.uaService' || (this.swaggerEndpoint && req.routeOptions.url?.startsWith(this.swaggerEndpoint)))
215
- return;
216
- try {
217
- req.auth = await config.authenticate(req);
218
- }
219
- catch (e) {
220
- await res.status(401).send('Failed to authenticate.');
221
- return res;
222
- }
223
- });
224
- }
225
- this.app.addHook('onSend', this.https && process.env.NODE_ENV !== 'development'
226
- ? async (_, resp) => {
227
- void resp.removeHeader('X-Powered-By');
228
- void resp.header('Strict-Transport-Security', 'max-age=31536000');
229
- if (resp.getHeader('content-type') === 'text/html')
230
- void resp.type('text/html; charset=utf-8');
231
- }
232
- : async (_, resp) => {
233
- void resp.removeHeader('X-Powered-By');
234
- if (resp.getHeader('content-type') === 'text/html')
235
- void resp.type('text/html; charset=utf-8');
236
- });
237
- this.app.setNotFoundHandler((req, res) => { void res.status(404).send('Not Found.'); });
238
- this.app.setErrorHandler(async (err, req, res) => {
239
- req.log.warn(err);
240
- for (const errorHandler of this.errorHandlers) {
241
- if (!res.sent)
242
- await errorHandler(err, req, res);
243
- }
244
- if (!res.sent) {
245
- if (err instanceof error_1.FailedValidationError) {
246
- await res.status(err.statusCode).send(err.errors);
247
- }
248
- else if (err instanceof error_1.ValidationError) {
249
- await res.status(err.statusCode).send({ success: false, messages: [{ message: err.message, path: err.path, type: err.type ?? 'error' }] });
250
- }
251
- else if (err instanceof error_1.ValidationErrors) {
252
- await res.status(err.statusCode).send({ success: false, messages: err.errors });
253
- }
254
- else if (err instanceof error_1.HttpError) {
255
- await res.status(err.statusCode).send(err.message);
256
- }
257
- else if (err.code === 'FST_ERR_VALIDATION') {
258
- const developerErrors = [];
259
- const userErrors = [];
260
- for (const v of err.validation ?? []) {
261
- if (v.keyword === 'errorMessage') {
262
- for (const ov of v.params.errors) {
263
- if (['type', 'additionalProperties', 'minProperties'].includes(ov.keyword))
264
- developerErrors.push({ ...ov, message: v.message });
265
- else if (ov.keyword === 'required')
266
- userErrors.push({ ...ov, message: 'This field is required.' });
267
- else
268
- userErrors.push({ ...ov, message: v.message });
269
- }
270
- }
271
- else {
272
- if (['type', 'additionalProperties', 'minProperties'].includes(v.keyword))
273
- developerErrors.push(v);
274
- else if (v.keyword === 'required')
275
- userErrors.push({ ...v, message: 'This field is required.' });
276
- else
277
- userErrors.push(v);
278
- }
279
- }
280
- if (userErrors.length)
281
- await res.status(422).send({ success: false, messages: userErrors.map(error_1.fstValidationToMessage) });
282
- else
283
- await res.status(400).send(developerErrors.map(error_1.fstValidationToMessage));
284
- }
285
- else if (err.statusCode) {
286
- await res.status(err.statusCode).send(new error_1.HttpError(err.statusCode).message);
287
- }
288
- else {
289
- await res.status(500).send('Internal Server Error.');
290
- }
291
- }
292
- });
293
- this.app.get('/health', { logLevel: 'warn' }, async (req, res) => {
294
- if (this.shuttingDown) {
295
- res.log.warn('Returning 503 on /health because we are shutting down/restarting.');
296
- void res.status(503);
297
- return 'MAINTENANCE';
298
- }
299
- else if (this.healthMessage) {
300
- res.log.error(this.healthMessage);
301
- void res.status(500);
302
- return this.healthMessage;
303
- }
304
- else if (this.healthCallback) {
305
- const resp = await this.healthCallback();
306
- const [status, msg] = typeof resp === 'string' ? [500, resp] : [resp?.status, resp?.message];
307
- if (!!msg || !!status) {
308
- res.log.error(resp, 'Health check callback failed.');
309
- void res.status(status ?? 500);
310
- return msg ?? 'FAIL';
311
- }
312
- }
313
- return 'OK';
314
- });
315
- this.sigHandler = () => {
316
- this.close().then(() => {
317
- process.exit();
318
- }).catch(e => { console.error(e); });
319
- };
320
- process.on('SIGTERM', this.sigHandler);
321
- process.on('SIGINT', this.sigHandler);
322
- }
323
- async start(port) {
324
- const customPort = port ?? parseInt(process.env.PORT ?? '0');
325
- await this.app.ready();
326
- this.app.swagger?.();
327
- if (customPort) {
328
- await this.app.listen({ port: customPort, host: '0.0.0.0' });
329
- }
330
- else if (this.https) {
331
- // redirect 80 to 443
332
- node_http_1.default.createServer((req, res) => {
333
- res.writeHead(301, { Location: 'https://' + (req?.headers?.host?.replace(/:\d+$/, '') ?? '') + (req.url ?? '') });
334
- res.end();
335
- }).listen(80);
336
- await this.app.listen({ port: 443, host: '0.0.0.0' });
337
- }
338
- else {
339
- await this.app.listen({ port: 80, host: '0.0.0.0' });
340
- }
341
- }
342
- addErrorHandler(handler) {
343
- this.errorHandlers.push(handler);
344
- }
345
- setUnhealthy(message) {
346
- this.healthMessage = message;
347
- }
348
- setHealthy() {
349
- this.healthMessage = undefined;
350
- }
351
- setValidOrigins(origins) {
352
- this.validOrigins = origins.reduce((validOrigins, origin) => ({ ...validOrigins, [origin]: true }), {});
353
- }
354
- setValidOriginHosts(hosts) {
355
- this.validOriginHosts = hosts.reduce((validHosts, host) => ({ ...validHosts, [host]: true }), {});
356
- }
357
- setValidOriginSuffixes(suffixes) {
358
- this.validOriginSuffixes.clear();
359
- for (const s of suffixes)
360
- this.validOriginSuffixes.add(s.replace(/^\./, ''));
361
- }
362
- async swagger(opts) {
363
- let openapi = opts?.openapi ?? {};
364
- if (this.config.authenticate != null) {
365
- openapi = (0, txstate_utils_1.set)(openapi, 'components.securitySchemes', {
366
- unifiedAuth: {
367
- type: 'http',
368
- scheme: 'bearer',
369
- bearerFormat: 'JWT',
370
- description: `Enter a token obtained from the TxState Unified Authentication service. An easy way to do
371
- this is log into this application and use dev tools to pull your token from the Authorization header.`
372
- }
373
- });
374
- // Apply the security globally to all operations
375
- openapi.security = [{ unifiedAuth: [] }];
376
- }
377
- function findRefs(obj, id) {
378
- if (obj == null)
379
- return undefined;
380
- if (obj.$id?.length)
381
- id = obj.$id;
382
- if (obj.$ref === '#' && id?.length) {
383
- obj.type = 'string';
384
- obj.enum = [id];
385
- delete obj.$ref;
386
- }
387
- else {
388
- for (const val of Object.values(obj)) {
389
- if (typeof val === 'object' && !(val instanceof Date))
390
- findRefs(val, id);
391
- }
392
- }
393
- return obj;
394
- }
395
- await this.app.register(swagger_1.default, {
396
- openapi,
397
- transform(transformArgs) {
398
- const newSchema = findRefs((0, txstate_utils_1.clone)(transformArgs.schema));
399
- return { ...transformArgs, schema: newSchema };
400
- }
401
- });
402
- this.swaggerEndpoint = opts?.path ?? opts?.ui?.routePrefix ?? '/docs';
403
- await this.app.register(swagger_ui_1.default, { ...opts?.ui, routePrefix: this.swaggerEndpoint });
404
- }
405
- async close(softSeconds) {
406
- if (typeof softSeconds === 'undefined')
407
- softSeconds = parseInt(process.env.LOAD_BALANCE_TIMEOUT ?? '0');
408
- process.removeListener('SIGTERM', this.sigHandler);
409
- process.removeListener('SIGINT', this.sigHandler);
410
- if (softSeconds) {
411
- this.shuttingDown = true;
412
- await (0, txstate_utils_1.sleep)(softSeconds);
413
- }
414
- await this.app.close();
415
- }
416
- }
417
- exports.default = Server;
418
- __exportStar(require("./analytics"), exports);
419
- __exportStar(require("./error"), exports);
420
- __exportStar(require("./filestorage"), exports);
421
- __exportStar(require("./unified-auth"), exports);
422
- __exportStar(require("./postformdata"), exports);
1
+ export { default } from "./server.js";
2
+ export * from "./server.js";
3
+ export * from "./analytics.js";
4
+ export * from "./error.js";
5
+ export * from "./filestorage.js";
6
+ export * from "./jwt-auth.js";
7
+ export * from "./unified-auth.js";
8
+ export * from "./oauth.js";
9
+ export * from "./postformdata.js";
@@ -0,0 +1,98 @@
1
+ import type { FastifyRequest } from 'fastify';
2
+ import { type JWTPayload } from 'jose';
3
+ import type { FastifyTxStateAuthInfo, IssuerConfig } from './server.ts';
4
+ export interface OAuthDiscovery {
5
+ issuer: string;
6
+ jwks_uri: string;
7
+ authorization_endpoint?: string;
8
+ token_endpoint?: string;
9
+ end_session_endpoint?: string;
10
+ }
11
+ declare module 'fastify' {
12
+ interface FastifyRequest {
13
+ pendingOAuthCookies?: string[];
14
+ }
15
+ }
16
+ export declare const oauthCookieName: string;
17
+ export declare const refreshCookieName: string;
18
+ export declare const accessTokenCookieName: string;
19
+ export declare const uaCookieName: string;
20
+ export declare function wrapRefreshToken(token: string): string;
21
+ export declare function unwrapRefreshToken(value: string): string | undefined;
22
+ type JwtIssuerType = 'oauth' | 'jwks' | 'unified-auth' | 'publicKey' | 'secret';
23
+ export interface JwtIssuerConfigRaw {
24
+ iss: string;
25
+ /** Explicitly set the issuer type. Optional — when omitted, the type is inferred:
26
+ * iss === 'unified-auth' → 'unified-auth'; secret → 'secret'; publicKey → 'publicKey';
27
+ * url → 'jwks'. Set type: 'oauth' to enable OAuth/OIDC auto-discovery via
28
+ * .well-known/openid-configuration on the issuer URL. */
29
+ type?: JwtIssuerType;
30
+ /** Issuer URL. For 'oauth' this is the issuer (.well-known/openid-configuration is
31
+ * fetched relative to it). For 'jwks' this is the JWKS endpoint directly. For
32
+ * 'unified-auth' this is the UA service URL. */
33
+ url?: string;
34
+ /** PEM-encoded public key (publicKey type). */
35
+ publicKey?: string;
36
+ /** Symmetric HMAC secret (secret type). */
37
+ secret?: string;
38
+ /** Override URL for the unified-auth /validateToken poll. Resolved relative to `url`. */
39
+ validateUrl?: string;
40
+ /** End-session URL surfaced as `req.auth.issuerConfig.logoutUrl`. For 'oauth' issuers
41
+ * this is auto-discovered; set this only to override. Resolved relative to `url`. */
42
+ logoutUrl?: string;
43
+ /** Server-to-server URL prefix override for split-horizon DNS (e.g. talking to the
44
+ * issuer over a docker-internal hostname while the browser uses the public URL). */
45
+ internalUrl?: string;
46
+ /** If set, only accept tokens whose `aud` claim contains one of these values. */
47
+ audiences?: string[];
48
+ /** If set, only accept tokens whose `client_id` claim matches one of these values. */
49
+ clientIds?: string[];
50
+ }
51
+ export declare function toInternalUrl(url: string): string;
52
+ export declare function getOAuthIssuerUrls(): string[];
53
+ export declare function getIssuerConfig(iss: string): IssuerConfig | undefined;
54
+ export declare function getOAuthDiscovery(issuerUrl: string): Promise<OAuthDiscovery | undefined>;
55
+ export declare function init(): void;
56
+ export interface JwtAuthenticateOptions {
57
+ /** If true, all requests require authentication, except routes listed in exceptRoutes or optionalRoutes. */
58
+ authenticateAll?: boolean;
59
+ /** Routes that skip authentication entirely. They will not receive an auth object. */
60
+ exceptRoutes?: Set<string>;
61
+ /** Routes that do not require authentication, but will fill req.auth if a session is available. */
62
+ optionalRoutes?: Set<string>;
63
+ /** Receives the full JWT payload and returns extra properties to merge into the auth object.
64
+ * If you use this, set per-issuer `audiences` to prevent tokens from other applications
65
+ * carrying unexpected authorization claims. */
66
+ extraClaims?: (payload: JWTPayload) => Record<string, unknown>;
67
+ }
68
+ export declare const registeredExceptRoutes: Set<string>;
69
+ export declare const registeredOptionalRoutes: Set<string>;
70
+ /**
71
+ * Build an `authenticate` function that validates JWTs from the Authorization Bearer
72
+ * header or a session cookie. Supports any mix of issuer types via the
73
+ * JWT_TRUSTED_ISSUERS env var:
74
+ *
75
+ * - 'oauth' — OAuth/OIDC provider with .well-known auto-discovery
76
+ * - 'jwks' — JWKS endpoint URL (no discovery)
77
+ * - 'unified-auth' — TxState Unified Auth (JWKS + /validateToken poll for deauth)
78
+ * - 'publicKey' — PEM-encoded asymmetric public key
79
+ * - 'secret' — symmetric HMAC secret
80
+ *
81
+ * Usage:
82
+ * new Server({ authenticate: jwtAuthenticate({ authenticateAll: true }) })
83
+ *
84
+ * Or with no options:
85
+ * new Server({ authenticate: jwtAuthenticate() })
86
+ *
87
+ * Calling `registerOAuthCookieRoutes` or `registerUaCookieRoutes` automatically excludes
88
+ * their callback/redirect routes from authentication requirements and marks their logout
89
+ * routes as optional, so you do not need to configure that here.
90
+ *
91
+ * If a refresh-token cookie is present (set by registerOAuthCookieRoutes) and the access
92
+ * token has expired, the returned authenticator transparently exchanges the refresh
93
+ * token for a new access token and queues the replacement cookies on
94
+ * `req.pendingOAuthCookies`. The onSend hook installed by registerOAuthCookieRoutes
95
+ * flushes those cookies onto the response.
96
+ */
97
+ export declare function jwtAuthenticate(options?: JwtAuthenticateOptions): (req: FastifyRequest) => Promise<FastifyTxStateAuthInfo | undefined>;
98
+ export {};