@meistrari/data-token 0.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/README.md +77 -0
- package/dist/adapters/elysia.d.ts +52 -0
- package/dist/adapters/elysia.js +35 -0
- package/dist/adapters/hono.d.ts +26 -0
- package/dist/adapters/hono.js +36 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.js +11 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @meistrari/data-token
|
|
2
|
+
|
|
3
|
+
Internal library to verify and decode the API Gateway data token.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```shell
|
|
7
|
+
pnpm install @meistrari/data-token
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
### Basic Usage (No Adapter)
|
|
13
|
+
```ts
|
|
14
|
+
import { getValidator } from '@meistrari/data-token'
|
|
15
|
+
|
|
16
|
+
const validator = getValidator('https://some.api.com/well-known/jwks.json')
|
|
17
|
+
|
|
18
|
+
async function validateToken(token: string): Promise<DataToken> {
|
|
19
|
+
return await validator(token)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Usage with Hono
|
|
24
|
+
```ts
|
|
25
|
+
import { Hono } from 'hono'
|
|
26
|
+
import { DataTokenContext, createDataTokenMiddleware } from '@meistrari/data-token/hono'
|
|
27
|
+
|
|
28
|
+
// Combine DataTokenContext with your own context type
|
|
29
|
+
interface MyContext {
|
|
30
|
+
customProp: string
|
|
31
|
+
}
|
|
32
|
+
type AppContext = MyContext & DataTokenContext
|
|
33
|
+
|
|
34
|
+
const app = new Hono<AppContext>()
|
|
35
|
+
const dataToken = createDataTokenMiddleware('https://some.api.com/well-known/jwks.json')
|
|
36
|
+
|
|
37
|
+
app.use(dataToken)
|
|
38
|
+
|
|
39
|
+
app.get('/', (c) => {
|
|
40
|
+
const authData = c.get('authData')
|
|
41
|
+
return c.json({ data: authData })
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Usage with Elysia
|
|
46
|
+
```ts
|
|
47
|
+
import { Elysia } from 'elysia'
|
|
48
|
+
import { createDataTokenPlugin } from '@meistrari/data-token/elysia'
|
|
49
|
+
|
|
50
|
+
const app = new Elysia()
|
|
51
|
+
|
|
52
|
+
const dataToken = createDataTokenPlugin('https://some.api.com/well-known/jwks.json')
|
|
53
|
+
|
|
54
|
+
app
|
|
55
|
+
.use(dataToken)
|
|
56
|
+
.get('/', ({ authData }) => ({ authData }))
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API Reference
|
|
60
|
+
|
|
61
|
+
### getValidator(jwksUrl: string, options?: { algorithms?: string[] })
|
|
62
|
+
Creates a validator function that can be used to validate and decode tokens.
|
|
63
|
+
|
|
64
|
+
### createDataTokenMiddleware(url: string, getToken?: (c: Context) => string)
|
|
65
|
+
Creates a Hono middleware for token validation.
|
|
66
|
+
|
|
67
|
+
### createDataTokenPlugin(url: string, getToken?: (c: Context) => string)
|
|
68
|
+
Creates an Elysia plugin for token validation.
|
|
69
|
+
|
|
70
|
+
### Types
|
|
71
|
+
The DataToken type, as well as a Zod schema for the token payload is available from the /types entrypoint, both under the name of `DataToken`:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { DataToken } from '@meistrari/data-token/types'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This type represents the structure of the decoded token data.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Context } from 'elysia';
|
|
2
|
+
import Elysia from 'elysia';
|
|
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 declare function createDataTokenPlugin(url: string, skipOnError?: boolean, getToken?: (c: Context) => string): Elysia<"", false, {
|
|
22
|
+
decorator: {};
|
|
23
|
+
store: {};
|
|
24
|
+
derive: {
|
|
25
|
+
readonly authData: {
|
|
26
|
+
workspace: {
|
|
27
|
+
id: string;
|
|
28
|
+
title: string;
|
|
29
|
+
};
|
|
30
|
+
user?: {
|
|
31
|
+
id: string | null;
|
|
32
|
+
name: string | null;
|
|
33
|
+
} | undefined;
|
|
34
|
+
} | null;
|
|
35
|
+
};
|
|
36
|
+
resolve: {};
|
|
37
|
+
}, {
|
|
38
|
+
type: {};
|
|
39
|
+
error: {};
|
|
40
|
+
}, {
|
|
41
|
+
schema: {};
|
|
42
|
+
macro: {};
|
|
43
|
+
macroFn: {};
|
|
44
|
+
}, {}, {
|
|
45
|
+
derive: {};
|
|
46
|
+
resolve: {};
|
|
47
|
+
schema: {};
|
|
48
|
+
}, {
|
|
49
|
+
derive: {};
|
|
50
|
+
resolve: {};
|
|
51
|
+
schema: {};
|
|
52
|
+
}>;
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import type { DataToken } from '../types';
|
|
3
|
+
export interface DataTokenContext {
|
|
4
|
+
Variables: {
|
|
5
|
+
authData?: DataToken;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a data token middleware for Hono
|
|
10
|
+
* @param url - The URL of the JWKS endpoint
|
|
11
|
+
* @param skipOnError - Whether to skip the error and return null if the token is invalid
|
|
12
|
+
* @param getToken - A function to get the token from the context
|
|
13
|
+
* @returns A Hono middleware
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createDataTokenMiddleware } from 'hono-jwt'
|
|
18
|
+
*
|
|
19
|
+
* const app = new Hono<DataTokenContext>()
|
|
20
|
+
* const dataToken = createDataTokenMiddleware('https://some.api.com/well-known/jwks.json')
|
|
21
|
+
*
|
|
22
|
+
* app.use(dataToken)
|
|
23
|
+
* app.get('/', c => c.json({ data: c.get('authData') }))
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function createDataTokenMiddleware(url: string, skipOnError?: boolean, getToken?: (c: Context) => string): import("hono").MiddlewareHandler<DataTokenContext, string, {}>;
|
|
@@ -0,0 +1,36 @@
|
|
|
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 validator = getValidator(url);
|
|
24
|
+
const token = getToken(c);
|
|
25
|
+
const data = await validator(token).catch(() => {
|
|
26
|
+
if (skipOnError) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
throw new Error('Invalid or missing data token');
|
|
30
|
+
});
|
|
31
|
+
if (data) {
|
|
32
|
+
c.set('authData', data);
|
|
33
|
+
}
|
|
34
|
+
await next();
|
|
35
|
+
});
|
|
36
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function getValidator(jwksUrl: string, { algorithms }?: {
|
|
2
|
+
algorithms: string[];
|
|
3
|
+
}): (token: string) => Promise<{
|
|
4
|
+
workspace: {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
};
|
|
8
|
+
user?: {
|
|
9
|
+
id: string | null;
|
|
10
|
+
name: string | null;
|
|
11
|
+
} | undefined;
|
|
12
|
+
}>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
2
|
+
import { DataToken } from './types';
|
|
3
|
+
export function getValidator(jwksUrl, { algorithms } = { algorithms: ['RS256'] }) {
|
|
4
|
+
const JWKS = createRemoteJWKSet(new URL(jwksUrl));
|
|
5
|
+
return async (token) => {
|
|
6
|
+
const { payload } = await jwtVerify(token, JWKS, { algorithms });
|
|
7
|
+
return DataToken.parse(payload);
|
|
8
|
+
};
|
|
9
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const DataToken: z.ZodObject<{
|
|
3
|
+
workspace: z.ZodObject<{
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
title: z.ZodString;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
}, {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
}>;
|
|
13
|
+
user: z.ZodOptional<z.ZodObject<{
|
|
14
|
+
id: z.ZodNullable<z.ZodString>;
|
|
15
|
+
name: z.ZodNullable<z.ZodString>;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
id: string | null;
|
|
18
|
+
name: string | null;
|
|
19
|
+
}, {
|
|
20
|
+
id: string | null;
|
|
21
|
+
name: string | null;
|
|
22
|
+
}>>;
|
|
23
|
+
}, "strip", z.ZodTypeAny, {
|
|
24
|
+
workspace: {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
};
|
|
28
|
+
user?: {
|
|
29
|
+
id: string | null;
|
|
30
|
+
name: string | null;
|
|
31
|
+
} | undefined;
|
|
32
|
+
}, {
|
|
33
|
+
workspace: {
|
|
34
|
+
id: string;
|
|
35
|
+
title: string;
|
|
36
|
+
};
|
|
37
|
+
user?: {
|
|
38
|
+
id: string | null;
|
|
39
|
+
name: string | null;
|
|
40
|
+
} | undefined;
|
|
41
|
+
}>;
|
|
42
|
+
export type DataToken = z.infer<typeof DataToken>;
|
|
43
|
+
export interface ValidatorConfig {
|
|
44
|
+
jwksUrl: string;
|
|
45
|
+
algorithms?: string[];
|
|
46
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meistrari/data-token",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"packageManager": "pnpm@9.0.1",
|
|
6
|
+
"description": "Internal library to verify and decode the API Gateway data token",
|
|
7
|
+
"author": "Roz <roz@meistrari.com>",
|
|
8
|
+
"license": "UNLICENSED",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/index.js",
|
|
11
|
+
"./types": "./dist/types.js",
|
|
12
|
+
"./hono": "./dist/adapters/hono.js"
|
|
13
|
+
},
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": "^20.15.1"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"prepare": "husky",
|
|
25
|
+
"lint": "eslint src",
|
|
26
|
+
"lint:fix": "eslint src --fix",
|
|
27
|
+
"check": "pnpm run lint && pnpm tsc --noEmit",
|
|
28
|
+
"prepack": "pnpm run build"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"elysia": "^1.1.9",
|
|
32
|
+
"hono": "^4.5.9"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"jose": "^5.8.0",
|
|
36
|
+
"zod": "^3.23.8"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@antfu/eslint-config": "^3.0.0",
|
|
40
|
+
"elysia": "^1.1.9",
|
|
41
|
+
"eslint": "^9.9.1",
|
|
42
|
+
"hono": "^4.5.9",
|
|
43
|
+
"husky": "^9.1.5",
|
|
44
|
+
"lint-staged": "^15.2.9",
|
|
45
|
+
"pnpm": "^9.9.0",
|
|
46
|
+
"typescript": "^5.5.4"
|
|
47
|
+
},
|
|
48
|
+
"lint-staged": {
|
|
49
|
+
"*.{js,ts}": [
|
|
50
|
+
"eslint --fix"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|