@meistrari/data-token 0.0.19 → 1.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.
@@ -1,6 +1,8 @@
1
1
  import Elysia from 'elysia';
2
2
  import { getValidator } from '../index.mjs';
3
+ import 'node:buffer';
3
4
  import 'jose';
5
+ import 'jose/dist/types/util/errors';
4
6
  import 'zod';
5
7
 
6
8
  function createDataTokenPlugin(url, skipOnError = false, getToken = (c) => c.headers["x-data-token"] ?? "") {
@@ -1,6 +1,8 @@
1
1
  import { createMiddleware } from 'hono/factory';
2
2
  import { getValidator } from '../index.mjs';
3
+ import 'node:buffer';
3
4
  import 'jose';
5
+ import 'jose/dist/types/util/errors';
4
6
  import 'zod';
5
7
 
6
8
  function createDataTokenMiddleware(url, skipOnError = false, getToken = (c) => c.req.header("x-data-token") ?? "") {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as jose from 'jose';
2
- import { errors } from 'jose';
2
+ import { RemoteJWKSetOptions, errors } from 'jose';
3
3
  import * as zod from 'zod';
4
4
  export { D as DataToken, V as ValidatorConfig } from './shared/data-token.1d70cb5e.mjs';
5
5
 
@@ -9,10 +9,14 @@ export { D as DataToken, V as ValidatorConfig } from './shared/data-token.1d70cb
9
9
  * @param jwksUrl - The URL of the JWKS endpoint.
10
10
  * @param options - The options for the validator.
11
11
  * @param options.algorithms - The algorithms to use for validation.
12
+ * @param options.remoteJwksOptions - The options to pass to `createRemoteJWKSet`.
13
+ * @param options.logger - The logger to use for logging errors.
12
14
  * @returns A function that can validate a data token.
13
15
  */
14
- declare function getValidator(jwksUrl: string, { algorithms }?: {
15
- algorithms: string[];
16
+ declare function getValidator(jwksUrl: string, { algorithms, remoteJwksOptions, logger, }?: {
17
+ algorithms?: string[] | undefined;
18
+ remoteJwksOptions?: RemoteJWKSetOptions | undefined;
19
+ logger?: any;
16
20
  }): (token: string) => Promise<{
17
21
  requestId: string;
18
22
  workspace: {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as jose from 'jose';
2
- import { errors } from 'jose';
2
+ import { RemoteJWKSetOptions, errors } from 'jose';
3
3
  import * as zod from 'zod';
4
4
  export { D as DataToken, V as ValidatorConfig } from './shared/data-token.1d70cb5e.js';
5
5
 
@@ -9,10 +9,14 @@ export { D as DataToken, V as ValidatorConfig } from './shared/data-token.1d70cb
9
9
  * @param jwksUrl - The URL of the JWKS endpoint.
10
10
  * @param options - The options for the validator.
11
11
  * @param options.algorithms - The algorithms to use for validation.
12
+ * @param options.remoteJwksOptions - The options to pass to `createRemoteJWKSet`.
13
+ * @param options.logger - The logger to use for logging errors.
12
14
  * @returns A function that can validate a data token.
13
15
  */
14
- declare function getValidator(jwksUrl: string, { algorithms }?: {
15
- algorithms: string[];
16
+ declare function getValidator(jwksUrl: string, { algorithms, remoteJwksOptions, logger, }?: {
17
+ algorithms?: string[] | undefined;
18
+ remoteJwksOptions?: RemoteJWKSetOptions | undefined;
19
+ logger?: any;
16
20
  }): (token: string) => Promise<{
17
21
  requestId: string;
18
22
  workspace: {
package/dist/index.mjs CHANGED
@@ -1,4 +1,6 @@
1
+ import { Buffer } from 'node:buffer';
1
2
  import { createRemoteJWKSet, jwtVerify, errors, decodeJwt } from 'jose';
3
+ import { JWKSNoMatchingKey } from 'jose/dist/types/util/errors';
2
4
  import { z } from 'zod';
3
5
 
4
6
  const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -56,17 +58,45 @@ const retry = createExponentialBackoff({
56
58
  maxRetries: 3,
57
59
  jitter: true
58
60
  });
59
- function getValidator(jwksUrl, { algorithms } = { algorithms: ["RS256"] }) {
60
- const JWKS = createRemoteJWKSet(
61
- new URL(jwksUrl),
62
- { cacheMaxAge: 0, headers: { "cache-control": "no-cache", "pragma": "no-cache" } }
63
- );
61
+ function getValidator(jwksUrl, {
62
+ algorithms = ["RS256"],
63
+ remoteJwksOptions = {},
64
+ logger = console
65
+ } = {}) {
66
+ let JWKS;
64
67
  return async (token) => {
65
- const { payload } = await retry(
66
- async () => jwtVerify(token, JWKS, { algorithms }),
67
- (err) => err instanceof errors.JWKSTimeout || err instanceof Error && err.message.includes("ECONNRESET")
68
- );
69
- return DataToken.parse(payload);
68
+ JWKS || (JWKS = createRemoteJWKSet(
69
+ new URL(jwksUrl),
70
+ {
71
+ // Cache control is done by the remote JWKS instance, so we need to set it to no-cache to avoid
72
+ // interference from any CDN that might be caching the JWKS.
73
+ headers: { "cache-control": "no-cache", "pragma": "no-cache" },
74
+ ...remoteJwksOptions
75
+ }
76
+ ));
77
+ try {
78
+ const { payload } = await retry(
79
+ async () => jwtVerify(token, JWKS, { algorithms }),
80
+ (err) => err instanceof errors.JWKSTimeout || err instanceof Error && err.message.includes("ECONNRESET")
81
+ );
82
+ return DataToken.parse(payload);
83
+ } catch (error) {
84
+ if (error instanceof JWKSNoMatchingKey) {
85
+ const [header] = token.split(".");
86
+ const tokenHeader = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
87
+ const jwksResponse = await fetch(jwksUrl);
88
+ const jwksText = await jwksResponse.json();
89
+ logger.error({
90
+ level: "error",
91
+ message: "No matching key found in JWKS for token",
92
+ jwksUrl,
93
+ algorithms,
94
+ jwks: jwksText,
95
+ tokenHeader
96
+ });
97
+ }
98
+ throw error;
99
+ }
70
100
  };
71
101
  }
72
102
  function isAuthenticationError(err) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/data-token",
3
3
  "type": "module",
4
- "version": "0.0.19",
4
+ "version": "1.0.1",
5
5
  "description": "Internal library to verify and decode the API Gateway data token",
6
6
  "author": "Roz <roz@meistrari.com>",
7
7
  "license": "UNLICENSED",