@rebasepro/server-postgres 0.9.1-canary.a57c262 → 0.9.1-canary.a8dbf1c
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 +21 -0
- package/dist/PostgresBackendDriver.d.ts +18 -0
- package/dist/PostgresBootstrapper.d.ts +17 -1
- package/dist/auth/services.d.ts +68 -52
- package/dist/connection.d.ts +21 -0
- package/dist/index.es.js +914 -226
- package/dist/index.es.js.map +1 -1
- package/dist/schema/auth-bootstrap-sql.d.ts +1 -1
- package/dist/schema/auth-schema.d.ts +24 -24
- package/dist/schema/doctor.d.ts +1 -1
- package/dist/schema/introspect-db-logic.d.ts +0 -5
- package/dist/schema/introspect-db-naming.d.ts +10 -0
- package/dist/security/policy-drift.d.ts +30 -0
- package/dist/security/rls-enforcement.d.ts +2 -2
- package/dist/services/channel-history.d.ts +118 -0
- package/dist/services/realtimeService.d.ts +69 -2
- package/dist/services/row-pipeline.d.ts +5 -2
- package/package.json +8 -31
- package/src/PostgresBackendDriver.ts +74 -10
- package/src/PostgresBootstrapper.ts +69 -3
- package/src/auth/ensure-tables.ts +170 -28
- package/src/auth/services.ts +181 -150
- package/src/cli.ts +60 -0
- package/src/connection.ts +61 -1
- package/src/databasePoolManager.ts +2 -0
- package/src/schema/auth-bootstrap-sql.ts +7 -1
- package/src/schema/auth-schema.ts +13 -13
- package/src/schema/doctor.ts +45 -20
- package/src/schema/introspect-db-inference.ts +1 -1
- package/src/schema/introspect-db-logic.ts +1 -10
- package/src/schema/introspect-db-naming.ts +15 -0
- package/src/schema/introspect-db.ts +11 -1
- package/src/schema/introspect-runtime.ts +1 -1
- package/src/security/policy-drift.test.ts +106 -1
- package/src/security/policy-drift.ts +56 -0
- package/src/security/rls-enforcement.ts +11 -5
- package/src/services/PersistService.ts +9 -2
- package/src/services/channel-history.ts +343 -0
- package/src/services/realtimeService.ts +198 -10
- package/src/services/row-pipeline.ts +70 -7
- package/src/utils/drizzle-conditions.ts +13 -0
- package/src/websocket.ts +30 -11
package/README.md
CHANGED
|
@@ -76,6 +76,27 @@ process.on("SIGTERM", async () => {
|
|
|
76
76
|
| `statementTimeout` | 30,000 |
|
|
77
77
|
| `keepAlive` | true |
|
|
78
78
|
|
|
79
|
+
## Testing
|
|
80
|
+
|
|
81
|
+
This package runs **two test runners**, split by directory. This is deliberate — check which half you are in before running anything.
|
|
82
|
+
|
|
83
|
+
| Tests | Runner | Config | Command |
|
|
84
|
+
|-------|--------|--------|---------|
|
|
85
|
+
| `test/*.ts` (unit) | jest | [`jest.config.cjs`](./jest.config.cjs) | `pnpm test` |
|
|
86
|
+
| `test/e2e/**` (integration) | vitest | [`vitest.e2e.config.ts`](./vitest.e2e.config.ts) | `pnpm test:e2e` |
|
|
87
|
+
|
|
88
|
+
The unit tests use jest's injected globals (`describe`/`it`/`expect`) and `jest.mock`. The e2e tests import explicitly from `vitest` and need Docker (testcontainers spins up a real Postgres).
|
|
89
|
+
|
|
90
|
+
**Running a single unit test:**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx jest test/auth-services.test.ts
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Pointing `vitest` at a unit test is the easy mistake here — it produces a bare `ReferenceError: jest is not defined` and a "no tests" result, which looks exactly like a dead or broken test file rather than the wrong runner. [`vitest.config.ts`](./vitest.config.ts) exists solely to intercept that and print the command you actually wanted.
|
|
97
|
+
|
|
98
|
+
`pnpm test` runs jest **without** `--passWithNoTests`: if the unit suite ever stops being collected, CI fails instead of going quietly green.
|
|
99
|
+
|
|
79
100
|
## Related Packages
|
|
80
101
|
|
|
81
102
|
| Package | Role |
|
|
@@ -114,6 +114,24 @@ export declare class PostgresBackendDriver implements DataDriver {
|
|
|
114
114
|
}): Promise<Record<string, unknown>[]>;
|
|
115
115
|
fetchAvailableDatabases(): Promise<string[]>;
|
|
116
116
|
fetchAvailableRoles(): Promise<string[]>;
|
|
117
|
+
/**
|
|
118
|
+
* Application-level roles actually in use in this project.
|
|
119
|
+
*
|
|
120
|
+
* Distinct from {@link fetchAvailableRoles}, which returns native
|
|
121
|
+
* PostgreSQL roles from `pg_roles` (`postgres`, `rebase_user`, …). Those
|
|
122
|
+
* are the roles the SQL editor can `SET ROLE` to. *These* are the strings
|
|
123
|
+
* held in the users table's `roles` column, injected per-transaction as
|
|
124
|
+
* `auth.roles()` and matched by `SecurityRule.roles`. Feeding the pg roles
|
|
125
|
+
* into a `SecurityRule.roles` field produces a condition no user can ever
|
|
126
|
+
* satisfy, so the two must not be conflated.
|
|
127
|
+
*
|
|
128
|
+
* Roles have no registry table — they were migrated out of
|
|
129
|
+
* `rebase.user_roles` onto an inline `roles TEXT[]` column — so the live
|
|
130
|
+
* set is derived from what is assigned. A role that is declared in a policy
|
|
131
|
+
* but held by nobody yet cannot be discovered here; callers that need it
|
|
132
|
+
* should union in the roles they already know about.
|
|
133
|
+
*/
|
|
134
|
+
fetchApplicationRoles(): Promise<string[]>;
|
|
117
135
|
fetchCurrentDatabase(): Promise<string | undefined>;
|
|
118
136
|
/**
|
|
119
137
|
* Fetch public tables that are not yet mapped to a collection.
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* Implements the `BackendBootstrapper` interface for PostgreSQL.
|
|
5
5
|
*/
|
|
6
6
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
7
|
-
import { BackendBootstrapper } from "@rebasepro/types";
|
|
7
|
+
import { BackendBootstrapper, type RealtimeChannelsConfig } from "@rebasepro/types";
|
|
8
8
|
import { PostgresBackendDriver } from "./PostgresBackendDriver";
|
|
9
9
|
import { RealtimeService } from "./services/realtimeService";
|
|
10
10
|
import { DatabasePoolManager } from "./databasePoolManager";
|
|
11
11
|
import { PostgresCollectionRegistry } from "./collections/PostgresCollectionRegistry";
|
|
12
|
+
import { type CdcTableRef } from "./services/cdc/trigger-cdc";
|
|
12
13
|
export interface PostgresDriverConfig {
|
|
13
14
|
connectionString?: string;
|
|
14
15
|
adminConnectionString?: string;
|
|
@@ -24,6 +25,12 @@ export interface PostgresDriverConfig {
|
|
|
24
25
|
* (BaaS mode). Defaults to `public`.
|
|
25
26
|
*/
|
|
26
27
|
introspectionSchema?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Realtime options. Currently only channel retention, which is opt-in:
|
|
30
|
+
* without rules here no channel keeps any history and broadcast stays
|
|
31
|
+
* fire-and-forget. See {@link ChannelRetentionRule}.
|
|
32
|
+
*/
|
|
33
|
+
realtime?: RealtimeChannelsConfig;
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
36
|
* Opaque internals bag that PostgresBootstrapper stores during `initializeDriver()`
|
|
@@ -36,6 +43,15 @@ export interface PostgresDriverInternals {
|
|
|
36
43
|
realtimeService: RealtimeService;
|
|
37
44
|
driver: PostgresBackendDriver;
|
|
38
45
|
poolManager?: DatabasePoolManager;
|
|
46
|
+
/**
|
|
47
|
+
* Attach CDC triggers to tables that did not exist when the driver
|
|
48
|
+
* bootstrapped. Only set when database-level capture is actually active.
|
|
49
|
+
*
|
|
50
|
+
* Auth owns its own tables and creates them later in boot, so at driver
|
|
51
|
+
* bootstrap they are legitimately missing and get skipped; without this
|
|
52
|
+
* they would stay uninstrumented until the next restart.
|
|
53
|
+
*/
|
|
54
|
+
provisionCdcForTables?: (tables: CdcTableRef[]) => Promise<void>;
|
|
39
55
|
}
|
|
40
56
|
/**
|
|
41
57
|
* Default PostgreSQL bootstrapper.
|
package/dist/auth/services.d.ts
CHANGED
|
@@ -19,14 +19,30 @@ export declare class UserService implements UserRepository {
|
|
|
19
19
|
private userIdentitiesTable;
|
|
20
20
|
constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
|
|
21
21
|
private getQualifiedUsersTableName;
|
|
22
|
+
/**
|
|
23
|
+
* Run a privileged auth write with an explicitly cleared RLS context.
|
|
24
|
+
*
|
|
25
|
+
* The auth services run on the base/owner connection, which by design
|
|
26
|
+
* carries a NULL `app.uid` so the `auth.uid() IS NULL` server-escape
|
|
27
|
+
* in the default policies applies. That NULL is normally guaranteed by
|
|
28
|
+
* `set_config(..., is_local = true)` resetting at transaction end — but a
|
|
29
|
+
* GUC that survives on a pooled connection (or a connection role that
|
|
30
|
+
* doesn't bypass RLS: FORCE ROW LEVEL SECURITY, or a non-owner role)
|
|
31
|
+
* turns the trusted write into an RLS-scoped one and denies it with
|
|
32
|
+
* SQLSTATE 42501. Clearing the GUCs here, transaction-locally at the
|
|
33
|
+
* single chokepoint, makes the server context deterministic instead of
|
|
34
|
+
* trusting whatever state the pool hands us. `auth.uid()` reads '' as
|
|
35
|
+
* NULL via NULLIF, so '' is the server context.
|
|
36
|
+
*/
|
|
37
|
+
private withServerContext;
|
|
22
38
|
private mapRowToUser;
|
|
23
39
|
private mapPayload;
|
|
24
40
|
createUser(data: CreateUserData): Promise<UserData>;
|
|
25
41
|
getUserById(id: string): Promise<UserData | null>;
|
|
26
42
|
getUserByEmail(email: string): Promise<UserData | null>;
|
|
27
43
|
getUserByIdentity(provider: string, providerId: string): Promise<UserData | null>;
|
|
28
|
-
getUserIdentities(
|
|
29
|
-
linkUserIdentity(
|
|
44
|
+
getUserIdentities(uid: string): Promise<UserIdentityData[]>;
|
|
45
|
+
linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
|
|
30
46
|
updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
|
|
31
47
|
deleteUser(id: string): Promise<void>;
|
|
32
48
|
listUsers(): Promise<UserData[]>;
|
|
@@ -50,23 +66,23 @@ export declare class UserService implements UserRepository {
|
|
|
50
66
|
/**
|
|
51
67
|
* Get roles for a user from database (inline TEXT[] column)
|
|
52
68
|
*/
|
|
53
|
-
getUserRoles(
|
|
69
|
+
getUserRoles(uid: string): Promise<Role[]>;
|
|
54
70
|
/**
|
|
55
71
|
* Get role IDs for a user
|
|
56
72
|
*/
|
|
57
|
-
getUserRoleIds(
|
|
73
|
+
getUserRoleIds(uid: string): Promise<string[]>;
|
|
58
74
|
/**
|
|
59
75
|
* Set roles for a user (replaces existing roles)
|
|
60
76
|
*/
|
|
61
|
-
setUserRoles(
|
|
77
|
+
setUserRoles(uid: string, roleIds: string[]): Promise<void>;
|
|
62
78
|
/**
|
|
63
79
|
* Assign a specific role to new user (appends if not present)
|
|
64
80
|
*/
|
|
65
|
-
assignDefaultRole(
|
|
81
|
+
assignDefaultRole(uid: string, roleId: string): Promise<void>;
|
|
66
82
|
/**
|
|
67
83
|
* Get user with their roles
|
|
68
84
|
*/
|
|
69
|
-
getUserWithRoles(
|
|
85
|
+
getUserWithRoles(uid: string): Promise<{
|
|
70
86
|
user: UserData;
|
|
71
87
|
roles: Role[];
|
|
72
88
|
} | null>;
|
|
@@ -75,12 +91,12 @@ export declare class RefreshTokenService {
|
|
|
75
91
|
private db;
|
|
76
92
|
private refreshTokensTable;
|
|
77
93
|
constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
|
|
78
|
-
createToken(
|
|
94
|
+
createToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
|
|
79
95
|
findByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
|
|
80
96
|
deleteByHash(tokenHash: string): Promise<void>;
|
|
81
|
-
deleteAllForUser(
|
|
82
|
-
listForUser(
|
|
83
|
-
deleteById(id: string,
|
|
97
|
+
deleteAllForUser(uid: string): Promise<void>;
|
|
98
|
+
listForUser(uid: string): Promise<RefreshTokenInfo[]>;
|
|
99
|
+
deleteById(id: string, uid: string): Promise<void>;
|
|
84
100
|
}
|
|
85
101
|
/**
|
|
86
102
|
* Password reset token service
|
|
@@ -93,12 +109,12 @@ export declare class PasswordResetTokenService {
|
|
|
93
109
|
/**
|
|
94
110
|
* Create a password reset token
|
|
95
111
|
*/
|
|
96
|
-
createToken(
|
|
112
|
+
createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
97
113
|
/**
|
|
98
114
|
* Find a valid (not expired, not used) token by hash
|
|
99
115
|
*/
|
|
100
116
|
findValidByHash(tokenHash: string): Promise<{
|
|
101
|
-
|
|
117
|
+
uid: string;
|
|
102
118
|
expiresAt: Date;
|
|
103
119
|
} | null>;
|
|
104
120
|
/**
|
|
@@ -108,7 +124,7 @@ export declare class PasswordResetTokenService {
|
|
|
108
124
|
/**
|
|
109
125
|
* Delete all tokens for a user
|
|
110
126
|
*/
|
|
111
|
-
deleteAllForUser(
|
|
127
|
+
deleteAllForUser(uid: string): Promise<void>;
|
|
112
128
|
/**
|
|
113
129
|
* Clean up expired tokens
|
|
114
130
|
*/
|
|
@@ -123,7 +139,7 @@ export declare class MagicLinkTokenService {
|
|
|
123
139
|
private magicLinkTokensTable;
|
|
124
140
|
constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
|
|
125
141
|
private getQualifiedTableName;
|
|
126
|
-
createToken(
|
|
142
|
+
createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
127
143
|
findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
|
|
128
144
|
markAsUsed(tokenHash: string): Promise<void>;
|
|
129
145
|
}
|
|
@@ -137,18 +153,18 @@ export declare class PostgresTokenRepository implements TokenRepository {
|
|
|
137
153
|
private passwordResetTokenService;
|
|
138
154
|
private magicLinkTokenService;
|
|
139
155
|
constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
|
|
140
|
-
createRefreshToken(
|
|
156
|
+
createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
|
|
141
157
|
findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
|
|
142
158
|
deleteRefreshToken(tokenHash: string): Promise<void>;
|
|
143
|
-
deleteAllRefreshTokensForUser(
|
|
144
|
-
listRefreshTokensForUser(
|
|
145
|
-
deleteRefreshTokenById(id: string,
|
|
146
|
-
createPasswordResetToken(
|
|
159
|
+
deleteAllRefreshTokensForUser(uid: string): Promise<void>;
|
|
160
|
+
listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]>;
|
|
161
|
+
deleteRefreshTokenById(id: string, uid: string): Promise<void>;
|
|
162
|
+
createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
147
163
|
findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
|
|
148
164
|
markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
|
|
149
|
-
deleteAllPasswordResetTokensForUser(
|
|
165
|
+
deleteAllPasswordResetTokensForUser(uid: string): Promise<void>;
|
|
150
166
|
deleteExpiredTokens(): Promise<void>;
|
|
151
|
-
createMagicLinkToken(
|
|
167
|
+
createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
152
168
|
findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
|
|
153
169
|
markMagicLinkTokenUsed(tokenHash: string): Promise<void>;
|
|
154
170
|
}
|
|
@@ -166,8 +182,8 @@ export declare class PostgresAuthRepository implements AuthRepository {
|
|
|
166
182
|
getUserById(id: string): Promise<UserData | null>;
|
|
167
183
|
getUserByEmail(email: string): Promise<UserData | null>;
|
|
168
184
|
getUserByIdentity(provider: string, providerId: string): Promise<UserData | null>;
|
|
169
|
-
getUserIdentities(
|
|
170
|
-
linkUserIdentity(
|
|
185
|
+
getUserIdentities(uid: string): Promise<UserIdentityData[]>;
|
|
186
|
+
linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
|
|
171
187
|
updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
|
|
172
188
|
deleteUser(id: string): Promise<void>;
|
|
173
189
|
listUsers(): Promise<UserData[]>;
|
|
@@ -176,11 +192,11 @@ export declare class PostgresAuthRepository implements AuthRepository {
|
|
|
176
192
|
setEmailVerified(id: string, verified: boolean): Promise<void>;
|
|
177
193
|
setVerificationToken(id: string, token: string | null): Promise<void>;
|
|
178
194
|
getUserByVerificationToken(token: string): Promise<UserData | null>;
|
|
179
|
-
getUserRoles(
|
|
180
|
-
getUserRoleIds(
|
|
181
|
-
setUserRoles(
|
|
182
|
-
assignDefaultRole(
|
|
183
|
-
getUserWithRoles(
|
|
195
|
+
getUserRoles(uid: string): Promise<RoleData[]>;
|
|
196
|
+
getUserRoleIds(uid: string): Promise<string[]>;
|
|
197
|
+
setUserRoles(uid: string, roleIds: string[]): Promise<void>;
|
|
198
|
+
assignDefaultRole(uid: string, roleId: string): Promise<void>;
|
|
199
|
+
getUserWithRoles(uid: string): Promise<{
|
|
184
200
|
user: UserData;
|
|
185
201
|
roles: RoleData[];
|
|
186
202
|
} | null>;
|
|
@@ -189,37 +205,37 @@ export declare class PostgresAuthRepository implements AuthRepository {
|
|
|
189
205
|
createRole(_data: CreateRoleData): Promise<RoleData>;
|
|
190
206
|
updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null>;
|
|
191
207
|
deleteRole(_id: string): Promise<void>;
|
|
192
|
-
createRefreshToken(
|
|
208
|
+
createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
|
|
193
209
|
findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
|
|
194
210
|
deleteRefreshToken(tokenHash: string): Promise<void>;
|
|
195
|
-
deleteAllRefreshTokensForUser(
|
|
196
|
-
listRefreshTokensForUser(
|
|
197
|
-
deleteRefreshTokenById(id: string,
|
|
198
|
-
createPasswordResetToken(
|
|
211
|
+
deleteAllRefreshTokensForUser(uid: string): Promise<void>;
|
|
212
|
+
listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]>;
|
|
213
|
+
deleteRefreshTokenById(id: string, uid: string): Promise<void>;
|
|
214
|
+
createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
199
215
|
findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
|
|
200
216
|
markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
|
|
201
|
-
deleteAllPasswordResetTokensForUser(
|
|
217
|
+
deleteAllPasswordResetTokensForUser(uid: string): Promise<void>;
|
|
202
218
|
deleteExpiredTokens(): Promise<void>;
|
|
203
|
-
createMagicLinkToken(
|
|
219
|
+
createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
204
220
|
findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
|
|
205
221
|
markMagicLinkTokenUsed(tokenHash: string): Promise<void>;
|
|
206
222
|
private _mfaService;
|
|
207
223
|
private getMfaService;
|
|
208
|
-
createMfaFactor(
|
|
209
|
-
getMfaFactors(
|
|
224
|
+
createMfaFactor(uid: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
|
|
225
|
+
getMfaFactors(uid: string): Promise<MfaFactor[]>;
|
|
210
226
|
getMfaFactorById(factorId: string): Promise<(MfaFactor & {
|
|
211
227
|
secretEncrypted: string;
|
|
212
228
|
}) | null>;
|
|
213
229
|
verifyMfaFactor(factorId: string): Promise<void>;
|
|
214
|
-
deleteMfaFactor(factorId: string,
|
|
230
|
+
deleteMfaFactor(factorId: string, uid: string): Promise<void>;
|
|
215
231
|
createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
|
|
216
232
|
getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
|
|
217
233
|
verifyMfaChallenge(challengeId: string): Promise<void>;
|
|
218
|
-
createRecoveryCodes(
|
|
219
|
-
useRecoveryCode(
|
|
220
|
-
getUnusedRecoveryCodeCount(
|
|
221
|
-
deleteAllRecoveryCodes(
|
|
222
|
-
hasVerifiedMfaFactors(
|
|
234
|
+
createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void>;
|
|
235
|
+
useRecoveryCode(uid: string, codeHash: string): Promise<boolean>;
|
|
236
|
+
getUnusedRecoveryCodeCount(uid: string): Promise<number>;
|
|
237
|
+
deleteAllRecoveryCodes(uid: string): Promise<void>;
|
|
238
|
+
hasVerifiedMfaFactors(uid: string): Promise<boolean>;
|
|
223
239
|
}
|
|
224
240
|
/**
|
|
225
241
|
* PostgreSQL implementation of MfaRepository.
|
|
@@ -230,21 +246,21 @@ export declare class MfaService implements MfaRepository {
|
|
|
230
246
|
private schemaName;
|
|
231
247
|
constructor(db: NodePgDatabase, schemaName?: string);
|
|
232
248
|
private qualify;
|
|
233
|
-
createMfaFactor(
|
|
234
|
-
getMfaFactors(
|
|
249
|
+
createMfaFactor(uid: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
|
|
250
|
+
getMfaFactors(uid: string): Promise<MfaFactor[]>;
|
|
235
251
|
getMfaFactorById(factorId: string): Promise<(MfaFactor & {
|
|
236
252
|
secretEncrypted: string;
|
|
237
253
|
}) | null>;
|
|
238
254
|
verifyMfaFactor(factorId: string): Promise<void>;
|
|
239
|
-
deleteMfaFactor(factorId: string,
|
|
255
|
+
deleteMfaFactor(factorId: string, uid: string): Promise<void>;
|
|
240
256
|
createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
|
|
241
257
|
getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
|
|
242
258
|
verifyMfaChallenge(challengeId: string): Promise<void>;
|
|
243
|
-
createRecoveryCodes(
|
|
244
|
-
useRecoveryCode(
|
|
245
|
-
getUnusedRecoveryCodeCount(
|
|
246
|
-
deleteAllRecoveryCodes(
|
|
247
|
-
hasVerifiedMfaFactors(
|
|
259
|
+
createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void>;
|
|
260
|
+
useRecoveryCode(uid: string, codeHash: string): Promise<boolean>;
|
|
261
|
+
getUnusedRecoveryCodeCount(uid: string): Promise<number>;
|
|
262
|
+
deleteAllRecoveryCodes(uid: string): Promise<void>;
|
|
263
|
+
hasVerifiedMfaFactors(uid: string): Promise<boolean>;
|
|
248
264
|
}
|
|
249
265
|
/** PostgreSQL user repository implementation */
|
|
250
266
|
export type PostgresUserRepository = UserService;
|
package/dist/connection.d.ts
CHANGED
|
@@ -19,6 +19,27 @@ export interface PostgresPoolConfig {
|
|
|
19
19
|
/** Enable TCP keep-alive (default: true) */
|
|
20
20
|
keepAlive?: boolean;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Destroy pool clients that are released while still inside a transaction.
|
|
24
|
+
*
|
|
25
|
+
* pg-pool returns a client to the idle list whenever `release()` is called
|
|
26
|
+
* without an error — even if the connection is still mid-transaction (status
|
|
27
|
+
* `T`/`E`). That happens in practice: drizzle's pool transaction releases in
|
|
28
|
+
* a `finally` after attempting ROLLBACK, and if the ROLLBACK itself fails
|
|
29
|
+
* (e.g. it was queued behind a statement that hit the client-side
|
|
30
|
+
* query_timeout), the client goes back dirty. The next checkout then runs
|
|
31
|
+
* its statements inside the zombie transaction — with the previous request's
|
|
32
|
+
* `app.*` RLS GUCs still applied, which turns unrelated queries into
|
|
33
|
+
* RLS-scoped ones (observed in production as registration failing with
|
|
34
|
+
* SQLSTATE 42501 under a leaked anonymous context).
|
|
35
|
+
*
|
|
36
|
+
* pg-pool emits `release` before it consults its private `_expired` set, so
|
|
37
|
+
* marking the client expired here makes `_release()` destroy it instead of
|
|
38
|
+
* pooling it. Both `client._txStatus` (pg ≥ 8.16) and `pool._expired` are
|
|
39
|
+
* private APIs — feature-detect and fall back to loud logging so an upstream
|
|
40
|
+
* change degrades to observability, never to silent corruption.
|
|
41
|
+
*/
|
|
42
|
+
export declare function guardPoolAgainstDirtyRelease(pool: Pool, label: string): void;
|
|
22
43
|
/**
|
|
23
44
|
* Create a Drizzle-backed Postgres connection with a production-grade
|
|
24
45
|
* connection pool.
|