@stacksjs/database 0.70.350 → 0.70.352

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.
@@ -42,6 +42,26 @@ export declare function usersTwoFactorColumnsSql(sql: SqlHelpers): string[];
42
42
  * stacksjs/status#1 Phase 9).
43
43
  */
44
44
  export declare function usersStripeIdSql(): string;
45
+ /**
46
+ * Defensive ALTERs guaranteeing the device columns on
47
+ * `oauth_access_tokens` - `user_agent` and `ip_address`.
48
+ *
49
+ * They exist for one feature and it cannot be built without them: **a list of
50
+ * your own sessions that you can revoke.** Without something to recognise a row
51
+ * by, that page is a column of identical timestamps and "revoke" is guesswork,
52
+ * so the page nobody can act on is the page nobody builds - which is why
53
+ * "somebody is signed in as me on a laptop I sold" has no answer in most
54
+ * self-hosted software.
55
+ *
56
+ * Nullable, and populated only by callers that have a request to read them
57
+ * from. A token minted by a script has no user agent and no meaningful address,
58
+ * and inventing one would make the list less trustworthy rather than more.
59
+ *
60
+ * Same pure-builder + try/catch-swallow pattern as the `users` guarantees
61
+ * above, for the same reason: no generated migration creates this table, so
62
+ * both schema paths have to be able to add to it idempotently.
63
+ */
64
+ export declare function oauthAccessTokenDeviceColumnsSql(): string[];
45
65
  /**
46
66
  * Runs every `users` guarantee-column ALTER (email_verified_at,
47
67
  * 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 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 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,
@@ -21,10 +21,15 @@ import process from"node:process";import{randomBytes}from"node:crypto";import{lo
21
21
  scopes TEXT,
22
22
  revoked BOOLEAN NOT NULL DEFAULT ${sql.boolFalse},
23
23
  expires_at ${nullableTimestamp},
24
+ -- What the browser called itself and where it came from, so a person
25
+ -- can recognise their own sessions well enough to revoke one. Null for
26
+ -- a token minted by a script, which has neither.
27
+ user_agent VARCHAR(255),
28
+ ip_address VARCHAR(45),
24
29
  created_at ${datetime} DEFAULT CURRENT_TIMESTAMP,
25
30
  updated_at ${nullableTimestamp}
26
31
  )
27
- `).execute();await createTokenIndex("idx_oauth_access_tokens_token","oauth_access_tokens","token");if(options.verbose)log.info("Creating oauth_refresh_tokens table...");await db.unsafe(`
32
+ `).execute();await createTokenIndex("idx_oauth_access_tokens_token","oauth_access_tokens","token");for(const alterSql of oauthAccessTokenDeviceColumnsSql())try{await db.unsafe(alterSql).execute()}catch{if(options.verbose)log.debug(`[auth-tables] Skipped (already applied): ${alterSql}`)}if(options.verbose)log.info("Creating oauth_refresh_tokens table...");await db.unsafe(`
28
33
  CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
29
34
  ${pkColumn},
30
35
  access_token_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.350",
5
+ "version": "0.70.352",
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.350",
69
- "@stacksjs/config": "0.70.350",
70
- "@stacksjs/logging": "0.70.350",
71
- "@stacksjs/router": "0.70.350",
68
+ "@stacksjs/cli": "0.70.352",
69
+ "@stacksjs/config": "0.70.352",
70
+ "@stacksjs/logging": "0.70.352",
71
+ "@stacksjs/router": "0.70.352",
72
72
  "better-dx": "^0.2.17",
73
- "@stacksjs/path": "0.70.350",
74
- "@stacksjs/query-builder": "0.70.350",
75
- "@stacksjs/storage": "0.70.350",
76
- "@stacksjs/strings": "0.70.350",
77
- "@stacksjs/utils": "0.70.350"
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"
78
78
  }
79
79
  }