@xenterprises/fastify-xauth-local 1.0.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/.gitlab-ci.yml +27 -0
- package/README.md +453 -0
- package/package.json +39 -0
- package/src/routes/local.js +531 -0
- package/src/services/jwt.js +143 -0
- package/src/services/middleware.js +177 -0
- package/src/xAuthLocal.js +242 -0
- package/test/xAuthLocal.test.js +744 -0
package/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
image: node:20
|
|
2
|
+
|
|
3
|
+
stages:
|
|
4
|
+
- test
|
|
5
|
+
- publish
|
|
6
|
+
|
|
7
|
+
cache:
|
|
8
|
+
paths:
|
|
9
|
+
- node_modules/
|
|
10
|
+
|
|
11
|
+
test:
|
|
12
|
+
stage: test
|
|
13
|
+
script:
|
|
14
|
+
- npm install
|
|
15
|
+
- npm test
|
|
16
|
+
rules:
|
|
17
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
18
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
19
|
+
|
|
20
|
+
publish:
|
|
21
|
+
stage: publish
|
|
22
|
+
script:
|
|
23
|
+
- echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
|
|
24
|
+
- npm publish --access public
|
|
25
|
+
rules:
|
|
26
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
27
|
+
when: manual
|
package/README.md
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# xAuthLocal
|
|
2
|
+
|
|
3
|
+
Fastify 5 plugin for JWT authentication with role-based access control, supporting multiple authentication configurations for different route prefixes.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple Auth Configs**: Separate authentication for different route prefixes (e.g., `/api`, `/admin`, `/portal`)
|
|
8
|
+
- **JWT Authentication**: RS256 (RSA keys) or HS256 (symmetric secret) support per config
|
|
9
|
+
- **Route Exclusions**: Express-jwt compatible `.unless()` style patterns
|
|
10
|
+
- **Role-Based Access Control**: Support for multiple roles via `scope` claim
|
|
11
|
+
- **Local Auth Routes**: Built-in login, register, me, and password-reset endpoints per config
|
|
12
|
+
- **Skip User Lookup**: Option to use token data only for `/me` endpoint (no database call)
|
|
13
|
+
- **Backwards Compatible**: Uses `request.auth` pattern familiar from express-jwt
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install xauthlocal jsonwebtoken bcryptjs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Single Config
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
import Fastify from "fastify";
|
|
27
|
+
import xAuthLocal from "xauthlocal";
|
|
28
|
+
|
|
29
|
+
const fastify = Fastify();
|
|
30
|
+
|
|
31
|
+
await fastify.register(xAuthLocal, {
|
|
32
|
+
configs: [
|
|
33
|
+
{
|
|
34
|
+
name: "api",
|
|
35
|
+
prefix: "/api",
|
|
36
|
+
secret: process.env.JWT_SECRET,
|
|
37
|
+
excludedPaths: ["/api/public", "/api/health"],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Protected route
|
|
43
|
+
fastify.get("/api/users", async (request) => {
|
|
44
|
+
// request.auth contains the decoded JWT payload
|
|
45
|
+
return { userId: request.auth.id };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await fastify.listen({ port: 3000 });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Multiple Configs
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
await fastify.register(xAuthLocal, {
|
|
55
|
+
configs: [
|
|
56
|
+
{
|
|
57
|
+
name: "api",
|
|
58
|
+
prefix: "/api",
|
|
59
|
+
secret: process.env.API_SECRET,
|
|
60
|
+
local: {
|
|
61
|
+
enabled: true,
|
|
62
|
+
userLookup: async (email) => db.users.findByEmail(email),
|
|
63
|
+
createUser: async (userData) => db.users.create(userData),
|
|
64
|
+
skipUserLookup: true, // /me returns token data, no DB call
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "admin",
|
|
69
|
+
prefix: "/admin",
|
|
70
|
+
secret: process.env.ADMIN_SECRET,
|
|
71
|
+
local: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
userLookup: async (email) => db.admins.findByEmail(email),
|
|
74
|
+
skipUserLookup: false, // /me fetches fresh data from DB
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "portal",
|
|
79
|
+
prefix: "/portal",
|
|
80
|
+
publicKey: "./keys/portal-public.pem",
|
|
81
|
+
privateKey: "./keys/portal-private.pem",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Each config protects routes under its prefix
|
|
87
|
+
// Tokens are config-specific (API token won't work for admin routes)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Configuration Options
|
|
91
|
+
|
|
92
|
+
### Plugin Options
|
|
93
|
+
|
|
94
|
+
| Option | Type | Required | Default | Description |
|
|
95
|
+
|--------|------|----------|---------|-------------|
|
|
96
|
+
| `configs` | Array | Yes | - | Array of auth configurations |
|
|
97
|
+
| `basePath` | string | No | `process.cwd()` | Base path for relative key paths |
|
|
98
|
+
| `active` | boolean | No | `true` | Enable/disable the plugin |
|
|
99
|
+
|
|
100
|
+
### Config Options
|
|
101
|
+
|
|
102
|
+
Each config in the `configs` array supports:
|
|
103
|
+
|
|
104
|
+
| Option | Type | Required | Default | Description |
|
|
105
|
+
|--------|------|----------|---------|-------------|
|
|
106
|
+
| `name` | string | Yes | - | Unique identifier for this config |
|
|
107
|
+
| `prefix` | string | Yes | - | Route prefix to protect (e.g., `/api`) |
|
|
108
|
+
| `secret` | string | Yes* | - | Symmetric secret for HS256 |
|
|
109
|
+
| `publicKey` | string | Yes* | - | Public key content or path for RS256 |
|
|
110
|
+
| `privateKey` | string | Yes* | - | Private key content or path for RS256 |
|
|
111
|
+
| `algorithm` | string | No | Auto | `'RS256'` for keys, `'HS256'` for secret |
|
|
112
|
+
| `expiresIn` | string | No | `'4d'` | Default token expiration |
|
|
113
|
+
| `audience` | string | No | - | JWT audience claim |
|
|
114
|
+
| `issuer` | string | No | - | JWT issuer claim |
|
|
115
|
+
| `excludedPaths` | Array | No | `[]` | Paths to exclude from auth |
|
|
116
|
+
| `requestProperty` | string | No | `'auth'` | Property to attach decoded token |
|
|
117
|
+
| `credentialsRequired` | boolean | No | `true` | Whether token is required |
|
|
118
|
+
| `getToken` | Function | No | - | Custom token extraction function |
|
|
119
|
+
| `local` | Object | No | - | Local routes configuration |
|
|
120
|
+
|
|
121
|
+
*One of `secret` or `publicKey`/`privateKey` is required per config.
|
|
122
|
+
|
|
123
|
+
### Local Route Options
|
|
124
|
+
|
|
125
|
+
| Option | Type | Required | Default | Description |
|
|
126
|
+
|--------|------|----------|---------|-------------|
|
|
127
|
+
| `enabled` | boolean | No | `false` | Enable local auth routes |
|
|
128
|
+
| `loginPath` | string | No | `{prefix}/local` | Login route path |
|
|
129
|
+
| `mePath` | string | No | `{loginPath}/me` | Me route path |
|
|
130
|
+
| `skipUserLookup` | boolean | No | `false` | Use token data only for /me |
|
|
131
|
+
| `userLookup` | Function | Yes** | - | Function to lookup user by email |
|
|
132
|
+
| `createUser` | Function | No | - | Function to create new user |
|
|
133
|
+
| `passwordReset` | Function | No | - | Function to handle password reset |
|
|
134
|
+
| `saltRounds` | number | No | `10` | bcrypt salt rounds |
|
|
135
|
+
|
|
136
|
+
**Required if local routes are enabled.
|
|
137
|
+
|
|
138
|
+
## Route Exclusions
|
|
139
|
+
|
|
140
|
+
Exclude routes from authentication using patterns compatible with express-jwt:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
await fastify.register(xAuthLocal, {
|
|
144
|
+
configs: [
|
|
145
|
+
{
|
|
146
|
+
name: "api",
|
|
147
|
+
prefix: "/api",
|
|
148
|
+
secret: process.env.JWT_SECRET,
|
|
149
|
+
excludedPaths: [
|
|
150
|
+
// String prefix match
|
|
151
|
+
"/api/public",
|
|
152
|
+
"/api/health",
|
|
153
|
+
|
|
154
|
+
// Regex match
|
|
155
|
+
/^\/api\/v\d+\/public/,
|
|
156
|
+
|
|
157
|
+
// Object with URL and optional methods
|
|
158
|
+
{ url: "/api/webhook", methods: ["POST"] },
|
|
159
|
+
{ url: /^\/api\/callback/, methods: ["GET", "POST"] },
|
|
160
|
+
|
|
161
|
+
// Object without methods (matches all methods)
|
|
162
|
+
{ url: "/api/status" },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Role-Based Access Control
|
|
170
|
+
|
|
171
|
+
Use the `requireRole` helper to protect routes by role:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
const apiConfig = fastify.xauthlocal.get("api");
|
|
175
|
+
|
|
176
|
+
// Single role required
|
|
177
|
+
fastify.get(
|
|
178
|
+
"/api/admin/dashboard",
|
|
179
|
+
{ preHandler: [apiConfig.requireRole("admin")] },
|
|
180
|
+
async (request) => ({ dashboard: true })
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Multiple roles (any match)
|
|
184
|
+
fastify.get(
|
|
185
|
+
"/api/manage/users",
|
|
186
|
+
{ preHandler: [apiConfig.requireRole(["admin", "manager"])] },
|
|
187
|
+
async (request) => ({ users: [] })
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Roles are read from the `scope` claim in the JWT payload:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
const apiConfig = fastify.xauthlocal.get("api");
|
|
195
|
+
const token = apiConfig.jwt.sign({
|
|
196
|
+
id: 1,
|
|
197
|
+
email: "admin@example.com",
|
|
198
|
+
scope: ["admin", "user"], // Array of roles
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Local Auth Routes
|
|
203
|
+
|
|
204
|
+
When `local.enabled` is true, the following endpoints are registered:
|
|
205
|
+
|
|
206
|
+
| Method | Path | Auth Required | Description |
|
|
207
|
+
|--------|------|---------------|-------------|
|
|
208
|
+
| POST | `{prefix}/local` | No | Login with email/password |
|
|
209
|
+
| GET | `{prefix}/local/me` | Yes | Get current user |
|
|
210
|
+
| POST | `{prefix}/local/register` | No | Register new user |
|
|
211
|
+
| POST | `{prefix}/local/password-reset` | No | Request password reset |
|
|
212
|
+
| PUT | `{prefix}/local/password-reset` | No | Complete password reset |
|
|
213
|
+
|
|
214
|
+
### skipUserLookup Option
|
|
215
|
+
|
|
216
|
+
Control whether `/me` endpoint makes a database call:
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
{
|
|
220
|
+
name: "api",
|
|
221
|
+
prefix: "/api",
|
|
222
|
+
secret: process.env.JWT_SECRET,
|
|
223
|
+
local: {
|
|
224
|
+
enabled: true,
|
|
225
|
+
userLookup: async (email) => db.users.findByEmail(email),
|
|
226
|
+
skipUserLookup: true, // Returns token data directly, no DB call
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- **`skipUserLookup: true`**: Returns user data stored in the JWT token (faster, no DB call)
|
|
232
|
+
- **`skipUserLookup: false`**: Fetches fresh user data from database via `userLookup` (default)
|
|
233
|
+
|
|
234
|
+
### Login Request/Response
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
// POST /api/local
|
|
238
|
+
// Request
|
|
239
|
+
{
|
|
240
|
+
"email": "user@example.com",
|
|
241
|
+
"password": "password123"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Response
|
|
245
|
+
{
|
|
246
|
+
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
247
|
+
"user": {
|
|
248
|
+
"id": 1,
|
|
249
|
+
"email": "user@example.com",
|
|
250
|
+
"first_name": "John",
|
|
251
|
+
"last_name": "Doe",
|
|
252
|
+
"admin": false,
|
|
253
|
+
"scope": ["user"]
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## API
|
|
259
|
+
|
|
260
|
+
After registration, access the plugin via `fastify.xauthlocal`:
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
// Get all configs
|
|
264
|
+
fastify.xauthlocal.configs; // { api: {...}, admin: {...} }
|
|
265
|
+
|
|
266
|
+
// Get specific config by name
|
|
267
|
+
const apiConfig = fastify.xauthlocal.get("api");
|
|
268
|
+
|
|
269
|
+
// JWT Service (per config)
|
|
270
|
+
apiConfig.jwt.sign(payload, options);
|
|
271
|
+
apiConfig.jwt.verify(token, options);
|
|
272
|
+
apiConfig.jwt.decode(token);
|
|
273
|
+
|
|
274
|
+
// Role middleware factory (per config)
|
|
275
|
+
apiConfig.requireRole("admin");
|
|
276
|
+
apiConfig.requireRole(["admin", "manager"]);
|
|
277
|
+
|
|
278
|
+
// Custom middleware factory (per config)
|
|
279
|
+
apiConfig.createMiddleware({
|
|
280
|
+
excludedPaths: ["/special"],
|
|
281
|
+
credentialsRequired: false,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Check if route is excluded (per config)
|
|
285
|
+
apiConfig.isExcluded("/api/public/health", "GET"); // true
|
|
286
|
+
|
|
287
|
+
// Config info (per config)
|
|
288
|
+
apiConfig.name; // "api"
|
|
289
|
+
apiConfig.prefix; // "/api"
|
|
290
|
+
apiConfig.hasLocalRoutes; // true
|
|
291
|
+
apiConfig.localPrefix; // "/api/local"
|
|
292
|
+
apiConfig.mePath; // "/api/local/me"
|
|
293
|
+
|
|
294
|
+
// Password utilities (global)
|
|
295
|
+
await fastify.xauthlocal.password.hash("password123");
|
|
296
|
+
await fastify.xauthlocal.password.compare("password123", hash);
|
|
297
|
+
|
|
298
|
+
// Summary config (read-only)
|
|
299
|
+
fastify.xauthlocal.config;
|
|
300
|
+
// {
|
|
301
|
+
// configCount: 2,
|
|
302
|
+
// configNames: ["api", "admin"]
|
|
303
|
+
// }
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Using RSA Keys
|
|
307
|
+
|
|
308
|
+
Generate keys:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# Generate private key
|
|
312
|
+
openssl genrsa -out private.pem 2048
|
|
313
|
+
|
|
314
|
+
# Generate public key
|
|
315
|
+
openssl rsa -in private.pem -pubout -out public.pem
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Configure with key paths or content:
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
// Using file paths
|
|
322
|
+
await fastify.register(xAuthLocal, {
|
|
323
|
+
configs: [
|
|
324
|
+
{
|
|
325
|
+
name: "api",
|
|
326
|
+
prefix: "/api",
|
|
327
|
+
publicKey: "./keys/public.pem",
|
|
328
|
+
privateKey: "./keys/private.pem",
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Using key content (e.g., from environment)
|
|
334
|
+
await fastify.register(xAuthLocal, {
|
|
335
|
+
configs: [
|
|
336
|
+
{
|
|
337
|
+
name: "api",
|
|
338
|
+
prefix: "/api",
|
|
339
|
+
publicKey: process.env.JWT_PUBLIC_KEY,
|
|
340
|
+
privateKey: process.env.JWT_PRIVATE_KEY,
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Optional Authentication
|
|
347
|
+
|
|
348
|
+
Allow routes to work with or without authentication:
|
|
349
|
+
|
|
350
|
+
```javascript
|
|
351
|
+
await fastify.register(xAuthLocal, {
|
|
352
|
+
configs: [
|
|
353
|
+
{
|
|
354
|
+
name: "api",
|
|
355
|
+
prefix: "/api",
|
|
356
|
+
secret: process.env.JWT_SECRET,
|
|
357
|
+
credentialsRequired: false,
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
fastify.get("/api/posts", async (request) => {
|
|
363
|
+
if (request.auth) {
|
|
364
|
+
// Authenticated user - return personalized content
|
|
365
|
+
return getPersonalizedPosts(request.auth.id);
|
|
366
|
+
}
|
|
367
|
+
// Anonymous user - return public content
|
|
368
|
+
return getPublicPosts();
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Complete Example
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
import Fastify from "fastify";
|
|
376
|
+
import xAuthLocal from "xauthlocal";
|
|
377
|
+
|
|
378
|
+
const fastify = Fastify({ logger: true });
|
|
379
|
+
|
|
380
|
+
// In-memory users for demo
|
|
381
|
+
const users = new Map();
|
|
382
|
+
const admins = new Map();
|
|
383
|
+
|
|
384
|
+
await fastify.register(xAuthLocal, {
|
|
385
|
+
configs: [
|
|
386
|
+
{
|
|
387
|
+
name: "api",
|
|
388
|
+
prefix: "/api",
|
|
389
|
+
secret: process.env.API_SECRET || "api-development-secret",
|
|
390
|
+
expiresIn: "7d",
|
|
391
|
+
excludedPaths: ["/api/health", { url: "/api/public", methods: ["GET"] }],
|
|
392
|
+
local: {
|
|
393
|
+
enabled: true,
|
|
394
|
+
userLookup: async (email) => users.get(email),
|
|
395
|
+
createUser: async (userData) => {
|
|
396
|
+
const user = { id: Date.now(), ...userData, scope: ["user"] };
|
|
397
|
+
users.set(userData.email, user);
|
|
398
|
+
return user;
|
|
399
|
+
},
|
|
400
|
+
passwordReset: async (email) => {
|
|
401
|
+
console.log(`Password reset requested for ${email}`);
|
|
402
|
+
},
|
|
403
|
+
skipUserLookup: true, // Fast /me endpoint
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "admin",
|
|
408
|
+
prefix: "/admin",
|
|
409
|
+
secret: process.env.ADMIN_SECRET || "admin-development-secret",
|
|
410
|
+
expiresIn: "1d",
|
|
411
|
+
local: {
|
|
412
|
+
enabled: true,
|
|
413
|
+
userLookup: async (email) => admins.get(email),
|
|
414
|
+
skipUserLookup: false, // Always fetch fresh admin data
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Public routes
|
|
421
|
+
fastify.get("/api/health", async () => ({ status: "ok" }));
|
|
422
|
+
fastify.get("/api/public/info", async () => ({ version: "1.0.0" }));
|
|
423
|
+
|
|
424
|
+
// Protected API routes
|
|
425
|
+
fastify.get("/api/profile", async (request) => ({
|
|
426
|
+
id: request.auth.id,
|
|
427
|
+
email: request.auth.email,
|
|
428
|
+
}));
|
|
429
|
+
|
|
430
|
+
// Admin-only route
|
|
431
|
+
const adminConfig = fastify.xauthlocal.get("admin");
|
|
432
|
+
fastify.get(
|
|
433
|
+
"/admin/stats",
|
|
434
|
+
{ preHandler: [adminConfig.requireRole("admin")] },
|
|
435
|
+
async () => ({ users: users.size, admins: admins.size })
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
await fastify.listen({ port: 3000 });
|
|
439
|
+
|
|
440
|
+
console.log("Server running on http://localhost:3000");
|
|
441
|
+
console.log("API: POST /api/local to login, POST /api/local/register to create account");
|
|
442
|
+
console.log("Admin: POST /admin/local to login");
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Testing
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
npm test
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## License
|
|
452
|
+
|
|
453
|
+
ISC
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xenterprises/fastify-xauth-local",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fastify plugin for JWT authentication with role-based access control - compatible with Express JWT patterns",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/xAuthLocal.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/xAuthLocal.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test test/xAuthLocal.test.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"fastify",
|
|
15
|
+
"fastify-plugin",
|
|
16
|
+
"jwt",
|
|
17
|
+
"authentication",
|
|
18
|
+
"authorization",
|
|
19
|
+
"roles",
|
|
20
|
+
"rbac",
|
|
21
|
+
"local-auth"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "ISC",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"bcryptjs": "^2.4.3",
|
|
27
|
+
"fastify-plugin": "^5.0.1",
|
|
28
|
+
"jsonwebtoken": "^9.0.2"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"fastify": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"fastify": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|