@zudojs/auth 1.2.1 → 1.3.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 +73 -21
- package/dist/authErrors/authError.base.d.ts +6 -20
- package/dist/authErrors/authError.base.js +9 -44
- package/dist/authErrors/authError.throttle.d.ts +42 -0
- package/dist/authErrors/authError.throttle.js +69 -0
- package/dist/authErrors/index.d.ts +2 -1
- package/dist/authErrors/index.js +2 -1
- package/dist/authPassword/authPassword.rehash.d.ts +4 -2
- package/dist/authPassword/authPassword.rehash.js +24 -7
- package/dist/authProvider/authProvider.core.d.ts +30 -0
- package/dist/authProvider/authProvider.core.js +19 -2
- package/dist/authProvider/authProvider.external.d.ts +51 -0
- package/dist/authProvider/authProvider.external.js +50 -0
- package/dist/authProvider/authProvider.throttle.js +1 -1
- package/dist/authProvider/index.d.ts +1 -0
- package/dist/authProvider/index.js +1 -0
- package/dist/authUtils/authUtils.identifier.d.ts +21 -0
- package/dist/authUtils/authUtils.identifier.js +25 -0
- package/dist/authUtils/index.d.ts +3 -1
- package/dist/authUtils/index.js +3 -1
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -126,8 +126,22 @@ logout can revoke them. **The service therefore rejects a token without a
|
|
|
126
126
|
Accepting them by default let a session-less refresh chain outlive
|
|
127
127
|
`logoutAll()`.
|
|
128
128
|
|
|
129
|
+
### Login identifiers
|
|
130
|
+
|
|
131
|
+
`login()` normalizes the identifier before `findUser()` sees it: NFKC, trim,
|
|
132
|
+
and lower-case when it is an email address (a username keeps its case). So
|
|
133
|
+
`" Alice@Example.COM"` finds the account stored as `alice@example.com`, and
|
|
134
|
+
case variants share one lockout budget. Store identifiers through the same
|
|
135
|
+
exported `normalizeLoginIdentifier()` at registration. Pass
|
|
136
|
+
`normalizeIdentifier: false` to receive the raw string, or your own function.
|
|
137
|
+
|
|
129
138
|
### Brute-force lockout
|
|
130
139
|
|
|
140
|
+
A locked identifier gets `AccountLockedError` (`423`, `ERR_ACCOUNT_LOCKED`)
|
|
141
|
+
with `retryAfterSeconds` set to the time left on the lockout and a
|
|
142
|
+
`Retry-After` header in `error.headers`, which `@zudojs/http` copies onto the
|
|
143
|
+
response. `AuthRateLimitError` (`429`) carries `Retry-After` the same way.
|
|
144
|
+
|
|
131
145
|
A failure is reserved *before* the password is checked and cleared on
|
|
132
146
|
success, so a parallel burst gets exactly `maxFailedAttempts` guesses before
|
|
133
147
|
the lockout, not `maxAttemptsPerWindow`. Custom `LoginAttemptStore`s must make
|
|
@@ -143,6 +157,36 @@ unlocked failure streak after `failureTtlSeconds` of inactivity (default 900)
|
|
|
143
157
|
and caps the tracked identifiers at `maxEntries` (default 100 000, oldest
|
|
144
158
|
unlocked evicted first), so spraying identifiers cannot grow it without bound.
|
|
145
159
|
|
|
160
|
+
### Sessions for OAuth and other sign-ins
|
|
161
|
+
|
|
162
|
+
A user authenticated some other way — the OAuth callback of
|
|
163
|
+
[`@zudojs/auth-oauth`](../auth-oauth), a passkey, a magic link — gets a
|
|
164
|
+
session and tokens from `createSessionForUser()`, exactly as `login()` would
|
|
165
|
+
issue them, without a password check:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const auth = createAuthService({
|
|
169
|
+
/* … */
|
|
170
|
+
externalSessionMethods: ["oauth"], // off by default
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// In the OAuth callback, after state/PKCE were verified and the provider
|
|
174
|
+
// identity was mapped to one of your users:
|
|
175
|
+
const { tokens, sessionId } = await auth.createSessionForUser(user.id, {
|
|
176
|
+
method: "oauth",
|
|
177
|
+
metadata: { provider: "github" }, // stored on the session
|
|
178
|
+
ip: req.socket.remoteAddress,
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
It checks no credential — your code asserts the user is authenticated — so
|
|
183
|
+
it throws `AuthConfigurationError` unless the `method` is listed in
|
|
184
|
+
`externalSessionMethods`. It still loads the user with `findUserById()` and
|
|
185
|
+
refuses an unknown user (`InvalidCredentialsError`) or a deactivated one
|
|
186
|
+
(`AccountDeactivatedError`). **Never pass it a user id taken from the
|
|
187
|
+
request.** The session records `metadata.authMethod`, and `logout()` /
|
|
188
|
+
`logoutAll()` revoke it like any other.
|
|
189
|
+
|
|
146
190
|
### Refresh-token rotation
|
|
147
191
|
|
|
148
192
|
With a `revocationStore` configured, `refresh()` claims the presented token's
|
|
@@ -163,8 +207,11 @@ const decision = await auth.checkAccess({
|
|
|
163
207
|
});
|
|
164
208
|
```
|
|
165
209
|
|
|
166
|
-
When a `permissions` engine is configured the decision comes from it
|
|
167
|
-
|
|
210
|
+
When a `permissions` engine is configured the decision comes from it, with
|
|
211
|
+
`resourceOwnerId` passed as the resource `{ ownerId }`. An ownership policy
|
|
212
|
+
that should grant on its own must say `effect: "grant"` — since
|
|
213
|
+
`@zudojs/permissions` 1.4 an allowing policy only constrains what the roles
|
|
214
|
+
grant. When it is not, `checkAccess()` **throws** `AuthConfigurationError` rather than
|
|
168
215
|
guessing. Setting `allowInsecureFallbackGuard: true` opts into a built-in
|
|
169
216
|
fallback that grants a resource owner *every* permission and grants the
|
|
170
217
|
`fallbackAdminRole` (default `"admin"`) everything; its allowed results carry
|
|
@@ -210,7 +257,9 @@ with OWASP's N=2^14, r=8, p=5 row. Hashes written by earlier versions of this
|
|
|
210
257
|
package (`scrypt$N$r$p$…`, including the p=1 default and the param-less legacy
|
|
211
258
|
format) still verify, and `needsRehash()` returns `true` for every hash that is
|
|
212
259
|
not a current-parameter `@zudojs/crypto` scrypt hash, so they upgrade on the
|
|
213
|
-
next login. `hashPassword(
|
|
260
|
+
next login. A hash made by `@zudojs/crypto`'s own `hashPassword()` with its
|
|
261
|
+
defaults (same N, r, p; 16-byte salt, 32-byte key) is current too, and
|
|
262
|
+
`needsRehash()` returns `false` for it. `hashPassword("")` throws `AuthError` (`INVALID_INPUT`).
|
|
214
263
|
|
|
215
264
|
- `createAuthService()` validates its configuration up front: bad or
|
|
216
265
|
identical secrets, a non-positive or `NaN` `sessionTtlSeconds` /
|
|
@@ -244,23 +293,25 @@ throw.
|
|
|
244
293
|
|
|
245
294
|
Every error carries an accurate HTTP status and is safe to expose:
|
|
246
295
|
|
|
247
|
-
| Error | Status | Category |
|
|
248
|
-
| --- | --- | --- |
|
|
249
|
-
| `AuthError` | 401 | authentication |
|
|
250
|
-
| `InvalidCredentialsError` | 401 | authentication |
|
|
251
|
-
| `TokenExpiredError` | 401 | authentication |
|
|
252
|
-
| `TokenInvalidError` | 401 | authentication |
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
| `AccountDeactivatedError` | 403 | authorization |
|
|
256
|
-
| `AccessDeniedError` | 403 | authorization |
|
|
257
|
-
| `AccountLockedError` | 423 | rate_limit |
|
|
258
|
-
| `AuthRateLimitError` | 429 | rate_limit |
|
|
259
|
-
| `AuthConfigurationError` | 500 | configuration (not exposed) |
|
|
260
|
-
|
|
261
|
-
`AccountLockedError` and `AuthRateLimitError` carry
|
|
262
|
-
`metadata
|
|
263
|
-
carries `metadata.requiredPermission`.
|
|
296
|
+
| Error | Status | Code | Category |
|
|
297
|
+
| --- | --- | --- | --- |
|
|
298
|
+
| `AuthError` | 401 | `ERR_AUTHENTICATION` | authentication |
|
|
299
|
+
| `InvalidCredentialsError` | 401 | `ERR_INVALID_CREDENTIALS` | authentication |
|
|
300
|
+
| `TokenExpiredError` | 401 | `ERR_TOKEN_EXPIRED` | authentication |
|
|
301
|
+
| `TokenInvalidError` | 401 | `ERR_TOKEN_INVALID` | authentication |
|
|
302
|
+
| `TokenRevokedError` | 401 | `ERR_TOKEN_REVOKED` | authentication |
|
|
303
|
+
| `SessionExpiredError` | 401 | `ERR_SESSION_EXPIRED` | authentication |
|
|
304
|
+
| `AccountDeactivatedError` | 403 | `ERR_ACCOUNT_DEACTIVATED` | authorization |
|
|
305
|
+
| `AccessDeniedError` | 403 | `ERR_ACCESS_DENIED` | authorization |
|
|
306
|
+
| `AccountLockedError` | 423 | `ERR_ACCOUNT_LOCKED` | rate_limit |
|
|
307
|
+
| `AuthRateLimitError` | 429 | `ERR_RATE_LIMITED` | rate_limit |
|
|
308
|
+
| `AuthConfigurationError` | 500 | `ERR_CONFIGURATION_INVALID` | configuration (not exposed) |
|
|
309
|
+
|
|
310
|
+
`AccountLockedError` and `AuthRateLimitError` carry `retryAfterSeconds` (also
|
|
311
|
+
in `metadata`) and a `Retry-After` header in `headers`; `AccessDeniedError`
|
|
312
|
+
carries `metadata.requiredPermission`. Before 1.3, `TokenRevokedError` was
|
|
313
|
+
`403 ERR_FORBIDDEN`, and the lockout and deactivation errors shared
|
|
314
|
+
`ERR_FORBIDDEN` too, so a client could not tell them apart.
|
|
264
315
|
|
|
265
316
|
`login()` throws the same `InvalidCredentialsError` for an unknown user and a
|
|
266
317
|
wrong password, and performs equivalent scrypt work on both paths, so the
|
|
@@ -273,7 +324,8 @@ after the password has been proven correct.
|
|
|
273
324
|
[`@zudojs/auth-oauth`](../auth-oauth), which implements the
|
|
274
325
|
authorization-code flow with PKCE, mandatory `state`, and provider presets
|
|
275
326
|
for Google, GitHub, Microsoft, Apple and Discord. Nothing OAuth-related is
|
|
276
|
-
exported from this package any more
|
|
327
|
+
exported from this package any more; once the callback has identified the
|
|
328
|
+
user, `createSessionForUser()` issues the session.
|
|
277
329
|
- **No password-reset flow.** `generateRandomToken()` gives you a random
|
|
278
330
|
token; storage, expiry, single-use enforcement and constant-time comparison
|
|
279
331
|
are yours to build.
|
|
@@ -45,22 +45,16 @@ export declare class TokenInvalidError extends AuthError {
|
|
|
45
45
|
constructor(message?: string, options?: AuthErrorOptions);
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
* Token has been revoked
|
|
48
|
+
* Token has been revoked (logout, or a refresh token replayed after
|
|
49
|
+
* rotation).
|
|
50
|
+
*
|
|
51
|
+
* `401` with `ERR_TOKEN_REVOKED`: the credential is no longer valid, so the
|
|
52
|
+
* client must authenticate again. It was `403 ERR_FORBIDDEN`, which told a
|
|
53
|
+
* client that re-authenticating would not help.
|
|
49
54
|
*/
|
|
50
55
|
export declare class TokenRevokedError extends AuthError {
|
|
51
56
|
constructor(message?: string, options?: AuthErrorOptions);
|
|
52
57
|
}
|
|
53
|
-
/**
|
|
54
|
-
* User account is locked (too many failed attempts).
|
|
55
|
-
*
|
|
56
|
-
* `423 Locked`; `metadata.retryAfterSeconds` is intended for a `Retry-After`
|
|
57
|
-
* response header.
|
|
58
|
-
*/
|
|
59
|
-
export declare class AccountLockedError extends AuthError {
|
|
60
|
-
constructor(message?: string, options?: AuthErrorOptions & {
|
|
61
|
-
readonly retryAfterSeconds?: number;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
58
|
/**
|
|
65
59
|
* User account is deactivated.
|
|
66
60
|
*/
|
|
@@ -81,12 +75,4 @@ export declare class AccessDeniedError extends AuthError {
|
|
|
81
75
|
export declare class SessionExpiredError extends AuthError {
|
|
82
76
|
constructor(message?: string, options?: AuthErrorOptions);
|
|
83
77
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Rate limit exceeded for an auth endpoint.
|
|
86
|
-
*/
|
|
87
|
-
export declare class AuthRateLimitError extends AuthError {
|
|
88
|
-
constructor(message?: string, options?: AuthErrorOptions & {
|
|
89
|
-
readonly retryAfterSeconds?: number;
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
78
|
//# sourceMappingURL=authError.base.d.ts.map
|
|
@@ -73,46 +73,29 @@ export class TokenInvalidError extends AuthError {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
|
-
* Token has been revoked
|
|
76
|
+
* Token has been revoked (logout, or a refresh token replayed after
|
|
77
|
+
* rotation).
|
|
78
|
+
*
|
|
79
|
+
* `401` with `ERR_TOKEN_REVOKED`: the credential is no longer valid, so the
|
|
80
|
+
* client must authenticate again. It was `403 ERR_FORBIDDEN`, which told a
|
|
81
|
+
* client that re-authenticating would not help.
|
|
77
82
|
*/
|
|
78
83
|
export class TokenRevokedError extends AuthError {
|
|
79
84
|
constructor(message = "Token has been revoked", options) {
|
|
80
85
|
super(message, {
|
|
81
|
-
code: ErrorCode.
|
|
82
|
-
|
|
83
|
-
statusCode: 403,
|
|
86
|
+
code: ErrorCode.TOKEN_REVOKED,
|
|
87
|
+
statusCode: 401,
|
|
84
88
|
...options,
|
|
85
89
|
});
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
|
-
/**
|
|
89
|
-
* User account is locked (too many failed attempts).
|
|
90
|
-
*
|
|
91
|
-
* `423 Locked`; `metadata.retryAfterSeconds` is intended for a `Retry-After`
|
|
92
|
-
* response header.
|
|
93
|
-
*/
|
|
94
|
-
export class AccountLockedError extends AuthError {
|
|
95
|
-
constructor(message = "Account is locked due to too many failed attempts", options) {
|
|
96
|
-
const { retryAfterSeconds, metadata, ...rest } = options ?? {};
|
|
97
|
-
super(message, {
|
|
98
|
-
code: ErrorCode.FORBIDDEN,
|
|
99
|
-
category: ErrorCategory.RATE_LIMIT,
|
|
100
|
-
statusCode: 423,
|
|
101
|
-
...rest,
|
|
102
|
-
metadata: {
|
|
103
|
-
retryAfterSeconds: retryAfterSeconds ?? 900,
|
|
104
|
-
...metadata,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
92
|
/**
|
|
110
93
|
* User account is deactivated.
|
|
111
94
|
*/
|
|
112
95
|
export class AccountDeactivatedError extends AuthError {
|
|
113
96
|
constructor(message = "User account is deactivated", options) {
|
|
114
97
|
super(message, {
|
|
115
|
-
code: ErrorCode.
|
|
98
|
+
code: ErrorCode.ACCOUNT_DEACTIVATED,
|
|
116
99
|
category: ErrorCategory.AUTHORIZATION,
|
|
117
100
|
statusCode: 403,
|
|
118
101
|
...options,
|
|
@@ -149,22 +132,4 @@ export class SessionExpiredError extends AuthError {
|
|
|
149
132
|
});
|
|
150
133
|
}
|
|
151
134
|
}
|
|
152
|
-
/**
|
|
153
|
-
* Rate limit exceeded for an auth endpoint.
|
|
154
|
-
*/
|
|
155
|
-
export class AuthRateLimitError extends AuthError {
|
|
156
|
-
constructor(message = "Too many authentication attempts", options) {
|
|
157
|
-
const { retryAfterSeconds, metadata, ...rest } = options ?? {};
|
|
158
|
-
super(message, {
|
|
159
|
-
code: ErrorCode.RATE_LIMITED,
|
|
160
|
-
category: ErrorCategory.RATE_LIMIT,
|
|
161
|
-
statusCode: 429,
|
|
162
|
-
...rest,
|
|
163
|
-
metadata: {
|
|
164
|
-
retryAfterSeconds: retryAfterSeconds ?? 60,
|
|
165
|
-
...metadata,
|
|
166
|
-
},
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
135
|
//# sourceMappingURL=authError.base.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throttling errors: lockout and rate limit.
|
|
3
|
+
*
|
|
4
|
+
* Both carry `retryAfterSeconds` and a `Retry-After` header in `headers`.
|
|
5
|
+
* `@zudojs/http` copies an error's `headers` onto the response it answers
|
|
6
|
+
* the error with, so a client told `423 Locked` or `429 Too Many Requests`
|
|
7
|
+
* also learns when to try again; the lockout used to go out without one.
|
|
8
|
+
*
|
|
9
|
+
* @module authErrors/authError.throttle
|
|
10
|
+
*/
|
|
11
|
+
import { AuthError, type AuthErrorOptions } from "@zudojs/errors";
|
|
12
|
+
/** Options shared by the throttling errors. */
|
|
13
|
+
export type ThrottleErrorOptions = AuthErrorOptions & {
|
|
14
|
+
/** Seconds until the client may retry. Rounded up; at least 1. */
|
|
15
|
+
readonly retryAfterSeconds?: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* User account is locked (too many failed attempts).
|
|
19
|
+
*
|
|
20
|
+
* `423 Locked`, code `ERR_ACCOUNT_LOCKED`. `retryAfterSeconds` is the time
|
|
21
|
+
* left on the lockout, and `headers` carries it as `Retry-After`.
|
|
22
|
+
*/
|
|
23
|
+
export declare class AccountLockedError extends AuthError {
|
|
24
|
+
/** Seconds until the lockout lapses. */
|
|
25
|
+
readonly retryAfterSeconds: number;
|
|
26
|
+
/** Response headers for this error: `Retry-After`. */
|
|
27
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
28
|
+
constructor(message?: string, options?: ThrottleErrorOptions);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Rate limit exceeded for an auth endpoint.
|
|
32
|
+
*
|
|
33
|
+
* `429 Too Many Requests`; `headers` carries `Retry-After`.
|
|
34
|
+
*/
|
|
35
|
+
export declare class AuthRateLimitError extends AuthError {
|
|
36
|
+
/** Seconds until the window resets. */
|
|
37
|
+
readonly retryAfterSeconds: number;
|
|
38
|
+
/** Response headers for this error: `Retry-After`. */
|
|
39
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
40
|
+
constructor(message?: string, options?: ThrottleErrorOptions);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=authError.throttle.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throttling errors: lockout and rate limit.
|
|
3
|
+
*
|
|
4
|
+
* Both carry `retryAfterSeconds` and a `Retry-After` header in `headers`.
|
|
5
|
+
* `@zudojs/http` copies an error's `headers` onto the response it answers
|
|
6
|
+
* the error with, so a client told `423 Locked` or `429 Too Many Requests`
|
|
7
|
+
* also learns when to try again; the lockout used to go out without one.
|
|
8
|
+
*
|
|
9
|
+
* @module authErrors/authError.throttle
|
|
10
|
+
*/
|
|
11
|
+
import { AuthError, ErrorCategory, ErrorCode, } from "@zudojs/errors";
|
|
12
|
+
function toRetryAfter(value, fallback) {
|
|
13
|
+
if (value === undefined || !Number.isFinite(value))
|
|
14
|
+
return fallback;
|
|
15
|
+
return Math.max(1, Math.ceil(value));
|
|
16
|
+
}
|
|
17
|
+
function retryAfterHeaders(seconds) {
|
|
18
|
+
return Object.freeze({ "retry-after": String(seconds) });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* User account is locked (too many failed attempts).
|
|
22
|
+
*
|
|
23
|
+
* `423 Locked`, code `ERR_ACCOUNT_LOCKED`. `retryAfterSeconds` is the time
|
|
24
|
+
* left on the lockout, and `headers` carries it as `Retry-After`.
|
|
25
|
+
*/
|
|
26
|
+
export class AccountLockedError extends AuthError {
|
|
27
|
+
/** Seconds until the lockout lapses. */
|
|
28
|
+
retryAfterSeconds;
|
|
29
|
+
/** Response headers for this error: `Retry-After`. */
|
|
30
|
+
headers;
|
|
31
|
+
constructor(message = "Account is locked due to too many failed attempts", options) {
|
|
32
|
+
const { retryAfterSeconds, metadata, ...rest } = options ?? {};
|
|
33
|
+
const seconds = toRetryAfter(retryAfterSeconds, 900);
|
|
34
|
+
super(message, {
|
|
35
|
+
code: ErrorCode.ACCOUNT_LOCKED,
|
|
36
|
+
category: ErrorCategory.RATE_LIMIT,
|
|
37
|
+
statusCode: 423,
|
|
38
|
+
...rest,
|
|
39
|
+
metadata: { retryAfterSeconds: seconds, ...metadata },
|
|
40
|
+
});
|
|
41
|
+
this.retryAfterSeconds = seconds;
|
|
42
|
+
this.headers = retryAfterHeaders(seconds);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Rate limit exceeded for an auth endpoint.
|
|
47
|
+
*
|
|
48
|
+
* `429 Too Many Requests`; `headers` carries `Retry-After`.
|
|
49
|
+
*/
|
|
50
|
+
export class AuthRateLimitError extends AuthError {
|
|
51
|
+
/** Seconds until the window resets. */
|
|
52
|
+
retryAfterSeconds;
|
|
53
|
+
/** Response headers for this error: `Retry-After`. */
|
|
54
|
+
headers;
|
|
55
|
+
constructor(message = "Too many authentication attempts", options) {
|
|
56
|
+
const { retryAfterSeconds, metadata, ...rest } = options ?? {};
|
|
57
|
+
const seconds = toRetryAfter(retryAfterSeconds, 60);
|
|
58
|
+
super(message, {
|
|
59
|
+
code: ErrorCode.RATE_LIMITED,
|
|
60
|
+
category: ErrorCategory.RATE_LIMIT,
|
|
61
|
+
statusCode: 429,
|
|
62
|
+
...rest,
|
|
63
|
+
metadata: { retryAfterSeconds: seconds, ...metadata },
|
|
64
|
+
});
|
|
65
|
+
this.retryAfterSeconds = seconds;
|
|
66
|
+
this.headers = retryAfterHeaders(seconds);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=authError.throttle.js.map
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module authErrors
|
|
5
5
|
*/
|
|
6
|
-
export { AuthError, type AuthErrorOptions, AuthConfigurationError, InvalidCredentialsError, TokenExpiredError, TokenInvalidError, TokenRevokedError,
|
|
6
|
+
export { AuthError, type AuthErrorOptions, AuthConfigurationError, InvalidCredentialsError, TokenExpiredError, TokenInvalidError, TokenRevokedError, AccountDeactivatedError, AccessDeniedError, SessionExpiredError, } from "./authError.base.js";
|
|
7
|
+
export { AccountLockedError, AuthRateLimitError, type ThrottleErrorOptions, } from "./authError.throttle.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/authErrors/index.js
CHANGED
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module authErrors
|
|
5
5
|
*/
|
|
6
|
-
export { AuthError, AuthConfigurationError, InvalidCredentialsError, TokenExpiredError, TokenInvalidError, TokenRevokedError,
|
|
6
|
+
export { AuthError, AuthConfigurationError, InvalidCredentialsError, TokenExpiredError, TokenInvalidError, TokenRevokedError, AccountDeactivatedError, AccessDeniedError, SessionExpiredError, } from "./authError.base.js";
|
|
7
|
+
export { AccountLockedError, AuthRateLimitError, } from "./authError.throttle.js";
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Check if a password hash needs rehashing: every hash that is not a
|
|
8
|
-
* `@zudojs/crypto` scrypt hash with the current parameters
|
|
9
|
-
* and
|
|
8
|
+
* `@zudojs/crypto` scrypt hash with the current parameters. Current means
|
|
9
|
+
* N, r and p equal to this package's policy, with the salt and key lengths
|
|
10
|
+
* either this package writes (32/64 bytes) or `@zudojs/crypto` writes by
|
|
11
|
+
* default (16/32 bytes). All legacy `scrypt$…` hashes return `true`.
|
|
10
12
|
*
|
|
11
13
|
* @param hashedPassword - The stored hash
|
|
12
14
|
* @returns Whether the hash should be regenerated
|
|
@@ -3,12 +3,28 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module authPassword/authPassword.rehash
|
|
5
5
|
*/
|
|
6
|
-
import { CryptoAlgorithm, decodePasswordHash } from "@zudojs/crypto";
|
|
6
|
+
import { CryptoAlgorithm, decodePasswordHash, getDefaultPasswordHashOptions, } from "@zudojs/crypto";
|
|
7
7
|
import { KEY_LENGTH, SALT_LENGTH, SCRYPT_N, SCRYPT_P, SCRYPT_R, } from "./authPassword.policy.js";
|
|
8
|
+
/**
|
|
9
|
+
* Salt and key lengths a current hash may have: the ones this package's
|
|
10
|
+
* `hashPassword()` writes, and the ones `@zudojs/crypto`'s `hashPassword()`
|
|
11
|
+
* writes with its own defaults. Both use the same scrypt N, r and p, and
|
|
12
|
+
* both are current — flagging a crypto-default hash made every login
|
|
13
|
+
* rewrite it for nothing.
|
|
14
|
+
*/
|
|
15
|
+
function currentLengths() {
|
|
16
|
+
const crypto = getDefaultPasswordHashOptions();
|
|
17
|
+
return [
|
|
18
|
+
[SALT_LENGTH, KEY_LENGTH],
|
|
19
|
+
[crypto.saltBytes, crypto.keyBytes],
|
|
20
|
+
];
|
|
21
|
+
}
|
|
8
22
|
/**
|
|
9
23
|
* Check if a password hash needs rehashing: every hash that is not a
|
|
10
|
-
* `@zudojs/crypto` scrypt hash with the current parameters
|
|
11
|
-
* and
|
|
24
|
+
* `@zudojs/crypto` scrypt hash with the current parameters. Current means
|
|
25
|
+
* N, r and p equal to this package's policy, with the salt and key lengths
|
|
26
|
+
* either this package writes (32/64 bytes) or `@zudojs/crypto` writes by
|
|
27
|
+
* default (16/32 bytes). All legacy `scrypt$…` hashes return `true`.
|
|
12
28
|
*
|
|
13
29
|
* @param hashedPassword - The stored hash
|
|
14
30
|
* @returns Whether the hash should be regenerated
|
|
@@ -20,11 +36,12 @@ export function needsRehash(hashedPassword) {
|
|
|
20
36
|
const decoded = decodePasswordHash(hashedPassword);
|
|
21
37
|
if (decoded.algorithm !== CryptoAlgorithm.SCRYPT)
|
|
22
38
|
return true;
|
|
23
|
-
|
|
24
|
-
decoded.hash.byteLength !== KEY_LENGTH ||
|
|
25
|
-
decoded.cost !== SCRYPT_N ||
|
|
39
|
+
if (decoded.cost !== SCRYPT_N ||
|
|
26
40
|
decoded.blockSize !== SCRYPT_R ||
|
|
27
|
-
decoded.parallelization !== SCRYPT_P)
|
|
41
|
+
decoded.parallelization !== SCRYPT_P) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return !currentLengths().some(([salt, key]) => decoded.salt.byteLength === salt && decoded.hash.byteLength === key);
|
|
28
45
|
}
|
|
29
46
|
catch {
|
|
30
47
|
return true;
|
|
@@ -7,6 +7,7 @@ import type { AuthUser, UserCredentials, UserId } from "../authTypes/authUser.ty
|
|
|
7
7
|
import type { TokenPair, TokenPayload, TokenConfig, TokenRevocationStore } from "../authTypes/authToken.type.js";
|
|
8
8
|
import type { SessionStore, SessionId } from "../authTypes/authSession.type.js";
|
|
9
9
|
import type { LoginThrottleConfig } from "../authTypes/authAttempt.type.js";
|
|
10
|
+
import { type ExternalSessionOptions } from "./authProvider.external.js";
|
|
10
11
|
import type { GuardContext, GuardResult } from "../authTypes/authRbac.type.js";
|
|
11
12
|
import type { PermissionEngine } from "@zudojs/permissions";
|
|
12
13
|
export { throttleKey } from "./authProvider.throttle.js";
|
|
@@ -82,6 +83,23 @@ export interface AuthServiceConfig {
|
|
|
82
83
|
* and verify them through this service.
|
|
83
84
|
*/
|
|
84
85
|
readonly allowSessionlessTokens?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Methods `createSessionForUser()` may start a session for, e.g.
|
|
88
|
+
* `["oauth"]`. Default: none, so `createSessionForUser()` throws.
|
|
89
|
+
*
|
|
90
|
+
* That method checks no credential — your code asserts the user is
|
|
91
|
+
* already authenticated — so it is off until you name the flows that
|
|
92
|
+
* verify authentication themselves (an OAuth callback that validated
|
|
93
|
+
* `state` and exchanged the code).
|
|
94
|
+
*/
|
|
95
|
+
readonly externalSessionMethods?: readonly string[];
|
|
96
|
+
/**
|
|
97
|
+
* How `login()` normalizes the submitted identifier before `findUser()`
|
|
98
|
+
* sees it. Default: {@link normalizeLoginIdentifier} (NFKC, trim, and
|
|
99
|
+
* lower-case for an email). Pass `false` to hand `findUser()` the raw
|
|
100
|
+
* string, or your own function to match how you store identifiers.
|
|
101
|
+
*/
|
|
102
|
+
readonly normalizeIdentifier?: false | ((identifier: string) => string);
|
|
85
103
|
}
|
|
86
104
|
/**
|
|
87
105
|
* Auth service interface.
|
|
@@ -101,6 +119,18 @@ export interface AuthService {
|
|
|
101
119
|
refresh(refreshToken: string): Promise<TokenPair>;
|
|
102
120
|
logout(sessionId: SessionId, refreshToken?: string): Promise<void>;
|
|
103
121
|
logoutAll(userId: UserId): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Start a session for a user your code has already authenticated some
|
|
124
|
+
* other way — OAuth (`@zudojs/auth-oauth`), a passkey, a magic link — and
|
|
125
|
+
* return tokens exactly as `login()` does, without a password check.
|
|
126
|
+
*
|
|
127
|
+
* Only for methods listed in `externalSessionMethods`; otherwise it throws
|
|
128
|
+
* `AuthConfigurationError`. The user is loaded with `findUserById()`, and
|
|
129
|
+
* an unknown user (`InvalidCredentialsError`) or a deactivated one
|
|
130
|
+
* (`AccountDeactivatedError`) is refused. Never pass it a user id taken
|
|
131
|
+
* from the request.
|
|
132
|
+
*/
|
|
133
|
+
createSessionForUser(userId: UserId, options: ExternalSessionOptions): Promise<LoginResult>;
|
|
104
134
|
checkAccess(context: GuardContext): Promise<GuardResult>;
|
|
105
135
|
hashPassword(password: string): Promise<string>;
|
|
106
136
|
verifyPasswordHash(password: string, hash: string): Promise<boolean>;
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* @module authProvider/authProvider
|
|
5
5
|
*/
|
|
6
6
|
import { createLoginThrottleGate } from "./authProvider.throttle.js";
|
|
7
|
+
import { createExternalSessionStarter, } from "./authProvider.external.js";
|
|
8
|
+
import { normalizeLoginIdentifier } from "../authUtils/authUtils.identifier.js";
|
|
7
9
|
import { hashPassword, verifyPassword, } from "../authPassword/authPassword.core.js";
|
|
8
10
|
import { createTokenPair, verifyAccessToken, verifyRefreshToken, } from "../authToken/authToken.core.js";
|
|
9
11
|
import { assertTokenSecrets } from "../authToken/authToken.signing.js";
|
|
@@ -22,7 +24,7 @@ export { throttleKey } from "./authProvider.throttle.js";
|
|
|
22
24
|
* Create an auth service.
|
|
23
25
|
*/
|
|
24
26
|
export function createAuthService(config) {
|
|
25
|
-
const { token: tokenConfig, sessionStore, findUser, findUserById, verifyPassword: verifyPwd, sessionTtlSeconds, absoluteSessionTtlSeconds, permissions, revocationStore, loginThrottle, allowInsecureFallbackGuard, fallbackAdminRole, allowSessionlessTokens, } = config;
|
|
27
|
+
const { token: tokenConfig, sessionStore, findUser, findUserById, verifyPassword: verifyPwd, sessionTtlSeconds, absoluteSessionTtlSeconds, permissions, revocationStore, loginThrottle, allowInsecureFallbackGuard, fallbackAdminRole, allowSessionlessTokens, externalSessionMethods, normalizeIdentifier, } = config;
|
|
26
28
|
// Fail at construction, not at the first login: a bad secret or a NaN
|
|
27
29
|
// lifetime (`Number(process.env.X)` with X unset) otherwise surfaced as
|
|
28
30
|
// a runtime error on the request path — or, for the session TTL, not at
|
|
@@ -31,6 +33,17 @@ export function createAuthService(config) {
|
|
|
31
33
|
assertPositiveSeconds(sessionTtlSeconds, "sessionTtlSeconds");
|
|
32
34
|
assertPositiveSeconds(absoluteSessionTtlSeconds, "absoluteSessionTtlSeconds");
|
|
33
35
|
const throttle = createLoginThrottleGate(loginThrottle);
|
|
36
|
+
const normalize = normalizeIdentifier === false
|
|
37
|
+
? (identifier) => identifier
|
|
38
|
+
: (normalizeIdentifier ?? normalizeLoginIdentifier);
|
|
39
|
+
const createSessionForUser = createExternalSessionStarter({
|
|
40
|
+
methods: externalSessionMethods,
|
|
41
|
+
findUserById,
|
|
42
|
+
sessionStore,
|
|
43
|
+
tokenConfig,
|
|
44
|
+
sessionTtlSeconds,
|
|
45
|
+
absoluteSessionTtlSeconds,
|
|
46
|
+
});
|
|
34
47
|
/**
|
|
35
48
|
* Reject the token unless the session it was issued against is still
|
|
36
49
|
* alive; refresh the session's idle timer when it is.
|
|
@@ -72,9 +85,12 @@ export function createAuthService(config) {
|
|
|
72
85
|
* deactivated account are indistinguishable to the caller: the same
|
|
73
86
|
* `InvalidCredentialsError` is thrown, and the unknown-user path performs
|
|
74
87
|
* the same scrypt work as the known-user path.
|
|
88
|
+
*
|
|
89
|
+
* The identifier is normalized first (see `normalizeIdentifier`), so
|
|
90
|
+
* `findUser()` and the lockout counters see one spelling of it.
|
|
75
91
|
*/
|
|
76
92
|
async login(credentials, context) {
|
|
77
|
-
const identifier = credentials.identifier;
|
|
93
|
+
const identifier = normalize(credentials.identifier);
|
|
78
94
|
const slot = await throttle.begin(identifier);
|
|
79
95
|
const user = await findUser(identifier);
|
|
80
96
|
let authenticated = false;
|
|
@@ -197,6 +213,7 @@ export function createAuthService(config) {
|
|
|
197
213
|
async logoutAll(userId) {
|
|
198
214
|
await sessionStore.destroyAllForUser(userId);
|
|
199
215
|
},
|
|
216
|
+
createSessionForUser,
|
|
200
217
|
/**
|
|
201
218
|
* Check if a user has a specific permission.
|
|
202
219
|
* Delegates to the @zudojs/permissions engine when configured.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions for users authenticated outside `login()` — OAuth, passkeys,
|
|
3
|
+
* magic links.
|
|
4
|
+
*
|
|
5
|
+
* @module authProvider/authProvider.external
|
|
6
|
+
*/
|
|
7
|
+
import type { AuthUser, UserId } from "../authTypes/authUser.type.js";
|
|
8
|
+
import type { TokenConfig, TokenPair } from "../authTypes/authToken.type.js";
|
|
9
|
+
import type { SessionId, SessionStore } from "../authTypes/authSession.type.js";
|
|
10
|
+
/** Options for `AuthService.createSessionForUser()`. */
|
|
11
|
+
export interface ExternalSessionOptions {
|
|
12
|
+
/**
|
|
13
|
+
* How your code authenticated the user — `"oauth"`, `"passkey"`,
|
|
14
|
+
* `"magic-link"`. It must be listed in the service's
|
|
15
|
+
* `externalSessionMethods`, and is recorded on the session as
|
|
16
|
+
* `metadata.authMethod`.
|
|
17
|
+
*/
|
|
18
|
+
readonly method: string;
|
|
19
|
+
/** Client user-agent string, recorded on the session. */
|
|
20
|
+
readonly userAgent?: string;
|
|
21
|
+
/** Client IP address, recorded on the session. */
|
|
22
|
+
readonly ip?: string;
|
|
23
|
+
/** Extra session metadata, e.g. `{ provider: "github" }`. */
|
|
24
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
25
|
+
}
|
|
26
|
+
/** What `createSessionForUser()` returns: the same shape as `login()`. */
|
|
27
|
+
export interface ExternalSessionResult {
|
|
28
|
+
readonly user: AuthUser;
|
|
29
|
+
readonly tokens: TokenPair;
|
|
30
|
+
readonly sessionId: SessionId;
|
|
31
|
+
}
|
|
32
|
+
/** The parts of the service configuration the starter needs. */
|
|
33
|
+
export interface ExternalSessionDependencies {
|
|
34
|
+
readonly methods: readonly string[] | undefined;
|
|
35
|
+
readonly findUserById: (userId: UserId) => Promise<AuthUser | null>;
|
|
36
|
+
readonly sessionStore: SessionStore;
|
|
37
|
+
readonly tokenConfig: TokenConfig;
|
|
38
|
+
readonly sessionTtlSeconds: number;
|
|
39
|
+
readonly absoluteSessionTtlSeconds: number | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build `createSessionForUser()`.
|
|
43
|
+
*
|
|
44
|
+
* It performs no credential check: the caller asserts the user is already
|
|
45
|
+
* authenticated. It is therefore off unless the service lists the method in
|
|
46
|
+
* `externalSessionMethods` — a route that forwards a client-supplied user id
|
|
47
|
+
* cannot reach it by default — and it still refuses unknown and deactivated
|
|
48
|
+
* users.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createExternalSessionStarter(deps: ExternalSessionDependencies): (userId: UserId, options: ExternalSessionOptions) => Promise<ExternalSessionResult>;
|
|
51
|
+
//# sourceMappingURL=authProvider.external.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions for users authenticated outside `login()` — OAuth, passkeys,
|
|
3
|
+
* magic links.
|
|
4
|
+
*
|
|
5
|
+
* @module authProvider/authProvider.external
|
|
6
|
+
*/
|
|
7
|
+
import { createTokenPair } from "../authToken/authToken.core.js";
|
|
8
|
+
import { AccountDeactivatedError, AuthConfigurationError, InvalidCredentialsError, } from "../authErrors/authError.base.js";
|
|
9
|
+
/**
|
|
10
|
+
* Build `createSessionForUser()`.
|
|
11
|
+
*
|
|
12
|
+
* It performs no credential check: the caller asserts the user is already
|
|
13
|
+
* authenticated. It is therefore off unless the service lists the method in
|
|
14
|
+
* `externalSessionMethods` — a route that forwards a client-supplied user id
|
|
15
|
+
* cannot reach it by default — and it still refuses unknown and deactivated
|
|
16
|
+
* users.
|
|
17
|
+
*/
|
|
18
|
+
export function createExternalSessionStarter(deps) {
|
|
19
|
+
const allowed = new Set(deps.methods ?? []);
|
|
20
|
+
return async (userId, options) => {
|
|
21
|
+
const method = options?.method;
|
|
22
|
+
if (typeof method !== "string" || !allowed.has(method)) {
|
|
23
|
+
throw new AuthConfigurationError(`createSessionForUser(): method "${String(method)}" is not enabled. ` +
|
|
24
|
+
"List it in `externalSessionMethods` once your code verifies that " +
|
|
25
|
+
"authentication itself (e.g. the OAuth callback).");
|
|
26
|
+
}
|
|
27
|
+
if (typeof userId !== "string" || userId.length === 0) {
|
|
28
|
+
throw new InvalidCredentialsError();
|
|
29
|
+
}
|
|
30
|
+
const user = await deps.findUserById(userId);
|
|
31
|
+
if (!user)
|
|
32
|
+
throw new InvalidCredentialsError();
|
|
33
|
+
if (!user.active)
|
|
34
|
+
throw new AccountDeactivatedError();
|
|
35
|
+
const session = await deps.sessionStore.create({
|
|
36
|
+
userId: user.id,
|
|
37
|
+
userAgent: options.userAgent,
|
|
38
|
+
ip: options.ip,
|
|
39
|
+
ttlSeconds: deps.sessionTtlSeconds,
|
|
40
|
+
absoluteTtlSeconds: deps.absoluteSessionTtlSeconds,
|
|
41
|
+
metadata: { ...options.metadata, authMethod: method },
|
|
42
|
+
});
|
|
43
|
+
const tokens = createTokenPair(user.id, deps.tokenConfig, {
|
|
44
|
+
roles: user.roles,
|
|
45
|
+
sessionId: session.id,
|
|
46
|
+
});
|
|
47
|
+
return { user, tokens, sessionId: session.id };
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=authProvider.external.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module authProvider/authProvider.throttle
|
|
6
6
|
*/
|
|
7
|
-
import { AccountLockedError, AuthRateLimitError, } from "../authErrors/authError.
|
|
7
|
+
import { AccountLockedError, AuthRateLimitError, } from "../authErrors/authError.throttle.js";
|
|
8
8
|
const DEFAULT_MAX_FAILED_ATTEMPTS = 5;
|
|
9
9
|
const DEFAULT_LOCKOUT_SECONDS = 900;
|
|
10
10
|
const DEFAULT_MAX_ATTEMPTS_PER_WINDOW = 20;
|
|
@@ -5,4 +5,5 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { createAuthService, type AuthService, type AuthServiceConfig, type LoginResult, type UserLookup, type UserByIdLookup, type PasswordVerifier, } from "./authProvider.core.js";
|
|
7
7
|
export { createMemoryLoginAttemptStore } from "./authAttempt.memory.js";
|
|
8
|
+
export { type ExternalSessionOptions, type ExternalSessionResult, } from "./authProvider.external.js";
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login identifier normalization.
|
|
3
|
+
*
|
|
4
|
+
* @module authUtils/authUtils.identifier
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Normalize a login identifier the way `login()` does before looking the
|
|
8
|
+
* user up: Unicode-compatibility fold (NFKC) and trim, then lower-case the
|
|
9
|
+
* whole identifier when it is an email address. A username keeps its case.
|
|
10
|
+
*
|
|
11
|
+
* Store identifiers through this same function at registration, so
|
|
12
|
+
* `" Alice@Example.com"` at sign-in finds the account registered as
|
|
13
|
+
* `alice@example.com`. The lockout counters are keyed case-insensitively
|
|
14
|
+
* independently of this, so case variants never get their own attempt
|
|
15
|
+
* budget either way.
|
|
16
|
+
*
|
|
17
|
+
* @param identifier - The identifier as submitted.
|
|
18
|
+
* @returns The normalized identifier.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeLoginIdentifier(identifier: string): string;
|
|
21
|
+
//# sourceMappingURL=authUtils.identifier.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login identifier normalization.
|
|
3
|
+
*
|
|
4
|
+
* @module authUtils/authUtils.identifier
|
|
5
|
+
*/
|
|
6
|
+
import { isEmail } from "@zudojs/types";
|
|
7
|
+
/**
|
|
8
|
+
* Normalize a login identifier the way `login()` does before looking the
|
|
9
|
+
* user up: Unicode-compatibility fold (NFKC) and trim, then lower-case the
|
|
10
|
+
* whole identifier when it is an email address. A username keeps its case.
|
|
11
|
+
*
|
|
12
|
+
* Store identifiers through this same function at registration, so
|
|
13
|
+
* `" Alice@Example.com"` at sign-in finds the account registered as
|
|
14
|
+
* `alice@example.com`. The lockout counters are keyed case-insensitively
|
|
15
|
+
* independently of this, so case variants never get their own attempt
|
|
16
|
+
* budget either way.
|
|
17
|
+
*
|
|
18
|
+
* @param identifier - The identifier as submitted.
|
|
19
|
+
* @returns The normalized identifier.
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeLoginIdentifier(identifier) {
|
|
22
|
+
const trimmed = String(identifier).normalize("NFKC").trim();
|
|
23
|
+
return isEmail(trimmed) ? trimmed.toLowerCase() : trimmed;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=authUtils.identifier.js.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth utility helpers: token parsing, cookie parsing, CSRF
|
|
2
|
+
* Auth utility helpers: token parsing, cookie parsing, CSRF, login
|
|
3
|
+
* identifier normalization.
|
|
3
4
|
*
|
|
4
5
|
* @module authUtils
|
|
5
6
|
*/
|
|
6
7
|
export { parseBearerToken, parseCookies, isTokenExpired, extractUserId, generateCsrfToken, } from "./authUtils.helper.js";
|
|
8
|
+
export { normalizeLoginIdentifier } from "./authUtils.identifier.js";
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/authUtils/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth utility helpers: token parsing, cookie parsing, CSRF
|
|
2
|
+
* Auth utility helpers: token parsing, cookie parsing, CSRF, login
|
|
3
|
+
* identifier normalization.
|
|
3
4
|
*
|
|
4
5
|
* @module authUtils
|
|
5
6
|
*/
|
|
6
7
|
export { parseBearerToken, parseCookies, isTokenExpired, extractUserId, generateCsrfToken, } from "./authUtils.helper.js";
|
|
8
|
+
export { normalizeLoginIdentifier } from "./authUtils.identifier.js";
|
|
7
9
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Authentication and authorization services for the Zudojs framework — JWT, sessions, RBAC, and password management.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -25,17 +25,18 @@
|
|
|
25
25
|
"!dist/.tsbuildinfo"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@zudojs/constants": "1.1.
|
|
29
|
-
"@zudojs/crypto": "1.3.
|
|
30
|
-
"@zudojs/errors": "1.
|
|
31
|
-
"@zudojs/permissions": "1.
|
|
28
|
+
"@zudojs/constants": "1.1.2",
|
|
29
|
+
"@zudojs/crypto": "1.3.1",
|
|
30
|
+
"@zudojs/errors": "1.3.0",
|
|
31
|
+
"@zudojs/permissions": "1.4.0",
|
|
32
|
+
"@zudojs/types": "1.2.0"
|
|
32
33
|
},
|
|
33
34
|
"engines": {
|
|
34
35
|
"node": ">=24.0.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"typescript": "7.0.2",
|
|
38
|
-
"vitest": "^
|
|
39
|
+
"vitest": "^5.0.1"
|
|
39
40
|
},
|
|
40
41
|
"publishConfig": {
|
|
41
42
|
"access": "public"
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"sessions",
|
|
49
50
|
"rbac"
|
|
50
51
|
],
|
|
51
|
-
"homepage": "https://
|
|
52
|
+
"homepage": "https://zudojs.oyinlola.site/docs/packages-auth",
|
|
52
53
|
"bugs": {
|
|
53
54
|
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
54
55
|
},
|