@xenterprises/fastify-xauth-jwks 1.1.1 → 1.2.0

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/LICENSE ADDED
@@ -0,0 +1,60 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2024-2026 X Enterprises LLC. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ exclusive property of X Enterprises LLC, a Washington limited liability
7
+ company.
8
+
9
+ TERMS AND CONDITIONS
10
+
11
+ 1. OWNERSHIP
12
+ All rights, title, and interest in and to the Software, including all
13
+ intellectual property rights, are and shall remain the exclusive property
14
+ of X Enterprises LLC.
15
+
16
+ 2. RESTRICTIONS
17
+ Without the prior written consent of X Enterprises LLC, you may not:
18
+ - Copy, modify, or distribute the Software
19
+ - Reverse engineer, decompile, or disassemble the Software
20
+ - Sublicense, sell, lease, or otherwise transfer the Software
21
+ - Remove or alter any proprietary notices or labels
22
+
23
+ 3. AUTHORIZED USE
24
+ Use of this Software is limited to authorized employees, contractors, and
25
+ agents of X Enterprises LLC, solely for purposes approved by X Enterprises
26
+ LLC.
27
+
28
+ 4. NO WARRANTY
29
+ THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
32
+ X ENTERPRISES LLC BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
33
+ WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF,
34
+ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35
+ SOFTWARE.
36
+
37
+ 5. LIMITATION OF LIABILITY
38
+ IN NO EVENT SHALL X ENTERPRISES LLC BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
39
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
40
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
41
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
42
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
43
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
44
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45
+
46
+ 6. GOVERNING LAW
47
+ This license shall be governed by and construed in accordance with the laws
48
+ of the State of Washington, United States, without regard to its conflict
49
+ of law provisions.
50
+
51
+ 7. TERMINATION
52
+ This license is effective until terminated. X Enterprises LLC may terminate
53
+ this license at any time without notice. Upon termination, you must destroy
54
+ all copies of the Software in your possession.
55
+
56
+ For licensing inquiries, contact: legal@x.enterprises
57
+
58
+ ---
59
+ X Enterprises LLC
60
+ Bothell, Washington, United States
package/README.md CHANGED
@@ -1,20 +1,6 @@
1
- # xAuthJWSK
1
+ # @xenterprises/fastify-xauth-jwks
2
2
 
3
- **Lightweight, zero-config path-based JWT/JWKS validation for Fastify v5.**
4
-
5
- Protect multiple API paths with independent JWKS providers. Simple, fast, and production-ready.
6
-
7
- ## Features
8
-
9
- ✅ **Path-Based Protection** - Protect `/admin`, `/portal`, `/api` with different JWKS providers
10
- ✅ **Bearer Token Validation** - Automatic JWT validation against remote JWKS endpoints
11
- ✅ **Local or Remote JWKS** - Use remote URLs for production or local JWKS data for development
12
- ✅ **Dual-Level Caching** - JWKS cache (30 min) + JWT payload cache (5 min)
13
- ✅ **Excluded Paths** - Skip auth for health checks, docs, etc.
14
- ✅ **Zero Dependencies** - Only uses `jose` and `fastify-plugin`
15
- ✅ **Slim & Focused** - ~200 lines of core code, no bloat
16
- ✅ **Configurable** - All caching parameters customizable
17
- ✅ **Request Isolation** - Each path has separate validator & cache
3
+ Path-based JWT/JWKS validation for Fastify v5. Protect multiple API paths with independent JWKS providers.
18
4
 
19
5
  ## Installation
20
6
 
@@ -22,52 +8,203 @@ Protect multiple API paths with independent JWKS providers. Simple, fast, and pr
22
8
  npm install @xenterprises/fastify-xauth-jwks
23
9
  ```
24
10
 
25
- ## Quick Example
11
+ ## Quick Start
26
12
 
27
13
  ```javascript
28
- import Fastify from 'fastify';
29
- import xAuthJWSK from '@xenterprises/fastify-xauth-jwks';
14
+ import Fastify from "fastify";
15
+ import xAuth from "@xenterprises/fastify-xauth-jwks";
30
16
 
31
17
  const fastify = Fastify();
32
18
 
33
- await fastify.register(xAuthJWSK, {
19
+ await fastify.register(xAuth, {
34
20
  paths: {
35
21
  admin: {
36
22
  pathPattern: "/admin",
37
23
  jwksUrl: "https://your-auth.com/.well-known/jwks.json",
38
- }
39
- }
24
+ excludedPaths: ["/health"],
25
+ },
26
+ portal: {
27
+ pathPattern: "/portal",
28
+ jwksUrl: "https://your-auth.com/.well-known/jwks.json",
29
+ },
30
+ },
40
31
  });
41
32
 
42
- fastify.get('/admin/users', (request) => {
43
- return { userId: request.auth.userId };
33
+ // Protected route - requires valid JWT
34
+ fastify.get("/admin/dashboard", (request) => {
35
+ return { userId: request.auth.userId, user: request.user };
44
36
  });
45
37
 
46
- fastify.listen({ port: 3000 });
38
+ // Excluded route - no auth required
39
+ fastify.get("/admin/health", () => ({ status: "ok" }));
40
+
41
+ // Public route - not under any protected path
42
+ fastify.get("/public/info", () => ({ info: "open" }));
43
+
44
+ await fastify.listen({ port: 3000 });
45
+ ```
46
+
47
+ ## Options
48
+
49
+ The plugin accepts a single `paths` object where each key is a path name and each value is a path configuration:
50
+
51
+ | Option | Type | Default | Required | Description |
52
+ |---|---|---|---|---|
53
+ | `paths` | `object` | — | Yes | Map of path name to path configuration. Must contain at least one entry. |
54
+
55
+ ### Path Configuration
56
+
57
+ | Option | Type | Default | Required | Description |
58
+ |---|---|---|---|---|
59
+ | `pathPattern` | `string` | `/<pathName>` | No | URL prefix to protect. All routes starting with this prefix require authentication. |
60
+ | `jwksUrl` | `string` | — | One of `jwksUrl` / `jwksData` | Remote JWKS endpoint URL for token verification. |
61
+ | `jwksData` | `object` | — | One of `jwksUrl` / `jwksData` | Local JWKS data (`{ keys: [...] }`) or a single JWK object for development/testing. |
62
+ | `active` | `boolean` | `true` | No | Set to `false` to skip registration of this path. |
63
+ | `excludedPaths` | `string[]` | `[]` | No | Sub-paths that bypass authentication (e.g., `["/health", "/docs"]`). |
64
+ | `jwksCooldownDuration` | `number` | `30000` | No | Minimum milliseconds between JWKS refetches (remote only). |
65
+ | `jwksCacheMaxAge` | `number` | `1800000` | No | Maximum age in milliseconds for cached JWKS keys (remote only). |
66
+ | `enablePayloadCache` | `boolean` | `true` | No | Cache verified JWT payloads in memory to avoid re-verification. |
67
+ | `payloadCacheTTL` | `number` | `300000` | No | Time-to-live in milliseconds for cached JWT payloads. |
68
+
69
+ ## Decorated Properties
70
+
71
+ After registration, the plugin decorates `fastify.xAuth`:
72
+
73
+ | Property | Type | Description |
74
+ |---|---|---|
75
+ | `fastify.xAuth.validators` | `object` | Map of path name to validator object. |
76
+
77
+ ### Validator Object
78
+
79
+ Each validator in `fastify.xAuth.validators.<name>` exposes:
80
+
81
+ | Property / Method | Type | Description |
82
+ |---|---|---|
83
+ | `name` | `string` | Path name (e.g., `"admin"`). |
84
+ | `pathPattern` | `string` | URL prefix being protected. |
85
+ | `jwksUrl` | `string \| undefined` | Remote JWKS URL if configured. |
86
+ | `config` | `object` | Caching configuration values. |
87
+ | `verifyJWT(token)` | `async function` | Verify a JWT string. Returns the payload object or `null`. |
88
+ | `clearPayloadCache()` | `function` | Clear the in-memory JWT payload cache. |
89
+ | `getPayloadCacheStats()` | `function` | Returns `{ size, enabled, ttl }` for monitoring. |
90
+
91
+ ## Request Properties
92
+
93
+ On authenticated requests, the plugin sets:
94
+
95
+ | Property | Type | Description |
96
+ |---|---|---|
97
+ | `request.user` | `object` | Full JWT payload (claims). |
98
+ | `request.auth.path` | `string` | Name of the path that authenticated this request. |
99
+ | `request.auth.userId` | `string` | The `sub` claim from the JWT. |
100
+ | `request.auth.payload` | `object` | Full JWT payload (same as `request.user`). |
101
+
102
+ ## Utility Exports
103
+
104
+ Import from `@xenterprises/fastify-xauth-jwks/utils`:
105
+
106
+ ```javascript
107
+ import {
108
+ extractToken,
109
+ hasRole,
110
+ hasPermission,
111
+ getUserId,
112
+ getAuthEndpoint,
113
+ requireRole,
114
+ requirePermission,
115
+ requireEndpoint,
116
+ decodeToken,
117
+ decodeHeader,
118
+ } from "@xenterprises/fastify-xauth-jwks/utils";
47
119
  ```
48
120
 
49
- ## Documentation
121
+ | Utility | Signature | Description |
122
+ |---|---|---|
123
+ | `extractToken(request)` | `(req) => string \| null` | Extract Bearer token from Authorization header. |
124
+ | `hasRole(user, roles)` | `(user, string \| string[]) => boolean` | Check if user has any of the specified roles (reads `user.roles`). |
125
+ | `hasPermission(user, perms)` | `(user, string \| string[]) => boolean` | Check if user has any of the specified permissions (reads `user.permissions`). |
126
+ | `getUserId(request)` | `(req) => string \| null` | Get user ID from `request.auth.userId` or `request.user.sub`. |
127
+ | `getAuthEndpoint(request)` | `(req) => string \| null` | Get the auth path name from `request.auth.path`. |
128
+ | `requireRole(roles)` | `(string \| string[]) => preHandler` | Fastify preHandler that returns 403 if user lacks the role. |
129
+ | `requirePermission(perms)` | `(string \| string[]) => preHandler` | Fastify preHandler that returns 403 if user lacks the permission. |
130
+ | `requireEndpoint(name)` | `(string) => preHandler` | Fastify preHandler that returns 403 if request was not authenticated by the named path. |
131
+ | `decodeToken(token)` | `(string) => object` | Decode JWT payload without verification (re-export of `jose.decodeJwt`). |
132
+ | `decodeHeader(token)` | `(string) => object` | Decode JWT header without verification (re-export of `jose.decodeProtectedHeader`). |
133
+
134
+ ### Usage with Route Hooks
135
+
136
+ ```javascript
137
+ import { requireRole, requirePermission } from "@xenterprises/fastify-xauth-jwks/utils";
138
+
139
+ fastify.get("/admin/settings", {
140
+ preHandler: requireRole("admin"),
141
+ handler: async (request) => ({ settings: "..." }),
142
+ });
143
+
144
+ fastify.delete("/admin/users/:id", {
145
+ preHandler: requirePermission("users:delete"),
146
+ handler: async (request) => ({ deleted: true }),
147
+ });
148
+ ```
149
+
150
+ ## Environment Variables
151
+
152
+ The plugin itself does not read environment variables. Your application should pass JWKS URLs from environment variables:
153
+
154
+ | Variable | Required | Description |
155
+ |---|---|---|
156
+ | `ADMIN_JWKS_URL` | Per path | JWKS endpoint for admin path validation. |
157
+ | `PORTAL_JWKS_URL` | Per path | JWKS endpoint for portal path validation. |
158
+
159
+ Example:
160
+
161
+ ```javascript
162
+ await fastify.register(xAuth, {
163
+ paths: {
164
+ admin: {
165
+ pathPattern: "/admin",
166
+ jwksUrl: process.env.ADMIN_JWKS_URL,
167
+ },
168
+ },
169
+ });
170
+ ```
171
+
172
+ ## Error Reference
173
+
174
+ | HTTP Status | Error | When |
175
+ |---|---|---|
176
+ | 401 | `Access token required` | No `Authorization: Bearer <token>` header present on a protected route. |
177
+ | 401 | `Invalid token` | Token failed JWKS verification, is expired, or missing `sub` claim. |
178
+ | 401 | `Authentication failed` | Unexpected error during authentication (logged server-side). |
179
+ | 403 | `Insufficient permissions` | `requireRole` or `requirePermission` check failed. |
180
+ | 403 | `Must authenticate via <name> endpoint` | `requireEndpoint` check failed. |
181
+
182
+ Startup errors (thrown during plugin registration):
183
+
184
+ | Error Message | Cause |
185
+ |---|---|
186
+ | `xAuth: options object is required` | No options passed to the plugin. |
187
+ | `xAuth: 'paths' option is required and must contain at least one path configuration` | Empty or missing `paths` option. |
188
+ | `<name>: Either jwksUrl or jwksData is required` | Path config missing both JWKS source options. |
189
+ | `<name>: Cannot specify both jwksUrl and jwksData` | Path config has both JWKS source options. |
190
+
191
+ ## How It Works
192
+
193
+ 1. **Registration**: For each entry in `paths`, the plugin creates a path validator. Remote JWKS endpoints are set up via `jose.createRemoteJWKSet` with configurable caching; local JWKS data uses `jose.createLocalJWKSet`.
50
194
 
51
- **Getting Started:**
52
- - **[QUICK_START.md](./QUICK_START.md)** - Get started in 5 minutes
53
- - **[CONFIGURATION.md](./CONFIGURATION.md)** - Complete configuration reference (all options + examples)
195
+ 2. **Request Hook**: An `onRequest` hook checks every incoming request URL against each registered `pathPattern` using `String.startsWith()`. If the URL matches a protected path and is not in `excludedPaths`, the hook extracts the Bearer token and verifies it against the path's JWKS.
54
196
 
55
- **Examples & Guides:**
56
- - **[AUTHENTICATION_EXAMPLE.md](./AUTHENTICATION_EXAMPLE.md)** - Email/password auth with JWT signing
57
- - **[DEVELOPMENT.md](./DEVELOPMENT.md)** - Local development with test tokens
58
- - **[KEYS_GENERATION.md](./KEYS_GENERATION.md)** - Generate JWKS keys and test tokens
197
+ 3. **Caching**: Two levels of caching are used. JWKS keys are cached by the `jose` library with configurable cooldown and max age. JWT payloads can optionally be cached in a `Map` keyed by the raw token string, with configurable TTL.
59
198
 
60
- **Advanced:**
61
- - **[CACHING.md](./CACHING.md)** - Configure caching for performance
62
- - **[JOSE_UTILITIES.md](./JOSE_UTILITIES.md)** - Advanced JWT inspection
199
+ 4. **Request Decoration**: On successful verification, `request.user` is set to the full JWT payload and `request.auth` is set with the path name, user ID, and payload for downstream route handlers.
63
200
 
64
201
  ## Tests
65
202
 
66
203
  ```bash
67
204
  npm test
68
- # 49/49 tests passing
205
+ # 63 tests passing
69
206
  ```
70
207
 
71
208
  ## License
72
209
 
73
- ISC
210
+ UNLICENSED
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xenterprises/fastify-xauth-jwks",
3
3
  "type": "module",
4
- "version": "1.1.1",
4
+ "version": "1.2.0",
5
5
  "description": "Fastify plugin for path-based JWT/JWKS validation. Protect multiple paths with independent JWKS providers.",
6
6
  "main": "src/index.js",
7
7
  "exports": {
@@ -22,7 +22,7 @@
22
22
  "path-based"
23
23
  ],
24
24
  "author": "Tim Mushen",
25
- "license": "ISC",
25
+ "license": "UNLICENSED",
26
26
  "repository": {
27
27
  "type": "git",
28
28
  "url": "https://gitlab.com/x-enterprises/fastify-plugins/fastify-x-auth-jwks"
@@ -67,7 +67,7 @@ export function getUserId(request) {
67
67
  * @returns {string|null}
68
68
  */
69
69
  export function getAuthEndpoint(request) {
70
- return request.auth?.endpoint || null;
70
+ return request.auth?.path || null;
71
71
  }
72
72
 
73
73
  /**
package/src/xAuth.js CHANGED
@@ -3,10 +3,16 @@ import fp from "fastify-plugin";
3
3
  import { createPathValidator } from "./services/pathValidator.js";
4
4
 
5
5
  async function xAuthPlugin(fastify, options) {
6
- const { paths = {} } = options;
6
+ if (!options || typeof options !== "object") {
7
+ throw new Error("xAuth: options object is required");
8
+ }
9
+
10
+ const { paths } = options;
7
11
 
8
- if (Object.keys(paths).length === 0) {
9
- throw new Error("At least one protected path configuration is required");
12
+ if (!paths || typeof paths !== "object" || Object.keys(paths).length === 0) {
13
+ throw new Error(
14
+ "xAuth: 'paths' option is required and must contain at least one path configuration"
15
+ );
10
16
  }
11
17
 
12
18
  // Create xAuth namespace
@@ -33,4 +39,5 @@ async function xAuthPlugin(fastify, options) {
33
39
 
34
40
  export default fp(xAuthPlugin, {
35
41
  name: "xAuth",
42
+ fastify: ">=5.0.0",
36
43
  });
package/.gitlab-ci.yml DELETED
@@ -1,45 +0,0 @@
1
- # ============================================================================
2
- # GitLab CI/CD Pipeline - xAuthJWSK
3
- # ============================================================================
4
- # Runs tests on merge requests and commits to main/master
5
-
6
- stages:
7
- - test
8
-
9
- variables:
10
- NODE_ENV: test
11
-
12
- # ============================================================================
13
- # Shared Configuration
14
- # ============================================================================
15
- .shared_rules: &shared_rules
16
- rules:
17
- - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
18
- - if: '$CI_COMMIT_BRANCH == "main"'
19
- - if: '$CI_COMMIT_BRANCH == "master"'
20
- - if: '$CI_COMMIT_TAG'
21
-
22
- # ============================================================================
23
- # STAGE: TEST
24
- # ============================================================================
25
- test:
26
- stage: test
27
- image: node:20-alpine
28
- <<: *shared_rules
29
-
30
- cache:
31
- key: ${CI_COMMIT_REF_SLUG}
32
- paths:
33
- - node_modules/
34
-
35
- before_script:
36
- - npm ci
37
-
38
- script:
39
- - echo "Running xAuthJWSK tests..."
40
- - npm test
41
- - npm audit --audit-level=high || true
42
-
43
- retry:
44
- max: 2
45
- when: runner_system_failure
package/server/app.js DELETED
@@ -1,370 +0,0 @@
1
- /**
2
- * xAuthJWSK Demo Server
3
- *
4
- * Shows path-based JWT/JWKS validation with flexible configuration:
5
- * - Development: Local JWKS data (example-jwks.json)
6
- * - Production: Remote JWKS URLs (via environment variables)
7
- *
8
- * Environment Variables:
9
- * USE_LOCAL_JWKS=true - Force local JWKS (dev mode)
10
- * ADMIN_JWKS_URL=https://... - Admin path JWKS endpoint
11
- * PORTAL_JWKS_URL=https://... - Portal path JWKS endpoint
12
- * PARTNER_JWKS_URL=https://... - Partner path JWKS endpoint
13
- * PORT=3000 - Server port (default: 3000)
14
- *
15
- * Quick Start (Development):
16
- * npm run dev
17
- * node server/generate-demo-token.js admin user-123 admin
18
- * curl -H "Authorization: Bearer <TOKEN>" http://localhost:3000/admin/dashboard
19
- *
20
- * Production Setup:
21
- * export ADMIN_JWKS_URL="https://auth.example.com/.well-known/jwks.json"
22
- * export PORTAL_JWKS_URL="https://auth.example.com/.well-known/jwks.json"
23
- * npm start
24
- */
25
-
26
- import Fastify from "fastify";
27
- import xAuthJWSK from "../src/xAuth.js";
28
- import { extractToken, decodeToken, decodeHeader, requireRole } from "../src/utils/index.js";
29
- import exampleJwks from "./example-jwks.json" assert { type: "json" };
30
-
31
- const fastify = Fastify({
32
- logger: true,
33
- });
34
-
35
- // ============================================================================
36
- // Configuration Strategy
37
- // ============================================================================
38
- // Flexible configuration for both development and production:
39
- // - Development: Use local JWKS (example-jwks.json) - no external calls
40
- // - Production: Use remote JWKS URLs from environment variables
41
- const useLocalJwks = process.env.USE_LOCAL_JWKS === "true" || !process.env.ADMIN_JWKS_URL;
42
-
43
- // ============================================================================
44
- // Register xAuthJWSK Plugin
45
- // ============================================================================
46
- // Protect multiple API paths with independent JWKS providers
47
- // Each path can have different JWKS source, caching strategy, excluded paths
48
- // See CONFIGURATION.md for complete reference
49
- await fastify.register(xAuthJWSK, {
50
- paths: {
51
- // ========================================================================
52
- // ADMIN API - Strict authentication (e.g., internal admin panel)
53
- // ========================================================================
54
- // Protected: /admin/*
55
- // Excluded: /admin/health, /admin/status
56
- // Use case: Internal dashboards, user management, sensitive operations
57
- admin: {
58
- pathPattern: "/admin",
59
- // JWKS: Local OR remote (not both) - choose one
60
- ...(useLocalJwks
61
- ? { jwksData: exampleJwks } // Development: local file
62
- : { jwksUrl: process.env.ADMIN_JWKS_URL || "https://example.com/admin/.well-known/jwks.json" }), // Production: env var
63
- // Paths that don't require authentication (health checks, etc)
64
- excludedPaths: ["/health", "/status"],
65
- // Caching: Strict cache - refresh every 5 minutes
66
- enablePayloadCache: true,
67
- payloadCacheTTL: 300000, // 5 min token payload cache
68
- jwksCacheMaxAge: 1800000, // 30 min JWKS cache (production keys rarely change)
69
- },
70
-
71
- // ========================================================================
72
- // PORTAL API - User-facing (e.g., SaaS application)
73
- // ========================================================================
74
- // Protected: /portal/*
75
- // Excluded: /portal/public/*, /portal/docs
76
- // Use case: User dashboards, API access, profile management
77
- portal: {
78
- pathPattern: "/portal",
79
- ...(useLocalJwks
80
- ? { jwksData: exampleJwks }
81
- : { jwksUrl: process.env.PORTAL_JWKS_URL || "https://example.com/portal/.well-known/jwks.json" }),
82
- // Public endpoints (marketing pages, documentation)
83
- excludedPaths: ["/public", "/docs"],
84
- // Caching: Standard cache - balance performance and freshness
85
- enablePayloadCache: true,
86
- payloadCacheTTL: 300000, // 5 min
87
- },
88
-
89
- // ========================================================================
90
- // PARTNER API - Integration endpoint (e.g., webhooks, B2B integrations)
91
- // ========================================================================
92
- // Protected: /partner/*
93
- // No excluded paths - all routes require authentication
94
- // Use case: Partner integrations, webhooks, third-party API access
95
- partner: {
96
- pathPattern: "/partner",
97
- ...(useLocalJwks
98
- ? { jwksData: exampleJwks }
99
- : { jwksUrl: process.env.PARTNER_JWKS_URL || "https://example.com/partner/.well-known/jwks.json" }),
100
- // All routes require authentication - no exclusions
101
- // Longer cache TTL - partner tokens typically valid longer
102
- enablePayloadCache: true,
103
- payloadCacheTTL: 600000, // 10 min - partners have longer-lived tokens
104
- },
105
- },
106
- });
107
-
108
- // ============================================================================
109
- // Public Routes (No Authentication Required)
110
- // ============================================================================
111
-
112
- fastify.get("/", async () => {
113
- return {
114
- service: "xAuthJWSK Demo",
115
- version: "1.0.0",
116
- protectedPaths: Object.keys(fastify.xAuth.validators),
117
- docs: "https://github.com/xenterprises/fastify-xauth-jwks",
118
- };
119
- });
120
-
121
- fastify.get("/health", async () => {
122
- return { status: "ok", timestamp: new Date().toISOString() };
123
- });
124
-
125
- // ============================================================================
126
- // Admin Routes (Protected by /admin)
127
- // ============================================================================
128
-
129
- fastify.get("/admin/health", async () => {
130
- return { status: "healthy", service: "admin-api" };
131
- });
132
-
133
- fastify.get("/admin/status", async () => {
134
- return { status: "operational", uptime: process.uptime() };
135
- });
136
-
137
- fastify.get("/admin/dashboard", async (request) => {
138
- return {
139
- message: "Admin Dashboard",
140
- userId: request.auth.userId,
141
- user: request.auth.payload,
142
- authenticatedVia: request.auth.path,
143
- };
144
- });
145
-
146
- fastify.get("/admin/users", async (request) => {
147
- return {
148
- message: "List of users",
149
- adminId: request.auth.userId,
150
- users: [
151
- { id: "user_1", name: "John Doe", email: "john@example.com" },
152
- { id: "user_2", name: "Jane Smith", email: "jane@example.com" },
153
- ],
154
- };
155
- });
156
-
157
- fastify.get("/admin/settings", {
158
- preHandler: requireRole("admin"),
159
- handler: async (request) => {
160
- return {
161
- message: "Admin settings (admin role required)",
162
- adminId: request.auth.userId,
163
- roles: request.user?.roles || [],
164
- };
165
- },
166
- });
167
-
168
- // Cache management endpoint
169
- fastify.post("/admin/cache/clear", async (request) => {
170
- const validator = fastify.xAuth.validators.admin;
171
- validator.clearPayloadCache();
172
- return {
173
- message: "Cache cleared",
174
- stats: validator.getPayloadCacheStats(),
175
- };
176
- });
177
-
178
- fastify.get("/admin/cache/stats", async (request) => {
179
- const stats = {};
180
- for (const [name, validator] of Object.entries(fastify.xAuth.validators)) {
181
- stats[name] = validator.getPayloadCacheStats();
182
- }
183
- return { cacheStats: stats };
184
- });
185
-
186
- // ============================================================================
187
- // Portal Routes (Protected by /portal)
188
- // ============================================================================
189
-
190
- fastify.get("/portal/dashboard", async (request) => {
191
- return {
192
- message: "Portal Dashboard",
193
- userId: request.auth.userId,
194
- user: request.auth.payload,
195
- };
196
- });
197
-
198
- fastify.get("/portal/profile", async (request) => {
199
- return {
200
- message: "User Profile",
201
- userId: request.auth.userId,
202
- email: request.user?.email,
203
- name: request.user?.name,
204
- };
205
- });
206
-
207
- fastify.get("/portal/public/docs", async () => {
208
- return {
209
- message: "API Documentation",
210
- endpoints: ["GET /portal/dashboard", "GET /portal/profile", "POST /portal/update"],
211
- };
212
- });
213
-
214
- fastify.get("/portal/public/pricing", async () => {
215
- return {
216
- plans: [
217
- { tier: "free", price: 0 },
218
- { tier: "pro", price: 29 },
219
- { tier: "enterprise", price: 299 },
220
- ],
221
- };
222
- });
223
-
224
- // ============================================================================
225
- // Partner Routes (Protected by /partner)
226
- // ============================================================================
227
-
228
- fastify.get("/partner/api/data", async (request) => {
229
- return {
230
- message: "Partner API Data",
231
- partnerId: request.auth.userId,
232
- data: { status: "connected", lastSync: new Date().toISOString() },
233
- };
234
- });
235
-
236
- fastify.get("/partner/api/webhooks", async (request) => {
237
- return {
238
- message: "Partner Webhooks",
239
- partnerId: request.auth.userId,
240
- webhooks: [
241
- { id: "wh_1", event: "user.created", url: "https://partner.example.com/webhooks" },
242
- ],
243
- };
244
- });
245
-
246
- // ============================================================================
247
- // Token Inspection Endpoints (No Auth Required - for debugging)
248
- // ============================================================================
249
-
250
- fastify.post("/debug/decode-token", async (request, reply) => {
251
- const { token } = request.body;
252
-
253
- if (!token) {
254
- return reply.code(400).send({ error: "Token required" });
255
- }
256
-
257
- try {
258
- const header = decodeHeader(token);
259
- const payload = decodeToken(token);
260
-
261
- return {
262
- header,
263
- payload,
264
- warning: "⚠️ This is UNVERIFIED token data. Only for debugging. Do NOT trust in production.",
265
- };
266
- } catch (error) {
267
- return reply.code(400).send({ error: "Invalid token format" });
268
- }
269
- });
270
-
271
- fastify.post("/debug/extract-token", async (request, reply) => {
272
- const authHeader = request.headers.authorization;
273
-
274
- if (!authHeader) {
275
- return reply.code(400).send({ error: "Authorization header required" });
276
- }
277
-
278
- const token = extractToken(request);
279
-
280
- if (!token) {
281
- return reply.code(400).send({ error: "Invalid Bearer token format" });
282
- }
283
-
284
- try {
285
- const payload = decodeToken(token);
286
- return {
287
- extracted: true,
288
- userId: payload.sub,
289
- expiresAt: new Date(payload.exp * 1000).toISOString(),
290
- payload,
291
- };
292
- } catch (error) {
293
- return reply.code(400).send({ error: "Failed to decode token" });
294
- }
295
- });
296
-
297
- // ============================================================================
298
- // Validator Inspection Endpoint
299
- // ============================================================================
300
-
301
- fastify.get("/admin/validators", async (request) => {
302
- const validators = {};
303
- for (const [name, validator] of Object.entries(fastify.xAuth.validators)) {
304
- validators[name] = {
305
- name: validator.name,
306
- pathPattern: validator.pathPattern,
307
- jwksUrl: validator.jwksUrl,
308
- config: validator.config,
309
- cacheStats: validator.getPayloadCacheStats(),
310
- };
311
- }
312
- return { validators };
313
- });
314
-
315
- // ============================================================================
316
- // Server Startup
317
- // ============================================================================
318
-
319
- const start = async () => {
320
- try {
321
- await fastify.listen({ port: process.env.PORT || 3000, host: "0.0.0.0" });
322
-
323
- console.log("\n🚀 xAuthJWSK Demo Server Started!\n");
324
- console.log(`Mode: ${useLocalJwks ? "🏠 LOCAL (Development)" : "☁️ REMOTE (Production)"}`);
325
- if (useLocalJwks) {
326
- console.log(" Using example-jwks.json - perfect for testing!");
327
- console.log(" Generate test tokens: node server/generate-demo-token.js [path] [userId] [role]");
328
- }
329
- console.log("\nProtected Paths:");
330
- for (const [name, validator] of Object.entries(fastify.xAuth.validators)) {
331
- console.log(`\n 📍 ${name.toUpperCase()}`);
332
- console.log(` Path: ${validator.pathPattern}`);
333
- console.log(` JWKS: ${useLocalJwks ? "local (example-jwks.json)" : validator.jwksUrl}`);
334
- console.log(` Cache: ${validator.config.enablePayloadCache ? "enabled" : "disabled"}`);
335
- }
336
-
337
- console.log("\n\nPublic Endpoints:");
338
- console.log(" GET / - API info");
339
- console.log(" GET /health - Health check");
340
- console.log(" POST /debug/decode-token - Decode JWT (debug only)");
341
- console.log(" POST /debug/extract-token - Extract token from header");
342
- console.log(" GET /admin/validators - View validator configs");
343
-
344
- console.log("\n\nProtected Endpoints (require Bearer token):");
345
- console.log(" GET /admin/dashboard - Admin dashboard");
346
- console.log(" GET /admin/users - List users");
347
- console.log(" GET /admin/settings - Settings (requires 'admin' role)");
348
- console.log(" POST /admin/cache/clear - Clear auth cache");
349
- console.log(" GET /admin/cache/stats - View cache statistics");
350
- console.log(" GET /portal/dashboard - Portal dashboard");
351
- console.log(" GET /portal/profile - User profile");
352
- console.log(" GET /partner/api/data - Partner data");
353
-
354
- if (useLocalJwks) {
355
- console.log("\n\n🧪 Testing with Local JWKS:");
356
- console.log(" 1. Generate a test token:");
357
- console.log(" node server/generate-demo-token.js admin user-123 admin");
358
- console.log(" 2. Copy the token and test a protected endpoint:");
359
- console.log(" curl -H \"Authorization: Bearer <TOKEN>\" http://localhost:3000/admin/dashboard");
360
- console.log(" 3. For more info, see KEYS_GENERATION.md and QUICK_START.md");
361
- }
362
-
363
- console.log("\n💡 Tip: Add '-H \"Authorization: Bearer YOUR_TOKEN\"' to protected requests\n");
364
- } catch (err) {
365
- fastify.log.error(err);
366
- process.exit(1);
367
- }
368
- };
369
-
370
- start();
@@ -1,12 +0,0 @@
1
- {
2
- "keys": [
3
- {
4
- "kty": "RSA",
5
- "use": "sig",
6
- "alg": "RS256",
7
- "kid": "demo-key-admin",
8
- "n": "xjlCRBqkQWeBpaMWV2E2h6L1zcqmxm0W3Z5BbMwP9jfYEJ_ZHvMdV8fYaWDV8xzGqL7Z9fQaL7bXmVzYcPz0Xq5L_VmE8V7K0L1M2N3O4P5Q6R7S8T9U0V1W2X3Y4Z5A6B7C8D9E0F1G2H3I4J5K6L7M8N9O0P1Q2R3S4T5U6V7W8X9Y0Z1A2B3C4D5E6F7G8H9I0J1K2L3M4N5O6P7Q8R9S0T1U2V3W4X5Y6Z7A8B9C0D1E2F3G4H5I6J7K8L9M0N1O2P3Q4R5S6T7U8V9W0X1Y2Z3A4B5C6D7E8F9G0H1I2J3K4L5M6N7O8P9Q0R1S2T3U4V5W6X7Y8Z9",
9
- "e": "AQAB"
10
- }
11
- ]
12
- }
@@ -1,232 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Generate demo JWT tokens for testing xAuthJWSK
5
- *
6
- * Usage:
7
- * node generate-demo-token.js [pathName] [userId] [role]
8
- *
9
- * Examples:
10
- * node generate-demo-token.js admin user-123 admin
11
- * node generate-demo-token.js portal user-456 user
12
- * node generate-demo-token.js partner partner-789
13
- */
14
-
15
- import * as jose from 'jose';
16
-
17
- // Demo JWKS keys - these are public keys only
18
- // In production, private keys would be stored securely
19
- const demoKeys = {
20
- admin: {
21
- kid: 'demo-key-admin',
22
- // This is a demo key for testing ONLY - never use in production
23
- privateKeyPem: `-----BEGIN PRIVATE KEY-----
24
- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDGOUJEGqRBZ4Gl
25
- oxZXYTaHovXNyqbGbRbdnkFszA/2N9gQn9ke8x1Xx9hpYNXzHMaovtn19BovttmZ
26
- XNhw/PRerkv9WYTxXsrQnUjczUjczVTNzNT0xM01M1MzQzM1M9T1M9M1TzU0MzU0
27
- M1MzQzMzQ0M1MzMzM1M1QzMzMzM1RDMzM1M1QzMzM1M1QzMzM1M1M1MzMzM1M1Mz
28
- MzM1M1MzMzM1M1MzMzMzUzMzMzM1M1MzMzMzUzMzMzM1M1MzMzMzUzMzMzM1M1Mz
29
- MzMzUzMzMzM1M1MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMz
30
- MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMz
31
- MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMz
32
- AgMBAAECggEAQQECDQwVFBcWGRgbHBwdHR4eHx8gICEhISIiIiIjIyMjIyMkJCQk
33
- JCQlJSUlJSUlJSYmJiYmJiYmJiYnJycnJycnJycnJycnJycnJycnJycnJycnJycnJ
34
- yckJycnJycnJycoJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJyc
35
- nJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
36
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
37
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
38
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
39
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
40
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
41
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
42
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
43
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
44
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
45
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
46
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
47
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
48
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
49
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
50
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
51
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
52
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
53
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
54
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
55
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
56
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
57
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
58
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
59
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
60
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
61
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
62
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
63
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
64
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
65
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
66
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
67
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
68
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
69
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
70
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
71
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
72
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
73
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
74
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
75
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
76
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
77
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
78
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
79
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
80
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
81
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
82
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
83
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
84
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
85
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
86
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
87
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
88
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
89
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
90
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
91
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
92
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
93
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
94
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
95
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
96
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
97
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
98
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
99
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
100
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
101
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
102
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
103
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
104
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
105
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
106
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
107
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
108
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
109
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
110
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
111
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
112
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
113
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
114
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
115
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
116
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
117
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
118
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
119
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
120
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
121
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
122
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
123
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
124
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
125
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
126
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
127
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
128
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
129
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
130
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
131
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
132
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
133
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
134
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
135
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
136
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
137
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
138
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
139
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
140
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
141
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
142
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
143
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
144
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
145
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
146
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
147
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
148
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
149
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
150
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
151
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
152
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
153
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
154
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
155
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
156
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
157
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
158
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
159
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
160
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
161
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
162
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
163
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
164
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
165
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
166
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
167
- cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJy
168
- -----END PRIVATE KEY-----`,
169
- },
170
- };
171
-
172
- async function generateToken() {
173
- const args = process.argv.slice(2);
174
- const pathName = args[0] || 'admin';
175
- const userId = args[1] || 'user-' + Math.random().toString(36).substring(7);
176
- const role = args[2] || 'user';
177
-
178
- const keyConfig = demoKeys[pathName];
179
- if (!keyConfig) {
180
- console.error(`❌ No demo key for path: ${pathName}`);
181
- console.error(` Available paths: ${Object.keys(demoKeys).join(', ')}`);
182
- process.exit(1);
183
- }
184
-
185
- try {
186
- // Import private key
187
- const privateKey = await jose.importPKCS8(
188
- keyConfig.privateKeyPem,
189
- 'RS256'
190
- );
191
-
192
- // Create JWT
193
- const token = await new jose.SignJWT({
194
- sub: userId,
195
- userId: userId,
196
- name: `Test User (${pathName})`,
197
- email: `${userId}@example.com`,
198
- roles: [role],
199
- iat: Math.floor(Date.now() / 1000),
200
- exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
201
- })
202
- .setProtectedHeader({
203
- alg: 'RS256',
204
- kid: `demo-key-${pathName}`,
205
- typ: 'JWT',
206
- })
207
- .sign(privateKey);
208
-
209
- console.log('🎫 Generated Test Token:\n');
210
- console.log(token);
211
- console.log('\n📋 Token Details:');
212
- console.log(` Path: ${pathName}`);
213
- console.log(` User ID: ${userId}`);
214
- console.log(` Role: ${role}`);
215
- console.log(` Expires: 1 hour`);
216
- console.log('\n🔗 Usage Examples:\n');
217
- console.log('# cURL:');
218
- console.log(`curl -H "Authorization: Bearer ${token}" \\`);
219
- console.log(` http://localhost:3000/${pathName}/dashboard\n`);
220
- console.log('# JavaScript fetch:');
221
- console.log(`fetch('http://localhost:3000/${pathName}/dashboard', {`);
222
- console.log(` headers: { Authorization: 'Bearer ${token}' }`);
223
- console.log(`})\n`);
224
- console.log('# Decode token (for inspection):');
225
- console.log(`node -e "console.log(JSON.stringify(JSON.parse(Buffer.from('${token}'.split('.')[1], 'base64').toString()), null, 2))"\n`);
226
- } catch (error) {
227
- console.error('❌ Error generating token:', error.message);
228
- process.exit(1);
229
- }
230
- }
231
-
232
- generateToken();