@rebasepro/server-postgresql 0.2.3 → 0.2.5
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/dist/common/src/collections/default-collections.d.ts +9 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/util/permissions.d.ts +1 -0
- package/dist/index.es.js +1075 -470
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1071 -466
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +3 -1
- package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +1 -0
- package/dist/server-postgresql/src/auth/services.d.ts +48 -31
- package/dist/server-postgresql/src/connection.d.ts +25 -0
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +2135 -41
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +4 -0
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +4 -0
- package/dist/server-postgresql/src/services/entityService.d.ts +6 -0
- package/dist/server-postgresql/src/services/realtimeService.d.ts +20 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +18 -0
- package/dist/types/src/controllers/auth.d.ts +4 -26
- package/dist/types/src/controllers/client.d.ts +25 -43
- package/dist/types/src/controllers/collection_registry.d.ts +1 -1
- package/dist/types/src/controllers/data.d.ts +4 -0
- package/dist/types/src/controllers/data_driver.d.ts +23 -0
- package/dist/types/src/controllers/registry.d.ts +5 -4
- package/dist/types/src/rebase_context.d.ts +1 -1
- package/dist/types/src/types/auth_adapter.d.ts +5 -60
- package/dist/types/src/types/backend.d.ts +2 -2
- package/dist/types/src/types/backend_hooks.d.ts +2 -17
- package/dist/types/src/types/collections.d.ts +0 -4
- package/dist/types/src/types/component_ref.d.ts +1 -1
- package/dist/types/src/types/cron.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +1 -0
- package/dist/types/src/types/export_import.d.ts +1 -1
- package/dist/types/src/types/formex.d.ts +2 -2
- package/dist/types/src/types/properties.d.ts +9 -7
- package/dist/types/src/types/translations.d.ts +28 -12
- package/dist/types/src/types/user_management_delegate.d.ts +22 -57
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -1
- package/package.json +6 -6
- package/src/PostgresBackendDriver.ts +14 -2
- package/src/PostgresBootstrapper.ts +30 -20
- package/src/auth/ensure-tables.ts +116 -103
- package/src/auth/services.ts +347 -177
- package/src/connection.ts +77 -0
- package/src/data-transformer.ts +2 -2
- package/src/schema/auth-schema.ts +85 -75
- package/src/schema/doctor.ts +44 -3
- package/src/schema/generate-drizzle-schema-logic.ts +33 -3
- package/src/schema/generate-drizzle-schema.ts +6 -6
- package/src/schema/introspect-db-logic.ts +7 -0
- package/src/services/EntityFetchService.ts +69 -10
- package/src/services/EntityPersistService.ts +9 -0
- package/src/services/entityService.ts +9 -0
- package/src/services/realtimeService.ts +214 -2
- package/src/utils/drizzle-conditions.ts +74 -2
- package/src/websocket.ts +10 -2
- package/test/auth-services.test.ts +10 -166
- package/test/doctor.test.ts +6 -2
- package/test/drizzle-conditions.test.ts +168 -0
- package/vite.config.ts +1 -1
- package/dist/server-postgresql/src/schema/default-collections.d.ts +0 -2
- package/dist/types/src/users/roles.d.ts +0 -22
- package/src/schema/default-collections.ts +0 -69
|
@@ -3,40 +3,16 @@ import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
|
3
3
|
import { getTableConfig, AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
|
|
4
4
|
import { getColumnMeta } from "../services/entity-helpers";
|
|
5
5
|
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
6
|
+
import { logger } from "@rebasepro/server-core";
|
|
7
|
+
|
|
6
8
|
|
|
7
|
-
/**
|
|
8
|
-
* Default roles to seed on first run
|
|
9
|
-
*/
|
|
10
|
-
const DEFAULT_ROLES = [
|
|
11
|
-
{
|
|
12
|
-
id: "admin",
|
|
13
|
-
name: "Admin",
|
|
14
|
-
is_admin: true,
|
|
15
|
-
default_permissions: { read: true, create: true, edit: true, delete: true },
|
|
16
|
-
config: { createCollections: true, editCollections: "all", deleteCollections: "all" }
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: "editor",
|
|
20
|
-
name: "Editor",
|
|
21
|
-
is_admin: false,
|
|
22
|
-
default_permissions: { read: true, create: true, edit: true, delete: true },
|
|
23
|
-
config: { createCollections: true, editCollections: "own", deleteCollections: "own" }
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: "viewer",
|
|
27
|
-
name: "Viewer",
|
|
28
|
-
is_admin: false,
|
|
29
|
-
default_permissions: { read: true, create: false, edit: false, delete: false },
|
|
30
|
-
config: null
|
|
31
|
-
}
|
|
32
|
-
];
|
|
33
9
|
|
|
34
10
|
/**
|
|
35
11
|
* Auto-create auth tables if they don't exist
|
|
36
12
|
* This runs on startup to ensure the database is ready for auth
|
|
37
13
|
*/
|
|
38
14
|
export async function ensureAuthTablesExist(db: NodePgDatabase, registry?: PostgresCollectionRegistry): Promise<void> {
|
|
39
|
-
|
|
15
|
+
logger.info("🔍 Checking auth tables...");
|
|
40
16
|
|
|
41
17
|
try {
|
|
42
18
|
// Resolve dynamic user table name and ID type
|
|
@@ -66,31 +42,19 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, registry?: Postg
|
|
|
66
42
|
}
|
|
67
43
|
}
|
|
68
44
|
|
|
69
|
-
|
|
70
|
-
let rolesSchema = "rebase";
|
|
71
|
-
if (registry) {
|
|
72
|
-
const rolesTable = registry.getTable("roles");
|
|
73
|
-
if (rolesTable) {
|
|
74
|
-
rolesSchema = getTableConfig(rolesTable).schema || "public";
|
|
75
|
-
}
|
|
76
|
-
}
|
|
45
|
+
|
|
77
46
|
|
|
78
47
|
// ── Create schemas (idempotent) ──────────────────────────────────
|
|
79
48
|
if (usersSchema !== "public") {
|
|
80
49
|
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(usersSchema)}`);
|
|
81
50
|
}
|
|
82
|
-
if (rolesSchema !== "public" && rolesSchema !== usersSchema) {
|
|
83
|
-
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(rolesSchema)}`);
|
|
84
|
-
}
|
|
85
51
|
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);
|
|
86
52
|
|
|
87
|
-
|
|
88
|
-
const userIdentitiesTable = `"${
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
|
|
93
|
-
const appConfigTableName = `"${rolesSchema}"."app_config"`;
|
|
53
|
+
const authSchema = usersSchema === "public" ? "rebase" : usersSchema;
|
|
54
|
+
const userIdentitiesTable = `"${authSchema}"."user_identities"`;
|
|
55
|
+
const refreshTokensTableName = `"${authSchema}"."refresh_tokens"`;
|
|
56
|
+
const passwordResetTokensTableName = `"${authSchema}"."password_reset_tokens"`;
|
|
57
|
+
const appConfigTableName = `"${authSchema}"."app_config"`;
|
|
94
58
|
|
|
95
59
|
// ── Create tables (idempotent) ──────────────────────────────────
|
|
96
60
|
|
|
@@ -115,33 +79,6 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, registry?: Postg
|
|
|
115
79
|
`);
|
|
116
80
|
|
|
117
81
|
|
|
118
|
-
// Create roles table
|
|
119
|
-
await db.execute(sql`
|
|
120
|
-
CREATE TABLE IF NOT EXISTS ${sql.raw(rolesTableName)} (
|
|
121
|
-
id TEXT PRIMARY KEY,
|
|
122
|
-
name TEXT NOT NULL,
|
|
123
|
-
is_admin BOOLEAN DEFAULT FALSE,
|
|
124
|
-
default_permissions JSONB,
|
|
125
|
-
collection_permissions JSONB,
|
|
126
|
-
config JSONB,
|
|
127
|
-
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
128
|
-
)
|
|
129
|
-
`);
|
|
130
|
-
|
|
131
|
-
// Create user_roles junction table
|
|
132
|
-
await db.execute(sql`
|
|
133
|
-
CREATE TABLE IF NOT EXISTS ${sql.raw(userRolesTableName)} (
|
|
134
|
-
user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
135
|
-
role_id TEXT NOT NULL REFERENCES ${sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
|
|
136
|
-
PRIMARY KEY (user_id, role_id)
|
|
137
|
-
)
|
|
138
|
-
`);
|
|
139
|
-
|
|
140
|
-
// Create index on user_id for faster lookups
|
|
141
|
-
await db.execute(sql`
|
|
142
|
-
CREATE INDEX IF NOT EXISTS idx_user_roles_user
|
|
143
|
-
ON ${sql.raw(userRolesTableName)}(user_id)
|
|
144
|
-
`);
|
|
145
82
|
|
|
146
83
|
// Create refresh tokens table (includes user_agent, ip_address, and unique constraint)
|
|
147
84
|
await db.execute(sql`
|
|
@@ -232,43 +169,119 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, registry?: Postg
|
|
|
232
169
|
});
|
|
233
170
|
|
|
234
171
|
// Seed default roles if none exist
|
|
235
|
-
|
|
172
|
+
// (no-op: roles are now stored inline on the users table)
|
|
236
173
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
174
|
+
// ── Migration: Add is_anonymous column (safe for existing tables) ────
|
|
175
|
+
await db.execute(sql`
|
|
176
|
+
ALTER TABLE ${sql.raw(usersTableName)}
|
|
177
|
+
ADD COLUMN IF NOT EXISTS is_anonymous BOOLEAN DEFAULT FALSE
|
|
178
|
+
`);
|
|
243
179
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const result = await db.execute(sql`SELECT COUNT(*) as count FROM ${sql.raw(rolesTableName)}`);
|
|
250
|
-
const count = parseInt((result.rows[0] as Record<string, string | number>)?.count as string || "0", 10);
|
|
251
|
-
|
|
252
|
-
if (count > 0) {
|
|
253
|
-
console.log(`📋 Found ${count} existing roles`);
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
180
|
+
// ── Migration: Add inline roles column (safe for existing tables) ────
|
|
181
|
+
await db.execute(sql`
|
|
182
|
+
ALTER TABLE ${sql.raw(usersTableName)}
|
|
183
|
+
ADD COLUMN IF NOT EXISTS roles TEXT[] DEFAULT '{}' NOT NULL
|
|
184
|
+
`);
|
|
256
185
|
|
|
257
|
-
|
|
186
|
+
// ── Migration: Copy roles from legacy junction table to inline column ──
|
|
187
|
+
// If the old rebase.user_roles and rebase.roles tables exist, migrate
|
|
188
|
+
// the data into the new TEXT[] column then drop the legacy tables.
|
|
189
|
+
try {
|
|
190
|
+
const legacyCheck = await db.execute(sql`
|
|
191
|
+
SELECT EXISTS (
|
|
192
|
+
SELECT 1 FROM information_schema.tables
|
|
193
|
+
WHERE table_schema = 'rebase' AND table_name = 'user_roles'
|
|
194
|
+
) AS has_user_roles
|
|
195
|
+
`);
|
|
196
|
+
const hasLegacyTables = (legacyCheck.rows[0] as { has_user_roles: boolean }).has_user_roles;
|
|
197
|
+
|
|
198
|
+
if (hasLegacyTables) {
|
|
199
|
+
logger.info("🔄 Migrating roles from legacy user_roles table...");
|
|
200
|
+
// Update users' roles column from the junction table
|
|
201
|
+
await db.execute(sql`
|
|
202
|
+
UPDATE ${sql.raw(usersTableName)} u
|
|
203
|
+
SET roles = COALESCE((
|
|
204
|
+
SELECT array_agg(ur.role_id)
|
|
205
|
+
FROM "rebase"."user_roles" ur
|
|
206
|
+
WHERE ur.user_id = u.id
|
|
207
|
+
), '{}')
|
|
208
|
+
WHERE u.roles = '{}' OR u.roles IS NULL
|
|
209
|
+
`);
|
|
210
|
+
|
|
211
|
+
// Drop legacy tables (junction first due to FK)
|
|
212
|
+
await db.execute(sql`DROP TABLE IF EXISTS "rebase"."user_roles" CASCADE`);
|
|
213
|
+
await db.execute(sql`DROP TABLE IF EXISTS "rebase"."roles" CASCADE`);
|
|
214
|
+
logger.info("✅ Legacy roles tables migrated and dropped");
|
|
215
|
+
}
|
|
216
|
+
} catch (migrationError: unknown) {
|
|
217
|
+
// Non-fatal: log and continue — the column exists and will work
|
|
218
|
+
logger.warn(`⚠️ Legacy roles migration skipped: ${migrationError instanceof Error ? migrationError.message : String(migrationError)}`);
|
|
219
|
+
}
|
|
258
220
|
|
|
259
|
-
|
|
221
|
+
// ── MFA tables ──────────────────────────────────────────────────────
|
|
222
|
+
const mfaFactorsTableName = `"${authSchema}"."mfa_factors"`;
|
|
223
|
+
const mfaChallengesTableName = `"${authSchema}"."mfa_challenges"`;
|
|
224
|
+
const recoveryCodesTableName = `"${authSchema}"."recovery_codes"`;
|
|
225
|
+
|
|
226
|
+
// Create mfa_factors table
|
|
260
227
|
await db.execute(sql`
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
${
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
228
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(mfaFactorsTableName)} (
|
|
229
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
230
|
+
user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
231
|
+
factor_type TEXT NOT NULL DEFAULT 'totp',
|
|
232
|
+
secret_encrypted TEXT NOT NULL,
|
|
233
|
+
friendly_name TEXT,
|
|
234
|
+
verified BOOLEAN DEFAULT FALSE,
|
|
235
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
236
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
268
237
|
)
|
|
269
|
-
ON CONFLICT (id) DO NOTHING
|
|
270
238
|
`);
|
|
271
|
-
}
|
|
272
239
|
|
|
273
|
-
|
|
240
|
+
// Create indexes on mfa_factors
|
|
241
|
+
await db.execute(sql`
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_mfa_factors_user
|
|
243
|
+
ON ${sql.raw(mfaFactorsTableName)}(user_id)
|
|
244
|
+
`);
|
|
245
|
+
|
|
246
|
+
// Create mfa_challenges table
|
|
247
|
+
await db.execute(sql`
|
|
248
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(mfaChallengesTableName)} (
|
|
249
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
250
|
+
factor_id TEXT NOT NULL REFERENCES ${sql.raw(mfaFactorsTableName)}(id) ON DELETE CASCADE,
|
|
251
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
252
|
+
verified_at TIMESTAMP WITH TIME ZONE,
|
|
253
|
+
ip_address TEXT,
|
|
254
|
+
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
|
|
255
|
+
)
|
|
256
|
+
`);
|
|
257
|
+
|
|
258
|
+
// Create indexes on mfa_challenges
|
|
259
|
+
await db.execute(sql`
|
|
260
|
+
CREATE INDEX IF NOT EXISTS idx_mfa_challenges_factor
|
|
261
|
+
ON ${sql.raw(mfaChallengesTableName)}(factor_id)
|
|
262
|
+
`);
|
|
263
|
+
|
|
264
|
+
// Create recovery_codes table
|
|
265
|
+
await db.execute(sql`
|
|
266
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(recoveryCodesTableName)} (
|
|
267
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
268
|
+
user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
269
|
+
code_hash TEXT NOT NULL,
|
|
270
|
+
used_at TIMESTAMP WITH TIME ZONE,
|
|
271
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
272
|
+
)
|
|
273
|
+
`);
|
|
274
|
+
|
|
275
|
+
// Create indexes on recovery_codes
|
|
276
|
+
await db.execute(sql`
|
|
277
|
+
CREATE INDEX IF NOT EXISTS idx_recovery_codes_user
|
|
278
|
+
ON ${sql.raw(recoveryCodesTableName)}(user_id)
|
|
279
|
+
`);
|
|
280
|
+
|
|
281
|
+
logger.info("✅ Auth tables ready");
|
|
282
|
+
} catch (error) {
|
|
283
|
+
logger.error("❌ Failed to create auth tables", { error });
|
|
284
|
+
logger.warn("⚠️ Continuing without creating auth tables.");
|
|
285
|
+
}
|
|
274
286
|
}
|
|
287
|
+
|