@nitrotool/jwt 0.0.6 → 0.0.8

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,182 @@
1
+ # @nitrotool/jwt
2
+
3
+ Lightweight JWT utilities for Nitro/UnJS environments with optional h3 helpers.
4
+
5
+ - Built on `@tsndr/cloudflare-worker-jwt`
6
+ - Helpers that read `jwtSecret` from your Nitro runtime config
7
+ - h3 utilities to extract tokens from requests and enforce authentication
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @nitrotool/jwt
13
+ ```
14
+
15
+ Peer dependency:
16
+ - `h3` is required only when using the h3 helpers.
17
+
18
+ ## Importing
19
+
20
+ You can import from the main entry or subpath exports:
21
+
22
+ ```ts
23
+ // Main (all helpers)
24
+ import { encodeJwt, verifyJwt, decodeJwt } from '@nitrotool/jwt';
25
+
26
+ // Subpath (JWT-only)
27
+ import { encodeJwtRaw, verifyJwtRaw, decodeJwtRaw } from '@nitrotool/jwt/jwt';
28
+
29
+ // Subpath (h3 helpers)
30
+ import { extractApiToken, requireApiToken } from '@nitrotool/jwt/h3';
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```ts
36
+ import { encodeJwt, verifyJwt, decodeJwt } from '@nitrotool/jwt';
37
+
38
+ type MyClaims = { userId: string };
39
+
40
+ const token = await encodeJwt<MyClaims>({ userId: '123' });
41
+ // Later…
42
+ const isValid = await verifyJwt(token);
43
+ const payload = await decodeJwt<MyClaims>(token); // { userId: '123', exp: ... }
44
+ ```
45
+
46
+ By default, non-`Raw` helpers read the secret from your runtime config:
47
+ - `useRuntimeConfig().jwtSecret`
48
+
49
+ If you need to pass a secret explicitly, use the `*Raw` variants.
50
+
51
+ ## Usage with h3
52
+
53
+ Extract API tokens and enforce authentication in request handlers:
54
+
55
+ ```ts
56
+ import { defineEventHandler } from 'h3';
57
+ import { extractApiToken, requireApiToken } from '@nitrotool/jwt/h3';
58
+ import { decodeJwt } from '@nitrotool/jwt';
59
+
60
+ export default defineEventHandler(async (event) => {
61
+ // Try to read token (Authorization: Bearer <token> or ?token=<token>)
62
+ const token = extractApiToken(event);
63
+
64
+ // Or strictly require it (throws UnauthenticatedError if missing)
65
+ const requiredToken = requireApiToken(event);
66
+
67
+ // Optionally decode/verify
68
+ const claims = await decodeJwt<{ userId: string }>(requiredToken);
69
+
70
+ return { ok: true, userId: claims.userId };
71
+ });
72
+ ```
73
+
74
+ Supported token locations:
75
+ - Authorization header: `Authorization: Bearer <token>`
76
+ - Query string: `?token=<token>`
77
+
78
+ ## Configuration
79
+
80
+ When using non-`Raw` helpers, ensure a secret is available at runtime:
81
+
82
+ ```ts
83
+ // Example: Nuxt/Nitro runtime config
84
+ export default defineNuxtConfig({
85
+ runtimeConfig: {
86
+ jwtSecret: process.env.JWT_SECRET || 'super-secret',
87
+ },
88
+ });
89
+ ```
90
+
91
+ ## Examples
92
+
93
+ Sign with custom TTL:
94
+
95
+ ```ts
96
+ import { encodeJwtRaw } from '@nitrotool/jwt';
97
+
98
+ const token = await encodeJwtRaw(
99
+ { userId: '123', role: 'admin' },
100
+ process.env.JWT_SECRET!,
101
+ 60 * 10 // 10 minutes
102
+ );
103
+ ```
104
+
105
+ Decode without verifying signature (use only for non-sensitive scenarios):
106
+
107
+ ```ts
108
+ import { decodeJwt } from '@nitrotool/jwt';
109
+
110
+ const payload = await decodeJwt<{ userId: string }>(token, { verify: false });
111
+ ```
112
+
113
+ Verify with an explicit secret:
114
+
115
+ ```ts
116
+ import { verifyJwtRaw } from '@nitrotool/jwt';
117
+
118
+ const ok = await verifyJwtRaw(token, process.env.JWT_SECRET!);
119
+ ```
120
+
121
+ ## Errors
122
+
123
+ - `UnauthorizedError('Invalid JWT token.')` is thrown when verification fails in `decodeJwt` / `decodeJwtRaw`.
124
+ - `UnauthenticatedError()` is thrown by `requireApiToken` when no token is present.
125
+
126
+ ## Security Notes
127
+
128
+ - Keep `jwtSecret` strong and private.
129
+ - Prefer short TTLs and refresh flows.
130
+ - Only set `verify: false` for non-sensitive, debug-like operations.
131
+ - Rotate secrets periodically and invalidate old tokens if needed.
132
+
133
+ ## API Reference
134
+
135
+ All helpers are asynchronous.
136
+
137
+ ### JWT helpers
138
+
139
+ - `encodeJwtRaw<T>(payload, secret, ttl = 60): Promise<string>`
140
+ - Signs a token with the provided `secret`.
141
+ - `ttl` is in seconds. Default: `60`.
142
+ - `exp` is set automatically from `ttl`.
143
+
144
+ - `encodeJwt<T>(payload): Promise<string>`
145
+ - Same as `encodeJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.
146
+
147
+ - `verifyJwtRaw(token, secret): Promise<boolean>`
148
+ - Verifies signature and expiry using the provided `secret`.
149
+
150
+ - `verifyJwt(token): Promise<boolean>`
151
+ - Same as `verifyJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.
152
+
153
+ - `decodeJwtRaw<T>(token, secret, { verify = true } = {}): Promise<T & Partial<JwtPayload>>`
154
+ - Decodes the token. When `verify` is `true`, verifies signature and expiry.
155
+ - Throws `UnauthorizedError('Invalid JWT token.')` if verification fails.
156
+ - Throws if `verify` is `true` but `secret` is empty.
157
+
158
+ - `decodeJwt<T>(token, { verify = true } = {}): Promise<T & Partial<JwtPayload>>`
159
+ - Same as `decodeJwtRaw`, but uses `useRuntimeConfig().jwtSecret`.
160
+ - Throws `UnauthorizedError('Invalid JWT token.')` if verification fails.
161
+
162
+ Types:
163
+ - `ExtendableJwtPayload<T>` lets you define custom claims merged with standard JWT claims.
164
+
165
+ ### h3 helpers
166
+
167
+ - `extractBearerToken(event): string | undefined`
168
+ - Reads `Authorization` header and returns the token without `Bearer `.
169
+
170
+ - `extractQueryToken(event): string | undefined`
171
+ - Reads `token` from the query string.
172
+
173
+ - `extractApiToken(event): string | undefined`
174
+ - Returns the first non-empty token found by `extractBearerToken` or `extractQueryToken`.
175
+
176
+ - `requireApiToken(event): string`
177
+ - Same as `extractApiToken`, but throws `UnauthenticatedError` if missing.
178
+
179
+
180
+ ## License
181
+
182
+ MIT
package/dist/h3.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { H3Event } from 'h3';
2
2
 
3
3
  declare const extractBearerToken: (event: H3Event) => string;
4
- declare const extractQueryToken: (event: H3Event) => string | undefined;
4
+ declare const extractQueryToken: (event: H3Event, key?: string) => string | undefined;
5
5
  declare const extractApiToken: (event: H3Event) => string | undefined;
6
6
  declare const requireApiToken: (event: H3Event) => string;
7
7
 
package/dist/h3.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { H3Event } from 'h3';
2
2
 
3
3
  declare const extractBearerToken: (event: H3Event) => string;
4
- declare const extractQueryToken: (event: H3Event) => string | undefined;
4
+ declare const extractQueryToken: (event: H3Event, key?: string) => string | undefined;
5
5
  declare const extractApiToken: (event: H3Event) => string | undefined;
6
6
  declare const requireApiToken: (event: H3Event) => string;
7
7
 
package/dist/h3.mjs CHANGED
@@ -2,7 +2,7 @@ import { getRequestHeader, getQuery } from 'h3';
2
2
  import { UnauthenticatedError } from '@nitrotool/errors';
3
3
 
4
4
  const extractBearerToken = (event) => getRequestHeader(event, "Authorization")?.replace("Bearer ", "") || void 0;
5
- const extractQueryToken = (event) => getQuery(event)?.token || void 0;
5
+ const extractQueryToken = (event, key = "token") => getQuery(event)?.[key] || void 0;
6
6
  const extractApiToken = (event) => extractBearerToken(event) || extractQueryToken(event);
7
7
  const requireApiToken = (event) => {
8
8
  const token = extractApiToken(event);
package/dist/jwt.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import jwt from '@tsndr/cloudflare-worker-jwt';
2
- import { createError } from 'h3';
2
+ import { UnauthorizedError } from '@nitrotool/errors';
3
3
 
4
4
  const encodeJwtRaw = async (payload, secret, ttl = 60) => {
5
5
  return await jwt.sign(
@@ -33,16 +33,17 @@ const verifyJwt = async (token) => {
33
33
  return verifyJwtRaw(token, secret);
34
34
  };
35
35
  const decodeJwtRaw = async (token, secret, { verify } = { verify: true }) => {
36
- if (!secret && verify) throw new Error("Cannot check signature without secret.");
36
+ if (!secret && verify)
37
+ throw new Error("Cannot check signature without secret.");
37
38
  if (secret && verify && !await verifyJwtRaw(token, secret)) {
38
- throw createError("Invalid JWT token.");
39
+ throw UnauthorizedError("Invalid JWT token.");
39
40
  }
40
41
  const { payload } = jwt.decode(token);
41
42
  return payload;
42
43
  };
43
44
  const decodeJwt = async (token, { verify } = { verify: true }) => {
44
45
  if (verify && !await verifyJwt(token)) {
45
- throw createError("Invalid JWT token.");
46
+ throw UnauthorizedError("Invalid JWT token.");
46
47
  }
47
48
  const { payload } = jwt.decode(token);
48
49
  return payload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitrotool/jwt",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "main": "dist/index.mjs",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,7 +11,7 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "dependencies": {
13
13
  "@tsndr/cloudflare-worker-jwt": "^3.2.0",
14
- "@nitrotool/errors": "0.0.6"
14
+ "@nitrotool/errors": "0.0.8"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "h3": "^1.15.3"
package/src/h3.ts DELETED
@@ -1,24 +0,0 @@
1
- import {
2
- type H3Event,
3
- getRequestHeader,
4
- getQuery,
5
- } from 'h3';
6
- import {UnauthenticatedError} from "@nitrotool/errors";
7
-
8
- export const extractBearerToken = (event: H3Event) =>
9
- getRequestHeader(event, 'Authorization')?.replace('Bearer ', '') || undefined;
10
-
11
- export const extractQueryToken = (event: H3Event): string | undefined =>
12
- getQuery<{ token: string }>(event)?.token || undefined;
13
-
14
- export const extractApiToken = (event: H3Event): string | undefined =>
15
- extractBearerToken(event) || extractQueryToken(event);
16
-
17
- export const requireApiToken = (event: H3Event) => {
18
- const token = extractApiToken(event);
19
- if (!token) {
20
- throw UnauthenticatedError();
21
- }
22
-
23
- return token;
24
- }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- // @ts-ignore
2
- export * from './h3';
3
- // @ts-ignore
4
- export * from './jwt';
5
-
package/src/jwt.ts DELETED
@@ -1,80 +0,0 @@
1
- import jwt, {JwtPayload} from '@tsndr/cloudflare-worker-jwt';
2
-
3
- import { createError } from 'h3';
4
- export type ExtendableJwtPayload<T extends Record<string, any> = {}> =
5
- Partial<JwtPayload> & T;
6
-
7
- export const encodeJwtRaw = async <
8
- T extends Record<string, any> = {},
9
- >(
10
- payload: ExtendableJwtPayload<T>,
11
- secret: string,
12
- ttl = 60,
13
- ): Promise<string> => {
14
- return await jwt.sign(
15
- {
16
- exp: Date.now() / 1000 + ttl,
17
- ...payload,
18
- },
19
- secret,
20
- );
21
- };
22
-
23
- export const encodeJwt = async <
24
- T extends Record<string, any> = {},
25
- >(
26
- payload: ExtendableJwtPayload<T>,
27
- ): Promise<string> => {
28
- const secret =
29
- //@ts-expect-error Expected.
30
- typeof useRuntimeConfig === 'function' ? useRuntimeConfig()?.jwtSecret : '';
31
- return encodeJwtRaw(payload, secret);
32
- };
33
-
34
- export const verifyJwtRaw = async (
35
- token: string,
36
- secret: string,
37
- ): Promise<boolean> => {
38
- return new Promise(async (resolve) => {
39
- if (await jwt.verify(token, secret, {throwError: false})) {
40
- return resolve(true);
41
- }
42
-
43
- return resolve(false);
44
- });
45
- };
46
-
47
- export const verifyJwt = async (token: string): Promise<boolean> => {
48
- const secret =
49
- //@ts-expect-error Expected.
50
- typeof useRuntimeConfig === 'function' ? useRuntimeConfig()?.jwtSecret : '';
51
- return verifyJwtRaw(token, secret);
52
- };
53
-
54
- export const decodeJwtRaw = async <
55
- T extends Record<string, any> = {},
56
- >(
57
- token: string,
58
- secret: string,
59
- { verify }: { verify?: boolean } = { verify: true },
60
- ): Promise<ExtendableJwtPayload<T>> => {
61
- if (!secret && verify) throw new Error('Cannot check signature without secret.');
62
- if (secret && verify && !(await verifyJwtRaw(token, secret))) {
63
- throw createError('Invalid JWT token.');
64
- }
65
- const { payload } = jwt.decode<ExtendableJwtPayload<T>>(token);
66
- return payload;
67
- };
68
-
69
- export const decodeJwt = async <
70
- T extends Record<string, any> = {},
71
- >(
72
- token: string,
73
- { verify }: { verify?: boolean } = { verify: true },
74
- ): Promise<ExtendableJwtPayload<T>> => {
75
- if (verify && !(await verifyJwt(token))) {
76
- throw createError('Invalid JWT token.');
77
- }
78
- const { payload } = jwt.decode<ExtendableJwtPayload<T>>(token);
79
- return payload;
80
- };