@rebasepro/server-postgres 0.17.3 → 0.18.1
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/LICENSE +0 -1
- package/README.md +31 -0
- package/dist/BranchService-ByLfM_Tt.js +360 -0
- package/dist/BranchService-ByLfM_Tt.js.map +1 -0
- package/dist/__vite-browser-external-BnuHet1e.js +46 -0
- package/dist/__vite-browser-external-BnuHet1e.js.map +1 -0
- package/dist/arg-Dni7MzLB.js +87 -0
- package/dist/arg-Dni7MzLB.js.map +1 -0
- package/dist/auth-users-columns-D2LBFrMH.js +116 -0
- package/dist/auth-users-columns-D2LBFrMH.js.map +1 -0
- package/dist/backup-argv.d.ts +19 -0
- package/dist/backup-cli-oM9WQVlV.js +408 -0
- package/dist/backup-cli-oM9WQVlV.js.map +1 -0
- package/dist/backup-service-Br3tqwsy.js +715 -0
- package/dist/backup-service-Br3tqwsy.js.map +1 -0
- package/dist/branch-argv.d.ts +28 -0
- package/dist/branch-prune.d.ts +77 -0
- package/dist/cli-collections-path.d.ts +31 -0
- package/dist/cli-errors-DxN4yzdH.js +657 -0
- package/dist/cli-errors-DxN4yzdH.js.map +1 -0
- package/dist/cli-errors.d.ts +123 -0
- package/dist/cli-flags.d.ts +101 -0
- package/dist/cli-helpers.d.ts +94 -10
- package/dist/cli-output-CNdMql-L.js +47 -0
- package/dist/cli-output-CNdMql-L.js.map +1 -0
- package/dist/cli.js +2020 -0
- package/dist/cli.js.map +1 -0
- package/dist/collection-index-BHgT_TKd.js +276 -0
- package/dist/collection-index-BHgT_TKd.js.map +1 -0
- package/dist/{collections-schema-version-BMeu3cgv.js → collections-schema-version-T6cGvrDi.js} +1 -2
- package/dist/{collections-schema-version-BMeu3cgv.js.map → collections-schema-version-T6cGvrDi.js.map} +1 -1
- package/dist/{connection-GOKU3Hu5.js → databasePoolManager-Bj5FbeAs.js} +112 -40
- package/dist/databasePoolManager-Bj5FbeAs.js.map +1 -0
- package/dist/databasePoolManager.d.ts +27 -0
- package/dist/doctor-D7HWmGzs.js +974 -0
- package/dist/doctor-D7HWmGzs.js.map +1 -0
- package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-2c2NV8zX.js} +7 -27
- package/dist/ensure-collection-policies-2c2NV8zX.js.map +1 -0
- package/dist/ensure-collection-tables-D4wJzM4y.js +820 -0
- package/dist/ensure-collection-tables-D4wJzM4y.js.map +1 -0
- package/dist/ensure-tables-CIojK-Rx.js +650 -0
- package/dist/ensure-tables-CIojK-Rx.js.map +1 -0
- package/dist/generate-drizzle-schema-CwEgVyjO.js +123 -0
- package/dist/generate-drizzle-schema-CwEgVyjO.js.map +1 -0
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +528 -0
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +1 -0
- package/dist/{ensure-collection-tables-DgVixhX3.js → generate-postgres-ddl-logic-CtnpIBVv.js} +126 -984
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +1 -0
- package/dist/generated-schema-staleness-DQdfJl2A.js +157 -0
- package/dist/generated-schema-staleness-DQdfJl2A.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.es.js +7423 -1133
- package/dist/index.es.js.map +1 -1
- package/dist/introspect-db-logic-C6LQdTxj.js +1378 -0
- package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -0
- package/dist/policy-drift-kNJ6X2Uj.js +313 -0
- package/dist/policy-drift-kNJ6X2Uj.js.map +1 -0
- package/dist/{backup-service-DCk7KhhL.js → rls-bootstrap-sql-DLDnUjtx.js} +737 -2689
- package/dist/rls-bootstrap-sql-DLDnUjtx.js.map +1 -0
- package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-DeYEjYLt.js} +65 -190
- package/dist/rls-enforcement-DeYEjYLt.js.map +1 -0
- package/dist/schema/bin/generate-drizzle-schema.d.ts +1 -0
- package/dist/schema/bin/generate-postgres-ddl.d.ts +1 -0
- package/dist/schema/column-type-drift.d.ts +79 -0
- package/dist/schema/doctor-cli.js +129 -0
- package/dist/schema/doctor-cli.js.map +1 -0
- package/dist/schema/doctor.d.ts +42 -0
- package/dist/schema/ensure-collection-tables.d.ts +90 -0
- package/dist/schema/generate-drizzle-schema.d.ts +1 -1
- package/dist/schema/generate-drizzle-schema.js +24 -0
- package/dist/schema/generate-drizzle-schema.js.map +1 -0
- package/dist/schema/generate-next-step.d.ts +30 -0
- package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
- package/dist/schema/generate-postgres-ddl.d.ts +1 -1
- package/dist/schema/generate-postgres-ddl.js +138 -0
- package/dist/schema/generate-postgres-ddl.js.map +1 -0
- package/dist/schema/generated-schema-staleness.d.ts +37 -0
- package/dist/schema/introspect-db-queries.d.ts +34 -0
- package/dist/schema/introspect-db.js +866 -0
- package/dist/schema/introspect-db.js.map +1 -0
- package/dist/security/rls-enforcement.d.ts +63 -2
- package/dist/services/BranchService.d.ts +86 -1
- package/dist/services/collection-helpers.d.ts +1 -1
- package/dist/services/realtimeService.d.ts +30 -2
- package/dist/services/row-pipeline.d.ts +12 -1
- package/dist/source-Br7L7GOI.js +410 -0
- package/dist/source-Br7L7GOI.js.map +1 -0
- package/dist/utils/pg-error-utils.d.ts +3 -1
- package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
- package/dist/vector-index-DkWxm2de.js +309 -0
- package/dist/vector-index-DkWxm2de.js.map +1 -0
- package/package.json +32 -27
- package/dist/backup-service-DCk7KhhL.js.map +0 -1
- package/dist/collection-index-BRUg10H5.js +0 -5690
- package/dist/collection-index-BRUg10H5.js.map +0 -1
- package/dist/connection-GOKU3Hu5.js.map +0 -1
- package/dist/ensure-collection-policies-UCqgv_8c.js.map +0 -1
- package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
- package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
- package/dist/src-DiB5RP2Z.js +0 -541
- package/dist/src-DiB5RP2Z.js.map +0 -1
- package/dist/websocket-BZ4H5wUz.js +0 -8198
- package/dist/websocket-BZ4H5wUz.js.map +0 -1
- package/src/PostgresAdapter.ts +0 -91
- package/src/PostgresBackendDriver.ts +0 -1857
- package/src/PostgresBootstrapper.ts +0 -1157
- package/src/auth/ensure-tables.ts +0 -896
- package/src/auth/schema-version.ts +0 -260
- package/src/auth/services.ts +0 -1610
- package/src/backup/backup-cli.ts +0 -520
- package/src/backup/backup-cron.ts +0 -212
- package/src/backup/backup-logic.ts +0 -93
- package/src/backup/backup-service.ts +0 -485
- package/src/backup/index.ts +0 -12
- package/src/backup/pg-tools.ts +0 -403
- package/src/backup/retention.ts +0 -86
- package/src/cli-errors.ts +0 -265
- package/src/cli-helpers.ts +0 -569
- package/src/cli-output.ts +0 -43
- package/src/cli.ts +0 -1339
- package/src/collections/PostgresCollectionRegistry.ts +0 -108
- package/src/collections/buildRegistry.ts +0 -68
- package/src/collections/validate-relations.ts +0 -402
- package/src/connection.ts +0 -333
- package/src/data-transformer.ts +0 -845
- package/src/databasePoolManager.ts +0 -95
- package/src/history/HistoryService.ts +0 -254
- package/src/history/ensure-history-table.ts +0 -53
- package/src/index.ts +0 -19
- package/src/interfaces.ts +0 -60
- package/src/module-dir.ts +0 -7
- package/src/schema/atlas-argv.ts +0 -94
- package/src/schema/auth-schema.ts +0 -298
- package/src/schema/auth-users-columns.ts +0 -131
- package/src/schema/carved-out-migration.ts +0 -404
- package/src/schema/classify-change.ts +0 -436
- package/src/schema/collection-index.ts +0 -427
- package/src/schema/collections-schema-version.ts +0 -103
- package/src/schema/destructive-sql.ts +0 -94
- package/src/schema/doctor-cli.ts +0 -66
- package/src/schema/doctor-policy-checks.ts +0 -105
- package/src/schema/doctor.ts +0 -837
- package/src/schema/drizzle-ddl.ts +0 -33
- package/src/schema/dynamic-tables.test.ts +0 -302
- package/src/schema/dynamic-tables.ts +0 -293
- package/src/schema/ensure-collection-policies.ts +0 -198
- package/src/schema/ensure-collection-tables.test.ts +0 -518
- package/src/schema/ensure-collection-tables.ts +0 -1267
- package/src/schema/generate-drizzle-schema-logic.ts +0 -957
- package/src/schema/generate-drizzle-schema.ts +0 -143
- package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
- package/src/schema/generate-postgres-ddl.ts +0 -180
- package/src/schema/generate-schema-commit.ts +0 -267
- package/src/schema/generated-schema-staleness.ts +0 -284
- package/src/schema/introspect-db-constraints.ts +0 -385
- package/src/schema/introspect-db-inference.ts +0 -255
- package/src/schema/introspect-db-logic.ts +0 -1387
- package/src/schema/introspect-db-naming.ts +0 -15
- package/src/schema/introspect-db-project.ts +0 -78
- package/src/schema/introspect-db-queries.ts +0 -326
- package/src/schema/introspect-db-structure.ts +0 -670
- package/src/schema/introspect-db-types.ts +0 -56
- package/src/schema/introspect-db.ts +0 -257
- package/src/schema/introspect-runtime.test.ts +0 -260
- package/src/schema/introspect-runtime.ts +0 -327
- package/src/schema/non-sql-collections.test.ts +0 -131
- package/src/schema/rls-bootstrap-sql.ts +0 -288
- package/src/schema/search-column.ts +0 -643
- package/src/schema/test-schema.ts +0 -11
- package/src/schema/vector-index.ts +0 -491
- package/src/security/anonymous-grants.test.ts +0 -73
- package/src/security/policy-drift.test.ts +0 -435
- package/src/security/policy-drift.ts +0 -473
- package/src/security/rls-enforcement.ts +0 -505
- package/src/services/BranchService.ts +0 -326
- package/src/services/FetchService.ts +0 -2115
- package/src/services/PersistService.ts +0 -557
- package/src/services/RelationService.ts +0 -978
- package/src/services/RelationWriteService.ts +0 -653
- package/src/services/cdc/CdcListener.ts +0 -103
- package/src/services/cdc/junction-tables.ts +0 -91
- package/src/services/cdc/trigger-cdc.ts +0 -173
- package/src/services/channel-bus/ChannelBus.ts +0 -44
- package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
- package/src/services/channel-bus/index.ts +0 -114
- package/src/services/channel-history.ts +0 -424
- package/src/services/channel-presence.ts +0 -185
- package/src/services/collection-helpers.ts +0 -420
- package/src/services/dataService.ts +0 -256
- package/src/services/index.ts +0 -14
- package/src/services/junction-writes.ts +0 -295
- package/src/services/nested-path.ts +0 -145
- package/src/services/pg-notify-listener.ts +0 -151
- package/src/services/realtimeService.ts +0 -2545
- package/src/services/row-pipeline.ts +0 -240
- package/src/services/write-denial.ts +0 -55
- package/src/types.ts +0 -4
- package/src/utils/connection-string.ts +0 -58
- package/src/utils/drizzle-conditions.ts +0 -2743
- package/src/utils/pg-array-null-patch.ts +0 -42
- package/src/utils/pg-error-utils.ts +0 -391
- package/src/websocket.ts +0 -855
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import { pgSchema, pgTable, uuid, timestamp, boolean, jsonb, text, unique, index, integer, bigint } from "drizzle-orm/pg-core";
|
|
2
|
-
import { relations } from "drizzle-orm";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Factory function to dynamically create the auth tables bound to the specified schema names.
|
|
6
|
-
*
|
|
7
|
-
* This module builds queries; it does not create tables. `ensureAuthTablesExist`
|
|
8
|
-
* owns the DDL, which makes everything here a *claim* about a database it cannot
|
|
9
|
-
* enforce — and the claims drifted. Every column below was declared
|
|
10
|
-
* `varchar(n)` while the DDL created it as `TEXT`: `user_agent` as varchar(500),
|
|
11
|
-
* `ip_address` as varchar(45), `secret_encrypted` as varchar(500), every
|
|
12
|
-
* `token_hash` as varchar(255). None of it was true of any database this
|
|
13
|
-
* framework ever provisioned. Harmless at runtime — drizzle does not enforce a
|
|
14
|
-
* length client-side, so the widths only ever misled the next reader — but a
|
|
15
|
-
* schema module that describes columns that do not exist is worse than no
|
|
16
|
-
* schema module. They are `text` here now because they are TEXT there.
|
|
17
|
-
*/
|
|
18
|
-
export function createAuthSchema(usersSchemaName = "rebase") {
|
|
19
|
-
const usersSchema = usersSchemaName === "public" ? null : pgSchema(usersSchemaName);
|
|
20
|
-
|
|
21
|
-
const tableCreator = (usersSchema ? usersSchema.table.bind(usersSchema) : pgTable) as typeof pgTable;
|
|
22
|
-
const usersTableCreator = tableCreator;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Users table - stores both email/password and OAuth users
|
|
26
|
-
*/
|
|
27
|
-
const users = usersTableCreator("users", {
|
|
28
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
29
|
-
email: text("email").notNull().unique(),
|
|
30
|
-
passwordHash: text("password_hash"), // NULL for OAuth-only users
|
|
31
|
-
displayName: text("display_name"),
|
|
32
|
-
photoUrl: text("photo_url"),
|
|
33
|
-
emailVerified: boolean("email_verified").default(false).notNull(),
|
|
34
|
-
emailVerificationToken: text("email_verification_token"),
|
|
35
|
-
emailVerificationSentAt: timestamp("email_verification_sent_at"),
|
|
36
|
-
isAnonymous: boolean("is_anonymous").default(false).notNull(),
|
|
37
|
-
roles: text("roles").array().default([]).notNull(),
|
|
38
|
-
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}).notNull(),
|
|
39
|
-
/**
|
|
40
|
-
* Sessions that began before this instant are dead, whatever tokens
|
|
41
|
-
* they still hold. Password resets and admin revocations stamp it.
|
|
42
|
-
*
|
|
43
|
-
* Deleting the user's refresh-token rows (which we also do) is not
|
|
44
|
-
* sufficient on its own: a request already in flight can insert a
|
|
45
|
-
* freshly rotated row microseconds after the delete and survive it.
|
|
46
|
-
* This timestamp cannot be outrun that way — it is checked against
|
|
47
|
-
* `refresh_tokens.session_started_at`, which rotation carries forward.
|
|
48
|
-
*/
|
|
49
|
-
tokensValidAfter: timestamp("tokens_valid_after"),
|
|
50
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
51
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Refresh tokens for long-lived sessions.
|
|
57
|
-
*
|
|
58
|
-
* A row is one token, not one device. Every token minted from the same
|
|
59
|
-
* sign-in shares a `sessionId`, and rotation ADDS a row rather than
|
|
60
|
-
* replacing one: the superseded token stays on file, flagged `revoked`
|
|
61
|
-
* with a `rotatedAt` stamp. That record is what lets the refresh endpoint
|
|
62
|
-
* tell a client replaying a token it never got an answer for (a response
|
|
63
|
-
* lost to a redeploy, a second tab racing on boot) apart from a stranger
|
|
64
|
-
* presenting a token that was never issued. Deleting the old row on sight
|
|
65
|
-
* — the previous behaviour — made those two cases indistinguishable, and
|
|
66
|
-
* the legitimate one is overwhelmingly the common one.
|
|
67
|
-
*
|
|
68
|
-
* There is deliberately NO unique constraint on (uid, user_agent,
|
|
69
|
-
* ip_address). Keying a session on the IP meant one row per "device",
|
|
70
|
-
* so a second browser profile behind the same NAT silently evicted the
|
|
71
|
-
* first, and a phone changing networks orphaned a row on every hop.
|
|
72
|
-
* User agent and IP are descriptive metadata for the sessions list;
|
|
73
|
-
* `sessionId` is the identity.
|
|
74
|
-
*/
|
|
75
|
-
const refreshTokens = tableCreator("refresh_tokens", {
|
|
76
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
77
|
-
uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
78
|
-
sessionId: uuid("session_id").defaultRandom().notNull(),
|
|
79
|
-
tokenHash: text("token_hash").notNull().unique(),
|
|
80
|
-
expiresAt: timestamp("expires_at").notNull(),
|
|
81
|
-
revoked: boolean("revoked").default(false).notNull(),
|
|
82
|
-
rotatedAt: timestamp("rotated_at"),
|
|
83
|
-
/**
|
|
84
|
-
* When the sign-in this token descends from happened — carried across
|
|
85
|
-
* every rotation, unlike `createdAt`. `users.tokensValidAfter` is
|
|
86
|
-
* compared against this, so a revocation cannot be outrun by a token
|
|
87
|
-
* that rotates immediately after it.
|
|
88
|
-
*/
|
|
89
|
-
sessionStartedAt: timestamp("session_started_at").defaultNow().notNull(),
|
|
90
|
-
/**
|
|
91
|
-
* The assurance level the sign-in was established at — `aal2` only
|
|
92
|
-
* where a second factor was actually presented. Carried across
|
|
93
|
-
* rotations, because refresh is not a new authentication and has
|
|
94
|
-
* nothing else to read the level from.
|
|
95
|
-
*/
|
|
96
|
-
aal: text("aal"),
|
|
97
|
-
userAgent: text("user_agent"),
|
|
98
|
-
ipAddress: text("ip_address"),
|
|
99
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
100
|
-
}, (table) => ({
|
|
101
|
-
sessionIdx: index("idx_refresh_tokens_session").on(table.sessionId)
|
|
102
|
-
}));
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Password reset tokens for forgot password flow
|
|
106
|
-
*/
|
|
107
|
-
const passwordResetTokens = tableCreator("password_reset_tokens", {
|
|
108
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
109
|
-
uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
110
|
-
tokenHash: text("token_hash").notNull().unique(),
|
|
111
|
-
expiresAt: timestamp("expires_at").notNull(),
|
|
112
|
-
usedAt: timestamp("used_at"),
|
|
113
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* App config - key/value store for custom settings
|
|
118
|
-
*/
|
|
119
|
-
const appConfig = tableCreator("app_config", {
|
|
120
|
-
key: text("key").primaryKey(),
|
|
121
|
-
value: jsonb("value").notNull(),
|
|
122
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* User identities - maps external OAuth profiles back to local users
|
|
127
|
-
*/
|
|
128
|
-
const userIdentities = tableCreator("user_identities", {
|
|
129
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
130
|
-
uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
131
|
-
provider: text("provider").notNull(), // e.g. 'google', 'linkedin'
|
|
132
|
-
providerId: text("provider_id").notNull(),
|
|
133
|
-
profileData: jsonb("profile_data"),
|
|
134
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
135
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
136
|
-
}, (table) => ({
|
|
137
|
-
uniqueProviderId: unique("unique_provider_id").on(table.provider, table.providerId)
|
|
138
|
-
}));
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* MFA factors table - stores enrolled MFA methods
|
|
142
|
-
*/
|
|
143
|
-
const mfaFactors = tableCreator("mfa_factors", {
|
|
144
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
145
|
-
uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
146
|
-
factorType: text("factor_type").notNull(), // 'totp'
|
|
147
|
-
secretEncrypted: text("secret_encrypted").notNull(),
|
|
148
|
-
friendlyName: text("friendly_name"),
|
|
149
|
-
verified: boolean("verified").default(false).notNull(),
|
|
150
|
-
/**
|
|
151
|
-
* The highest TOTP time step ever accepted for this factor. RFC 6238
|
|
152
|
-
* §5.2 forbids accepting an OTP twice, and the ±1 step window that
|
|
153
|
-
* exists for clock drift is also a 90-second replay window: without
|
|
154
|
-
* this, one observed code buys a fresh session for a minute and a half.
|
|
155
|
-
*/
|
|
156
|
-
lastUsedCounter: bigint("last_used_counter", { mode: "number" }),
|
|
157
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
158
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* MFA challenges table - tracks active MFA verification attempts
|
|
163
|
-
*/
|
|
164
|
-
const mfaChallenges = tableCreator("mfa_challenges", {
|
|
165
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
166
|
-
factorId: uuid("factor_id").notNull().references(() => mfaFactors.id, { onDelete: "cascade" }),
|
|
167
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
168
|
-
verifiedAt: timestamp("verified_at"),
|
|
169
|
-
ipAddress: text("ip_address"),
|
|
170
|
-
/** Failed guesses recorded against this challenge; bounded by the route. */
|
|
171
|
-
attempts: integer("attempts").default(0).notNull(),
|
|
172
|
-
expiresAt: timestamp("expires_at").notNull()
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Recovery codes table - backup codes for MFA
|
|
177
|
-
*/
|
|
178
|
-
const recoveryCodes = tableCreator("recovery_codes", {
|
|
179
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
180
|
-
uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
181
|
-
codeHash: text("code_hash").notNull(),
|
|
182
|
-
usedAt: timestamp("used_at"),
|
|
183
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Magic link tokens for passwordless email login
|
|
188
|
-
*/
|
|
189
|
-
const magicLinkTokens = tableCreator("magic_link_tokens", {
|
|
190
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
191
|
-
uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
192
|
-
tokenHash: text("token_hash").notNull().unique(),
|
|
193
|
-
expiresAt: timestamp("expires_at").notNull(),
|
|
194
|
-
usedAt: timestamp("used_at"),
|
|
195
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
usersSchema,
|
|
200
|
-
users,
|
|
201
|
-
refreshTokens,
|
|
202
|
-
passwordResetTokens,
|
|
203
|
-
appConfig,
|
|
204
|
-
userIdentities,
|
|
205
|
-
mfaFactors,
|
|
206
|
-
mfaChallenges,
|
|
207
|
-
recoveryCodes,
|
|
208
|
-
magicLinkTokens
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Instantiate default schema and tables using the default "rebase" schema
|
|
213
|
-
const defaultAuthSchema = createAuthSchema("rebase");
|
|
214
|
-
|
|
215
|
-
export const usersSchema = defaultAuthSchema.usersSchema;
|
|
216
|
-
|
|
217
|
-
export const users = defaultAuthSchema.users;
|
|
218
|
-
export const refreshTokens = defaultAuthSchema.refreshTokens;
|
|
219
|
-
export const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
|
|
220
|
-
export const appConfig = defaultAuthSchema.appConfig;
|
|
221
|
-
export const userIdentities = defaultAuthSchema.userIdentities;
|
|
222
|
-
export const mfaFactors = defaultAuthSchema.mfaFactors;
|
|
223
|
-
export const mfaChallenges = defaultAuthSchema.mfaChallenges;
|
|
224
|
-
export const recoveryCodes = defaultAuthSchema.recoveryCodes;
|
|
225
|
-
export const magicLinkTokens = defaultAuthSchema.magicLinkTokens;
|
|
226
|
-
|
|
227
|
-
// Relations
|
|
228
|
-
export const usersRelations = relations(users, ({ many }) => ({
|
|
229
|
-
refreshTokens: many(refreshTokens),
|
|
230
|
-
passwordResetTokens: many(passwordResetTokens),
|
|
231
|
-
userIdentities: many(userIdentities),
|
|
232
|
-
mfaFactors: many(mfaFactors),
|
|
233
|
-
recoveryCodes: many(recoveryCodes),
|
|
234
|
-
magicLinkTokens: many(magicLinkTokens)
|
|
235
|
-
}));
|
|
236
|
-
|
|
237
|
-
export const refreshTokensRelations = relations(refreshTokens, ({ one }) => ({
|
|
238
|
-
user: one(users, {
|
|
239
|
-
fields: [refreshTokens.uid],
|
|
240
|
-
references: [users.id]
|
|
241
|
-
})
|
|
242
|
-
}));
|
|
243
|
-
|
|
244
|
-
export const passwordResetTokensRelations = relations(passwordResetTokens, ({ one }) => ({
|
|
245
|
-
user: one(users, {
|
|
246
|
-
fields: [passwordResetTokens.uid],
|
|
247
|
-
references: [users.id]
|
|
248
|
-
})
|
|
249
|
-
}));
|
|
250
|
-
|
|
251
|
-
export const userIdentitiesRelations = relations(userIdentities, ({ one }) => ({
|
|
252
|
-
user: one(users, {
|
|
253
|
-
fields: [userIdentities.uid],
|
|
254
|
-
references: [users.id]
|
|
255
|
-
})
|
|
256
|
-
}));
|
|
257
|
-
|
|
258
|
-
export const mfaFactorsRelations = relations(mfaFactors, ({ one, many }) => ({
|
|
259
|
-
user: one(users, {
|
|
260
|
-
fields: [mfaFactors.uid],
|
|
261
|
-
references: [users.id]
|
|
262
|
-
}),
|
|
263
|
-
challenges: many(mfaChallenges)
|
|
264
|
-
}));
|
|
265
|
-
|
|
266
|
-
export const mfaChallengesRelations = relations(mfaChallenges, ({ one }) => ({
|
|
267
|
-
factor: one(mfaFactors, {
|
|
268
|
-
fields: [mfaChallenges.factorId],
|
|
269
|
-
references: [mfaFactors.id]
|
|
270
|
-
})
|
|
271
|
-
}));
|
|
272
|
-
|
|
273
|
-
export const recoveryCodesRelations = relations(recoveryCodes, ({ one }) => ({
|
|
274
|
-
user: one(users, {
|
|
275
|
-
fields: [recoveryCodes.uid],
|
|
276
|
-
references: [users.id]
|
|
277
|
-
})
|
|
278
|
-
}));
|
|
279
|
-
|
|
280
|
-
export const magicLinkTokensRelations = relations(magicLinkTokens, ({ one }) => ({
|
|
281
|
-
user: one(users, {
|
|
282
|
-
fields: [magicLinkTokens.uid],
|
|
283
|
-
references: [users.id]
|
|
284
|
-
})
|
|
285
|
-
}));
|
|
286
|
-
|
|
287
|
-
// Type exports
|
|
288
|
-
export type User = typeof users.$inferSelect;
|
|
289
|
-
export type NewUser = typeof users.$inferInsert;
|
|
290
|
-
export type RefreshToken = typeof refreshTokens.$inferSelect;
|
|
291
|
-
export type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
|
|
292
|
-
export type AppConfig = typeof appConfig.$inferSelect;
|
|
293
|
-
export type UserIdentity = typeof userIdentities.$inferSelect;
|
|
294
|
-
export type NewUserIdentity = typeof userIdentities.$inferInsert;
|
|
295
|
-
export type MfaFactorRow = typeof mfaFactors.$inferSelect;
|
|
296
|
-
export type MfaChallengeRow = typeof mfaChallenges.$inferSelect;
|
|
297
|
-
export type RecoveryCodeRow = typeof recoveryCodes.$inferSelect;
|
|
298
|
-
export type MagicLinkToken = typeof magicLinkTokens.$inferSelect;
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The one description of what an auth user table's columns must be.
|
|
3
|
-
*
|
|
4
|
-
* ## Why this exists
|
|
5
|
-
*
|
|
6
|
-
* Three different code paths create `rebase.users`, and before this module they
|
|
7
|
-
* disagreed about it:
|
|
8
|
-
*
|
|
9
|
-
* | column | `db push` (generator) | `ensureAuthTablesExist` | `ensureCollectionTables` |
|
|
10
|
-
* |------------------|-----------------------|-------------------------|--------------------------|
|
|
11
|
-
* | `email` | `TEXT UNIQUE NOT NULL`| `TEXT NOT NULL` + CHECK | `TEXT` (nullable) |
|
|
12
|
-
* | `roles` | `TEXT[]` | `NOT NULL DEFAULT '{}'` | nullable, no default |
|
|
13
|
-
* | `email_verified` | `BOOLEAN` | `NOT NULL DEFAULT FALSE`| nullable |
|
|
14
|
-
* | `created_at` | `DEFAULT now()` | `NOT NULL DEFAULT NOW()`| no default |
|
|
15
|
-
*
|
|
16
|
-
* They are all `CREATE TABLE IF NOT EXISTS` / `ADD COLUMN IF NOT EXISTS`, so
|
|
17
|
-
* whichever ran first decided the table and the other two silently no-op'd.
|
|
18
|
-
* Boot order therefore chose the constraints: a managed deploy (collection
|
|
19
|
-
* tables first) got a users table whose `email` was nullable and whose
|
|
20
|
-
* `email_verified` had no default, while the same project pushed from a
|
|
21
|
-
* checkout got the strict one. In the same table, `is_anonymous` came out
|
|
22
|
-
* `NOT NULL DEFAULT false` — because *that* column existed in only one of the
|
|
23
|
-
* three lists, so its owner's definition won by default.
|
|
24
|
-
*
|
|
25
|
-
* A second consequence was drift in the other direction. The scaffold's
|
|
26
|
-
* `users.ts` describes 12 columns; auth needs 14. `db push` is declarative and
|
|
27
|
-
* builds its desired state from the collection alone, so the two columns only
|
|
28
|
-
* auth knows about (`is_anonymous`, `tokens_valid_after`) read as unmanaged
|
|
29
|
-
* drift, and a push run after the server had booted once planned to DROP them.
|
|
30
|
-
*
|
|
31
|
-
* Both problems are the same problem: no single place said what this table is.
|
|
32
|
-
* This is that place. Every creator asks here first and only then falls back to
|
|
33
|
-
* the collection's own property definitions, so a column auth owns has one
|
|
34
|
-
* definition regardless of who gets there first, and a column the developer
|
|
35
|
-
* added to their users collection still behaves like any other field.
|
|
36
|
-
*
|
|
37
|
-
* ## What belongs here
|
|
38
|
-
*
|
|
39
|
-
* Only columns the auth services read or write. A developer's own additions to
|
|
40
|
-
* their users collection (`bio`, `stripe_customer_id`) are ordinary columns and
|
|
41
|
-
* must NOT be listed — they are generated from the collection like every other
|
|
42
|
-
* field, and listing them here would freeze a user's schema.
|
|
43
|
-
*
|
|
44
|
-
* `id` is deliberately absent: its type comes from the collection's id property
|
|
45
|
-
* (uuid / increment / text) and each creator already derives it.
|
|
46
|
-
*
|
|
47
|
-
* Keep in step with {@link AUTH_SCHEMA_VERSION} when a change here makes an
|
|
48
|
-
* older runtime unable to work against a migrated table.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* A column auth owns.
|
|
53
|
-
*
|
|
54
|
-
* Structured rather than one SQL string because the same facts are needed in
|
|
55
|
-
* three grammars — a `CREATE TABLE` column list, an `ADD COLUMN IF NOT EXISTS`,
|
|
56
|
-
* and a pair of `ALTER COLUMN … SET DEFAULT` / `SET NOT NULL` reconciles for a
|
|
57
|
-
* table that already exists with the wrong shape. Parsing a string back apart
|
|
58
|
-
* for the third of those is how the copies drifted in the first place.
|
|
59
|
-
*/
|
|
60
|
-
export interface AuthUsersColumn {
|
|
61
|
-
/** Physical column name. */
|
|
62
|
-
column: string;
|
|
63
|
-
/** Postgres type. */
|
|
64
|
-
type: string;
|
|
65
|
-
/** Default expression, verbatim, or absent for no default. */
|
|
66
|
-
default?: string;
|
|
67
|
-
/** Whether the column is NOT NULL. */
|
|
68
|
-
notNull?: boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* `email` is NOT NULL on purpose, and the anonymous sign-in route depends on it
|
|
73
|
-
* — it synthesizes `anon_<32 hex>@anonymous.local` rather than inserting NULL.
|
|
74
|
-
* The 320-char bound (RFC 5321) is a CHECK rather than a `VARCHAR(n)`, added
|
|
75
|
-
* separately by `ensureAuthTablesExist` so it can be `NOT VALID` on an adopted
|
|
76
|
-
* table that already holds a longer row.
|
|
77
|
-
*/
|
|
78
|
-
export const AUTH_USERS_COLUMNS: readonly AuthUsersColumn[] = [
|
|
79
|
-
{ column: "email", type: "TEXT", notNull: true },
|
|
80
|
-
{ column: "display_name", type: "TEXT" },
|
|
81
|
-
{ column: "photo_url", type: "TEXT" },
|
|
82
|
-
{ column: "roles", type: "TEXT[]", default: "'{}'", notNull: true },
|
|
83
|
-
{ column: "password_hash", type: "TEXT" },
|
|
84
|
-
{ column: "email_verified", type: "BOOLEAN", default: "FALSE", notNull: true },
|
|
85
|
-
{ column: "email_verification_token", type: "TEXT" },
|
|
86
|
-
{ column: "email_verification_sent_at", type: "TIMESTAMP WITH TIME ZONE" },
|
|
87
|
-
{ column: "is_anonymous", type: "BOOLEAN", default: "FALSE", notNull: true },
|
|
88
|
-
{ column: "metadata", type: "JSONB", default: "'{}'", notNull: true },
|
|
89
|
-
{ column: "tokens_valid_after", type: "TIMESTAMP WITH TIME ZONE" },
|
|
90
|
-
{ column: "created_at", type: "TIMESTAMP WITH TIME ZONE", default: "NOW()", notNull: true },
|
|
91
|
-
{ column: "updated_at", type: "TIMESTAMP WITH TIME ZONE", default: "NOW()", notNull: true }
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
const BY_COLUMN = new Map(AUTH_USERS_COLUMNS.map(c => [c.column, c]));
|
|
95
|
-
|
|
96
|
-
/** Type + inline constraints, as they appear after the column name. */
|
|
97
|
-
export function authUsersColumnSql(spec: AuthUsersColumn): string {
|
|
98
|
-
return [
|
|
99
|
-
spec.type,
|
|
100
|
-
spec.default !== undefined ? `DEFAULT ${spec.default}` : "",
|
|
101
|
-
spec.notNull ? "NOT NULL" : ""
|
|
102
|
-
].filter(Boolean).join(" ");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* The auth-owned definition for a physical column name, or `undefined` when
|
|
107
|
-
* auth does not own it.
|
|
108
|
-
*
|
|
109
|
-
* Callers pass the RESOLVED column name (after `columnName` mapping), because
|
|
110
|
-
* that is the only name the three creators agree on: the scaffold's users
|
|
111
|
-
* collection spells the property `displayName` and the column `display_name`.
|
|
112
|
-
*/
|
|
113
|
-
export function authUsersColumnDefinition(column: string): string | undefined {
|
|
114
|
-
const spec = BY_COLUMN.get(column);
|
|
115
|
-
return spec ? authUsersColumnSql(spec) : undefined;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Whether a collection is an auth collection, i.e. whether the definitions in
|
|
120
|
-
* this module apply to its table at all.
|
|
121
|
-
*
|
|
122
|
-
* Duplicated in shape from `@rebasepro/common`'s policy defaults on purpose:
|
|
123
|
-
* that one takes a `CollectionConfig`, this one is called from DDL code paths
|
|
124
|
-
* that hold looser objects, and both spellings must accept `auth: true` as well
|
|
125
|
-
* as `auth: { enabled: true }`.
|
|
126
|
-
*/
|
|
127
|
-
export function isAuthCollection(collection: unknown): boolean {
|
|
128
|
-
const auth = (collection as { auth?: unknown } | undefined)?.auth;
|
|
129
|
-
if (auth === true) return true;
|
|
130
|
-
return typeof auth === "object" && auth !== null && (auth as { enabled?: unknown }).enabled === true;
|
|
131
|
-
}
|