@meistrari/data-token 0.0.12 → 0.0.14
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/adapters/elysia.d.mts +45 -0
- package/dist/adapters/elysia.d.ts +7 -16
- package/dist/adapters/elysia.mjs +21 -0
- package/dist/adapters/hono.d.mts +80 -0
- package/dist/adapters/hono.d.ts +58 -4
- package/dist/adapters/hono.mjs +24 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.mjs +73 -0
- package/package.json +19 -8
- package/dist/adapters/elysia.js +0 -35
- package/dist/adapters/hono.js +0 -37
- package/dist/index.js +0 -14
- package/dist/retry.d.ts +0 -34
- package/dist/retry.js +0 -65
- package/dist/types.d.ts +0 -54
- package/dist/types.js +0 -13
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Elysia, { Context } from 'elysia';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Obtain an instance of the data token plugin for Elysia
|
|
5
|
+
* @param url - The URL of the JWKS endpoint
|
|
6
|
+
* @param skipOnError - Whether to skip the error and return null if the token is invalid
|
|
7
|
+
* @param getToken - A function to get the token from the context
|
|
8
|
+
* @returns An Elysia plugin
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createDataTokenPlugin } from 'elysia-jwt'
|
|
13
|
+
*
|
|
14
|
+
* const app = new Elysia()
|
|
15
|
+
* const dataToken = createDataTokenPlugin((c) => c.env.JWKS_URL)
|
|
16
|
+
*
|
|
17
|
+
* app.use(dataToken)
|
|
18
|
+
* .get('/', ({ authData }) => ({ authData }))
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function createDataTokenPlugin(url: string, skipOnError?: boolean, getToken?: (c: Context) => string): Elysia<"", false, {
|
|
22
|
+
decorator: {};
|
|
23
|
+
store: {};
|
|
24
|
+
derive: {
|
|
25
|
+
readonly authData?: any;
|
|
26
|
+
};
|
|
27
|
+
resolve: {};
|
|
28
|
+
}, {
|
|
29
|
+
type: {};
|
|
30
|
+
error: {};
|
|
31
|
+
}, {
|
|
32
|
+
schema: {};
|
|
33
|
+
macro: {};
|
|
34
|
+
macroFn: {};
|
|
35
|
+
}, {}, {
|
|
36
|
+
derive: {};
|
|
37
|
+
resolve: {};
|
|
38
|
+
schema: {};
|
|
39
|
+
}, {
|
|
40
|
+
derive: {};
|
|
41
|
+
resolve: {};
|
|
42
|
+
schema: {};
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
export { createDataTokenPlugin };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import Elysia, { Context } from 'elysia';
|
|
2
|
+
|
|
3
3
|
/**
|
|
4
4
|
* Obtain an instance of the data token plugin for Elysia
|
|
5
5
|
* @param url - The URL of the JWKS endpoint
|
|
@@ -12,28 +12,17 @@ import Elysia from 'elysia';
|
|
|
12
12
|
* import { createDataTokenPlugin } from 'elysia-jwt'
|
|
13
13
|
*
|
|
14
14
|
* const app = new Elysia()
|
|
15
|
-
* const dataToken = createDataTokenPlugin(
|
|
15
|
+
* const dataToken = createDataTokenPlugin((c) => c.env.JWKS_URL)
|
|
16
16
|
*
|
|
17
17
|
* app.use(dataToken)
|
|
18
18
|
* .get('/', ({ authData }) => ({ authData }))
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
declare function createDataTokenPlugin(url: string, skipOnError?: boolean, getToken?: (c: Context) => string): Elysia<"", false, {
|
|
22
22
|
decorator: {};
|
|
23
23
|
store: {};
|
|
24
24
|
derive: {
|
|
25
|
-
readonly authData
|
|
26
|
-
requestId: string;
|
|
27
|
-
workspace: {
|
|
28
|
-
id: string;
|
|
29
|
-
title: string;
|
|
30
|
-
};
|
|
31
|
-
user?: {
|
|
32
|
-
id: string;
|
|
33
|
-
email: string;
|
|
34
|
-
uuid: string | null;
|
|
35
|
-
} | undefined;
|
|
36
|
-
} | null;
|
|
25
|
+
readonly authData?: any;
|
|
37
26
|
};
|
|
38
27
|
resolve: {};
|
|
39
28
|
}, {
|
|
@@ -52,3 +41,5 @@ export declare function createDataTokenPlugin(url: string, skipOnError?: boolean
|
|
|
52
41
|
resolve: {};
|
|
53
42
|
schema: {};
|
|
54
43
|
}>;
|
|
44
|
+
|
|
45
|
+
export { createDataTokenPlugin };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Elysia from 'elysia';
|
|
2
|
+
import { getValidator } from '../index.mjs';
|
|
3
|
+
import 'jose';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
function createDataTokenPlugin(url, skipOnError = false, getToken = (c) => c.headers["x-data-token"] ?? "") {
|
|
7
|
+
const validator = getValidator(url);
|
|
8
|
+
const plugin = new Elysia({ name: "data-token" }).derive({ as: "global" }, async (c) => {
|
|
9
|
+
const token = getToken(c);
|
|
10
|
+
const data = await validator(token).catch(() => {
|
|
11
|
+
if (skipOnError) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
throw new Error("Invalid data token");
|
|
15
|
+
});
|
|
16
|
+
return { authData: data };
|
|
17
|
+
});
|
|
18
|
+
return plugin;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { createDataTokenPlugin };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as hono from 'hono';
|
|
2
|
+
import { Context } from 'hono';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
declare const DataToken: z.ZodObject<{
|
|
6
|
+
requestId: z.ZodString;
|
|
7
|
+
workspace: z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
title: z.ZodString;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
id?: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
}, {
|
|
14
|
+
id?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
}>;
|
|
17
|
+
user: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
id: z.ZodString;
|
|
19
|
+
email: z.ZodString;
|
|
20
|
+
uuid: z.ZodNullable<z.ZodString>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
id?: string;
|
|
23
|
+
email?: string;
|
|
24
|
+
uuid?: string;
|
|
25
|
+
}, {
|
|
26
|
+
id?: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
uuid?: string;
|
|
29
|
+
}>>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
requestId?: string;
|
|
32
|
+
workspace?: {
|
|
33
|
+
id?: string;
|
|
34
|
+
title?: string;
|
|
35
|
+
};
|
|
36
|
+
user?: {
|
|
37
|
+
id?: string;
|
|
38
|
+
email?: string;
|
|
39
|
+
uuid?: string;
|
|
40
|
+
};
|
|
41
|
+
}, {
|
|
42
|
+
requestId?: string;
|
|
43
|
+
workspace?: {
|
|
44
|
+
id?: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
};
|
|
47
|
+
user?: {
|
|
48
|
+
id?: string;
|
|
49
|
+
email?: string;
|
|
50
|
+
uuid?: string;
|
|
51
|
+
};
|
|
52
|
+
}>;
|
|
53
|
+
type DataToken = z.infer<typeof DataToken>;
|
|
54
|
+
|
|
55
|
+
type DataTokenContext = {
|
|
56
|
+
Variables: {
|
|
57
|
+
authData?: DataToken;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Create a data token middleware for Hono
|
|
62
|
+
* @param url - The URL of the JWKS endpoint
|
|
63
|
+
* @param skipOnError - Whether to skip the error and return null if the token is invalid
|
|
64
|
+
* @param getToken - A function to get the token from the context
|
|
65
|
+
* @returns A Hono middleware
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { createDataTokenMiddleware } from 'hono-jwt'
|
|
70
|
+
*
|
|
71
|
+
* const app = new Hono<DataTokenContext>()
|
|
72
|
+
* const dataToken = createDataTokenMiddleware('https://some.api.com/well-known/jwks.json')
|
|
73
|
+
*
|
|
74
|
+
* app.use(dataToken)
|
|
75
|
+
* app.get('/', c => c.json({ data: c.get('authData') }))
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function createDataTokenMiddleware<Env extends DataTokenContext>(url: string | ((c: Context<Env>) => string), skipOnError?: boolean, getToken?: (c: Context<Env>) => string): hono.MiddlewareHandler<Env, string, {}>;
|
|
79
|
+
|
|
80
|
+
export { type DataTokenContext, createDataTokenMiddleware };
|
package/dist/adapters/hono.d.ts
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import * as hono from 'hono';
|
|
2
|
+
import { Context } from 'hono';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
declare const DataToken: z.ZodObject<{
|
|
6
|
+
requestId: z.ZodString;
|
|
7
|
+
workspace: z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
title: z.ZodString;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
id?: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
}, {
|
|
14
|
+
id?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
}>;
|
|
17
|
+
user: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
id: z.ZodString;
|
|
19
|
+
email: z.ZodString;
|
|
20
|
+
uuid: z.ZodNullable<z.ZodString>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
id?: string;
|
|
23
|
+
email?: string;
|
|
24
|
+
uuid?: string;
|
|
25
|
+
}, {
|
|
26
|
+
id?: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
uuid?: string;
|
|
29
|
+
}>>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
requestId?: string;
|
|
32
|
+
workspace?: {
|
|
33
|
+
id?: string;
|
|
34
|
+
title?: string;
|
|
35
|
+
};
|
|
36
|
+
user?: {
|
|
37
|
+
id?: string;
|
|
38
|
+
email?: string;
|
|
39
|
+
uuid?: string;
|
|
40
|
+
};
|
|
41
|
+
}, {
|
|
42
|
+
requestId?: string;
|
|
43
|
+
workspace?: {
|
|
44
|
+
id?: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
};
|
|
47
|
+
user?: {
|
|
48
|
+
id?: string;
|
|
49
|
+
email?: string;
|
|
50
|
+
uuid?: string;
|
|
51
|
+
};
|
|
52
|
+
}>;
|
|
53
|
+
type DataToken = z.infer<typeof DataToken>;
|
|
54
|
+
|
|
55
|
+
type DataTokenContext = {
|
|
4
56
|
Variables: {
|
|
5
57
|
authData?: DataToken;
|
|
6
58
|
};
|
|
@@ -23,4 +75,6 @@ export type DataTokenContext = {
|
|
|
23
75
|
* app.get('/', c => c.json({ data: c.get('authData') }))
|
|
24
76
|
* ```
|
|
25
77
|
*/
|
|
26
|
-
|
|
78
|
+
declare function createDataTokenMiddleware<Env extends DataTokenContext>(url: string | ((c: Context<Env>) => string), skipOnError?: boolean, getToken?: (c: Context<Env>) => string): hono.MiddlewareHandler<Env, string, {}>;
|
|
79
|
+
|
|
80
|
+
export { type DataTokenContext, createDataTokenMiddleware };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createMiddleware } from 'hono/factory';
|
|
2
|
+
import { getValidator } from '../index.mjs';
|
|
3
|
+
import 'jose';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
function createDataTokenMiddleware(url, skipOnError = false, getToken = (c) => c.req.header("x-data-token") ?? "") {
|
|
7
|
+
return createMiddleware(async (c, next) => {
|
|
8
|
+
const jwksUrl = typeof url === "function" ? url(c) : url;
|
|
9
|
+
const validator = getValidator(jwksUrl);
|
|
10
|
+
const token = getToken(c);
|
|
11
|
+
try {
|
|
12
|
+
const data = await validator(token);
|
|
13
|
+
c.set("authData", data);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (skipOnError) {
|
|
16
|
+
return next();
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
await next();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { createDataTokenMiddleware };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get a validator function that can validate a data token.
|
|
3
|
+
*
|
|
4
|
+
* @param jwksUrl - The URL of the JWKS endpoint.
|
|
5
|
+
* @param options - The options for the validator.
|
|
6
|
+
* @param options.algorithms - The algorithms to use for validation.
|
|
7
|
+
* @returns A function that can validate a data token.
|
|
8
|
+
*/
|
|
9
|
+
declare function getValidator(jwksUrl: string, { algorithms }?: {
|
|
10
|
+
algorithms: string[];
|
|
11
|
+
}): (token: string) => Promise<{
|
|
12
|
+
requestId?: string;
|
|
13
|
+
workspace?: {
|
|
14
|
+
id?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
};
|
|
17
|
+
user?: {
|
|
18
|
+
id?: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
uuid?: string;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
export { getValidator };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Get a validator function that can validate a data token.
|
|
3
|
+
*
|
|
4
|
+
* @param jwksUrl - The URL of the JWKS endpoint.
|
|
5
|
+
* @param options - The options for the validator.
|
|
6
|
+
* @param options.algorithms - The algorithms to use for validation.
|
|
7
|
+
* @returns A function that can validate a data token.
|
|
8
|
+
*/
|
|
9
|
+
declare function getValidator(jwksUrl: string, { algorithms }?: {
|
|
2
10
|
algorithms: string[];
|
|
3
11
|
}): (token: string) => Promise<{
|
|
4
|
-
requestId
|
|
5
|
-
workspace
|
|
6
|
-
id
|
|
7
|
-
title
|
|
12
|
+
requestId?: string;
|
|
13
|
+
workspace?: {
|
|
14
|
+
id?: string;
|
|
15
|
+
title?: string;
|
|
8
16
|
};
|
|
9
17
|
user?: {
|
|
10
|
-
id
|
|
11
|
-
email
|
|
12
|
-
uuid
|
|
13
|
-
}
|
|
18
|
+
id?: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
uuid?: string;
|
|
21
|
+
};
|
|
14
22
|
}>;
|
|
23
|
+
|
|
24
|
+
export { getValidator };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createRemoteJWKSet, jwtVerify, errors } from 'jose';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
function calculateDelay(options, attempt) {
|
|
6
|
+
let delay = options.baseDelay * options.factor ** attempt;
|
|
7
|
+
delay = Math.min(delay, options.maxDelay);
|
|
8
|
+
if (options.jitter) {
|
|
9
|
+
const jitterMultiplier = 0.5 + Math.random();
|
|
10
|
+
delay *= jitterMultiplier;
|
|
11
|
+
}
|
|
12
|
+
return Math.round(delay);
|
|
13
|
+
}
|
|
14
|
+
function createExponentialBackoff(userOptions = {}) {
|
|
15
|
+
const options = {
|
|
16
|
+
maxRetries: 5,
|
|
17
|
+
baseDelay: 1e3,
|
|
18
|
+
factor: 2,
|
|
19
|
+
maxDelay: 3e4,
|
|
20
|
+
jitter: true,
|
|
21
|
+
...userOptions
|
|
22
|
+
};
|
|
23
|
+
return async function executeWithBackoff(fn, errorFilter) {
|
|
24
|
+
let lastError;
|
|
25
|
+
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
|
|
26
|
+
try {
|
|
27
|
+
return await fn();
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const shouldRetry = errorFilter ? errorFilter(error) : attempt < options.maxRetries;
|
|
30
|
+
if (!shouldRetry) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
lastError = error;
|
|
34
|
+
const delay = calculateDelay(options, attempt);
|
|
35
|
+
await sleep(delay);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw lastError;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DataToken = z.object({
|
|
43
|
+
requestId: z.string(),
|
|
44
|
+
workspace: z.object({
|
|
45
|
+
id: z.string(),
|
|
46
|
+
title: z.string()
|
|
47
|
+
}),
|
|
48
|
+
user: z.object({
|
|
49
|
+
id: z.string(),
|
|
50
|
+
email: z.string(),
|
|
51
|
+
uuid: z.string().nullable()
|
|
52
|
+
}).optional()
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const retry = createExponentialBackoff({
|
|
56
|
+
maxRetries: 3,
|
|
57
|
+
jitter: true
|
|
58
|
+
});
|
|
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
|
+
);
|
|
64
|
+
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);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { getValidator };
|
package/package.json
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meistrari/data-token",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.14",
|
|
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",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./hono": {
|
|
14
|
+
"types": "./dist/adapters/hono.d.ts",
|
|
15
|
+
"import": "./dist/adapters/hono.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./elysia": {
|
|
18
|
+
"types": "./dist/adapters/elysia.d.ts",
|
|
19
|
+
"import": "./dist/adapters/elysia.mjs"
|
|
20
|
+
}
|
|
13
21
|
},
|
|
14
|
-
"main": "dist/index.
|
|
22
|
+
"main": "dist/index.mjs",
|
|
23
|
+
"types": "dist/index.d.ts",
|
|
15
24
|
"files": [
|
|
16
25
|
"dist"
|
|
17
26
|
],
|
|
@@ -20,7 +29,7 @@
|
|
|
20
29
|
},
|
|
21
30
|
"scripts": {
|
|
22
31
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
23
|
-
"build": "
|
|
32
|
+
"build": "unbuild",
|
|
24
33
|
"prepare": "husky",
|
|
25
34
|
"lint": "eslint src",
|
|
26
35
|
"lint:fix": "eslint src --fix",
|
|
@@ -34,12 +43,14 @@
|
|
|
34
43
|
},
|
|
35
44
|
"devDependencies": {
|
|
36
45
|
"@antfu/eslint-config": "^3.0.0",
|
|
46
|
+
"bun-types": "1.1.38",
|
|
37
47
|
"elysia": "^1.1.9",
|
|
38
48
|
"eslint": "^9.9.1",
|
|
39
49
|
"hono": "^4.5.9",
|
|
40
50
|
"husky": "^9.1.5",
|
|
41
51
|
"lint-staged": "^15.2.9",
|
|
42
|
-
"typescript": "^5.5.4"
|
|
52
|
+
"typescript": "^5.5.4",
|
|
53
|
+
"unbuild": "2.0.0"
|
|
43
54
|
},
|
|
44
55
|
"lint-staged": {
|
|
45
56
|
"*.{js,ts}": [
|
package/dist/adapters/elysia.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import Elysia from 'elysia';
|
|
2
|
-
import { getValidator } from '..';
|
|
3
|
-
/**
|
|
4
|
-
* Obtain an instance of the data token plugin for Elysia
|
|
5
|
-
* @param url - The URL of the JWKS endpoint
|
|
6
|
-
* @param skipOnError - Whether to skip the error and return null if the token is invalid
|
|
7
|
-
* @param getToken - A function to get the token from the context
|
|
8
|
-
* @returns An Elysia plugin
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { createDataTokenPlugin } from 'elysia-jwt'
|
|
13
|
-
*
|
|
14
|
-
* const app = new Elysia()
|
|
15
|
-
* const dataToken = createDataTokenPlugin('https://some.api.com/well-known/jwks.json')
|
|
16
|
-
*
|
|
17
|
-
* app.use(dataToken)
|
|
18
|
-
* .get('/', ({ authData }) => ({ authData }))
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export function createDataTokenPlugin(url, skipOnError = false, getToken = c => c.headers['x-data-token'] ?? '') {
|
|
22
|
-
const validator = getValidator(url);
|
|
23
|
-
const plugin = new Elysia({ name: 'data-token' })
|
|
24
|
-
.derive({ as: 'global' }, async (c) => {
|
|
25
|
-
const token = getToken(c);
|
|
26
|
-
const data = await validator(token).catch(() => {
|
|
27
|
-
if (skipOnError) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
throw new Error('Invalid data token');
|
|
31
|
-
});
|
|
32
|
-
return { authData: data };
|
|
33
|
-
});
|
|
34
|
-
return plugin;
|
|
35
|
-
}
|
package/dist/adapters/hono.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { createMiddleware } from 'hono/factory';
|
|
2
|
-
import { getValidator } from '..';
|
|
3
|
-
/**
|
|
4
|
-
* Create a data token middleware for Hono
|
|
5
|
-
* @param url - The URL of the JWKS endpoint
|
|
6
|
-
* @param skipOnError - Whether to skip the error and return null if the token is invalid
|
|
7
|
-
* @param getToken - A function to get the token from the context
|
|
8
|
-
* @returns A Hono middleware
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { createDataTokenMiddleware } from 'hono-jwt'
|
|
13
|
-
*
|
|
14
|
-
* const app = new Hono<DataTokenContext>()
|
|
15
|
-
* const dataToken = createDataTokenMiddleware('https://some.api.com/well-known/jwks.json')
|
|
16
|
-
*
|
|
17
|
-
* app.use(dataToken)
|
|
18
|
-
* app.get('/', c => c.json({ data: c.get('authData') }))
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export function createDataTokenMiddleware(url, skipOnError = false, getToken = c => c.req.header('x-data-token') ?? '') {
|
|
22
|
-
return createMiddleware(async (c, next) => {
|
|
23
|
-
const jwksUrl = typeof url === 'function' ? url(c) : url;
|
|
24
|
-
const validator = getValidator(jwksUrl);
|
|
25
|
-
const token = getToken(c);
|
|
26
|
-
const data = await validator(token).catch(() => {
|
|
27
|
-
if (skipOnError) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
throw new Error('Invalid or missing data token');
|
|
31
|
-
});
|
|
32
|
-
if (data) {
|
|
33
|
-
c.set('authData', data);
|
|
34
|
-
}
|
|
35
|
-
await next();
|
|
36
|
-
});
|
|
37
|
-
}
|
package/dist/index.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { createRemoteJWKSet, errors, jwtVerify } from 'jose';
|
|
2
|
-
import { createExponentialBackoff } from './retry';
|
|
3
|
-
import { DataToken } from './types';
|
|
4
|
-
const retry = createExponentialBackoff({
|
|
5
|
-
maxRetries: 3,
|
|
6
|
-
jitter: true,
|
|
7
|
-
});
|
|
8
|
-
export function getValidator(jwksUrl, { algorithms } = { algorithms: ['RS256'] }) {
|
|
9
|
-
const JWKS = createRemoteJWKSet(new URL(jwksUrl), { cacheMaxAge: 0, headers: { 'cache-control': 'no-cache', 'pragma': 'no-cache' } });
|
|
10
|
-
return async (token) => {
|
|
11
|
-
const { payload } = await retry(async () => jwtVerify(token, JWKS, { algorithms }), err => err instanceof errors.JWKSTimeout || (err instanceof Error && err.message.includes('ECONNRESET')));
|
|
12
|
-
return DataToken.parse(payload);
|
|
13
|
-
};
|
|
14
|
-
}
|
package/dist/retry.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
type BackoffOptions = {
|
|
2
|
-
/**
|
|
3
|
-
* Maximum number of retry attempts
|
|
4
|
-
* @default 5
|
|
5
|
-
*/
|
|
6
|
-
maxRetries?: number;
|
|
7
|
-
/**
|
|
8
|
-
* Initial delay in milliseconds
|
|
9
|
-
* @default 1000 (1 second)
|
|
10
|
-
*/
|
|
11
|
-
baseDelay?: number;
|
|
12
|
-
/**
|
|
13
|
-
* Multiplier for exponential backoff
|
|
14
|
-
* @default 2
|
|
15
|
-
*/
|
|
16
|
-
factor?: number;
|
|
17
|
-
/**
|
|
18
|
-
* Maximum delay cap in milliseconds
|
|
19
|
-
* @default 30000 (30 seconds)
|
|
20
|
-
*/
|
|
21
|
-
maxDelay?: number;
|
|
22
|
-
/**
|
|
23
|
-
* Jitter to prevent synchronized retries in distributed systems
|
|
24
|
-
* @default true
|
|
25
|
-
*/
|
|
26
|
-
jitter?: boolean;
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Create an exponential backoff retry function
|
|
30
|
-
* @param userOptions Configuration options for backoff strategy
|
|
31
|
-
* @returns Retry function with exponential backoff
|
|
32
|
-
*/
|
|
33
|
-
declare function createExponentialBackoff(userOptions?: BackoffOptions): <T>(fn: () => Promise<T>, errorFilter?: (error: unknown) => boolean) => Promise<T>;
|
|
34
|
-
export { createExponentialBackoff };
|
package/dist/retry.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const sleep = async (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
2
|
-
/**
|
|
3
|
-
* Calculate delay for a specific retry attempt
|
|
4
|
-
* @param options Backoff configuration options
|
|
5
|
-
* @param attempt Current retry attempt (0-indexed)
|
|
6
|
-
* @returns Calculated delay in milliseconds
|
|
7
|
-
*/
|
|
8
|
-
function calculateDelay(options, attempt) {
|
|
9
|
-
// Basic exponential backoff calculation
|
|
10
|
-
let delay = options.baseDelay * options.factor ** attempt;
|
|
11
|
-
// Cap the delay at maxDelay
|
|
12
|
-
delay = Math.min(delay, options.maxDelay);
|
|
13
|
-
// Apply jitter if enabled
|
|
14
|
-
if (options.jitter) {
|
|
15
|
-
// Add random variation between 0.5x and 1.5x of the calculated delay
|
|
16
|
-
const jitterMultiplier = 0.5 + Math.random();
|
|
17
|
-
delay *= jitterMultiplier;
|
|
18
|
-
}
|
|
19
|
-
return Math.round(delay);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Create an exponential backoff retry function
|
|
23
|
-
* @param userOptions Configuration options for backoff strategy
|
|
24
|
-
* @returns Retry function with exponential backoff
|
|
25
|
-
*/
|
|
26
|
-
function createExponentialBackoff(userOptions = {}) {
|
|
27
|
-
// Merge default options with user-provided options
|
|
28
|
-
const options = {
|
|
29
|
-
maxRetries: 5,
|
|
30
|
-
baseDelay: 1000,
|
|
31
|
-
factor: 2,
|
|
32
|
-
maxDelay: 30000,
|
|
33
|
-
jitter: true,
|
|
34
|
-
...userOptions,
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* Execute a function with exponential backoff retry strategy
|
|
38
|
-
* @param fn Function to execute
|
|
39
|
-
* @param errorFilter Optional function to determine if retry is needed
|
|
40
|
-
* @returns Promise resolving to the result of the function
|
|
41
|
-
*/
|
|
42
|
-
return async function executeWithBackoff(fn, errorFilter) {
|
|
43
|
-
let lastError;
|
|
44
|
-
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
|
|
45
|
-
try {
|
|
46
|
-
return await fn();
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
// If no error filter is provided, or the error passes the filter
|
|
50
|
-
const shouldRetry = errorFilter
|
|
51
|
-
? errorFilter(error)
|
|
52
|
-
: attempt < options.maxRetries;
|
|
53
|
-
if (!shouldRetry) {
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
lastError = error;
|
|
57
|
-
// Calculate and apply delay
|
|
58
|
-
const delay = calculateDelay(options, attempt);
|
|
59
|
-
await sleep(delay);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
throw lastError;
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
export { createExponentialBackoff };
|
package/dist/types.d.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare const DataToken: z.ZodObject<{
|
|
3
|
-
requestId: z.ZodString;
|
|
4
|
-
workspace: z.ZodObject<{
|
|
5
|
-
id: z.ZodString;
|
|
6
|
-
title: z.ZodString;
|
|
7
|
-
}, "strip", z.ZodTypeAny, {
|
|
8
|
-
id: string;
|
|
9
|
-
title: string;
|
|
10
|
-
}, {
|
|
11
|
-
id: string;
|
|
12
|
-
title: string;
|
|
13
|
-
}>;
|
|
14
|
-
user: z.ZodOptional<z.ZodObject<{
|
|
15
|
-
id: z.ZodString;
|
|
16
|
-
email: z.ZodString;
|
|
17
|
-
uuid: z.ZodNullable<z.ZodString>;
|
|
18
|
-
}, "strip", z.ZodTypeAny, {
|
|
19
|
-
id: string;
|
|
20
|
-
email: string;
|
|
21
|
-
uuid: string | null;
|
|
22
|
-
}, {
|
|
23
|
-
id: string;
|
|
24
|
-
email: string;
|
|
25
|
-
uuid: string | null;
|
|
26
|
-
}>>;
|
|
27
|
-
}, "strip", z.ZodTypeAny, {
|
|
28
|
-
requestId: string;
|
|
29
|
-
workspace: {
|
|
30
|
-
id: string;
|
|
31
|
-
title: string;
|
|
32
|
-
};
|
|
33
|
-
user?: {
|
|
34
|
-
id: string;
|
|
35
|
-
email: string;
|
|
36
|
-
uuid: string | null;
|
|
37
|
-
} | undefined;
|
|
38
|
-
}, {
|
|
39
|
-
requestId: string;
|
|
40
|
-
workspace: {
|
|
41
|
-
id: string;
|
|
42
|
-
title: string;
|
|
43
|
-
};
|
|
44
|
-
user?: {
|
|
45
|
-
id: string;
|
|
46
|
-
email: string;
|
|
47
|
-
uuid: string | null;
|
|
48
|
-
} | undefined;
|
|
49
|
-
}>;
|
|
50
|
-
export type DataToken = z.infer<typeof DataToken>;
|
|
51
|
-
export type ValidatorConfig = {
|
|
52
|
-
jwksUrl: string;
|
|
53
|
-
algorithms?: string[];
|
|
54
|
-
};
|
package/dist/types.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export const DataToken = z.object({
|
|
3
|
-
requestId: z.string(),
|
|
4
|
-
workspace: z.object({
|
|
5
|
-
id: z.string(),
|
|
6
|
-
title: z.string(),
|
|
7
|
-
}),
|
|
8
|
-
user: z.object({
|
|
9
|
-
id: z.string(),
|
|
10
|
-
email: z.string(),
|
|
11
|
-
uuid: z.string().nullable(),
|
|
12
|
-
}).optional(),
|
|
13
|
-
});
|