@zudojs/auth 0.1.0 → 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/LICENSE +21 -0
- package/README.md +228 -17
- package/dist/authErrors/authError.base.d.ts +50 -16
- package/dist/authErrors/authError.base.js +94 -18
- package/dist/authErrors/index.d.ts +1 -1
- package/dist/authErrors/index.js +1 -1
- package/dist/authPassword/authPassword.core.d.ts +31 -4
- package/dist/authPassword/authPassword.core.js +119 -20
- package/dist/authPassword/index.d.ts +1 -1
- package/dist/authPassword/index.js +1 -1
- package/dist/authProvider/authAttempt.memory.d.ts +21 -0
- package/dist/authProvider/authAttempt.memory.js +91 -0
- package/dist/authProvider/authProvider.core.d.ts +56 -8
- package/dist/authProvider/authProvider.core.js +211 -37
- package/dist/authProvider/index.d.ts +2 -1
- package/dist/authProvider/index.js +1 -0
- package/dist/authSession/authSession.core.d.ts +13 -3
- package/dist/authSession/authSession.core.js +62 -13
- package/dist/authToken/authToken.core.d.ts +27 -1
- package/dist/authToken/authToken.core.js +35 -4
- package/dist/authToken/authToken.encoding.d.ts +43 -0
- package/dist/authToken/authToken.encoding.js +74 -0
- package/dist/authToken/authToken.revocation.d.ts +25 -0
- package/dist/authToken/authToken.revocation.js +71 -0
- package/dist/authToken/authToken.signing.d.ts +25 -1
- package/dist/authToken/authToken.signing.js +111 -33
- package/dist/authToken/index.d.ts +1 -0
- package/dist/authToken/index.js +1 -0
- package/dist/authToken/jwt.namespace.d.ts +18 -8
- package/dist/authToken/jwt.namespace.js +11 -1
- package/dist/authTypes/authAttempt.type.d.ts +63 -0
- package/dist/authTypes/authAttempt.type.js +7 -0
- package/dist/authTypes/authCredentials.type.d.ts +26 -0
- package/dist/authTypes/authCredentials.type.js +11 -0
- package/dist/authTypes/authSession.type.d.ts +31 -3
- package/dist/authTypes/authSession.type.js +17 -1
- package/dist/authTypes/authToken.type.d.ts +48 -1
- package/dist/authTypes/authUser.type.d.ts +12 -0
- package/dist/authTypes/authUser.type.js +17 -1
- package/dist/authTypes/index.d.ts +6 -5
- package/dist/authTypes/index.js +5 -4
- package/dist/authUtils/authUtils.helper.d.ts +31 -10
- package/dist/authUtils/authUtils.helper.js +70 -31
- package/package.json +23 -16
- package/dist/.tsbuildinfo +0 -1
- package/dist/authErrors/authError.base.d.ts.map +0 -1
- package/dist/authErrors/authError.base.js.map +0 -1
- package/dist/authErrors/index.d.ts.map +0 -1
- package/dist/authErrors/index.js.map +0 -1
- package/dist/authPassword/authPassword.core.d.ts.map +0 -1
- package/dist/authPassword/authPassword.core.js.map +0 -1
- package/dist/authPassword/index.d.ts.map +0 -1
- package/dist/authPassword/index.js.map +0 -1
- package/dist/authProvider/authProvider.core.d.ts.map +0 -1
- package/dist/authProvider/authProvider.core.js.map +0 -1
- package/dist/authProvider/index.d.ts.map +0 -1
- package/dist/authProvider/index.js.map +0 -1
- package/dist/authSession/authSession.core.d.ts.map +0 -1
- package/dist/authSession/authSession.core.js.map +0 -1
- package/dist/authSession/index.d.ts.map +0 -1
- package/dist/authSession/index.js.map +0 -1
- package/dist/authToken/authToken.core.d.ts.map +0 -1
- package/dist/authToken/authToken.core.js.map +0 -1
- package/dist/authToken/authToken.signing.d.ts.map +0 -1
- package/dist/authToken/authToken.signing.js.map +0 -1
- package/dist/authToken/index.d.ts.map +0 -1
- package/dist/authToken/index.js.map +0 -1
- package/dist/authToken/jwt.namespace.d.ts.map +0 -1
- package/dist/authToken/jwt.namespace.js.map +0 -1
- package/dist/authTypes/authRbac.type.d.ts.map +0 -1
- package/dist/authTypes/authRbac.type.js.map +0 -1
- package/dist/authTypes/authSession.type.d.ts.map +0 -1
- package/dist/authTypes/authSession.type.js.map +0 -1
- package/dist/authTypes/authStrategy.type.d.ts +0 -83
- package/dist/authTypes/authStrategy.type.d.ts.map +0 -1
- package/dist/authTypes/authStrategy.type.js +0 -7
- package/dist/authTypes/authStrategy.type.js.map +0 -1
- package/dist/authTypes/authToken.type.d.ts.map +0 -1
- package/dist/authTypes/authToken.type.js.map +0 -1
- package/dist/authTypes/authUser.type.d.ts.map +0 -1
- package/dist/authTypes/authUser.type.js.map +0 -1
- package/dist/authTypes/index.d.ts.map +0 -1
- package/dist/authTypes/index.js.map +0 -1
- package/dist/authUtils/authUtils.helper.d.ts.map +0 -1
- package/dist/authUtils/authUtils.helper.js.map +0 -1
- package/dist/authUtils/index.d.ts.map +0 -1
- package/dist/authUtils/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module authToken/authToken
|
|
5
5
|
*/
|
|
6
6
|
import type { UserId } from "../authTypes/authUser.type.js";
|
|
7
|
-
import type { TokenId } from "@zudojs/constants";
|
|
7
|
+
import type { SessionId, TokenId } from "@zudojs/constants";
|
|
8
8
|
/** JWT token string. */
|
|
9
9
|
export type JwtToken = string;
|
|
10
10
|
/** Token identifier. Re-exported from @zudojs/constants for type safety. */
|
|
@@ -25,6 +25,14 @@ export interface TokenPayload {
|
|
|
25
25
|
readonly jti: TokenId;
|
|
26
26
|
/** User roles */
|
|
27
27
|
readonly roles?: readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* Session ID this token was issued against.
|
|
30
|
+
*
|
|
31
|
+
* Set by `createAuthService().login()`. `verifyToken()` and `refresh()`
|
|
32
|
+
* require the referenced session to still exist, which is what makes
|
|
33
|
+
* `logout()` actually invalidate outstanding tokens.
|
|
34
|
+
*/
|
|
35
|
+
readonly sid?: SessionId;
|
|
28
36
|
/** Custom claims */
|
|
29
37
|
readonly [key: string]: unknown;
|
|
30
38
|
}
|
|
@@ -57,6 +65,45 @@ export interface TokenConfig {
|
|
|
57
65
|
readonly issuer?: string;
|
|
58
66
|
/** JWT audience */
|
|
59
67
|
readonly audience?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Clock-skew tolerance in seconds applied to `exp`, `iat` and `nbf`
|
|
70
|
+
* (default: 0, maximum: 300).
|
|
71
|
+
*/
|
|
72
|
+
readonly clockToleranceSeconds?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Store for revoked token IDs (`jti` claims).
|
|
76
|
+
*
|
|
77
|
+
* Backs refresh-token rotation and explicit revocation. The in-memory
|
|
78
|
+
* implementation is good for development; production should use Redis
|
|
79
|
+
* or a database with the same interface.
|
|
80
|
+
*/
|
|
81
|
+
export interface TokenRevocationStore {
|
|
82
|
+
/**
|
|
83
|
+
* Mark a token ID as revoked.
|
|
84
|
+
*
|
|
85
|
+
* @param tokenId - The token's `jti` claim
|
|
86
|
+
* @param expiresAt - The token's `exp` claim (Unix seconds); entries
|
|
87
|
+
* may be discarded after this time since the token is then invalid anyway.
|
|
88
|
+
*/
|
|
89
|
+
revoke(tokenId: TokenId, expiresAt: number): Promise<void>;
|
|
90
|
+
/** Check whether a token ID has been revoked. */
|
|
91
|
+
isRevoked(tokenId: TokenId): Promise<boolean>;
|
|
92
|
+
/**
|
|
93
|
+
* Atomically revoke a token ID **only if it was not already revoked**,
|
|
94
|
+
* returning whether this caller was the one that revoked it.
|
|
95
|
+
*
|
|
96
|
+
* This is the compare-and-set that makes refresh-token rotation safe: a
|
|
97
|
+
* separate `isRevoked()` then `revoke()` leaves two `await` points during
|
|
98
|
+
* which a concurrent replay of the same token also observes "not revoked"
|
|
99
|
+
* and also mints a valid pair. Implement it with a single synchronous
|
|
100
|
+
* `Map.has`/`set` in-process, or `SET NX` in Redis.
|
|
101
|
+
*
|
|
102
|
+
* Optional for backwards compatibility: `createAuthService().refresh()`
|
|
103
|
+
* falls back to `isRevoked()` + `revoke()` when it is absent, which is
|
|
104
|
+
* racy. Implement it in any store used in production.
|
|
105
|
+
*/
|
|
106
|
+
revokeIfNotRevoked?(tokenId: TokenId, expiresAt: number): Promise<boolean>;
|
|
60
107
|
}
|
|
61
108
|
/**
|
|
62
109
|
* Result of token verification.
|
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { UserId } from "@zudojs/constants";
|
|
7
7
|
export type { UserId } from "@zudojs/constants";
|
|
8
|
+
/**
|
|
9
|
+
* Brands a plain string as a {@link UserId}.
|
|
10
|
+
*
|
|
11
|
+
* `UserId` is a branded type with no public constructor, so callers holding an
|
|
12
|
+
* id from a database row, a decoded token or a request parameter have no way to
|
|
13
|
+
* produce one without a cast. This is that constructor.
|
|
14
|
+
*
|
|
15
|
+
* @param value - Non-empty user identifier.
|
|
16
|
+
* @returns The same string, typed as a `UserId`.
|
|
17
|
+
* @throws {TypeError} If `value` is not a non-empty string.
|
|
18
|
+
*/
|
|
19
|
+
export declare function toUserId(value: string): UserId;
|
|
8
20
|
/**
|
|
9
21
|
* Authenticated user representation.
|
|
10
22
|
*/
|
|
@@ -3,5 +3,21 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module authTypes/authUser
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Brands a plain string as a {@link UserId}.
|
|
8
|
+
*
|
|
9
|
+
* `UserId` is a branded type with no public constructor, so callers holding an
|
|
10
|
+
* id from a database row, a decoded token or a request parameter have no way to
|
|
11
|
+
* produce one without a cast. This is that constructor.
|
|
12
|
+
*
|
|
13
|
+
* @param value - Non-empty user identifier.
|
|
14
|
+
* @returns The same string, typed as a `UserId`.
|
|
15
|
+
* @throws {TypeError} If `value` is not a non-empty string.
|
|
16
|
+
*/
|
|
17
|
+
export function toUserId(value) {
|
|
18
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
19
|
+
throw new TypeError("toUserId: value must be a non-empty string.");
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
7
23
|
//# sourceMappingURL=authUser.type.js.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Core auth types: users, tokens, sessions,
|
|
2
|
+
* Core auth types: users, tokens, sessions, credentials, and RBAC.
|
|
3
3
|
*
|
|
4
4
|
* @module authTypes
|
|
5
5
|
*/
|
|
6
|
-
export { type UserId, type AuthUser, type UserCredentials, type UserRegistration, } from "./authUser.type.js";
|
|
7
|
-
export { type JwtToken, type TokenId, type TokenPayload, type TokenPair, type TokenConfig, type TokenVerificationResult, } from "./authToken.type.js";
|
|
8
|
-
export { type SessionId, type AuthSession, type CreateSessionOptions, type SessionStore, } from "./authSession.type.js";
|
|
9
|
-
export { type
|
|
6
|
+
export { type UserId, toUserId, type AuthUser, type UserCredentials, type UserRegistration, } from "./authUser.type.js";
|
|
7
|
+
export { type JwtToken, type TokenId, type TokenPayload, type TokenPair, type TokenConfig, type TokenRevocationStore, type TokenVerificationResult, } from "./authToken.type.js";
|
|
8
|
+
export { type SessionId, toSessionId, type AuthSession, type CreateSessionOptions, type SessionStore, } from "./authSession.type.js";
|
|
9
|
+
export { type PasswordCredentials, type ApiKeyCredentials, } from "./authCredentials.type.js";
|
|
10
|
+
export { type LoginAttemptRecord, type LoginAttemptStore, type LoginThrottleConfig, } from "./authAttempt.type.js";
|
|
10
11
|
export { type Permission, type Role, type GuardResult, type GuardContext, } from "./authRbac.type.js";
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/authTypes/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Core auth types: users, tokens, sessions,
|
|
2
|
+
* Core auth types: users, tokens, sessions, credentials, and RBAC.
|
|
3
3
|
*
|
|
4
4
|
* @module authTypes
|
|
5
5
|
*/
|
|
6
|
-
export {} from "./authUser.type.js";
|
|
6
|
+
export { toUserId, } from "./authUser.type.js";
|
|
7
7
|
export {} from "./authToken.type.js";
|
|
8
|
-
export {} from "./authSession.type.js";
|
|
9
|
-
export {} from "./
|
|
8
|
+
export { toSessionId, } from "./authSession.type.js";
|
|
9
|
+
export {} from "./authCredentials.type.js";
|
|
10
|
+
export {} from "./authAttempt.type.js";
|
|
10
11
|
export {} from "./authRbac.type.js";
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -6,31 +6,52 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Parse a Bearer token from an Authorization header.
|
|
8
8
|
*
|
|
9
|
+
* Accepts `unknown` on purpose: this sits on the HTTP trust boundary, where
|
|
10
|
+
* a duplicated header gives `string[]` and adapter layers routinely pass
|
|
11
|
+
* through values they have not narrowed. Anything that is not a plausible
|
|
12
|
+
* header value is `null`, never a thrown `TypeError`.
|
|
13
|
+
*
|
|
9
14
|
* @param authorization - Raw Authorization header value
|
|
10
15
|
* @returns The token string, or null if not a Bearer token
|
|
11
16
|
*/
|
|
12
|
-
export declare function parseBearerToken(authorization:
|
|
17
|
+
export declare function parseBearerToken(authorization: unknown): string | null;
|
|
13
18
|
/**
|
|
14
19
|
* Parse cookie string into a key-value map.
|
|
15
20
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
21
|
+
* The returned object has a `null` prototype, so a cookie named
|
|
22
|
+
* `constructor`, `hasOwnProperty` or `__proto__` cannot shadow or confuse an
|
|
23
|
+
* inherited member for the caller. Oversized headers and cookie counts are
|
|
24
|
+
* capped rather than allocated.
|
|
25
|
+
*
|
|
26
|
+
* @param cookie - Raw Cookie header value (any type; see
|
|
27
|
+
* {@link parseBearerToken} for why)
|
|
28
|
+
* @returns Parsed cookies — an object with no prototype
|
|
18
29
|
*/
|
|
19
|
-
export declare function parseCookies(cookie:
|
|
30
|
+
export declare function parseCookies(cookie: unknown): Record<string, string>;
|
|
20
31
|
/**
|
|
21
|
-
* Check if a token is expired without verifying the signature
|
|
32
|
+
* Check if a token is expired **without verifying the signature**.
|
|
33
|
+
*
|
|
34
|
+
* The payload is attacker-controlled: treat the answer as a hint (e.g. "should
|
|
35
|
+
* I refresh before calling?"), never as an authorization decision.
|
|
22
36
|
*
|
|
23
37
|
* @param token - JWT token string
|
|
24
|
-
* @returns Whether the token appears expired
|
|
38
|
+
* @returns Whether the token appears expired. Unparseable, oversized, or
|
|
39
|
+
* malformed input is reported as expired.
|
|
25
40
|
*/
|
|
26
|
-
export declare function isTokenExpired(token:
|
|
41
|
+
export declare function isTokenExpired(token: unknown): boolean;
|
|
27
42
|
/**
|
|
28
|
-
* Extract the user ID from a JWT payload without verifying
|
|
43
|
+
* Extract the user ID from a JWT payload **without verifying the signature**.
|
|
44
|
+
*
|
|
45
|
+
* The returned value is attacker-controlled. It is safe to use as a
|
|
46
|
+
* diagnostic hint; it must never be used to decide who the caller is, or as
|
|
47
|
+
* a key in anything security-relevant, without verifying the token first.
|
|
29
48
|
*
|
|
30
49
|
* @param token - JWT token string
|
|
31
|
-
* @returns
|
|
50
|
+
* @returns The `sub` claim when it is a non-empty string, otherwise null.
|
|
51
|
+
* A `sub` that is a number, object, or array is reported as null rather
|
|
52
|
+
* than leaking a non-string through a `string | null` signature.
|
|
32
53
|
*/
|
|
33
|
-
export declare function extractUserId(token:
|
|
54
|
+
export declare function extractUserId(token: unknown): string | null;
|
|
34
55
|
/**
|
|
35
56
|
* Generate a CSRF token.
|
|
36
57
|
*
|
|
@@ -4,74 +4,113 @@
|
|
|
4
4
|
* @module authUtils
|
|
5
5
|
*/
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { decodeJsonSegment, splitToken, } from "../authToken/authToken.encoding.js";
|
|
8
|
+
/**
|
|
9
|
+
* Maximum accepted length of an `Authorization` header value.
|
|
10
|
+
* Sized to comfortably hold `Bearer ` plus a maximum-length JWT.
|
|
11
|
+
*/
|
|
12
|
+
const MAX_AUTHORIZATION_LENGTH = 8256;
|
|
13
|
+
/** Maximum accepted length of a `Cookie` header value. */
|
|
14
|
+
const MAX_COOKIE_HEADER_LENGTH = 8192;
|
|
15
|
+
/** Maximum number of cookie pairs parsed from one header. */
|
|
16
|
+
const MAX_COOKIE_PAIRS = 100;
|
|
17
|
+
/**
|
|
18
|
+
* `Bearer <token>` — the scheme is matched case-insensitively per RFC 7235
|
|
19
|
+
* §2.1, and surrounding/extra whitespace is tolerated.
|
|
20
|
+
*/
|
|
21
|
+
const BEARER_PATTERN = /^\s*bearer[ \t]+(\S+)\s*$/i;
|
|
7
22
|
/**
|
|
8
23
|
* Parse a Bearer token from an Authorization header.
|
|
9
24
|
*
|
|
25
|
+
* Accepts `unknown` on purpose: this sits on the HTTP trust boundary, where
|
|
26
|
+
* a duplicated header gives `string[]` and adapter layers routinely pass
|
|
27
|
+
* through values they have not narrowed. Anything that is not a plausible
|
|
28
|
+
* header value is `null`, never a thrown `TypeError`.
|
|
29
|
+
*
|
|
10
30
|
* @param authorization - Raw Authorization header value
|
|
11
31
|
* @returns The token string, or null if not a Bearer token
|
|
12
32
|
*/
|
|
13
33
|
export function parseBearerToken(authorization) {
|
|
14
|
-
if (
|
|
34
|
+
if (typeof authorization !== "string")
|
|
15
35
|
return null;
|
|
16
|
-
|
|
17
|
-
if (parts.length !== 2 || parts[0] !== "Bearer")
|
|
36
|
+
if (authorization.length > MAX_AUTHORIZATION_LENGTH)
|
|
18
37
|
return null;
|
|
19
|
-
|
|
38
|
+
const match = BEARER_PATTERN.exec(authorization);
|
|
39
|
+
return match?.[1] ?? null;
|
|
20
40
|
}
|
|
21
41
|
/**
|
|
22
42
|
* Parse cookie string into a key-value map.
|
|
23
43
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
44
|
+
* The returned object has a `null` prototype, so a cookie named
|
|
45
|
+
* `constructor`, `hasOwnProperty` or `__proto__` cannot shadow or confuse an
|
|
46
|
+
* inherited member for the caller. Oversized headers and cookie counts are
|
|
47
|
+
* capped rather than allocated.
|
|
48
|
+
*
|
|
49
|
+
* @param cookie - Raw Cookie header value (any type; see
|
|
50
|
+
* {@link parseBearerToken} for why)
|
|
51
|
+
* @returns Parsed cookies — an object with no prototype
|
|
26
52
|
*/
|
|
27
53
|
export function parseCookies(cookie) {
|
|
28
|
-
const result =
|
|
29
|
-
if (
|
|
54
|
+
const result = Object.create(null);
|
|
55
|
+
if (typeof cookie !== "string")
|
|
30
56
|
return result;
|
|
57
|
+
if (cookie.length === 0 || cookie.length > MAX_COOKIE_HEADER_LENGTH) {
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
let pairs = 0;
|
|
31
61
|
for (const part of cookie.split(";")) {
|
|
62
|
+
if (pairs >= MAX_COOKIE_PAIRS)
|
|
63
|
+
break;
|
|
32
64
|
const [key, ...rest] = part.trim().split("=");
|
|
33
65
|
if (key && rest.length > 0) {
|
|
34
66
|
result[key.trim()] = rest.join("=").trim();
|
|
67
|
+
pairs++;
|
|
35
68
|
}
|
|
36
69
|
}
|
|
37
70
|
return result;
|
|
38
71
|
}
|
|
39
72
|
/**
|
|
40
|
-
* Check if a token is expired without verifying the signature
|
|
73
|
+
* Check if a token is expired **without verifying the signature**.
|
|
74
|
+
*
|
|
75
|
+
* The payload is attacker-controlled: treat the answer as a hint (e.g. "should
|
|
76
|
+
* I refresh before calling?"), never as an authorization decision.
|
|
41
77
|
*
|
|
42
78
|
* @param token - JWT token string
|
|
43
|
-
* @returns Whether the token appears expired
|
|
79
|
+
* @returns Whether the token appears expired. Unparseable, oversized, or
|
|
80
|
+
* malformed input is reported as expired.
|
|
44
81
|
*/
|
|
45
82
|
export function isTokenExpired(token) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (parts.length !== 3)
|
|
49
|
-
return true;
|
|
50
|
-
const payload = JSON.parse(Buffer.from(parts[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf-8"));
|
|
51
|
-
const now = Math.floor(Date.now() / 1000);
|
|
52
|
-
return payload.exp < now;
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
83
|
+
const parts = splitToken(token);
|
|
84
|
+
if (!parts)
|
|
55
85
|
return true;
|
|
56
|
-
|
|
86
|
+
const payload = decodeJsonSegment(parts[1]);
|
|
87
|
+
if (!payload)
|
|
88
|
+
return true;
|
|
89
|
+
const exp = payload["exp"];
|
|
90
|
+
const now = Math.floor(Date.now() / 1000);
|
|
91
|
+
return typeof exp !== "number" || !Number.isFinite(exp) || exp < now;
|
|
57
92
|
}
|
|
58
93
|
/**
|
|
59
|
-
* Extract the user ID from a JWT payload without verifying
|
|
94
|
+
* Extract the user ID from a JWT payload **without verifying the signature**.
|
|
95
|
+
*
|
|
96
|
+
* The returned value is attacker-controlled. It is safe to use as a
|
|
97
|
+
* diagnostic hint; it must never be used to decide who the caller is, or as
|
|
98
|
+
* a key in anything security-relevant, without verifying the token first.
|
|
60
99
|
*
|
|
61
100
|
* @param token - JWT token string
|
|
62
|
-
* @returns
|
|
101
|
+
* @returns The `sub` claim when it is a non-empty string, otherwise null.
|
|
102
|
+
* A `sub` that is a number, object, or array is reported as null rather
|
|
103
|
+
* than leaking a non-string through a `string | null` signature.
|
|
63
104
|
*/
|
|
64
105
|
export function extractUserId(token) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (parts.length !== 3)
|
|
68
|
-
return null;
|
|
69
|
-
const payload = JSON.parse(Buffer.from(parts[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf-8"));
|
|
70
|
-
return payload.sub ?? null;
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
106
|
+
const parts = splitToken(token);
|
|
107
|
+
if (!parts)
|
|
73
108
|
return null;
|
|
74
|
-
|
|
109
|
+
const payload = decodeJsonSegment(parts[1]);
|
|
110
|
+
if (!payload)
|
|
111
|
+
return null;
|
|
112
|
+
const sub = payload["sub"];
|
|
113
|
+
return typeof sub === "string" && sub.length > 0 ? sub : null;
|
|
75
114
|
}
|
|
76
115
|
/**
|
|
77
116
|
* Generate a CSRF token.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/auth",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Authentication and authorization services for the Zudojs framework — JWT, sessions, RBAC,
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Authentication and authorization services for the Zudojs framework — JWT, sessions, RBAC, and password management.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -15,25 +15,21 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"!dist/**/*.map",
|
|
20
|
+
"!dist/**/*.tsbuildinfo",
|
|
21
|
+
"!dist/.tsbuildinfo"
|
|
19
22
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsc -p tsconfig.json",
|
|
22
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
-
"clean": "rm -rf dist",
|
|
24
|
-
"test": "vitest run",
|
|
25
|
-
"test:watch": "vitest"
|
|
26
|
-
},
|
|
27
23
|
"dependencies": {
|
|
28
|
-
"@zudojs/errors": "
|
|
29
|
-
"@zudojs/constants": "
|
|
30
|
-
"@zudojs/permissions": "
|
|
24
|
+
"@zudojs/errors": "1.0.0",
|
|
25
|
+
"@zudojs/constants": "1.0.0",
|
|
26
|
+
"@zudojs/permissions": "1.0.0"
|
|
31
27
|
},
|
|
32
28
|
"engines": {
|
|
33
29
|
"node": ">=24.0.0"
|
|
34
30
|
},
|
|
35
31
|
"devDependencies": {
|
|
36
|
-
"typescript": "
|
|
32
|
+
"typescript": "7.0.2",
|
|
37
33
|
"vitest": "^4.1.11"
|
|
38
34
|
},
|
|
39
35
|
"publishConfig": {
|
|
@@ -48,8 +44,19 @@
|
|
|
48
44
|
"rbac"
|
|
49
45
|
],
|
|
50
46
|
"homepage": "https://github.com/oyinlola-tech/zudo#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
49
|
+
},
|
|
51
50
|
"repository": {
|
|
52
51
|
"type": "git",
|
|
53
|
-
"url": "https://github.com/oyinlola-tech/zudo"
|
|
52
|
+
"url": "https://github.com/oyinlola-tech/zudo",
|
|
53
|
+
"directory": "packages/auth"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc -p tsconfig.json",
|
|
57
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
58
|
+
"clean": "rm -rf dist",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest"
|
|
54
61
|
}
|
|
55
|
-
}
|
|
62
|
+
}
|