@syncello/auth 3.6.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@ Authentication library for Hono + Drizzle + Cloudflare Workers.
14
14
  - Password reset
15
15
  - OAuth support (Google, Microsoft)
16
16
  - Mobile PKCE support
17
+ - Passwordless sign-in (emailed link and code)
17
18
 
18
19
  ## Installation
19
20
 
@@ -157,6 +158,145 @@ const sessionId = await auth.utils.createSession(db, userId);
157
158
  await auth.utils.deleteSession(db, sessionId);
158
159
  ```
159
160
 
161
+ ## Passwordless Sign-in
162
+
163
+ An emailed sign-in that carries both a link and a 6-digit code. The link works once, from any
164
+ browser. The code works only in the browser that asked for it, and only alongside that browser's
165
+ secret. Secrets are stored hashed, expire after 10 minutes, and asking again replaces the open
166
+ challenge for that email.
167
+
168
+ The module is table-injected and independent of `createAuth`, so it can sign in any kind of
169
+ account: `subjectId` is your own account's id, and the column has no foreign key.
170
+ `syncello-auth sync` generates the table as `passwordlessChallenges`; to use another table, give it
171
+ these columns:
172
+
173
+ | Column | Type | Notes |
174
+ | ----------------- | -------------------------- | --------------------------------------- |
175
+ | `id` | `uuid` primary key | |
176
+ | `email` | `varchar(255)` not null | lowercased |
177
+ | `subject_id` | `uuid` | your account id; null for a sign-up |
178
+ | `link_token_hash` | `varchar(128)` not null | unique |
179
+ | `code_hash` | `varchar(128)` not null | |
180
+ | `browser_hash` | `varchar(128)` not null | |
181
+ | `attempts` | `integer` not null, 0 | failed code tries |
182
+ | `payload` | `jsonb` | handed back on success |
183
+ | `expires_at` | `bigint` (ms) not null | |
184
+ | `consumed_at` | `bigint` (ms) | |
185
+ | `created_at` | `bigint` (ms) not null | indexed with `email` |
186
+
187
+ ```typescript
188
+ import {
189
+ createPasswordlessChallenge,
190
+ verifyPasswordlessLink,
191
+ verifyPasswordlessCode,
192
+ recentPasswordlessFailures,
193
+ passwordlessEmail,
194
+ cleanupExpiredPasswordlessChallenges,
195
+ } from '@syncello/auth';
196
+
197
+ const tables = { passwordlessChallenges };
198
+
199
+ // Start: email the link and code, keep the browser secret in an HttpOnly cookie.
200
+ const { linkToken, code, browserSecret } = await createPasswordlessChallenge(db, tables, {
201
+ email,
202
+ subjectId: account?.id ?? null, // null for a sign-up
203
+ payload: { firstName, phone }, // optional JSON, returned on success (see below)
204
+ ttlMs: 10 * 60 * 1000, // the default
205
+ });
206
+ const mail = passwordlessEmail({
207
+ appName: 'My App',
208
+ appUrl: 'https://example.com', // logo and footer links; the link must be on this host
209
+ linkUrl: `https://www.example.com/sign-in/confirm#t=${linkToken}`,
210
+ code,
211
+ ttlMs: 10 * 60 * 1000, // the default; the email states it in minutes
212
+ });
213
+ await send(email, mail.subject, mail.html, mail.text);
214
+ setCookie(c, 'sign_in_browser', browserSecret, { httpOnly: true, secure: true, sameSite: 'Lax', maxAge: 600 });
215
+
216
+ // The link, from any browser.
217
+ const viaLink = await verifyPasswordlessLink(db, tables, { linkToken });
218
+
219
+ // The code, only with the cookie of the browser that started it.
220
+ if ((await recentPasswordlessFailures(db, tables, { email, windowMs: 60 * 60 * 1000 })) >= 10) {
221
+ // refuse: too many wrong codes for this email
222
+ }
223
+ const viaCode = await verifyPasswordlessCode(db, tables, {
224
+ email,
225
+ code: submitted,
226
+ browserSecret: getCookie(c, 'sign_in_browser') ?? '',
227
+ maxAttempts: 5, // the default
228
+ });
229
+
230
+ // Either answers { ok: true, email, subjectId, payload }
231
+ // or { ok: false, reason: 'invalid' | 'expired' } (link) / 'invalid' | 'expired' | 'locked' (code).
232
+ ```
233
+
234
+ - **`payload` is chosen by whoever asked for the email, not by the email's owner.** Anyone can
235
+ start a sign-in for any address, so a success proves only that the owner opened the email. Keep
236
+ `payload` to details the requester may set for themselves (sign-up fields such as a name). Never
237
+ put an invitation, a role, a grant or an unchecked redirect in it; look those up server-side
238
+ after sign-in, and check any return path against your own allow-list.
239
+ - A success consumes every open challenge for the email, so the other half of the email stops
240
+ working too.
241
+ - Starting a challenge closes the email's open ones, then inserts the new one; the two writes are
242
+ not one transaction, since the injected `db` may be a driver without them (such as Neon HTTP).
243
+ Two requests at the same instant can leave two challenges open. That is harmless: the code is
244
+ checked against the newest, each link works once, and either success consumes both. Rate-limit
245
+ starting a sign-in per email anyway.
246
+ - A wrong code counts one attempt; at `maxAttempts` the challenge is `locked`, even for the right
247
+ code. A wrong browser secret is answered `invalid` without counting, so a stranger who knows
248
+ the address cannot lock its owner out or learn whether a challenge is expired or locked.
249
+ - `recentPasswordlessFailures` sums attempts across all of an email's challenges in the window,
250
+ so asking for a new code never resets the count. Failures are dated by when their challenge was
251
+ created, so one can leave the window up to one TTL early. The count is keyed by email only:
252
+ pair it with a limit per browser or IP, or a Turnstile check, on starting and on verifying.
253
+ - `passwordlessEmail` puts the code in the subject ("123456 is your sign-in code") and the body,
254
+ and states the lifetime from `ttlMs`. It throws unless `linkUrl` is http(s) on `appUrl`'s host
255
+ or a subdomain of it, or on `localhost` / `127.0.0.1` for local development. A `#` fragment is kept, which keeps the token out of server logs and
256
+ `Referer` headers.
257
+ - Run `cleanupExpiredPasswordlessChallenges(db, tables)` from a scheduled job: it deletes
258
+ challenges that expired more than a day ago.
259
+
260
+ ## Invitation-only Sign-up
261
+
262
+ By default anyone can create an account (`signup: 'open'`). Set `signup: 'invitation'` to refuse
263
+ every new account that does not come from an invitation your `invitations.verify` hook accepts
264
+ for that email. `createAuth` throws if `invitations` is missing.
265
+
266
+ ```typescript
267
+ const auth = createAuth({
268
+ db,
269
+ schema,
270
+ getEnv: (c) => c.env,
271
+ signup: 'invitation',
272
+ invitations: {
273
+ // Answer the invited email for a live token, or null.
274
+ verify: async ({ token, email, db }) => findLiveInvitation(db, token, email),
275
+ // Link the new user: create the membership, mark the invitation accepted.
276
+ onAccepted: async ({ token, user, db }) => acceptInvitation(db, token, user),
277
+ },
278
+ });
279
+ ```
280
+
281
+ | Path | Without a valid invitation | With one |
282
+ | --- | --- | --- |
283
+ | `POST /signup` (password) | `403` Problem JSON, "Sign-up is by invitation only" (or "This invitation is invalid, expired, or for a different email address" when a token was given) | verified account, no verification email, `onAccepted` |
284
+ | OAuth callback, new email (web and mobile) | nothing created; redirect to `${APP_URL}/login?error=invitation_required` | account created, `onAccepted`, usual login code |
285
+ | OAuth callback, existing user | signs in as usual | signs in as usual |
286
+
287
+ Pass the token to `GET /oauth/{provider}/authorize?invitationToken=…` so the callback can check
288
+ it. Refusals are logged as `signup_refused_no_invitation` or
289
+ `oauth_signup_refused_no_invitation` security events.
290
+
291
+ In either mode, a user the OAuth callback creates from a verified invitation gets `onAccepted`.
292
+ It is non-fatal: a failure is logged and the user is still signed in. The login code still
293
+ carries `invitationToken`, so make `onAccepted` and any accept-on-login of your own safe to run
294
+ twice.
295
+
296
+ A `verify` hook that throws is logged and treated as "not invited": invitation mode refuses, an
297
+ OAuth sign-up in open mode goes ahead unlinked, and a password signup with the token answers
298
+ `400`.
299
+
160
300
  ## Direct Imports
161
301
 
162
302
  For advanced usage, you can import utilities directly:
@@ -177,6 +317,59 @@ import {
177
317
  } from '@syncello/auth';
178
318
  ```
179
319
 
320
+ ## Release Notes
321
+
322
+ ### 4.1.0
323
+
324
+ - `emailBranding.marketingUrl`: the site the email footer's Privacy Policy (`/privacy`) and Get
325
+ in Touch (`/contact`) links point at. Without it the footer has no links; they used to point
326
+ at the app, where those pages don't exist.
327
+ - Emails state each link's or code's real lifetime from `AUTH_DEFAULTS`: the password reset
328
+ email said 1 hour for a 15-minute token, and the verification email 48 hours for a 24-hour
329
+ one. `AUTH_DEFAULTS` gains `EMAIL_VERIFICATION_TTL_HOURS`, `EMAIL_CHANGE_TTL_HOURS` and
330
+ `TWO_FACTOR_EMAIL_CODE_TTL_MINUTES`, which the token code now reads too (values unchanged).
331
+
332
+ ### 4.0.0
333
+
334
+ **Breaking:** `verifyWebhookSignature` takes all three svix headers. Svix signs
335
+ `<id>.<timestamp>.<body>` and refuses a stale timestamp, but 3.x passed an empty id and
336
+ timestamp, so every genuine Resend delivery was refused.
337
+
338
+ ```typescript
339
+ // 3.x
340
+ verifyWebhookSignature(payload, c.req.header('svix-signature'), secret);
341
+
342
+ // 4.0.0
343
+ const headers: SvixHeaders = {
344
+ 'svix-id': c.req.header('svix-id') ?? '',
345
+ 'svix-timestamp': c.req.header('svix-timestamp') ?? '',
346
+ 'svix-signature': c.req.header('svix-signature') ?? '',
347
+ };
348
+ verifyWebhookSignature(payload, headers, secret);
349
+ ```
350
+
351
+ **Breaking:** `POST /signup` no longer accepts `oauthToken`. Nothing has written the stored
352
+ OAuth signup state it read since 3.5.0, when the OAuth callback began creating first-time OAuth
353
+ users itself, so the path was unreachable.
354
+
355
+ - Passwordless sign-in (see above), with a `passwordlessChallenges` table and a `jsonb` column
356
+ type in the generated schema (schema version `2026.10.1`). Run `syncello-auth sync` to add the
357
+ table.
358
+ - `signup: 'open' | 'invitation'` on `createAuth` (see Invitation-only Sign-up). The default,
359
+ `'open'`, keeps today's behaviour, with two changes: the OAuth callback now calls
360
+ `invitations.verify` for a new user whose OAuth state carries an `invitationToken`, and
361
+ `onAccepted` when it verifies; and password signup checks an invitation token before looking
362
+ up the email, so a bad token on a registered email answers `400` rather than `409`.
363
+ - The session's sliding window moves at most every 15 minutes
364
+ (`SESSION_REFRESH_INTERVAL_MINUTES`), on the cookie and Bearer paths and in `validateSession`,
365
+ instead of writing the session row on every authenticated request.
366
+ - Emails name the app in the footer (`appName`, HTML-escaped) instead of "App", and
367
+ `EmailBranding.headingColor` sets the heading colour (default `#1e1b4b`, unchanged).
368
+ - `syncello-auth sync` now detects breaking changes it previously missed. Its parser read only the
369
+ first column or two of each table, so removed columns, type changes, shortened `varchar`s and
370
+ new `NOT NULL`s further down went unreported. Expect sync to flag such changes in a schema that
371
+ has drifted.
372
+
180
373
  ## License
181
374
 
182
375
  UNLICENSED - Syncello Internal Use Only
@@ -1,5 +1,5 @@
1
1
  // src/schema/definitions.ts
2
- var SCHEMA_VERSION = "2026.05.1";
2
+ var SCHEMA_VERSION = "2026.10.1";
3
3
  var enums = {
4
4
  identifierType: {
5
5
  name: "identifier_type_enum",
@@ -237,6 +237,23 @@ var tables = {
237
237
  { name: "oauth_accounts_user_provider_idx", columns: ["userId", "provider"], unique: true },
238
238
  { name: "oauth_accounts_provider_user_idx", columns: ["provider", "providerUserId"], unique: true }
239
239
  ]
240
+ },
241
+ passwordlessChallenges: {
242
+ columns: {
243
+ id: { type: "uuid", primaryKey: true, default: "gen_random_uuid()" },
244
+ email: { type: "varchar", length: 255, notNull: true },
245
+ // The id of whatever account signs in, so no foreign key; null for a sign-up.
246
+ subjectId: { type: "uuid" },
247
+ linkTokenHash: { type: "varchar", length: 128, notNull: true, unique: true },
248
+ codeHash: { type: "varchar", length: 128, notNull: true },
249
+ browserHash: { type: "varchar", length: 128, notNull: true },
250
+ attempts: { type: "integer", notNull: true, default: 0 },
251
+ payload: { type: "jsonb" },
252
+ expiresAt: { type: "bigint", mode: "number", notNull: true },
253
+ consumedAt: { type: "bigint", mode: "number" },
254
+ createdAt: { type: "bigint", mode: "number", notNull: true }
255
+ },
256
+ indexes: [{ name: "idx_passwordless_email_created", columns: ["email", "createdAt"] }]
240
257
  }
241
258
  };
242
259
  var tableNames = {
@@ -251,7 +268,8 @@ var tableNames = {
251
268
  emailEvents: "email_events",
252
269
  failedLoginAttempts: "failed_login_attempts",
253
270
  securityAuditLog: "security_audit_log",
254
- oauthAccounts: "oauth_accounts"
271
+ oauthAccounts: "oauth_accounts",
272
+ passwordlessChallenges: "passwordless_challenges"
255
273
  };
256
274
 
257
275
  export {
@@ -260,4 +278,4 @@ export {
260
278
  tables,
261
279
  tableNames
262
280
  };
263
- //# sourceMappingURL=chunk-OZ26T3ZA.js.map
281
+ //# sourceMappingURL=chunk-W35EDOHW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema/definitions.ts"],"sourcesContent":["/**\n * @syncello/auth Schema Definitions\n * Plain TypeScript objects for CLI-based Drizzle code generation\n */\n\nexport const SCHEMA_VERSION = '2026.10.1';\n\n// ====================================\n// TYPE DEFINITIONS\n// ====================================\n\nexport type ColumnType =\n\t| 'uuid'\n\t| 'varchar'\n\t| 'text'\n\t| 'boolean'\n\t| 'integer'\n\t| 'bigint'\n\t| 'timestamp'\n\t| 'json'\n\t| 'jsonb'\n\t| 'serial';\n\nexport type ColumnDefinition = {\n\ttype: ColumnType | string; // string allows enum references\n\tlength?: number;\n\tprimaryKey?: boolean;\n\tnotNull?: boolean;\n\tunique?: boolean;\n\tdefault?: string | number | boolean | null;\n\treferences?: {\n\t\ttable: string;\n\t\tcolumn: string;\n\t\tonDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';\n\t};\n\tmode?: 'number'; // for bigint\n\twithTimezone?: boolean; // for timestamp\n};\n\nexport type IndexDefinition = {\n\tname: string;\n\tcolumns: string[];\n\tunique?: boolean;\n};\n\nexport type TableDefinition = {\n\tcolumns: Record<string, ColumnDefinition>;\n\tindexes?: IndexDefinition[];\n};\n\nexport type EnumDefinition = {\n\tname: string;\n\tvalues: readonly string[];\n};\n\n// ====================================\n// ENUMS\n// ====================================\n\nexport const enums = {\n\tidentifierType: {\n\t\tname: 'identifier_type_enum',\n\t\tvalues: ['email', 'ip'] as const,\n\t},\n\ttwoFactorMethod: {\n\t\tname: 'two_factor_method',\n\t\tvalues: ['totp', 'email'] as const,\n\t},\n\tdeviceType: {\n\t\tname: 'device_type',\n\t\tvalues: ['desktop', 'mobile', 'tablet'] as const,\n\t},\n} as const satisfies Record<string, EnumDefinition>;\n\n// ====================================\n// TABLE DEFINITIONS\n// ====================================\n\nexport const tables = {\n\tusers: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\temail: { type: 'varchar', length: 255, notNull: true, unique: true },\n\t\t\tname: { type: 'text' },\n\t\t\thashedPassword: { type: 'varchar', length: 255 },\n\t\t\tpepperKid: { type: 'varchar', length: 10, default: 'v1' },\n\t\t\temailVerified: { type: 'boolean', default: false },\n\t\t\tisAdmin: { type: 'boolean', default: false, notNull: true },\n\t\t\tsessionVersion: { type: 'integer', default: 1 },\n\t\t\tlockedUntil: { type: 'bigint', mode: 'number' },\n\t\t\tfailedLoginCount: { type: 'integer', default: 0 },\n\t\t\temailBounced: { type: 'boolean', default: false, notNull: true },\n\t\t\temailBouncedAt: { type: 'timestamp', withTimezone: true },\n\t\t\temailComplained: { type: 'boolean', default: false, notNull: true },\n\t\t\temailComplainedAt: { type: 'timestamp', withTimezone: true },\n\t\t\tactivatedAt: { type: 'timestamp', withTimezone: true },\n\t\t\tdeletedAt: { type: 'timestamp', withTimezone: true },\n\t\t\tscheduledPurgeAt: { type: 'timestamp', withTimezone: true },\n\t\t\tcreatedAt: { type: 'timestamp', withTimezone: true, default: 'now()' },\n\t\t\tupdatedAt: { type: 'timestamp', withTimezone: true, default: 'now()' },\n\t\t},\n\t\tindexes: [{ name: 'idx_users_email', columns: ['email'] }],\n\t},\n\n\tsessions: {\n\t\tcolumns: {\n\t\t\tid: { type: 'varchar', length: 128, primaryKey: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tlastActiveAt: { type: 'bigint', mode: 'number' },\n\t\t\tfingerprint: { type: 'varchar', length: 64 },\n\t\t\tipAddress: { type: 'varchar', length: 45 },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_sessions_expires', columns: ['expiresAt'] },\n\t\t\t{ name: 'idx_sessions_user', columns: ['userId'] },\n\t\t],\n\t},\n\n\tuser2faMethods: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\tmethod: { type: 'two_factor_method', notNull: true },\n\t\t\ttotpSecret: { type: 'varchar', length: 256 },\n\t\t\tlastTotpCounter: { type: 'bigint', mode: 'number' },\n\t\t\tisPrimary: { type: 'boolean', default: false, notNull: true },\n\t\t\tverifiedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'user_2fa_methods_user_method_idx', columns: ['userId', 'method'], unique: true },\n\t\t\t{ name: 'user_2fa_methods_user_idx', columns: ['userId'] },\n\t\t],\n\t},\n\n\tuserBackupCodes: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\tcodeHash: { type: 'varchar', length: 255, notNull: true },\n\t\t\tusedAt: { type: 'bigint', mode: 'number' },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [{ name: 'user_backup_codes_user_idx', columns: ['userId'] }],\n\t},\n\n\tuserTrustedDevices: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\ttokenHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\tdeviceName: { type: 'varchar', length: 255, notNull: true },\n\t\t\tdeviceType: { type: 'device_type', notNull: true, default: 'desktop' },\n\t\t\tipAddress: { type: 'varchar', length: 45, notNull: true },\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tlastUsedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'user_trusted_devices_user_idx', columns: ['userId'] },\n\t\t\t{ name: 'user_trusted_devices_token_idx', columns: ['tokenHash'] },\n\t\t\t{ name: 'user_trusted_devices_expires_idx', columns: ['expiresAt'] },\n\t\t],\n\t},\n\n\temailVerificationTokens: {\n\t\tcolumns: {\n\t\t\ttokenHash: { type: 'varchar', length: 128, primaryKey: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\temail: { type: 'varchar', length: 255, notNull: true },\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_email_verification_user', columns: ['userId'] },\n\t\t\t{ name: 'idx_email_verification_expires', columns: ['expiresAt'] },\n\t\t],\n\t},\n\n\tpasswordResetTokens: {\n\t\tcolumns: {\n\t\t\ttokenHash: { type: 'varchar', length: 128, primaryKey: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tused: { type: 'boolean', default: false },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_password_reset_user', columns: ['userId'] },\n\t\t\t{ name: 'idx_password_reset_expires', columns: ['expiresAt'] },\n\t\t],\n\t},\n\n\temailChangeTokens: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\tnewEmail: { type: 'varchar', length: 255, notNull: true },\n\t\t\ttokenHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\tcancelTokenHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_email_change_tokens_user', columns: ['userId'], unique: true },\n\t\t\t{ name: 'idx_email_change_tokens_token', columns: ['tokenHash'] },\n\t\t\t{ name: 'idx_email_change_tokens_cancel', columns: ['cancelTokenHash'] },\n\t\t\t{ name: 'idx_email_change_tokens_expires', columns: ['expiresAt'] },\n\t\t],\n\t},\n\n\temailEvents: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\temailId: { type: 'text', notNull: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\teventType: { type: 'text', notNull: true },\n\t\t\temailAddress: { type: 'text', notNull: true },\n\t\t\tmetadata: { type: 'json' },\n\t\t\tcreatedAt: { type: 'timestamp', withTimezone: true, default: 'now()', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'email_events_email_address_idx', columns: ['emailAddress'] },\n\t\t\t{ name: 'email_events_user_id_idx', columns: ['userId'] },\n\t\t\t{ name: 'email_events_email_id_event_type_idx', columns: ['emailId', 'eventType'], unique: true },\n\t\t],\n\t},\n\n\tfailedLoginAttempts: {\n\t\tcolumns: {\n\t\t\tid: { type: 'serial', primaryKey: true },\n\t\t\tidentifier: { type: 'varchar', length: 255, notNull: true },\n\t\t\tidentifierType: { type: 'identifier_type_enum', notNull: true },\n\t\t\tattemptedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_failed_attempts_identifier', columns: ['identifier', 'attemptedAt'] },\n\t\t\t{ name: 'idx_failed_attempts_time', columns: ['attemptedAt'] },\n\t\t],\n\t},\n\n\tsecurityAuditLog: {\n\t\tcolumns: {\n\t\t\tid: { type: 'serial', primaryKey: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'set null' },\n\t\t\t},\n\t\t\teventType: { type: 'varchar', length: 50, notNull: true },\n\t\t\teventData: { type: 'json' },\n\t\t\tipAddress: { type: 'varchar', length: 45 },\n\t\t\tuserAgent: { type: 'text' },\n\t\t\tsuccess: { type: 'boolean', notNull: true },\n\t\t\tcreatedAt: { type: 'timestamp', withTimezone: true, default: 'now()' },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_audit_user', columns: ['userId'] },\n\t\t\t{ name: 'idx_audit_event', columns: ['eventType'] },\n\t\t\t{ name: 'idx_audit_created', columns: ['createdAt'] },\n\t\t],\n\t},\n\n\toauthAccounts: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\tprovider: { type: 'varchar', length: 50, notNull: true },\n\t\t\tproviderUserId: { type: 'varchar', length: 255, notNull: true },\n\t\t\temail: { type: 'varchar', length: 255, notNull: true },\n\t\t\tcreatedAt: { type: 'timestamp', withTimezone: true, default: 'now()', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'oauth_accounts_user_provider_idx', columns: ['userId', 'provider'], unique: true },\n\t\t\t{ name: 'oauth_accounts_provider_user_idx', columns: ['provider', 'providerUserId'], unique: true },\n\t\t],\n\t},\n\n\tpasswordlessChallenges: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\temail: { type: 'varchar', length: 255, notNull: true },\n\t\t\t// The id of whatever account signs in, so no foreign key; null for a sign-up.\n\t\t\tsubjectId: { type: 'uuid' },\n\t\t\tlinkTokenHash: { type: 'varchar', length: 128, notNull: true, unique: true },\n\t\t\tcodeHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\tbrowserHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\tattempts: { type: 'integer', notNull: true, default: 0 },\n\t\t\tpayload: { type: 'jsonb' },\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tconsumedAt: { type: 'bigint', mode: 'number' },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [{ name: 'idx_passwordless_email_created', columns: ['email', 'createdAt'] }],\n\t},\n} as const satisfies Record<string, TableDefinition>;\n\n// ====================================\n// TABLE NAME MAPPING\n// ====================================\n\nexport const tableNames = {\n\tusers: 'users',\n\tsessions: 'sessions',\n\tuser2faMethods: 'user_2fa_methods',\n\tuserBackupCodes: 'user_backup_codes',\n\tuserTrustedDevices: 'user_trusted_devices',\n\temailVerificationTokens: 'email_verification_tokens',\n\tpasswordResetTokens: 'password_reset_tokens',\n\temailChangeTokens: 'email_change_tokens',\n\temailEvents: 'email_events',\n\tfailedLoginAttempts: 'failed_login_attempts',\n\tsecurityAuditLog: 'security_audit_log',\n\toauthAccounts: 'oauth_accounts',\n\tpasswordlessChallenges: 'passwordless_challenges',\n} as const;\n\nexport type TableName = keyof typeof tables;\nexport type EnumName = keyof typeof enums;\n"],"mappings":"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","names":[]}
package/dist/cli/index.js CHANGED
@@ -8,7 +8,7 @@ import * as fs3 from "fs";
8
8
  import * as path2 from "path";
9
9
 
10
10
  // src/schema/definitions.ts
11
- var SCHEMA_VERSION = "2026.05.1";
11
+ var SCHEMA_VERSION = "2026.10.1";
12
12
  var enums = {
13
13
  identifierType: {
14
14
  name: "identifier_type_enum",
@@ -246,6 +246,23 @@ var tables = {
246
246
  { name: "oauth_accounts_user_provider_idx", columns: ["userId", "provider"], unique: true },
247
247
  { name: "oauth_accounts_provider_user_idx", columns: ["provider", "providerUserId"], unique: true }
248
248
  ]
249
+ },
250
+ passwordlessChallenges: {
251
+ columns: {
252
+ id: { type: "uuid", primaryKey: true, default: "gen_random_uuid()" },
253
+ email: { type: "varchar", length: 255, notNull: true },
254
+ // The id of whatever account signs in, so no foreign key; null for a sign-up.
255
+ subjectId: { type: "uuid" },
256
+ linkTokenHash: { type: "varchar", length: 128, notNull: true, unique: true },
257
+ codeHash: { type: "varchar", length: 128, notNull: true },
258
+ browserHash: { type: "varchar", length: 128, notNull: true },
259
+ attempts: { type: "integer", notNull: true, default: 0 },
260
+ payload: { type: "jsonb" },
261
+ expiresAt: { type: "bigint", mode: "number", notNull: true },
262
+ consumedAt: { type: "bigint", mode: "number" },
263
+ createdAt: { type: "bigint", mode: "number", notNull: true }
264
+ },
265
+ indexes: [{ name: "idx_passwordless_email_created", columns: ["email", "createdAt"] }]
249
266
  }
250
267
  };
251
268
  var tableNames = {
@@ -260,7 +277,8 @@ var tableNames = {
260
277
  emailEvents: "email_events",
261
278
  failedLoginAttempts: "failed_login_attempts",
262
279
  securityAuditLog: "security_audit_log",
263
- oauthAccounts: "oauth_accounts"
280
+ oauthAccounts: "oauth_accounts",
281
+ passwordlessChallenges: "passwordless_challenges"
264
282
  };
265
283
 
266
284
  // src/schema/generator.ts
@@ -312,6 +330,9 @@ function generateColumnCode(name, col) {
312
330
  case "json":
313
331
  parts.push(`json('${snakeName}')`);
314
332
  break;
333
+ case "jsonb":
334
+ parts.push(`jsonb('${snakeName}')`);
335
+ break;
315
336
  case "serial":
316
337
  parts.push(`serial('${snakeName}')`);
317
338
  break;
@@ -461,6 +482,7 @@ function generateSchemaFile() {
461
482
  lines.push(" bigint,");
462
483
  lines.push(" timestamp,");
463
484
  lines.push(" json,");
485
+ lines.push(" jsonb,");
464
486
  lines.push(" serial,");
465
487
  lines.push(" index,");
466
488
  lines.push(" uniqueIndex,");
@@ -753,7 +775,7 @@ function parseGeneratedSchema(content) {
753
775
  const values = valuesStr.match(/'([^']+)'/g)?.map((v) => v.replace(/'/g, "")) || [];
754
776
  result.enums[enumName] = { values };
755
777
  }
756
- const tableRegex = /export const (\w+) = pgTable\(\s*'(\w+)',\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/gs;
778
+ const tableRegex = /export const (\w+) = pgTable\(\s*'(\w+)',\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/gs;
757
779
  let tableMatch;
758
780
  while ((tableMatch = tableRegex.exec(content)) !== null) {
759
781
  const tableName = tableMatch[2];
@@ -766,7 +788,7 @@ function parseColumns(block) {
766
788
  const columns = {};
767
789
  const lines = block.split("\n");
768
790
  for (const line of lines) {
769
- const colMatch = line.match(/(\w+):\s*(uuid|varchar|text|boolean|integer|bigint|timestamp|json|serial)\(/);
791
+ const colMatch = line.match(/(\w+):\s*(uuid|varchar|text|boolean|integer|bigint|timestamp|jsonb|json|serial)\(/);
770
792
  if (colMatch) {
771
793
  const colName = colMatch[1];
772
794
  const colType = colMatch[2];
@@ -886,20 +908,7 @@ function detectBreakingChanges(oldSchemaContent) {
886
908
  };
887
909
  }
888
910
  function getTableNameMapping() {
889
- return {
890
- users: "users",
891
- sessions: "sessions",
892
- user_2fa_methods: "user2faMethods",
893
- user_backup_codes: "userBackupCodes",
894
- user_trusted_devices: "userTrustedDevices",
895
- email_verification_tokens: "emailVerificationTokens",
896
- password_reset_tokens: "passwordResetTokens",
897
- email_change_tokens: "emailChangeTokens",
898
- email_events: "emailEvents",
899
- failed_login_attempts: "failedLoginAttempts",
900
- security_audit_log: "securityAuditLog",
901
- oauth_accounts: "oauthAccounts"
902
- };
911
+ return Object.fromEntries(Object.entries(tableNames).map(([jsName, dbName]) => [dbName, jsName]));
903
912
  }
904
913
  function getNewTableByName(dbTableName) {
905
914
  const mapping = getTableNameMapping();