@authcore/core 0.6.0 → 0.12.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 (87) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +141 -125
  3. package/dist/auth.d.ts +141 -13
  4. package/dist/auth.d.ts.map +1 -1
  5. package/dist/auth.js +265 -7
  6. package/dist/auth.js.map +1 -1
  7. package/dist/features/emailVerification.d.ts +6 -2
  8. package/dist/features/emailVerification.d.ts.map +1 -1
  9. package/dist/features/emailVerification.js +7 -10
  10. package/dist/features/emailVerification.js.map +1 -1
  11. package/dist/features/invitation.d.ts +7 -2
  12. package/dist/features/invitation.d.ts.map +1 -1
  13. package/dist/features/invitation.js +7 -9
  14. package/dist/features/invitation.js.map +1 -1
  15. package/dist/features/magicLink.d.ts +56 -0
  16. package/dist/features/magicLink.d.ts.map +1 -0
  17. package/dist/features/magicLink.js +88 -0
  18. package/dist/features/magicLink.js.map +1 -0
  19. package/dist/features/oauth.d.ts +39 -0
  20. package/dist/features/oauth.d.ts.map +1 -0
  21. package/dist/features/oauth.js +161 -0
  22. package/dist/features/oauth.js.map +1 -0
  23. package/dist/features/passwordReset.d.ts +6 -2
  24. package/dist/features/passwordReset.d.ts.map +1 -1
  25. package/dist/features/passwordReset.js +7 -10
  26. package/dist/features/passwordReset.js.map +1 -1
  27. package/dist/features/refresh.d.ts +41 -0
  28. package/dist/features/refresh.d.ts.map +1 -0
  29. package/dist/features/refresh.js +58 -0
  30. package/dist/features/refresh.js.map +1 -0
  31. package/dist/features/templates.d.ts +46 -0
  32. package/dist/features/templates.d.ts.map +1 -0
  33. package/dist/features/templates.js +67 -0
  34. package/dist/features/templates.js.map +1 -0
  35. package/dist/features/twoFactor.d.ts +72 -0
  36. package/dist/features/twoFactor.d.ts.map +1 -0
  37. package/dist/features/twoFactor.js +119 -0
  38. package/dist/features/twoFactor.js.map +1 -0
  39. package/dist/index.d.ts +22 -8
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +14 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/oauth/apple.d.ts +80 -0
  44. package/dist/oauth/apple.d.ts.map +1 -0
  45. package/dist/oauth/apple.js +148 -0
  46. package/dist/oauth/apple.js.map +1 -0
  47. package/dist/oauth/discord.d.ts +32 -0
  48. package/dist/oauth/discord.d.ts.map +1 -0
  49. package/dist/oauth/discord.js +86 -0
  50. package/dist/oauth/discord.js.map +1 -0
  51. package/dist/oauth/github.d.ts +35 -0
  52. package/dist/oauth/github.d.ts.map +1 -0
  53. package/dist/oauth/github.js +114 -0
  54. package/dist/oauth/github.js.map +1 -0
  55. package/dist/oauth/google.d.ts +21 -0
  56. package/dist/oauth/google.d.ts.map +1 -0
  57. package/dist/oauth/google.js +76 -0
  58. package/dist/oauth/google.js.map +1 -0
  59. package/dist/oauth/microsoft.d.ts +40 -0
  60. package/dist/oauth/microsoft.d.ts.map +1 -0
  61. package/dist/oauth/microsoft.js +126 -0
  62. package/dist/oauth/microsoft.js.map +1 -0
  63. package/dist/utils/token.d.ts +37 -0
  64. package/dist/utils/token.d.ts.map +1 -1
  65. package/dist/utils/token.js +53 -0
  66. package/dist/utils/token.js.map +1 -1
  67. package/dist/utils/totp.d.ts +59 -0
  68. package/dist/utils/totp.d.ts.map +1 -0
  69. package/dist/utils/totp.js +176 -0
  70. package/dist/utils/totp.js.map +1 -0
  71. package/dist/utils/validation.d.ts +18 -0
  72. package/dist/utils/validation.d.ts.map +1 -1
  73. package/dist/utils/validation.js +8 -0
  74. package/dist/utils/validation.js.map +1 -1
  75. package/package.json +4 -3
  76. package/dist/adapters/database.interface.d.ts +0 -42
  77. package/dist/adapters/database.interface.d.ts.map +0 -1
  78. package/dist/adapters/database.interface.js +0 -2
  79. package/dist/adapters/database.interface.js.map +0 -1
  80. package/dist/adapters/email.interface.d.ts +0 -31
  81. package/dist/adapters/email.interface.d.ts.map +0 -1
  82. package/dist/adapters/email.interface.js +0 -2
  83. package/dist/adapters/email.interface.js.map +0 -1
  84. package/dist/types.d.ts +0 -76
  85. package/dist/types.d.ts.map +0 -1
  86. package/dist/types.js +0 -6
  87. package/dist/types.js.map +0 -1
@@ -7,6 +7,29 @@ import jwt from 'jsonwebtoken';
7
7
  export function generateOpaqueToken() {
8
8
  return randomBytes(32).toString('hex');
9
9
  }
10
+ /**
11
+ * Generate a CSRF token. Same shape as `generateOpaqueToken` (256 bits of entropy)
12
+ * but kept as a separate export to make intent explicit at call sites.
13
+ * The CSRF token is NOT hashed before storage — it's sent to the client as a cookie
14
+ * value AND compared byte-for-byte against the `X-CSRF-Token` header on each request.
15
+ */
16
+ export function generateCsrfToken() {
17
+ return randomBytes(32).toString('hex');
18
+ }
19
+ /**
20
+ * Generate a PKCE code verifier (RFC 7636 §4.1).
21
+ * 32 random bytes → 43-char base64url string (no padding), within the 43-128 char range.
22
+ */
23
+ export function generatePkceVerifier() {
24
+ return randomBytes(32).toString('base64url');
25
+ }
26
+ /**
27
+ * Build the PKCE code challenge (S256 method) from a verifier.
28
+ * SHA-256 of the verifier, base64url-encoded (no padding).
29
+ */
30
+ export function pkceChallenge(verifier) {
31
+ return createHash('sha256').update(verifier).digest('base64url');
32
+ }
10
33
  /**
11
34
  * Hash a raw token using SHA-256 for safe database storage.
12
35
  * Store the hash; return the raw token to the user.
@@ -61,4 +84,34 @@ export function verifyJwt(token, secret) {
61
84
  return null;
62
85
  }
63
86
  }
87
+ /**
88
+ * Sign a short-lived JWT used as a 2FA login challenge. The token carries the
89
+ * user id + a scope claim, and expires after `expiresIn` (default 5 minutes).
90
+ * It is NOT a session token — verify with {@link verifyTwoFactorChallenge}.
91
+ */
92
+ export function signTwoFactorChallenge(userId, secret, expiresIn = '5m') {
93
+ const payload = { sub: userId, scope: '2fa-pending' };
94
+ const options = { algorithm: 'HS256', expiresIn };
95
+ return jwt.sign(payload, secret, options);
96
+ }
97
+ /**
98
+ * Verify a 2FA challenge token. Rejects tokens with the wrong scope so a
99
+ * session JWT can't be reused as a challenge (and vice versa).
100
+ *
101
+ * @returns The decoded payload, or `null` if invalid / expired / wrong scope.
102
+ */
103
+ export function verifyTwoFactorChallenge(token, secret) {
104
+ try {
105
+ const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
106
+ if (typeof decoded === 'string')
107
+ return null;
108
+ const payload = decoded;
109
+ if (payload.scope !== '2fa-pending')
110
+ return null;
111
+ return payload;
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ }
64
117
  //# sourceMappingURL=token.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/utils/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,GAAG,MAAM,cAAc,CAAA;AAE9B;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AACxC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAS,EAAE,CAAS;IACpD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;IACnC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;IACnC,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AACpC,CAAC;AAUD;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CACrB,OAAwC,EACxC,MAAc,EACd,SAAS,GAAG,IAAI;IAEhB,mFAAmF;IACnF,MAAM,OAAO,GAAG,EAAE,SAAS,EAAE,OAAgB,EAAE,SAAS,EAAE,CAAA;IAC1D,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAA0B,CAAC,CAAA;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,MAAc;IACrD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACpE,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;QAC5C,OAAO,OAAqB,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/utils/token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Encode raw bytes to base32 (RFC 4648, no padding). Authenticator apps and
3
+ * QR codes universally expect base32 for the secret.
4
+ */
5
+ export declare function base32Encode(bytes: Buffer): string;
6
+ /**
7
+ * Decode a base32 string back to bytes. Tolerates lowercase + whitespace +
8
+ * trailing `=` padding so we accept whatever a user paste happens to contain.
9
+ */
10
+ export declare function base32Decode(encoded: string): Buffer;
11
+ /**
12
+ * Generate a new TOTP secret: 20 random bytes (160 bits, the RFC 4226 minimum
13
+ * recommended length), base32-encoded.
14
+ */
15
+ export declare function generateTotpSecret(): string;
16
+ /**
17
+ * Generate a TOTP code for the given secret and timestamp (defaults to now).
18
+ *
19
+ * @param secret - Base32-encoded secret
20
+ * @param timestamp - Unix timestamp in seconds (default: current time)
21
+ * @returns 6-digit numeric code as a zero-padded string
22
+ */
23
+ export declare function generateTotpCode(secret: string, timestamp?: number): string;
24
+ /**
25
+ * Verify a TOTP code against a secret. Accepts the current 30-second window
26
+ * AND ±1 step (i.e. ±30 seconds) to absorb clock drift between server and
27
+ * authenticator app — RFC 6238 §5.2 recommends this tolerance.
28
+ *
29
+ * Returns true on match. Uses timing-safe comparison.
30
+ *
31
+ * @param secret - Base32-encoded secret stored at enrollment time
32
+ * @param code - User-supplied 6-digit code (string; may include whitespace)
33
+ * @param window - Number of steps before AND after current to accept. Default 1.
34
+ * @param timestamp - Optional fixed timestamp (for testing)
35
+ */
36
+ export declare function verifyTotpCode(secret: string, code: string, window?: number, timestamp?: number): boolean;
37
+ /**
38
+ * Build an `otpauth://totp/...` URL for QR code rendering. Authenticator apps
39
+ * (Google Authenticator, 1Password, Authy, etc.) parse this URL format.
40
+ *
41
+ * @param secret - Base32-encoded secret
42
+ * @param accountName - Usually the user's email
43
+ * @param issuer - Your app name (e.g. "MyApp")
44
+ */
45
+ export declare function buildOtpauthUrl(params: {
46
+ secret: string;
47
+ accountName: string;
48
+ issuer: string;
49
+ }): string;
50
+ /**
51
+ * Generate `count` user-friendly recovery codes. Format: `xxxx-xxxx-xxxx`
52
+ * (12 chars + 2 dashes), drawn from an unambiguous alphabet (no 0/O/1/I/L).
53
+ *
54
+ * Recovery codes are intentionally meant to be one-time backups when a user
55
+ * loses their authenticator device. Store the SHA-256 hash; show the raw
56
+ * value to the user exactly once.
57
+ */
58
+ export declare function generateRecoveryCodes(count?: number): string[];
59
+ //# sourceMappingURL=totp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totp.d.ts","sourceRoot":"","sources":["../../src/utils/totp.ts"],"names":[],"mappings":"AAuBA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgBlD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkBpD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,MAAsC,GAChD,MAAM,CAGR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAU,EAClB,SAAS,GAAE,MAAsC,GAChD,OAAO,CAYT;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,MAAM,CAYT;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,SAAK,GAAG,MAAM,EAAE,CAgB1D"}
@@ -0,0 +1,176 @@
1
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ /**
3
+ * RFC 6238 (TOTP) and RFC 4226 (HOTP) primitives.
4
+ *
5
+ * Implemented from the spec — zero dependencies. We deliberately keep the
6
+ * audit surface small: 100 lines of straightforward code beats a third-party
7
+ * library whose changelog we have to follow.
8
+ *
9
+ * Algorithm summary:
10
+ * HOTP(K, C) = Truncate(HMAC-SHA1(K, big-endian-uint64(C)))
11
+ * Truncate picks 4 bytes at a dynamic offset, masks the MSB, and takes
12
+ * mod 10^6 to produce a 6-digit code.
13
+ * TOTP(K) = HOTP(K, floor(now / 30))
14
+ */
15
+ const TOTP_DIGITS = 6;
16
+ const TOTP_PERIOD_SECONDS = 30;
17
+ const TOTP_ALGORITHM = 'sha1'; // RFC 6238 default; what every authenticator app expects
18
+ /** RFC 4648 Base32 alphabet (uppercase, no padding for our use). */
19
+ const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
20
+ /**
21
+ * Encode raw bytes to base32 (RFC 4648, no padding). Authenticator apps and
22
+ * QR codes universally expect base32 for the secret.
23
+ */
24
+ export function base32Encode(bytes) {
25
+ let bits = 0;
26
+ let value = 0;
27
+ let output = '';
28
+ for (const byte of bytes) {
29
+ value = (value << 8) | byte;
30
+ bits += 8;
31
+ while (bits >= 5) {
32
+ output += BASE32_ALPHABET[(value >>> (bits - 5)) & 0x1f];
33
+ bits -= 5;
34
+ }
35
+ }
36
+ if (bits > 0) {
37
+ output += BASE32_ALPHABET[(value << (5 - bits)) & 0x1f];
38
+ }
39
+ return output;
40
+ }
41
+ /**
42
+ * Decode a base32 string back to bytes. Tolerates lowercase + whitespace +
43
+ * trailing `=` padding so we accept whatever a user paste happens to contain.
44
+ */
45
+ export function base32Decode(encoded) {
46
+ const cleaned = encoded.replace(/=+$/, '').replace(/\s/g, '').toUpperCase();
47
+ const bytes = [];
48
+ let bits = 0;
49
+ let value = 0;
50
+ for (const char of cleaned) {
51
+ const idx = BASE32_ALPHABET.indexOf(char);
52
+ if (idx === -1) {
53
+ throw new Error(`Invalid base32 character: ${char}`);
54
+ }
55
+ value = (value << 5) | idx;
56
+ bits += 5;
57
+ if (bits >= 8) {
58
+ bytes.push((value >>> (bits - 8)) & 0xff);
59
+ bits -= 8;
60
+ }
61
+ }
62
+ return Buffer.from(bytes);
63
+ }
64
+ /**
65
+ * Generate a new TOTP secret: 20 random bytes (160 bits, the RFC 4226 minimum
66
+ * recommended length), base32-encoded.
67
+ */
68
+ export function generateTotpSecret() {
69
+ return base32Encode(randomBytes(20));
70
+ }
71
+ /**
72
+ * Generate a TOTP code for the given secret and timestamp (defaults to now).
73
+ *
74
+ * @param secret - Base32-encoded secret
75
+ * @param timestamp - Unix timestamp in seconds (default: current time)
76
+ * @returns 6-digit numeric code as a zero-padded string
77
+ */
78
+ export function generateTotpCode(secret, timestamp = Math.floor(Date.now() / 1000)) {
79
+ const step = Math.floor(timestamp / TOTP_PERIOD_SECONDS);
80
+ return hotp(base32Decode(secret), step);
81
+ }
82
+ /**
83
+ * Verify a TOTP code against a secret. Accepts the current 30-second window
84
+ * AND ±1 step (i.e. ±30 seconds) to absorb clock drift between server and
85
+ * authenticator app — RFC 6238 §5.2 recommends this tolerance.
86
+ *
87
+ * Returns true on match. Uses timing-safe comparison.
88
+ *
89
+ * @param secret - Base32-encoded secret stored at enrollment time
90
+ * @param code - User-supplied 6-digit code (string; may include whitespace)
91
+ * @param window - Number of steps before AND after current to accept. Default 1.
92
+ * @param timestamp - Optional fixed timestamp (for testing)
93
+ */
94
+ export function verifyTotpCode(secret, code, window = 1, timestamp = Math.floor(Date.now() / 1000)) {
95
+ const normalized = code.replace(/\s/g, '');
96
+ if (!/^\d{6}$/.test(normalized))
97
+ return false;
98
+ const key = base32Decode(secret);
99
+ const currentStep = Math.floor(timestamp / TOTP_PERIOD_SECONDS);
100
+ for (let offset = -window; offset <= window; offset++) {
101
+ const candidate = hotp(key, currentStep + offset);
102
+ if (safeStringEqual(candidate, normalized))
103
+ return true;
104
+ }
105
+ return false;
106
+ }
107
+ /**
108
+ * Build an `otpauth://totp/...` URL for QR code rendering. Authenticator apps
109
+ * (Google Authenticator, 1Password, Authy, etc.) parse this URL format.
110
+ *
111
+ * @param secret - Base32-encoded secret
112
+ * @param accountName - Usually the user's email
113
+ * @param issuer - Your app name (e.g. "MyApp")
114
+ */
115
+ export function buildOtpauthUrl(params) {
116
+ const { secret, accountName, issuer } = params;
117
+ // Per Google Authenticator spec, label is "Issuer:AccountName" (URL-encoded).
118
+ const label = `${encodeURIComponent(issuer)}:${encodeURIComponent(accountName)}`;
119
+ const query = new URLSearchParams({
120
+ secret,
121
+ issuer,
122
+ algorithm: 'SHA1',
123
+ digits: String(TOTP_DIGITS),
124
+ period: String(TOTP_PERIOD_SECONDS),
125
+ });
126
+ return `otpauth://totp/${label}?${query.toString()}`;
127
+ }
128
+ /**
129
+ * Generate `count` user-friendly recovery codes. Format: `xxxx-xxxx-xxxx`
130
+ * (12 chars + 2 dashes), drawn from an unambiguous alphabet (no 0/O/1/I/L).
131
+ *
132
+ * Recovery codes are intentionally meant to be one-time backups when a user
133
+ * loses their authenticator device. Store the SHA-256 hash; show the raw
134
+ * value to the user exactly once.
135
+ */
136
+ export function generateRecoveryCodes(count = 10) {
137
+ const ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; // 31 chars, no 0/O/1/I/L
138
+ const codes = [];
139
+ for (let i = 0; i < count; i++) {
140
+ const groups = [];
141
+ for (let g = 0; g < 3; g++) {
142
+ const bytes = randomBytes(4);
143
+ let s = '';
144
+ for (let j = 0; j < 4; j++) {
145
+ s += ALPHABET[bytes[j] % ALPHABET.length];
146
+ }
147
+ groups.push(s);
148
+ }
149
+ codes.push(groups.join('-'));
150
+ }
151
+ return codes;
152
+ }
153
+ // --- internals ---
154
+ function hotp(key, counter) {
155
+ // RFC 4226: counter is a 64-bit big-endian unsigned integer.
156
+ const counterBuf = Buffer.alloc(8);
157
+ // JavaScript bitwise ops are 32-bit. Split into two halves.
158
+ const hi = Math.floor(counter / 0x100000000);
159
+ const lo = counter >>> 0;
160
+ counterBuf.writeUInt32BE(hi, 0);
161
+ counterBuf.writeUInt32BE(lo, 4);
162
+ const hmac = createHmac(TOTP_ALGORITHM, key).update(counterBuf).digest();
163
+ // Dynamic truncation: offset is the low 4 bits of the last byte.
164
+ const offset = hmac[hmac.length - 1] & 0x0f;
165
+ const binary = ((hmac[offset] & 0x7f) << 24) |
166
+ ((hmac[offset + 1] & 0xff) << 16) |
167
+ ((hmac[offset + 2] & 0xff) << 8) |
168
+ (hmac[offset + 3] & 0xff);
169
+ return String(binary % 10 ** TOTP_DIGITS).padStart(TOTP_DIGITS, '0');
170
+ }
171
+ function safeStringEqual(a, b) {
172
+ if (a.length !== b.length)
173
+ return false;
174
+ return timingSafeEqual(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));
175
+ }
176
+ //# sourceMappingURL=totp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totp.js","sourceRoot":"","sources":["../../src/utils/totp.ts"],"names":[],"mappings":"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"}
@@ -70,6 +70,22 @@ export declare const acceptInvitationSchema: (minPasswordLength?: number) => z.Z
70
70
  password: string;
71
71
  token: string;
72
72
  }>;
73
+ /** Schema for POST /magic-link/send */
74
+ export declare const sendMagicLinkSchema: z.ZodObject<{
75
+ email: z.ZodString;
76
+ }, "strip", z.ZodTypeAny, {
77
+ email: string;
78
+ }, {
79
+ email: string;
80
+ }>;
81
+ /** Schema for POST /magic-link/consume */
82
+ export declare const consumeMagicLinkSchema: z.ZodObject<{
83
+ token: z.ZodString;
84
+ }, "strip", z.ZodTypeAny, {
85
+ token: string;
86
+ }, {
87
+ token: string;
88
+ }>;
73
89
  export type RegisterInput = z.infer<ReturnType<typeof registerSchema>>;
74
90
  export type LoginInput = z.infer<typeof loginSchema>;
75
91
  export type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>;
@@ -77,4 +93,6 @@ export type ResetPasswordInput = z.infer<ReturnType<typeof resetPasswordSchema>>
77
93
  export type VerifyEmailInput = z.infer<typeof verifyEmailSchema>;
78
94
  export type InviteInput = z.infer<typeof inviteSchema>;
79
95
  export type AcceptInvitationInput = z.infer<ReturnType<typeof acceptInvitationSchema>>;
96
+ export type SendMagicLinkInput = z.infer<typeof sendMagicLinkSchema>;
97
+ export type ConsumeMagicLinkInput = z.infer<typeof consumeMagicLinkSchema>;
80
98
  //# sourceMappingURL=validation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAUvB,gCAAgC;AAChC,eAAO,MAAM,cAAc,GAAI,0BAAqB;;;;;;;;;EAIhD,CAAA;AAEJ,6BAA6B;AAC7B,eAAO,MAAM,WAAW;;;;;;;;;EAGtB,CAAA;AAEF,uCAAuC;AACvC,eAAO,MAAM,oBAAoB;;;;;;EAE/B,CAAA;AAEF,sCAAsC;AACtC,eAAO,MAAM,mBAAmB,GAAI,0BAAqB;;;;;;;;;EAIrD,CAAA;AAEJ,oCAAoC;AACpC,eAAO,MAAM,iBAAiB;;;;;;EAE5B,CAAA;AAEF,8BAA8B;AAC9B,eAAO,MAAM,YAAY;;;;;;;;;EAGvB,CAAA;AAEF,yCAAyC;AACzC,eAAO,MAAM,sBAAsB,GAAI,0BAAqB;;;;;;;;;EAIxD,CAAA;AAEJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAA;AACtE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AACpD,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAA;AAChF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAChE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AACtD,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAUvB,gCAAgC;AAChC,eAAO,MAAM,cAAc,GAAI,0BAAqB;;;;;;;;;EAIhD,CAAA;AAEJ,6BAA6B;AAC7B,eAAO,MAAM,WAAW;;;;;;;;;EAGtB,CAAA;AAEF,uCAAuC;AACvC,eAAO,MAAM,oBAAoB;;;;;;EAE/B,CAAA;AAEF,sCAAsC;AACtC,eAAO,MAAM,mBAAmB,GAAI,0BAAqB;;;;;;;;;EAIrD,CAAA;AAEJ,oCAAoC;AACpC,eAAO,MAAM,iBAAiB;;;;;;EAE5B,CAAA;AAEF,8BAA8B;AAC9B,eAAO,MAAM,YAAY;;;;;;;;;EAGvB,CAAA;AAEF,yCAAyC;AACzC,eAAO,MAAM,sBAAsB,GAAI,0BAAqB;;;;;;;;;EAIxD,CAAA;AAEJ,uCAAuC;AACvC,eAAO,MAAM,mBAAmB;;;;;;EAE9B,CAAA;AAEF,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB;;;;;;EAEjC,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAA;AACtE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AACpD,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAA;AAChF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAChE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AACtD,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAA;AACtF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AACpE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA"}
@@ -37,4 +37,12 @@ export const acceptInvitationSchema = (minPasswordLength = 8) => z.object({
37
37
  token: z.string().min(1, 'Token is required'),
38
38
  password: passwordSchema(minPasswordLength),
39
39
  });
40
+ /** Schema for POST /magic-link/send */
41
+ export const sendMagicLinkSchema = z.object({
42
+ email: emailSchema,
43
+ });
44
+ /** Schema for POST /magic-link/consume */
45
+ export const consumeMagicLinkSchema = z.object({
46
+ token: z.string().min(1, 'Token is required'),
47
+ });
40
48
  //# sourceMappingURL=validation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;AAE7D,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,EAAE,CAC3C,CAAC;KACE,MAAM,EAAE;KACR,GAAG,CAAC,SAAS,EAAE,6BAA6B,SAAS,aAAa,CAAC;KACnE,GAAG,CAAC,EAAE,EAAE,wCAAwC,CAAC,CAAA,CAAC,aAAa;AAEpE,gCAAgC;AAChC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,iBAAiB,GAAG,CAAC,EAAE,EAAE,CACtD,CAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,WAAW;IAClB,QAAQ,EAAE,cAAc,CAAC,iBAAiB,CAAC;CAC5C,CAAC,CAAA;AAEJ,6BAA6B;AAC7B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,WAAW;IAClB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,sBAAsB,CAAC;CACpD,CAAC,CAAA;AAEF,uCAAuC;AACvC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,KAAK,EAAE,WAAW;CACnB,CAAC,CAAA;AAEF,sCAAsC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,iBAAiB,GAAG,CAAC,EAAE,EAAE,CAC3D,CAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;IAC7C,QAAQ,EAAE,cAAc,CAAC,iBAAiB,CAAC;CAC5C,CAAC,CAAA;AAEJ,oCAAoC;AACpC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;CAC9C,CAAC,CAAA;AAEF,8BAA8B;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,KAAK,EAAE,WAAW;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAA;AAEF,yCAAyC;AACzC,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,iBAAiB,GAAG,CAAC,EAAE,EAAE,CAC9D,CAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;IAC7C,QAAQ,EAAE,cAAc,CAAC,iBAAiB,CAAC;CAC5C,CAAC,CAAA"}
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;AAE7D,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,EAAE,CAC3C,CAAC;KACE,MAAM,EAAE;KACR,GAAG,CAAC,SAAS,EAAE,6BAA6B,SAAS,aAAa,CAAC;KACnE,GAAG,CAAC,EAAE,EAAE,wCAAwC,CAAC,CAAA,CAAC,aAAa;AAEpE,gCAAgC;AAChC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,iBAAiB,GAAG,CAAC,EAAE,EAAE,CACtD,CAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,WAAW;IAClB,QAAQ,EAAE,cAAc,CAAC,iBAAiB,CAAC;CAC5C,CAAC,CAAA;AAEJ,6BAA6B;AAC7B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,WAAW;IAClB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,sBAAsB,CAAC;CACpD,CAAC,CAAA;AAEF,uCAAuC;AACvC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,KAAK,EAAE,WAAW;CACnB,CAAC,CAAA;AAEF,sCAAsC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,iBAAiB,GAAG,CAAC,EAAE,EAAE,CAC3D,CAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;IAC7C,QAAQ,EAAE,cAAc,CAAC,iBAAiB,CAAC;CAC5C,CAAC,CAAA;AAEJ,oCAAoC;AACpC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;CAC9C,CAAC,CAAA;AAEF,8BAA8B;AAC9B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,KAAK,EAAE,WAAW;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAA;AAEF,yCAAyC;AACzC,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,iBAAiB,GAAG,CAAC,EAAE,EAAE,CAC9D,CAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;IAC7C,QAAQ,EAAE,cAAc,CAAC,iBAAiB,CAAC;CAC5C,CAAC,CAAA;AAEJ,uCAAuC;AACvC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,KAAK,EAAE,WAAW;CACnB,CAAC,CAAA;AAEF,0CAA0C;AAC1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,CAAC;CAC9C,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authcore/core",
3
- "version": "0.6.0",
3
+ "version": "0.12.0",
4
4
  "description": "Framework-agnostic authentication core for AuthCore",
5
5
  "license": "MIT",
6
6
  "author": "David Ouatedem",
@@ -35,10 +35,11 @@
35
35
  "dependencies": {
36
36
  "bcryptjs": "^2.4.3",
37
37
  "jsonwebtoken": "^9.0.2",
38
- "zod": "^3.23.0"
38
+ "zod": "^3.23.0",
39
+ "@authcore/types": "^0.12.0"
39
40
  },
40
41
  "devDependencies": {
41
- "@types/bcryptjs": "^2.4.0",
42
+ "@types/bcryptjs": "^2.4.3",
42
43
  "@types/jsonwebtoken": "^9.0.6",
43
44
  "@types/node": "^20.0.0",
44
45
  "typescript": "^5.4.0",
@@ -1,42 +0,0 @@
1
- import type { User, Token, TokenType, CreateUserInput, CreateTokenInput } from '../types.js';
2
- /**
3
- * DatabaseAdapter defines the contract that any database implementation must fulfill.
4
- * Implement this interface to add support for a new database (Drizzle, Mongoose, etc.).
5
- *
6
- * @example
7
- * ```ts
8
- * import type { DatabaseAdapter } from '@authcore/core'
9
- *
10
- * export function myAdapter(client: MyClient): DatabaseAdapter {
11
- * return {
12
- * findUserByEmail: (email) => client.user.findUnique({ where: { email } }),
13
- * // ...
14
- * }
15
- * }
16
- * ```
17
- */
18
- export interface DatabaseAdapter {
19
- /** Find a user by their email address. Returns null if not found. */
20
- findUserByEmail(email: string): Promise<User | null>;
21
- /** Find a user by their ID. Returns null if not found. */
22
- findUserById(id: string): Promise<User | null>;
23
- /** Create a new user record. */
24
- createUser(data: CreateUserInput): Promise<User>;
25
- /** Update fields on an existing user. */
26
- updateUser(id: string, data: Partial<Omit<User, 'id' | 'createdAt'>>): Promise<User>;
27
- /**
28
- * Create a new token record.
29
- * The `token` field in `data` must be the SHA-256 hash of the raw token.
30
- */
31
- createToken(data: CreateTokenInput): Promise<Token>;
32
- /**
33
- * Find a token record by the raw token value and type.
34
- * Implementations MUST hash the raw token before querying.
35
- */
36
- findToken(rawToken: string, type: TokenType): Promise<Token | null>;
37
- /** Delete a token by its ID. */
38
- deleteToken(id: string): Promise<void>;
39
- /** Delete all expired tokens. Call periodically for housekeeping. */
40
- deleteExpiredTokens(): Promise<void>;
41
- }
42
- //# sourceMappingURL=database.interface.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"database.interface.d.ts","sourceRoot":"","sources":["../../src/adapters/database.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE5F;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IAEpD,0DAA0D;IAC1D,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IAE9C,gCAAgC;IAChC,UAAU,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEhD,yCAAyC;IACzC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEpF;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAEnD;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;IAEnE,gCAAgC;IAChC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtC,qEAAqE;IACrE,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=database.interface.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"database.interface.js","sourceRoot":"","sources":["../../src/adapters/database.interface.ts"],"names":[],"mappings":""}
@@ -1,31 +0,0 @@
1
- /**
2
- * EmailAdapter defines the contract for any email provider implementation.
3
- * Implement this interface to add support for a new email provider (SendGrid, Mailgun, etc.).
4
- *
5
- * @example
6
- * ```ts
7
- * import type { EmailAdapter } from '@authcore/core'
8
- *
9
- * export function myEmailAdapter(apiKey: string): EmailAdapter {
10
- * return {
11
- * send: async ({ to, subject, html, text }) => {
12
- * await myEmailClient.send({ apiKey, to, subject, html, text })
13
- * }
14
- * }
15
- * }
16
- * ```
17
- */
18
- export interface EmailAdapter {
19
- /**
20
- * Send an email.
21
- * Must throw on failure — AuthCore will not retry automatically.
22
- */
23
- send(options: {
24
- from: string;
25
- to: string;
26
- subject: string;
27
- html: string;
28
- text: string;
29
- }): Promise<void>;
30
- }
31
- //# sourceMappingURL=email.interface.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"email.interface.d.ts","sourceRoot":"","sources":["../../src/adapters/email.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE;QACZ,IAAI,EAAE,MAAM,CAAA;QACZ,EAAE,EAAE,MAAM,CAAA;QACV,OAAO,EAAE,MAAM,CAAA;QACf,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClB"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=email.interface.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"email.interface.js","sourceRoot":"","sources":["../../src/adapters/email.interface.ts"],"names":[],"mappings":""}
package/dist/types.d.ts DELETED
@@ -1,76 +0,0 @@
1
- /**
2
- * Core domain types for AuthCore.
3
- * These are the canonical shapes that adapters must produce/consume.
4
- */
5
- export type TokenType = 'EMAIL_VERIFICATION' | 'PASSWORD_RESET' | 'SESSION' | 'INVITATION';
6
- /** A user record as stored in the database. */
7
- export interface User {
8
- id: string;
9
- email: string;
10
- passwordHash: string;
11
- emailVerified: boolean;
12
- role: string;
13
- createdAt: Date;
14
- updatedAt: Date;
15
- }
16
- /** A token record as stored in the database. The `token` field is the hashed value. */
17
- export interface Token {
18
- id: string;
19
- userId: string;
20
- type: TokenType;
21
- /** SHA-256 hash of the raw token. Never the raw token itself. */
22
- token: string;
23
- expiresAt: Date;
24
- createdAt: Date;
25
- }
26
- /** Input shape for creating a new user. */
27
- export interface CreateUserInput {
28
- email: string;
29
- passwordHash: string;
30
- role?: string;
31
- }
32
- /** Input shape for creating a new token. */
33
- export interface CreateTokenInput {
34
- userId: string;
35
- type: TokenType;
36
- /** SHA-256 hash of the raw token. */
37
- token: string;
38
- expiresAt: Date;
39
- }
40
- /** Safe user shape returned to callers (no passwordHash). */
41
- export type PublicUser = Omit<User, 'passwordHash'>;
42
- /** Configuration for the AuthCore session/JWT strategy. */
43
- export interface SessionConfig {
44
- strategy: 'jwt';
45
- secret: string;
46
- expiresIn?: string;
47
- }
48
- /** Configuration for email features. */
49
- export interface EmailConfig {
50
- provider: import('./adapters/email.interface.js').EmailAdapter;
51
- from: string;
52
- }
53
- /** Optional lifecycle callbacks. */
54
- export interface AuthCallbacks {
55
- onSignUp?: (user: PublicUser) => void | Promise<void>;
56
- onSignIn?: (user: PublicUser) => void | Promise<void>;
57
- onSignOut?: (userId: string) => void | Promise<void>;
58
- onPasswordReset?: (user: PublicUser) => void | Promise<void>;
59
- }
60
- /** Top-level AuthCore configuration object. */
61
- export interface AuthCoreConfig {
62
- db: import('./adapters/database.interface.js').DatabaseAdapter;
63
- session: SessionConfig;
64
- email?: EmailConfig;
65
- features?: Array<'emailVerification' | 'passwordReset' | 'invitation'>;
66
- mode?: 'api' | 'monorepo' | 'auto';
67
- password?: {
68
- minLength?: number;
69
- saltRounds?: number;
70
- };
71
- rbac?: {
72
- defaultRole?: string;
73
- };
74
- callbacks?: AuthCallbacks;
75
- }
76
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,SAAS,GAAG,oBAAoB,GAAG,gBAAgB,GAAG,SAAS,GAAG,YAAY,CAAA;AAE1F,+CAA+C;AAC/C,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,OAAO,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,uFAAuF;AACvF,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,SAAS,CAAA;IACf,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,4CAA4C;AAC5C,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,SAAS,CAAA;IACf,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,6DAA6D;AAC7D,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;AAEnD,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,KAAK,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,+BAA+B,EAAE,YAAY,CAAA;IAC9D,IAAI,EAAE,MAAM,CAAA;CACb;AAED,oCAAoC;AACpC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7D;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,kCAAkC,EAAE,eAAe,CAAA;IAC9D,OAAO,EAAE,aAAa,CAAA;IACtB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,mBAAmB,GAAG,eAAe,GAAG,YAAY,CAAC,CAAA;IACtE,IAAI,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,MAAM,CAAA;IAClC,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,IAAI,CAAC,EAAE;QACL,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,SAAS,CAAC,EAAE,aAAa,CAAA;CAC1B"}
package/dist/types.js DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * Core domain types for AuthCore.
3
- * These are the canonical shapes that adapters must produce/consume.
4
- */
5
- export {};
6
- //# sourceMappingURL=types.js.map
package/dist/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}