@stacksjs/database 0.70.352 → 0.70.354

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.
@@ -62,6 +62,21 @@ export declare function usersStripeIdSql(): string;
62
62
  * both schema paths have to be able to add to it idempotently.
63
63
  */
64
64
  export declare function oauthAccessTokenDeviceColumnsSql(): string[];
65
+ /**
66
+ * Defensive ALTER guaranteeing `password_resets.expires_at`.
67
+ *
68
+ * `createResetToken` in `core/auth/src/password/reset.ts` has always written
69
+ * this column on insert, and the CREATE here never declared it, so on any
70
+ * install whose table came from this migrator "forgot my password" failed with
71
+ * `table password_resets has no column named expires_at`. It went unnoticed
72
+ * because the tests that cover the reset flow hand-rolled their own
73
+ * `password_resets` DDL and invented the column the migrator was missing.
74
+ *
75
+ * The read path deliberately tolerates its absence (see `isWithinExpiry`,
76
+ * which falls back to `created_at` plus the configured expiry), so adding it
77
+ * changes no behaviour for rows already written.
78
+ */
79
+ export declare function passwordResetsExpiresAtSql(): string[];
65
80
  /**
66
81
  * Runs every `users` guarantee-column ALTER (email_verified_at,
67
82
  * password_changed_at, two_factor_secret, two_factor_enabled,
@@ -1,4 +1,4 @@
1
- import process from"node:process";import{randomBytes}from"node:crypto";import{log}from"@stacksjs/logging";import{env as envVars}from"@stacksjs/env";import{db}from"./utils";import{sqlHelpers}from"./sql-helpers";import{indexSqlForDialect}from"./dialect";function getDbDriver(){return envVars.DB_CONNECTION||"sqlite"}export function usersEmailVerifiedAtSql(sql){return`ALTER TABLE users ADD COLUMN email_verified_at ${sql.nullableTimestamp}`}export function usersPasswordChangedAtSql(sql){return`ALTER TABLE users ADD COLUMN password_changed_at ${sql.nullableTimestamp}`}export function usersTwoFactorColumnsSql(sql){return["ALTER TABLE users ADD COLUMN two_factor_secret VARCHAR(255)",`ALTER TABLE users ADD COLUMN two_factor_enabled BOOLEAN NOT NULL DEFAULT ${sql.boolFalse}`,"ALTER TABLE users ADD COLUMN two_factor_last_used_step BIGINT"]}export function usersStripeIdSql(){return"ALTER TABLE users ADD COLUMN stripe_id VARCHAR(255)"}export function oauthAccessTokenDeviceColumnsSql(){return["ALTER TABLE oauth_access_tokens ADD COLUMN user_agent VARCHAR(255)","ALTER TABLE oauth_access_tokens ADD COLUMN ip_address VARCHAR(45)"]}export async function ensureUsersAuthColumns(sql,options={}){const alters=[usersEmailVerifiedAtSql(sql),usersPasswordChangedAtSql(sql),...usersTwoFactorColumnsSql(sql),usersStripeIdSql()];for(const alterSql of alters)try{await db.unsafe(alterSql).execute()}catch{if(options.verbose)log.debug(`[auth-tables] Skipped (already applied or users missing): ${alterSql}`)}try{await db.unsafe(indexSqlForDialect("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_stripe_id ON users(stripe_id)",getDbDriver())).execute()}catch{if(options.verbose)log.debug("[auth-tables] Skipped users.stripe_id unique index (already applied or users missing)")}}export async function migrateAuthTables(options={}){const dbDriver=getDbDriver(),sql=sqlHelpers(dbDriver),{isPostgres,boolTrue,now,pkColumn,nullableTimestamp,datetime}=sql;if(options.verbose)log.info(`Creating auth tables for ${dbDriver}...`);try{if(options.verbose)log.info("Creating oauth_clients table...");await db.unsafe(`
1
+ import process from"node:process";import{randomBytes}from"node:crypto";import{log}from"@stacksjs/logging";import{env as envVars}from"@stacksjs/env";import{db}from"./utils";import{sqlHelpers}from"./sql-helpers";import{indexSqlForDialect}from"./dialect";function getDbDriver(){return envVars.DB_CONNECTION||"sqlite"}export function usersEmailVerifiedAtSql(sql){return`ALTER TABLE users ADD COLUMN email_verified_at ${sql.nullableTimestamp}`}export function usersPasswordChangedAtSql(sql){return`ALTER TABLE users ADD COLUMN password_changed_at ${sql.nullableTimestamp}`}export function usersTwoFactorColumnsSql(sql){return["ALTER TABLE users ADD COLUMN two_factor_secret VARCHAR(255)",`ALTER TABLE users ADD COLUMN two_factor_enabled BOOLEAN NOT NULL DEFAULT ${sql.boolFalse}`,"ALTER TABLE users ADD COLUMN two_factor_last_used_step BIGINT"]}export function usersStripeIdSql(){return"ALTER TABLE users ADD COLUMN stripe_id VARCHAR(255)"}export function oauthAccessTokenDeviceColumnsSql(){return["ALTER TABLE oauth_access_tokens ADD COLUMN user_agent VARCHAR(255)","ALTER TABLE oauth_access_tokens ADD COLUMN ip_address VARCHAR(45)"]}export function passwordResetsExpiresAtSql(){return["ALTER TABLE password_resets ADD COLUMN expires_at TIMESTAMP"]}export async function ensureUsersAuthColumns(sql,options={}){const alters=[usersEmailVerifiedAtSql(sql),usersPasswordChangedAtSql(sql),...usersTwoFactorColumnsSql(sql),usersStripeIdSql()];for(const alterSql of alters)try{await db.unsafe(alterSql).execute()}catch{if(options.verbose)log.debug(`[auth-tables] Skipped (already applied or users missing): ${alterSql}`)}try{await db.unsafe(indexSqlForDialect("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_stripe_id ON users(stripe_id)",getDbDriver())).execute()}catch{if(options.verbose)log.debug("[auth-tables] Skipped users.stripe_id unique index (already applied or users missing)")}}export async function migrateAuthTables(options={}){const dbDriver=getDbDriver(),sql=sqlHelpers(dbDriver),{isPostgres,boolTrue,now,pkColumn,nullableTimestamp,datetime}=sql;if(options.verbose)log.info(`Creating auth tables for ${dbDriver}...`);try{if(options.verbose)log.info("Creating oauth_clients table...");await db.unsafe(`
2
2
  CREATE TABLE IF NOT EXISTS oauth_clients (
3
3
  ${pkColumn},
4
4
  name VARCHAR(255) NOT NULL,
@@ -43,9 +43,14 @@ import process from"node:process";import{randomBytes}from"node:crypto";import{lo
43
43
  ${pkColumn},
44
44
  email VARCHAR(255) NOT NULL,
45
45
  token VARCHAR(255) NOT NULL,
46
+ -- createResetToken writes this on every insert, so the table has to
47
+ -- have it or requesting a reset throws. The read side (isWithinExpiry)
48
+ -- still falls back to clock arithmetic against created_at, because
49
+ -- rows written before the column existed have to keep verifying.
50
+ expires_at ${nullableTimestamp},
46
51
  created_at ${datetime} DEFAULT CURRENT_TIMESTAMP
47
52
  )
48
- `).execute();try{await db.unsafe(indexSqlForDialect("CREATE INDEX IF NOT EXISTS idx_password_resets_email ON password_resets(email)",dbDriver)).execute()}catch{}if(options.verbose)log.info("Creating email_verifications table...");await db.unsafe(`
53
+ `).execute();for(const alterSql of passwordResetsExpiresAtSql())try{await db.unsafe(alterSql).execute()}catch{if(options.verbose)log.debug(`[auth-tables] Skipped (already applied): ${alterSql}`)}try{await db.unsafe(indexSqlForDialect("CREATE INDEX IF NOT EXISTS idx_password_resets_email ON password_resets(email)",dbDriver)).execute()}catch{}if(options.verbose)log.info("Creating email_verifications table...");await db.unsafe(`
49
54
  CREATE TABLE IF NOT EXISTS email_verifications (
50
55
  ${pkColumn},
51
56
  user_id INTEGER NOT NULL,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.352",
5
+ "version": "0.70.354",
6
6
  "description": "The Stacks database integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -65,15 +65,15 @@
65
65
  "dynamodb-tooling": "^0.3.2"
66
66
  },
67
67
  "devDependencies": {
68
- "@stacksjs/cli": "0.70.352",
69
- "@stacksjs/config": "0.70.352",
70
- "@stacksjs/logging": "0.70.352",
71
- "@stacksjs/router": "0.70.352",
68
+ "@stacksjs/cli": "0.70.354",
69
+ "@stacksjs/config": "0.70.354",
70
+ "@stacksjs/logging": "0.70.354",
71
+ "@stacksjs/router": "0.70.354",
72
72
  "better-dx": "^0.2.17",
73
- "@stacksjs/path": "0.70.352",
74
- "@stacksjs/query-builder": "0.70.352",
75
- "@stacksjs/storage": "0.70.352",
76
- "@stacksjs/strings": "0.70.352",
77
- "@stacksjs/utils": "0.70.352"
73
+ "@stacksjs/path": "0.70.354",
74
+ "@stacksjs/query-builder": "0.70.354",
75
+ "@stacksjs/storage": "0.70.354",
76
+ "@stacksjs/strings": "0.70.354",
77
+ "@stacksjs/utils": "0.70.354"
78
78
  }
79
79
  }