@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 +60 -0
- package/README.md +175 -38
- package/package.json +2 -2
- package/src/utils/index.js +1 -1
- package/src/xAuth.js +10 -3
- package/.gitlab-ci.yml +0 -45
- package/server/app.js +0 -370
- package/server/example-jwks.json +0 -12
- package/server/generate-demo-token.js +0 -232
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
|
-
#
|
|
1
|
+
# @xenterprises/fastify-xauth-jwks
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
11
|
+
## Quick Start
|
|
26
12
|
|
|
27
13
|
```javascript
|
|
28
|
-
import Fastify from
|
|
29
|
-
import
|
|
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(
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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
|
-
#
|
|
205
|
+
# 63 tests passing
|
|
69
206
|
```
|
|
70
207
|
|
|
71
208
|
## License
|
|
72
209
|
|
|
73
|
-
|
|
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.
|
|
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": "
|
|
25
|
+
"license": "UNLICENSED",
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
28
|
"url": "https://gitlab.com/x-enterprises/fastify-plugins/fastify-x-auth-jwks"
|
package/src/utils/index.js
CHANGED
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
|
-
|
|
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(
|
|
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();
|
package/server/example-jwks.json
DELETED
|
@@ -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();
|