sentri 1.0.5 → 1.1.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/README.md +182 -46
- package/dist/client.d.ts +43 -82
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +0 -7
- package/dist/client.js.map +1 -1
- package/dist/errors/AuthError.d.ts +9 -8
- package/dist/errors/AuthError.d.ts.map +1 -1
- package/dist/errors/AuthError.js +9 -8
- package/dist/errors/AuthError.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/libs/config.d.ts +45 -1
- package/dist/libs/config.d.ts.map +1 -1
- package/dist/libs/config.js +40 -2
- package/dist/libs/config.js.map +1 -1
- package/dist/libs/hash.d.ts +14 -0
- package/dist/libs/hash.d.ts.map +1 -1
- package/dist/libs/hash.js +14 -0
- package/dist/libs/hash.js.map +1 -1
- package/dist/libs/token.d.ts +40 -2
- package/dist/libs/token.d.ts.map +1 -1
- package/dist/libs/token.js +64 -0
- package/dist/libs/token.js.map +1 -1
- package/dist/middleware/authorize.d.ts +15 -0
- package/dist/middleware/authorize.d.ts.map +1 -1
- package/dist/middleware/authorize.js +15 -0
- package/dist/middleware/authorize.js.map +1 -1
- package/dist/middleware/protect.d.ts +27 -0
- package/dist/middleware/protect.d.ts.map +1 -1
- package/dist/middleware/protect.js +37 -2
- package/dist/middleware/protect.js.map +1 -1
- package/dist/middleware/router.d.ts +13 -6
- package/dist/middleware/router.d.ts.map +1 -1
- package/dist/middleware/router.js +49 -15
- package/dist/middleware/router.js.map +1 -1
- package/dist/services/auth.d.ts +77 -0
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +81 -2
- package/dist/services/auth.js.map +1 -1
- package/dist/types/auth.d.ts +189 -3
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/auth.js.map +1 -1
- package/package.json +15 -4
- package/templates/drizzle/adapter.ts +3 -9
- package/templates/drizzle/auth.ts +20 -0
- package/templates/prisma/adapter.ts +3 -9
- package/templates/prisma/auth.ts +20 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# sentri
|
|
2
2
|
|
|
3
|
-
Auth and authorization library for Express + PostgreSQL. Provides JWT-based authentication with refresh token rotation, role-based access control, fine-grained permission checks, and a pre-built Express router — all behind a single typed client.
|
|
3
|
+
Auth and authorization library for Express + PostgreSQL. Provides JWT-based authentication with session-bound access tokens, refresh token rotation, role-based access control, fine-grained permission checks, and a pre-built Express router — all behind a single typed client.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,6 +10,7 @@ Auth and authorization library for Express + PostgreSQL. Provides JWT-based auth
|
|
|
10
10
|
- [Quick Start](#quick-start)
|
|
11
11
|
- [CLI](#cli)
|
|
12
12
|
- [Configuration](#configuration)
|
|
13
|
+
- [Custom Route Handlers](#custom-route-handlers)
|
|
13
14
|
- [Adapter Interface](#adapter-interface)
|
|
14
15
|
- [Pre-built Router](#pre-built-router)
|
|
15
16
|
- [Middleware](#middleware)
|
|
@@ -98,7 +99,7 @@ npx sentri generate drizzle
|
|
|
98
99
|
import { createAuth } from 'sentri';
|
|
99
100
|
|
|
100
101
|
export const auth = createAuth({
|
|
101
|
-
secret: process.env.JWT_SECRET!, // required — keep in env
|
|
102
|
+
secret: process.env.JWT_SECRET!, // required — keep in env, min 32 chars
|
|
102
103
|
validRoles: ['user', 'admin'] as const, // required — use `as const` for type safety
|
|
103
104
|
adapter: myAdapter, // required — see Adapter Interface
|
|
104
105
|
|
|
@@ -108,6 +109,10 @@ export const auth = createAuth({
|
|
|
108
109
|
algorithm: 'HS256', // default: 'HS256' — also 'HS384' | 'HS512'
|
|
109
110
|
saltRounds: 12, // default: 12 (bcrypt rounds, min 10)
|
|
110
111
|
|
|
112
|
+
// Restrict POST /register to callers that supply X-Api-Key header.
|
|
113
|
+
// When set, only requests with this exact key can create new accounts.
|
|
114
|
+
apiKey: process.env.REGISTER_API_KEY, // optional
|
|
115
|
+
|
|
111
116
|
cookie: { // optional — enables httpOnly cookie for refresh token
|
|
112
117
|
secure: process.env.NODE_ENV === 'production',
|
|
113
118
|
// name: 'refresh_token', // default: 'refresh_token'
|
|
@@ -115,6 +120,15 @@ export const auth = createAuth({
|
|
|
115
120
|
// sameSite: 'strict', // default: 'strict'
|
|
116
121
|
// path: '/', // default: '/'
|
|
117
122
|
},
|
|
123
|
+
|
|
124
|
+
// router: { // optional — replace built-in service logic per route
|
|
125
|
+
// login: async (input) => { ... },
|
|
126
|
+
// register: async (input) => { ... },
|
|
127
|
+
// refresh: async (refreshToken) => { ... },
|
|
128
|
+
// logout: async (refreshToken) => { ... },
|
|
129
|
+
// logoutAll: async (userId) => { ... },
|
|
130
|
+
// assignRoles: async (userId, roles) => { ... },
|
|
131
|
+
// },
|
|
118
132
|
});
|
|
119
133
|
```
|
|
120
134
|
|
|
@@ -124,6 +138,111 @@ When `cookie` is configured, the refresh token is stored in an httpOnly cookie a
|
|
|
124
138
|
|
|
125
139
|
---
|
|
126
140
|
|
|
141
|
+
## apiKey — Restricting Registration
|
|
142
|
+
|
|
143
|
+
By default `POST /register` is open to the public. This can be a security risk when your application allows role selection at registration time — any caller could register themselves as `admin`.
|
|
144
|
+
|
|
145
|
+
Set `apiKey` in your config to lock the endpoint:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
export const auth = createAuth({
|
|
149
|
+
// ...
|
|
150
|
+
apiKey: process.env.REGISTER_API_KEY!,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Requests to `POST /register` must then include the header:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
X-Api-Key: <value of REGISTER_API_KEY>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Requests without the header, or with the wrong value, receive HTTP 401 `UNAUTHORIZED`. Keep the API key in an environment variable and share it only with trusted services (your back-office panel, CI scripts, etc.).
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Session-Bound Access Tokens
|
|
165
|
+
|
|
166
|
+
Since version 1.1.0, access tokens embed the `sessionId` of the session that was created at login. The `protect()` middleware validates this session against the database on every request.
|
|
167
|
+
|
|
168
|
+
**What this means in practice:**
|
|
169
|
+
|
|
170
|
+
- `POST /logout` deletes the session. Any access token issued during that login is immediately rejected — even if it has not expired yet.
|
|
171
|
+
- `POST /logout-all` deletes **all** sessions for the user. Every access token across all devices is immediately rejected.
|
|
172
|
+
- Tokens issued before 1.1.0 (without the `sessionId` claim) are still accepted but bypass session validation — plan a rolling upgrade if you need strict enforcement for existing tokens.
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
Login → session created → access token embeds sessionId
|
|
176
|
+
Request → protect() verifies JWT → checks session exists → ✓ allowed
|
|
177
|
+
Logout → session deleted
|
|
178
|
+
Request → protect() verifies JWT → session not found → ✗ 401 UNAUTHORIZED
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
> **Trade-off:** `protect()` now performs one additional database read per request. For most applications this is negligible. If you need to avoid any per-request DB access, keep `accessExpiresIn` short (e.g. `'5m'`) and rely on token expiry instead — but note that tokens will remain valid for up to `accessExpiresIn` after logout.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Custom Route Handlers
|
|
186
|
+
|
|
187
|
+
The `router` field in config lets you replace the built-in service logic for individual routes while the router still handles request parsing, input validation, and response formatting.
|
|
188
|
+
|
|
189
|
+
Each key is optional — only override what you need. Any key you omit falls back to the built-in behaviour.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { createAuth, AuthError } from 'sentri';
|
|
193
|
+
import type { AuthResult } from 'sentri';
|
|
194
|
+
|
|
195
|
+
export const auth = createAuth({
|
|
196
|
+
secret: process.env.JWT_SECRET!,
|
|
197
|
+
validRoles: ['user', 'admin'] as const,
|
|
198
|
+
adapter: myAdapter,
|
|
199
|
+
|
|
200
|
+
router: {
|
|
201
|
+
// Add an OTP check before issuing tokens
|
|
202
|
+
login: async (input): Promise<AuthResult> => {
|
|
203
|
+
const otpVerified = await redis.get(`otp:${input.identifier}`);
|
|
204
|
+
if (!otpVerified) {
|
|
205
|
+
return { success: false, error: new AuthError('INVALID_CREDENTIALS', 'OTP required') };
|
|
206
|
+
}
|
|
207
|
+
return defaultLogin(input);
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// Send a welcome email after registration
|
|
211
|
+
register: async (input) => {
|
|
212
|
+
const result = await defaultRegister(input);
|
|
213
|
+
if (result.success) {
|
|
214
|
+
await emailService.sendWelcome(input.identifier);
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// Audit-log every token rotation
|
|
220
|
+
refresh: async (refreshToken) => {
|
|
221
|
+
const result = await defaultRefresh(refreshToken);
|
|
222
|
+
if (result.success) {
|
|
223
|
+
await auditLog.record('token_rotated', result.user.id);
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Available handler signatures
|
|
232
|
+
|
|
233
|
+
| Key | Signature | Must return |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `register` | `(input: SignupInput) => Promise<SignupResult>` | `SignupResult` |
|
|
236
|
+
| `login` | `(input: LoginInput) => Promise<AuthResult>` | `AuthResult` |
|
|
237
|
+
| `refresh` | `(refreshToken: string) => Promise<RefreshResult>` | `RefreshResult` |
|
|
238
|
+
| `logout` | `(refreshToken: string \| undefined) => Promise<void>` | `void` |
|
|
239
|
+
| `logoutAll` | `(userId: string) => Promise<void>` | `void` |
|
|
240
|
+
| `assignRoles` | `(userId: string, roles: string[]) => Promise<AssignRolesResult>` | `AssignRolesResult` |
|
|
241
|
+
|
|
242
|
+
The router always validates the request body and URL parameters before calling any handler. Your function receives the already-validated, trimmed input.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
127
246
|
## Adapter Interface
|
|
128
247
|
|
|
129
248
|
The adapter connects sentri to your database. Implement `AuthAdapter` for any ORM or data layer.
|
|
@@ -168,7 +287,7 @@ import { createAdapter } from './adapter.js';
|
|
|
168
287
|
export const adapter = createAdapter(db);
|
|
169
288
|
```
|
|
170
289
|
|
|
171
|
-
`createAdapter` throws
|
|
290
|
+
`createAdapter` throws `AuthError` with code `CONFIGURATION_ERROR` at runtime if called without a `db` argument.
|
|
172
291
|
|
|
173
292
|
---
|
|
174
293
|
|
|
@@ -187,11 +306,12 @@ export const adapter = createAdapter(db);
|
|
|
187
306
|
|
|
188
307
|
### Endpoints
|
|
189
308
|
|
|
190
|
-
#### `POST /
|
|
309
|
+
#### `POST /register`
|
|
191
310
|
|
|
192
|
-
Register a new user. Does **not** issue tokens — call `/login` after
|
|
311
|
+
Register a new user. Does **not** issue tokens — call `/login` after registration.
|
|
193
312
|
|
|
194
313
|
```
|
|
314
|
+
Headers: X-Api-Key: <key> (required when config.apiKey is set)
|
|
195
315
|
Body: { identifier, password, roles?: string[] }
|
|
196
316
|
Returns: { user: { id, identifier, roles } }
|
|
197
317
|
Status: 201
|
|
@@ -239,7 +359,7 @@ Returns: null
|
|
|
239
359
|
Status: 200
|
|
240
360
|
```
|
|
241
361
|
|
|
242
|
-
Safe to call even if the cookie is missing or the token is already expired.
|
|
362
|
+
After logout, any access token bound to this session is immediately rejected by `protect()`. Safe to call even if the cookie is missing or the token is already expired.
|
|
243
363
|
|
|
244
364
|
---
|
|
245
365
|
|
|
@@ -253,6 +373,8 @@ Returns: null
|
|
|
253
373
|
Status: 200
|
|
254
374
|
```
|
|
255
375
|
|
|
376
|
+
All access tokens across all devices are immediately rejected by `protect()` after this call.
|
|
377
|
+
|
|
256
378
|
---
|
|
257
379
|
|
|
258
380
|
#### `GET /me`
|
|
@@ -284,14 +406,19 @@ Status: 200
|
|
|
284
406
|
|
|
285
407
|
### `auth.protect()`
|
|
286
408
|
|
|
287
|
-
Verifies the `Authorization: Bearer <token>` header and injects `
|
|
409
|
+
Verifies the `Authorization: Bearer <token>` header, confirms the session is still active in the database, and injects `request.user` into the request.
|
|
288
410
|
|
|
289
411
|
```typescript
|
|
290
|
-
router.get('/dashboard', auth.protect(), (
|
|
291
|
-
|
|
412
|
+
router.get('/dashboard', auth.protect(), (request, response) => {
|
|
413
|
+
response.json(request.user); // { id, identifier, roles }
|
|
292
414
|
});
|
|
293
415
|
```
|
|
294
416
|
|
|
417
|
+
Returns HTTP 401 if:
|
|
418
|
+
- The `Authorization` header is missing or malformed
|
|
419
|
+
- The token signature is invalid or the token is expired
|
|
420
|
+
- The session embedded in the token has been revoked (logout)
|
|
421
|
+
|
|
295
422
|
---
|
|
296
423
|
|
|
297
424
|
### `auth.authorize(...roles)`
|
|
@@ -341,43 +468,21 @@ router.delete(
|
|
|
341
468
|
|
|
342
469
|
## Programmatic API
|
|
343
470
|
|
|
344
|
-
|
|
471
|
+
Token and password utilities are available on the auth client for use outside the built-in router.
|
|
345
472
|
|
|
346
473
|
```typescript
|
|
347
|
-
// Auth
|
|
348
|
-
const result = await auth.signup({ identifier: 'user@example.com', password: 'secret123' });
|
|
349
|
-
const result = await auth.login({ identifier: 'user@example.com', password: 'secret123' });
|
|
350
|
-
const result = await auth.refresh(refreshToken);
|
|
351
|
-
await auth.logout(refreshToken);
|
|
352
|
-
await auth.logoutAll(userId);
|
|
353
|
-
|
|
354
|
-
// Roles
|
|
355
|
-
const result = await auth.assignRoles(userId, ['admin']);
|
|
356
|
-
// Merges with existing roles — result.user.roles has the full updated list
|
|
357
|
-
|
|
358
474
|
// Token utilities
|
|
359
475
|
const accessToken = auth.signAccessToken({ id, identifier, roles });
|
|
360
476
|
const user = auth.verifyAccessToken(accessToken); // throws AuthError if invalid
|
|
361
477
|
const { sessionId } = auth.verifyRefreshToken(token); // throws AuthError if invalid
|
|
478
|
+
const refreshToken = auth.signRefreshToken(sessionId);
|
|
362
479
|
|
|
363
480
|
// Password utilities
|
|
364
481
|
const hash = await auth.hashPassword('secret123');
|
|
365
482
|
const valid = await auth.verifyPassword('secret123', hash);
|
|
366
483
|
```
|
|
367
484
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
```typescript
|
|
371
|
-
const result = await auth.login({ identifier, password });
|
|
372
|
-
|
|
373
|
-
if (!result.success) {
|
|
374
|
-
console.error(result.error.code); // 'INVALID_CREDENTIALS'
|
|
375
|
-
console.error(result.error.message);
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const { accessToken, refreshToken, user } = result;
|
|
380
|
-
```
|
|
485
|
+
`verifyAccessToken` and `verifyRefreshToken` throw `AuthError` with code `TOKEN_EXPIRED` or `TOKEN_INVALID` — wrap them in a try/catch or use the router which handles this automatically.
|
|
381
486
|
|
|
382
487
|
---
|
|
383
488
|
|
|
@@ -396,6 +501,7 @@ import type {
|
|
|
396
501
|
ApiResponse,
|
|
397
502
|
SignupInput,
|
|
398
503
|
LoginInput,
|
|
504
|
+
RouterHandlers,
|
|
399
505
|
UserRecord,
|
|
400
506
|
SessionRecord,
|
|
401
507
|
CreateUserData,
|
|
@@ -417,11 +523,11 @@ All errors thrown by the library are instances of `AuthError` with a machine-rea
|
|
|
417
523
|
| Code | HTTP | Meaning |
|
|
418
524
|
|---|---|---|
|
|
419
525
|
| `INVALID_CREDENTIALS` | 401 | Wrong identifier or password |
|
|
420
|
-
| `USER_ALREADY_EXISTS` | 409 |
|
|
526
|
+
| `USER_ALREADY_EXISTS` | 409 | Registration with duplicate identifier |
|
|
421
527
|
| `USER_NOT_FOUND` | 404 | Operation on a non-existent user |
|
|
422
528
|
| `TOKEN_EXPIRED` | 401 | JWT `exp` claim is in the past |
|
|
423
529
|
| `TOKEN_INVALID` | 401 | JWT signature invalid or malformed |
|
|
424
|
-
| `UNAUTHORIZED` | 401 | No valid access token
|
|
530
|
+
| `UNAUTHORIZED` | 401 | No valid access token, revoked session, or invalid API key |
|
|
425
531
|
| `FORBIDDEN` | 403 | Authenticated but missing required role |
|
|
426
532
|
| `INVALID_ROLE` | 400 | Role name not in `validRoles` |
|
|
427
533
|
| `VALIDATION_ERROR` | 400 | Missing or invalid input field |
|
|
@@ -432,17 +538,47 @@ The built-in router converts all `AuthError` instances to the standard envelope
|
|
|
432
538
|
```typescript
|
|
433
539
|
import { AuthError } from 'sentri';
|
|
434
540
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
541
|
+
const AUTH_ERROR_STATUS: Record<string, number> = {
|
|
542
|
+
UNAUTHORIZED: 401,
|
|
543
|
+
TOKEN_EXPIRED: 401,
|
|
544
|
+
TOKEN_INVALID: 401,
|
|
545
|
+
INVALID_CREDENTIALS: 401,
|
|
546
|
+
FORBIDDEN: 403,
|
|
547
|
+
USER_NOT_FOUND: 404,
|
|
548
|
+
USER_ALREADY_EXISTS: 409,
|
|
549
|
+
INVALID_ROLE: 400,
|
|
550
|
+
VALIDATION_ERROR: 400,
|
|
551
|
+
CONFIGURATION_ERROR: 500,
|
|
552
|
+
};
|
|
443
553
|
|
|
444
|
-
|
|
554
|
+
app.use((error, _request, response, next) => {
|
|
555
|
+
if (error instanceof AuthError) {
|
|
556
|
+
const statusCode = AUTH_ERROR_STATUS[error.code] ?? 500;
|
|
557
|
+
return response.status(statusCode).json({
|
|
558
|
+
error: true,
|
|
559
|
+
statusCode,
|
|
560
|
+
code: error.code,
|
|
561
|
+
message: error.message,
|
|
562
|
+
data: null,
|
|
563
|
+
});
|
|
445
564
|
}
|
|
446
|
-
next(
|
|
565
|
+
next(error);
|
|
447
566
|
});
|
|
448
567
|
```
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## Migration from 1.0.x
|
|
572
|
+
|
|
573
|
+
### Breaking changes
|
|
574
|
+
|
|
575
|
+
| What changed | Action required |
|
|
576
|
+
|---|---|
|
|
577
|
+
| `POST /signup` renamed to `POST /register` | Update all client-side calls |
|
|
578
|
+
| `RouterHandlers.signup` renamed to `RouterHandlers.register` | Update config if you used a custom signup handler |
|
|
579
|
+
| `protect()` now performs one DB read per request | Ensure your adapter's `session.findById` is indexed on session ID |
|
|
580
|
+
|
|
581
|
+
### New features
|
|
582
|
+
|
|
583
|
+
- **Session-bound tokens** — access tokens are immediately invalidated after logout without waiting for expiry.
|
|
584
|
+
- **`apiKey` config** — lock `POST /register` to trusted callers via `X-Api-Key` header.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PermitCheck, PermitOptions } from './middleware/permit.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AuthConfig, AuthUser } from './types/auth.js';
|
|
3
3
|
import type { RequestHandler, Router } from 'express';
|
|
4
4
|
/**
|
|
5
5
|
* The bound auth client returned by {@link createAuth}.
|
|
@@ -8,61 +8,18 @@ import type { RequestHandler, Router } from 'express';
|
|
|
8
8
|
* you never need to pass config around yourself.
|
|
9
9
|
*
|
|
10
10
|
* `TRole` is inferred from `validRoles` and narrows role strings to your
|
|
11
|
-
* application's exact union type everywhere (
|
|
11
|
+
* application's exact union type everywhere (authorize, req.user, etc.).
|
|
12
12
|
*/
|
|
13
13
|
export interface AuthClient<TRole extends string = string> {
|
|
14
|
-
/**
|
|
15
|
-
* Register a new user.
|
|
16
|
-
*
|
|
17
|
-
* Validates that every requested role is in `validRoles`, rejects duplicate
|
|
18
|
-
* identifiers, hashes the password, creates the user record, and returns the
|
|
19
|
-
* created user. No tokens are issued — call `login` after signup.
|
|
20
|
-
*/
|
|
21
|
-
signup(input: SignupInput<TRole>): Promise<SignupResult<TRole>>;
|
|
22
|
-
/**
|
|
23
|
-
* Authenticate an existing user by email and password.
|
|
24
|
-
*
|
|
25
|
-
* On success, creates a new session and returns an access + refresh token pair.
|
|
26
|
-
* Returns `{ success: false, error }` with code `INVALID_CREDENTIALS` on any
|
|
27
|
-
* mismatch (intentionally vague to prevent user enumeration).
|
|
28
|
-
*/
|
|
29
|
-
login(input: LoginInput): Promise<AuthResult<TRole>>;
|
|
30
|
-
/**
|
|
31
|
-
* Exchange a valid refresh token for a new access + refresh token pair.
|
|
32
|
-
*
|
|
33
|
-
* Verifies the JWT, looks up the session in the database, checks expiry,
|
|
34
|
-
* then **rotates** the session: the old session is deleted and a new one is
|
|
35
|
-
* created. Old refresh tokens are immediately invalidated.
|
|
36
|
-
*/
|
|
37
|
-
refresh(refreshToken: string): Promise<RefreshResult<TRole>>;
|
|
38
|
-
/**
|
|
39
|
-
* Invalidate a single session identified by the refresh token.
|
|
40
|
-
*
|
|
41
|
-
* Safe to call even if the token is already expired — it will simply
|
|
42
|
-
* attempt a DB delete and resolve.
|
|
43
|
-
*/
|
|
44
|
-
logout(refreshToken: string): Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
* Delete all sessions for a user, effectively logging them out of every device.
|
|
47
|
-
*
|
|
48
|
-
* @param userId - The user's primary key as stored in the database.
|
|
49
|
-
*/
|
|
50
|
-
logoutAll(userId: string): Promise<void>;
|
|
51
|
-
/**
|
|
52
|
-
* Add roles to another user. Merges the given roles with the user's existing
|
|
53
|
-
* roles (no duplicates). The built-in router exposes this as
|
|
54
|
-
* `POST /users/:userId/roles` and restricts it to users with the `admin` role.
|
|
55
|
-
*/
|
|
56
|
-
assignRoles(userId: string, roles: string[]): Promise<AssignRolesResult<TRole>>;
|
|
57
14
|
/**
|
|
58
15
|
* Express middleware factory that enforces authentication.
|
|
59
16
|
*
|
|
60
17
|
* Reads the `Authorization: Bearer <token>` header, verifies the access token,
|
|
61
|
-
* and injects the decoded payload as `
|
|
18
|
+
* and injects the decoded payload as `request.user`. Calls `next(AuthError)` on failure.
|
|
62
19
|
*
|
|
63
20
|
* @example
|
|
64
|
-
* router.get('/me', auth.protect(), (
|
|
65
|
-
*
|
|
21
|
+
* router.get('/me', auth.protect(), (request, response) => {
|
|
22
|
+
* response.json(request.user);
|
|
66
23
|
* });
|
|
67
24
|
*/
|
|
68
25
|
protect(): RequestHandler;
|
|
@@ -77,36 +34,6 @@ export interface AuthClient<TRole extends string = string> {
|
|
|
77
34
|
* router.delete('/posts/:id', auth.protect(), auth.authorize('admin'), handler);
|
|
78
35
|
*/
|
|
79
36
|
authorize(...roles: TRole[]): RequestHandler;
|
|
80
|
-
/** Hash a plain-text password using the configured `saltRounds`. */
|
|
81
|
-
hashPassword(plain: string): Promise<string>;
|
|
82
|
-
/** Compare a plain-text password against a stored bcrypt hash. */
|
|
83
|
-
verifyPassword(plain: string, hash: string): Promise<boolean>;
|
|
84
|
-
/** Sign an access token for the given user payload. */
|
|
85
|
-
signAccessToken(payload: AuthUser<TRole>): string;
|
|
86
|
-
/** Sign a refresh token bound to a session ID. */
|
|
87
|
-
signRefreshToken(sessionId: string): string;
|
|
88
|
-
/** Verify and decode an access token. Throws `AuthError` if invalid or expired. */
|
|
89
|
-
verifyAccessToken(token: string): AuthUser<TRole>;
|
|
90
|
-
/** Verify and decode a refresh token. Throws `AuthError` if invalid or expired. */
|
|
91
|
-
verifyRefreshToken(token: string): {
|
|
92
|
-
sessionId: string;
|
|
93
|
-
};
|
|
94
|
-
/**
|
|
95
|
-
* Returns a pre-built Express Router with all standard auth endpoints mounted:
|
|
96
|
-
*
|
|
97
|
-
* - `POST /signup` — register, returns `{ user }`
|
|
98
|
-
* - `POST /login` — authenticate, sets refresh token cookie, returns `{ accessToken, user }`
|
|
99
|
-
* - `POST /refresh` — reads refresh token from cookie, returns `{ accessToken }`
|
|
100
|
-
* - `POST /logout` — invalidate current session
|
|
101
|
-
* - `POST /logout-all` — invalidate all sessions (requires valid access token)
|
|
102
|
-
*
|
|
103
|
-
* Requires `express.json()` to be applied before the router.
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* app.use(express.json());
|
|
107
|
-
* app.use('/auth', auth.router());
|
|
108
|
-
*/
|
|
109
|
-
router(): Router;
|
|
110
37
|
/**
|
|
111
38
|
* Express middleware factory for resource-level permission checks.
|
|
112
39
|
*
|
|
@@ -120,7 +47,7 @@ export interface AuthClient<TRole extends string = string> {
|
|
|
120
47
|
* // User can only update their own profile
|
|
121
48
|
* router.put('/users/:id',
|
|
122
49
|
* auth.protect(),
|
|
123
|
-
* auth.permit((
|
|
50
|
+
* auth.permit((request) => request.user!.id === request.params['id']),
|
|
124
51
|
* handler,
|
|
125
52
|
* );
|
|
126
53
|
*
|
|
@@ -130,9 +57,9 @@ export interface AuthClient<TRole extends string = string> {
|
|
|
130
57
|
* auth.protect(),
|
|
131
58
|
* auth.permit({
|
|
132
59
|
* roles: ['admin'],
|
|
133
|
-
* check: async (
|
|
134
|
-
* const post = await db.post.findUnique({ where: { id:
|
|
135
|
-
* return post?.authorId ===
|
|
60
|
+
* check: async (request) => {
|
|
61
|
+
* const post = await db.post.findUnique({ where: { id: request.params['id'] } });
|
|
62
|
+
* return post?.authorId === request.user!.id;
|
|
136
63
|
* },
|
|
137
64
|
* }),
|
|
138
65
|
* handler,
|
|
@@ -140,6 +67,40 @@ export interface AuthClient<TRole extends string = string> {
|
|
|
140
67
|
*/
|
|
141
68
|
permit(check: PermitCheck): RequestHandler;
|
|
142
69
|
permit(options: PermitOptions<TRole>): RequestHandler;
|
|
70
|
+
/** Hash a plain-text password using the configured `saltRounds`. */
|
|
71
|
+
hashPassword(plain: string): Promise<string>;
|
|
72
|
+
/** Compare a plain-text password against a stored bcrypt hash. */
|
|
73
|
+
verifyPassword(plain: string, hash: string): Promise<boolean>;
|
|
74
|
+
/** Sign an access token for the given user payload. */
|
|
75
|
+
signAccessToken(payload: AuthUser<TRole>): string;
|
|
76
|
+
/** Sign a refresh token bound to a session ID. */
|
|
77
|
+
signRefreshToken(sessionId: string): string;
|
|
78
|
+
/** Verify and decode an access token. Throws `AuthError` if invalid or expired. */
|
|
79
|
+
verifyAccessToken(token: string): AuthUser<TRole>;
|
|
80
|
+
/** Verify and decode a refresh token. Throws `AuthError` if invalid or expired. */
|
|
81
|
+
verifyRefreshToken(token: string): {
|
|
82
|
+
sessionId: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Returns a pre-built Express Router with all standard auth endpoints mounted:
|
|
86
|
+
*
|
|
87
|
+
* - `POST /register` — register a new user, returns `{ user }`.
|
|
88
|
+
* Requires `X-Api-Key` header when `config.apiKey` is set.
|
|
89
|
+
* - `POST /login` — authenticate, sets refresh token cookie, returns `{ accessToken, user }`
|
|
90
|
+
* - `POST /refresh` — reads refresh token from cookie, returns `{ accessToken }`
|
|
91
|
+
* - `POST /logout` — invalidate the current session; any access token bound to
|
|
92
|
+
* this session is immediately rejected by `protect()`.
|
|
93
|
+
* - `POST /logout-all` — invalidate all sessions for the user (requires valid access token)
|
|
94
|
+
* - `GET /me` — return the authenticated user
|
|
95
|
+
* - `POST /users/:userId/roles` — assign roles (requires admin)
|
|
96
|
+
*
|
|
97
|
+
* Requires `express.json()` to be applied before the router.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* app.use(express.json());
|
|
101
|
+
* app.use('/auth', auth.router());
|
|
102
|
+
*/
|
|
103
|
+
router(): Router;
|
|
143
104
|
}
|
|
144
105
|
/**
|
|
145
106
|
* Create a fully configured auth client for your application.
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACvD;;;;;;;;;;OAUG;IACH,OAAO,IAAI,cAAc,CAAC;IAE1B;;;;;;;;;OASG;IACH,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC;IAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,cAAc,CAAC;IAC3C,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC;IAEtD,oEAAoE;IACpE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE7C,kEAAkE;IAClE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9D,uDAAuD;IACvD,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;IAElD,kDAAkD;IAClD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5C,mFAAmF;IACnF,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,mFAAmF;IACnF,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzD;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,IAAI,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,EACtD,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GACxB,UAAU,CAAC,KAAK,CAAC,CAgBnB"}
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { hashPassword, verifyPassword } from './libs/hash.js';
|
|
2
2
|
import { signAccessToken, signRefreshToken, verifyAccessToken, verifyRefreshToken } from './libs/token.js';
|
|
3
3
|
import { resolveConfig, validateConfig } from './libs/config.js';
|
|
4
|
-
import { signup, login, refresh, logout, logoutAll, assignRoles } from './services/auth.js';
|
|
5
4
|
import { protect } from './middleware/protect.js';
|
|
6
5
|
import { authorize } from './middleware/authorize.js';
|
|
7
6
|
import { permit } from './middleware/permit.js';
|
|
@@ -29,12 +28,6 @@ export function createAuth(config) {
|
|
|
29
28
|
validateConfig(config);
|
|
30
29
|
const resolved = resolveConfig(config);
|
|
31
30
|
return {
|
|
32
|
-
signup: (input) => signup(input, config),
|
|
33
|
-
login: (input) => login(input, config),
|
|
34
|
-
refresh: (refreshToken) => refresh(refreshToken, config),
|
|
35
|
-
logout: (refreshToken) => logout(refreshToken, config),
|
|
36
|
-
logoutAll: (userId) => logoutAll(userId, config),
|
|
37
|
-
assignRoles: (userId, roles) => assignRoles(userId, roles, config),
|
|
38
31
|
protect: () => protect(config),
|
|
39
32
|
authorize: (...roles) => authorize(...roles),
|
|
40
33
|
hashPassword: (plain) => hashPassword(plain, resolved.saltRounds),
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAkH1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,UAAU,CACxB,MAAyB;IAEzB,cAAc,CAAC,MAAoB,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAoB,CAAC,CAAC;IAErD,OAAO;QACL,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAoB,CAAC;QAC5C,SAAS,EAAE,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;QAC5C,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC;QACjE,cAAc,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC;QAC5D,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,OAAmB,EAAE,MAAoB,CAAC;QACxF,gBAAgB,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAoB,CAAC;QAClF,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAoB,CAAoB;QAC/F,kBAAkB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAoB,CAAC;QAC9E,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC;QACtC,MAAM,EAAE,CAAC,cAAkD,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC;KACvF,CAAC;AACJ,CAAC"}
|
|
@@ -17,19 +17,20 @@ export type AuthErrorCode = 'INVALID_CREDENTIALS' | 'USER_NOT_FOUND' | 'USER_ALR
|
|
|
17
17
|
* Error class thrown by the library for all authentication and authorization failures.
|
|
18
18
|
*
|
|
19
19
|
* Carries a machine-readable `code` that lets you distinguish error types without
|
|
20
|
-
* string-matching on the message
|
|
20
|
+
* string-matching on the message.
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
23
|
-
* import { AuthError } from '
|
|
23
|
+
* import { AuthError } from 'sentri';
|
|
24
24
|
*
|
|
25
|
-
* app.use((
|
|
26
|
-
* if (
|
|
27
|
-
* const status =
|
|
28
|
-
*
|
|
25
|
+
* app.use((error, _request, response, next) => {
|
|
26
|
+
* if (error instanceof AuthError) {
|
|
27
|
+
* const status =
|
|
28
|
+
* error.code === 'UNAUTHORIZED' ? 401
|
|
29
|
+
* : error.code === 'FORBIDDEN' ? 403
|
|
29
30
|
* : 400;
|
|
30
|
-
*
|
|
31
|
+
* response.status(status).json({ error: error.code, message: error.message });
|
|
31
32
|
* } else {
|
|
32
|
-
* next(
|
|
33
|
+
* next(error);
|
|
33
34
|
* }
|
|
34
35
|
* });
|
|
35
36
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthError.d.ts","sourceRoot":"","sources":["../../src/errors/AuthError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GACrB,qBAAqB,GACrB,gBAAgB,GAChB,qBAAqB,GACrB,eAAe,GACf,eAAe,GACf,WAAW,GACX,cAAc,GACd,cAAc,GACd,kBAAkB,GAClB,qBAAqB,CAAC;AAE1B
|
|
1
|
+
{"version":3,"file":"AuthError.d.ts","sourceRoot":"","sources":["../../src/errors/AuthError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GACrB,qBAAqB,GACrB,gBAAgB,GAChB,qBAAqB,GACrB,eAAe,GACf,eAAe,GACf,WAAW,GACX,cAAc,GACd,cAAc,GACd,kBAAkB,GAClB,qBAAqB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAgB,IAAI,EAAE,aAAa,CAAC;gBAExB,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM;CAKjD"}
|
package/dist/errors/AuthError.js
CHANGED
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
* Error class thrown by the library for all authentication and authorization failures.
|
|
3
3
|
*
|
|
4
4
|
* Carries a machine-readable `code` that lets you distinguish error types without
|
|
5
|
-
* string-matching on the message
|
|
5
|
+
* string-matching on the message.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
|
-
* import { AuthError } from '
|
|
8
|
+
* import { AuthError } from 'sentri';
|
|
9
9
|
*
|
|
10
|
-
* app.use((
|
|
11
|
-
* if (
|
|
12
|
-
* const status =
|
|
13
|
-
*
|
|
10
|
+
* app.use((error, _request, response, next) => {
|
|
11
|
+
* if (error instanceof AuthError) {
|
|
12
|
+
* const status =
|
|
13
|
+
* error.code === 'UNAUTHORIZED' ? 401
|
|
14
|
+
* : error.code === 'FORBIDDEN' ? 403
|
|
14
15
|
* : 400;
|
|
15
|
-
*
|
|
16
|
+
* response.status(status).json({ error: error.code, message: error.message });
|
|
16
17
|
* } else {
|
|
17
|
-
* next(
|
|
18
|
+
* next(error);
|
|
18
19
|
* }
|
|
19
20
|
* });
|
|
20
21
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthError.js","sourceRoot":"","sources":["../../src/errors/AuthError.ts"],"names":[],"mappings":"AA0BA
|
|
1
|
+
{"version":3,"file":"AuthError.js","sourceRoot":"","sources":["../../src/errors/AuthError.ts"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClB,IAAI,CAAgB;IAEpC,YAAY,IAAmB,EAAE,OAAe;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ declare global {
|
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
export type { AuthConfig, CookieConfig, AuthUser,
|
|
9
|
+
export type { AuthConfig, CookieConfig, AuthUser, ApiResponse, AuthAdapter, UserRecord, SessionRecord, CreateUserData, RouterHandlers, SignupInput, LoginInput, SignupResult, AuthResult, RefreshResult, AssignRolesResult, } from './types/auth.js';
|
|
10
10
|
export type { AuthErrorCode } from './errors/AuthError.js';
|
|
11
11
|
export type { AuthClient } from './client.js';
|
|
12
12
|
export { AuthError } from './errors/AuthError.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE,QAAQ,CAAC;SACjB;KACF;CACF;AAED,YAAY,EACV,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE,QAAQ,CAAC;SACjB;KACF;CACF;AAED,YAAY,EACV,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,WAAW,EACX,UAAU,EACV,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,UAAU,EACV,YAAY,EACZ,UAAU,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|