@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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -17
  3. package/dist/authErrors/authError.base.d.ts +50 -16
  4. package/dist/authErrors/authError.base.js +94 -18
  5. package/dist/authErrors/index.d.ts +1 -1
  6. package/dist/authErrors/index.js +1 -1
  7. package/dist/authPassword/authPassword.core.d.ts +31 -4
  8. package/dist/authPassword/authPassword.core.js +119 -20
  9. package/dist/authPassword/index.d.ts +1 -1
  10. package/dist/authPassword/index.js +1 -1
  11. package/dist/authProvider/authAttempt.memory.d.ts +21 -0
  12. package/dist/authProvider/authAttempt.memory.js +91 -0
  13. package/dist/authProvider/authProvider.core.d.ts +56 -8
  14. package/dist/authProvider/authProvider.core.js +211 -37
  15. package/dist/authProvider/index.d.ts +2 -1
  16. package/dist/authProvider/index.js +1 -0
  17. package/dist/authSession/authSession.core.d.ts +13 -3
  18. package/dist/authSession/authSession.core.js +62 -13
  19. package/dist/authToken/authToken.core.d.ts +27 -1
  20. package/dist/authToken/authToken.core.js +35 -4
  21. package/dist/authToken/authToken.encoding.d.ts +43 -0
  22. package/dist/authToken/authToken.encoding.js +74 -0
  23. package/dist/authToken/authToken.revocation.d.ts +25 -0
  24. package/dist/authToken/authToken.revocation.js +71 -0
  25. package/dist/authToken/authToken.signing.d.ts +25 -1
  26. package/dist/authToken/authToken.signing.js +111 -33
  27. package/dist/authToken/index.d.ts +1 -0
  28. package/dist/authToken/index.js +1 -0
  29. package/dist/authToken/jwt.namespace.d.ts +18 -8
  30. package/dist/authToken/jwt.namespace.js +11 -1
  31. package/dist/authTypes/authAttempt.type.d.ts +63 -0
  32. package/dist/authTypes/authAttempt.type.js +7 -0
  33. package/dist/authTypes/authCredentials.type.d.ts +26 -0
  34. package/dist/authTypes/authCredentials.type.js +11 -0
  35. package/dist/authTypes/authSession.type.d.ts +31 -3
  36. package/dist/authTypes/authSession.type.js +17 -1
  37. package/dist/authTypes/authToken.type.d.ts +48 -1
  38. package/dist/authTypes/authUser.type.d.ts +12 -0
  39. package/dist/authTypes/authUser.type.js +17 -1
  40. package/dist/authTypes/index.d.ts +6 -5
  41. package/dist/authTypes/index.js +5 -4
  42. package/dist/authUtils/authUtils.helper.d.ts +31 -10
  43. package/dist/authUtils/authUtils.helper.js +70 -31
  44. package/package.json +23 -16
  45. package/dist/.tsbuildinfo +0 -1
  46. package/dist/authErrors/authError.base.d.ts.map +0 -1
  47. package/dist/authErrors/authError.base.js.map +0 -1
  48. package/dist/authErrors/index.d.ts.map +0 -1
  49. package/dist/authErrors/index.js.map +0 -1
  50. package/dist/authPassword/authPassword.core.d.ts.map +0 -1
  51. package/dist/authPassword/authPassword.core.js.map +0 -1
  52. package/dist/authPassword/index.d.ts.map +0 -1
  53. package/dist/authPassword/index.js.map +0 -1
  54. package/dist/authProvider/authProvider.core.d.ts.map +0 -1
  55. package/dist/authProvider/authProvider.core.js.map +0 -1
  56. package/dist/authProvider/index.d.ts.map +0 -1
  57. package/dist/authProvider/index.js.map +0 -1
  58. package/dist/authSession/authSession.core.d.ts.map +0 -1
  59. package/dist/authSession/authSession.core.js.map +0 -1
  60. package/dist/authSession/index.d.ts.map +0 -1
  61. package/dist/authSession/index.js.map +0 -1
  62. package/dist/authToken/authToken.core.d.ts.map +0 -1
  63. package/dist/authToken/authToken.core.js.map +0 -1
  64. package/dist/authToken/authToken.signing.d.ts.map +0 -1
  65. package/dist/authToken/authToken.signing.js.map +0 -1
  66. package/dist/authToken/index.d.ts.map +0 -1
  67. package/dist/authToken/index.js.map +0 -1
  68. package/dist/authToken/jwt.namespace.d.ts.map +0 -1
  69. package/dist/authToken/jwt.namespace.js.map +0 -1
  70. package/dist/authTypes/authRbac.type.d.ts.map +0 -1
  71. package/dist/authTypes/authRbac.type.js.map +0 -1
  72. package/dist/authTypes/authSession.type.d.ts.map +0 -1
  73. package/dist/authTypes/authSession.type.js.map +0 -1
  74. package/dist/authTypes/authStrategy.type.d.ts +0 -83
  75. package/dist/authTypes/authStrategy.type.d.ts.map +0 -1
  76. package/dist/authTypes/authStrategy.type.js +0 -7
  77. package/dist/authTypes/authStrategy.type.js.map +0 -1
  78. package/dist/authTypes/authToken.type.d.ts.map +0 -1
  79. package/dist/authTypes/authToken.type.js.map +0 -1
  80. package/dist/authTypes/authUser.type.d.ts.map +0 -1
  81. package/dist/authTypes/authUser.type.js.map +0 -1
  82. package/dist/authTypes/index.d.ts.map +0 -1
  83. package/dist/authTypes/index.js.map +0 -1
  84. package/dist/authUtils/authUtils.helper.d.ts.map +0 -1
  85. package/dist/authUtils/authUtils.helper.js.map +0 -1
  86. package/dist/authUtils/index.d.ts.map +0 -1
  87. package/dist/authUtils/index.js.map +0 -1
  88. package/dist/index.d.ts.map +0 -1
  89. 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
- export {};
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, strategies, and RBAC.
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 OAuthProvider, type OAuthConfig, type OAuthUserInfo, type OAuthResult, type AuthStrategy, type PasswordCredentials, type ApiKeyCredentials, } from "./authStrategy.type.js";
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
@@ -1,11 +1,12 @@
1
1
  /**
2
- * Core auth types: users, tokens, sessions, strategies, and RBAC.
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 "./authStrategy.type.js";
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: string | undefined): string | null;
17
+ export declare function parseBearerToken(authorization: unknown): string | null;
13
18
  /**
14
19
  * Parse cookie string into a key-value map.
15
20
  *
16
- * @param cookie - Raw Cookie header value
17
- * @returns Parsed cookies
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: string | undefined): Record<string, string>;
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: string): boolean;
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 User ID or null
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: string): string | null;
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 (!authorization)
34
+ if (typeof authorization !== "string")
15
35
  return null;
16
- const parts = authorization.split(" ");
17
- if (parts.length !== 2 || parts[0] !== "Bearer")
36
+ if (authorization.length > MAX_AUTHORIZATION_LENGTH)
18
37
  return null;
19
- return parts[1] ?? null;
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
- * @param cookie - Raw Cookie header value
25
- * @returns Parsed cookies
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 (!cookie)
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
- try {
47
- const parts = token.split(".");
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 User ID or null
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
- try {
66
- const parts = token.split(".");
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": "0.1.0",
4
- "description": "Authentication and authorization services for the Zudojs framework — JWT, sessions, RBAC, OAuth2, and password management.",
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": "0.1.0",
29
- "@zudojs/constants": "0.1.0",
30
- "@zudojs/permissions": "0.1.0"
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": "^7.0.2",
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
+ }