@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 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
+ }
@@ -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
+ }
@@ -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
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ export const DataToken = z.object({
3
+ workspace: z.object({
4
+ id: z.string(),
5
+ title: z.string(),
6
+ }),
7
+ user: z.object({
8
+ id: z.string().nullable(),
9
+ name: z.string().nullable(),
10
+ }).optional(),
11
+ });
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
+ }