@sixb/pg 0.0.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 +21 -0
- package/README.md +73 -0
- package/dist/agents/agent-storage.d.ts +20 -0
- package/dist/agents/agent-storage.d.ts.map +1 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/messages.d.ts +13 -0
- package/dist/agents/messages.d.ts.map +1 -0
- package/dist/agents/rows.d.ts +77 -0
- package/dist/agents/rows.d.ts.map +1 -0
- package/dist/agents/runs.d.ts +25 -0
- package/dist/agents/runs.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +13 -0
- package/dist/agents/threads.d.ts.map +1 -0
- package/dist/auth-storage/access-tokens.d.ts +33 -0
- package/dist/auth-storage/access-tokens.d.ts.map +1 -0
- package/dist/auth-storage/auth-storage.d.ts +41 -0
- package/dist/auth-storage/auth-storage.d.ts.map +1 -0
- package/dist/auth-storage/group-memberships.d.ts +21 -0
- package/dist/auth-storage/group-memberships.d.ts.map +1 -0
- package/dist/auth-storage/identities.d.ts +17 -0
- package/dist/auth-storage/identities.d.ts.map +1 -0
- package/dist/auth-storage/index.d.ts +3 -0
- package/dist/auth-storage/index.d.ts.map +1 -0
- package/dist/auth-storage/invitations.d.ts +29 -0
- package/dist/auth-storage/invitations.d.ts.map +1 -0
- package/dist/auth-storage/magic-links.d.ts +28 -0
- package/dist/auth-storage/magic-links.d.ts.map +1 -0
- package/dist/auth-storage/oidc-attempts.d.ts +18 -0
- package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
- package/dist/auth-storage/rows.d.ts +138 -0
- package/dist/auth-storage/rows.d.ts.map +1 -0
- package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
- package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
- package/dist/auth-storage/service-accounts.d.ts +14 -0
- package/dist/auth-storage/service-accounts.d.ts.map +1 -0
- package/dist/auth-storage/sessions.d.ts +55 -0
- package/dist/auth-storage/sessions.d.ts.map +1 -0
- package/dist/auth-storage/shared.d.ts +159 -0
- package/dist/auth-storage/shared.d.ts.map +1 -0
- package/dist/auth-storage/users.d.ts +19 -0
- package/dist/auth-storage/users.d.ts.map +1 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11957 -0
- package/dist/index.js.map +55 -0
- package/dist/latest-run-query.d.ts +24 -0
- package/dist/latest-run-query.d.ts.map +1 -0
- package/dist/migrations.d.ts +20 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/ontology-storage/commits.d.ts +11 -0
- package/dist/ontology-storage/commits.d.ts.map +1 -0
- package/dist/ontology-storage/index.d.ts +22 -0
- package/dist/ontology-storage/index.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-session.d.ts +63 -0
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-state.d.ts +69 -0
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-writer.d.ts +15 -0
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
- package/dist/ontology-storage/materializations.d.ts +33 -0
- package/dist/ontology-storage/materializations.d.ts.map +1 -0
- package/dist/ontology-storage/outbox.d.ts +12 -0
- package/dist/ontology-storage/outbox.d.ts.map +1 -0
- package/dist/ontology-storage/shared.d.ts +95 -0
- package/dist/ontology-storage/shared.d.ts.map +1 -0
- package/dist/ontology-storage/sources.d.ts +25 -0
- package/dist/ontology-storage/sources.d.ts.map +1 -0
- package/dist/pg-action-run-storage.d.ts +21 -0
- package/dist/pg-action-run-storage.d.ts.map +1 -0
- package/dist/pg-client.d.ts +58 -0
- package/dist/pg-client.d.ts.map +1 -0
- package/dist/pg-object-query-compiler.d.ts +55 -0
- package/dist/pg-object-query-compiler.d.ts.map +1 -0
- package/dist/pg-object-storage.d.ts +90 -0
- package/dist/pg-object-storage.d.ts.map +1 -0
- package/dist/pg-pipeline-run-storage.d.ts +18 -0
- package/dist/pg-pipeline-run-storage.d.ts.map +1 -0
- package/dist/pg-projection-run-storage.d.ts +19 -0
- package/dist/pg-projection-run-storage.d.ts.map +1 -0
- package/dist/pg-rules-storage.d.ts +25 -0
- package/dist/pg-rules-storage.d.ts.map +1 -0
- package/dist/pg-sync-run-storage.d.ts +15 -0
- package/dist/pg-sync-run-storage.d.ts.map +1 -0
- package/dist/pg-timeseries-storage.d.ts +32 -0
- package/dist/pg-timeseries-storage.d.ts.map +1 -0
- package/dist/pg-webhook-delivery-storage.d.ts +17 -0
- package/dist/pg-webhook-delivery-storage.d.ts.map +1 -0
- package/dist/pg-webhook-run-storage.d.ts +14 -0
- package/dist/pg-webhook-run-storage.d.ts.map +1 -0
- package/dist/pg-workflow-intervention-storage.d.ts +16 -0
- package/dist/pg-workflow-intervention-storage.d.ts.map +1 -0
- package/dist/pg-workflow-run-storage.d.ts +49 -0
- package/dist/pg-workflow-run-storage.d.ts.map +1 -0
- package/dist/run-list-query.d.ts +30 -0
- package/dist/run-list-query.d.ts.map +1 -0
- package/dist/storage-errors.d.ts +16 -0
- package/dist/storage-errors.d.ts.map +1 -0
- package/dist/transactions.d.ts +10 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/tsconfig.json +5 -0
- package/package.json +60 -0
- package/src/agents/agent-storage.ts +26 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/messages.ts +144 -0
- package/src/agents/rows.ts +227 -0
- package/src/agents/runs.ts +330 -0
- package/src/agents/threads.ts +116 -0
- package/src/auth-storage/access-tokens.ts +254 -0
- package/src/auth-storage/auth-storage.ts +798 -0
- package/src/auth-storage/group-memberships.ts +47 -0
- package/src/auth-storage/identities.ts +85 -0
- package/src/auth-storage/index.ts +2 -0
- package/src/auth-storage/invitations.ts +280 -0
- package/src/auth-storage/magic-links.ts +140 -0
- package/src/auth-storage/oidc-attempts.ts +93 -0
- package/src/auth-storage/rows.ts +352 -0
- package/src/auth-storage/service-account-group-memberships.ts +150 -0
- package/src/auth-storage/service-accounts.ts +154 -0
- package/src/auth-storage/sessions.ts +193 -0
- package/src/auth-storage/shared.ts +799 -0
- package/src/auth-storage/users.ts +196 -0
- package/src/index.ts +367 -0
- package/src/latest-run-query.ts +42 -0
- package/src/migrations/001-initial-schema.sql +1195 -0
- package/src/migrations.ts +280 -0
- package/src/ontology-storage/commits.ts +115 -0
- package/src/ontology-storage/index.ts +35 -0
- package/src/ontology-storage/materialization-session.ts +426 -0
- package/src/ontology-storage/materialization-state.ts +1109 -0
- package/src/ontology-storage/materialization-writer.ts +509 -0
- package/src/ontology-storage/materializations.ts +939 -0
- package/src/ontology-storage/outbox.ts +216 -0
- package/src/ontology-storage/shared.ts +293 -0
- package/src/ontology-storage/sources.ts +658 -0
- package/src/pg-action-run-storage.ts +657 -0
- package/src/pg-client.ts +124 -0
- package/src/pg-object-query-compiler.ts +1545 -0
- package/src/pg-object-storage.ts +638 -0
- package/src/pg-pipeline-run-storage.ts +457 -0
- package/src/pg-projection-run-storage.ts +526 -0
- package/src/pg-rules-storage.ts +308 -0
- package/src/pg-sync-run-storage.ts +263 -0
- package/src/pg-timeseries-storage.ts +180 -0
- package/src/pg-webhook-delivery-storage.ts +180 -0
- package/src/pg-webhook-run-storage.ts +185 -0
- package/src/pg-workflow-intervention-storage.ts +339 -0
- package/src/pg-workflow-run-storage.ts +1065 -0
- package/src/run-list-query.ts +108 -0
- package/src/sql.d.ts +4 -0
- package/src/storage-errors.ts +72 -0
- package/src/transactions.ts +58 -0
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import type { AuthSessionAudience, Principal } from "@sixb/core"
|
|
2
|
+
import type {
|
|
3
|
+
CompleteAuthSessionInput,
|
|
4
|
+
CreateAuthSessionInput,
|
|
5
|
+
GroupMembershipRecord,
|
|
6
|
+
InvitationRecord,
|
|
7
|
+
MagicLinkRecord,
|
|
8
|
+
OidcAuthorizationAttemptRecord,
|
|
9
|
+
SessionRecord,
|
|
10
|
+
UpsertAuthGroupMembershipInput,
|
|
11
|
+
UserRecord,
|
|
12
|
+
} from "@sixb/core/storage"
|
|
13
|
+
import { AuthStorageError } from "@sixb/core/storage"
|
|
14
|
+
import type { SQLClient } from "../pg-client"
|
|
15
|
+
import { isUniqueViolation } from "../storage-errors"
|
|
16
|
+
import type {
|
|
17
|
+
PgAuthGroupMembershipRow,
|
|
18
|
+
PgAuthInvitationRow,
|
|
19
|
+
PgAuthMagicLinkRow,
|
|
20
|
+
PgAuthOidcAttemptRow,
|
|
21
|
+
PgAuthSessionRow,
|
|
22
|
+
PgAuthUserIdentityRow,
|
|
23
|
+
PgAuthUserRow,
|
|
24
|
+
} from "./rows"
|
|
25
|
+
import {
|
|
26
|
+
rowToGroupMembershipRecord,
|
|
27
|
+
rowToInvitationRecord,
|
|
28
|
+
rowToMagicLinkRecord,
|
|
29
|
+
rowToOidcAuthorizationAttemptRecord,
|
|
30
|
+
rowToSessionRecord,
|
|
31
|
+
rowToUserRecord,
|
|
32
|
+
} from "./rows"
|
|
33
|
+
|
|
34
|
+
export type PgValue = string | number | Date | null
|
|
35
|
+
|
|
36
|
+
export function assertNonEmpty(value: string, label: string): string {
|
|
37
|
+
const normalized = value.trim()
|
|
38
|
+
if (!normalized) {
|
|
39
|
+
throw new AuthStorageError("invalid_input", `[Sixb] ${label} must be a non-empty string.`)
|
|
40
|
+
}
|
|
41
|
+
return normalized
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizeEmail(email: string): string {
|
|
45
|
+
return assertNonEmpty(email, "Email").toLowerCase()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function normalizeGroupIds(groupIds: readonly string[] | undefined): readonly string[] {
|
|
49
|
+
return [...new Set((groupIds ?? []).map((groupId) => assertNonEmpty(groupId, "Group id")))]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function dateOrNow(value: Date | undefined): Date {
|
|
53
|
+
return value ? new Date(value) : new Date()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hasEmptyFilter(values: readonly unknown[] | undefined): boolean {
|
|
57
|
+
return values !== undefined && values.length === 0
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function appendPagination(
|
|
61
|
+
query: string,
|
|
62
|
+
params: PgValue[],
|
|
63
|
+
nextIndex: number,
|
|
64
|
+
input: { readonly limit?: number; readonly offset?: number }
|
|
65
|
+
): string {
|
|
66
|
+
const offset = input.offset ?? 0
|
|
67
|
+
|
|
68
|
+
if (input.limit !== undefined) {
|
|
69
|
+
params.push(input.limit, offset)
|
|
70
|
+
return `${query} LIMIT $${nextIndex++} OFFSET $${nextIndex++}`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (offset > 0) {
|
|
74
|
+
params.push(offset)
|
|
75
|
+
return `${query} OFFSET $${nextIndex++}`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return query
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function placeholders(startIndex: number, count: number): readonly string[] {
|
|
82
|
+
return Array.from({ length: count }, (_, index) => `$${startIndex + index}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function getUserRowById(
|
|
86
|
+
sql: SQLClient,
|
|
87
|
+
params: { readonly projectId: string; readonly id: string },
|
|
88
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
89
|
+
): Promise<PgAuthUserRow | null> {
|
|
90
|
+
const [row] = await sql.unsafe<PgAuthUserRow[]>(
|
|
91
|
+
`
|
|
92
|
+
SELECT *
|
|
93
|
+
FROM auth_users
|
|
94
|
+
WHERE project_id = $1
|
|
95
|
+
AND id = $2
|
|
96
|
+
${options.forUpdate ? "FOR UPDATE" : ""}
|
|
97
|
+
`,
|
|
98
|
+
[params.projectId, params.id]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return row ?? null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function getUserById(
|
|
105
|
+
sql: SQLClient,
|
|
106
|
+
params: { readonly projectId: string; readonly id: string }
|
|
107
|
+
): Promise<UserRecord | null> {
|
|
108
|
+
const row = await getUserRowById(sql, params)
|
|
109
|
+
return row ? rowToUserRecord(row) : null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function getUserRowByEmail(
|
|
113
|
+
sql: SQLClient,
|
|
114
|
+
params: { readonly projectId: string; readonly email: string },
|
|
115
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
116
|
+
): Promise<PgAuthUserRow | null> {
|
|
117
|
+
const [row] = await sql.unsafe<PgAuthUserRow[]>(
|
|
118
|
+
`
|
|
119
|
+
SELECT *
|
|
120
|
+
FROM auth_users
|
|
121
|
+
WHERE project_id = $1
|
|
122
|
+
AND email = $2
|
|
123
|
+
${options.forUpdate ? "FOR UPDATE" : ""}
|
|
124
|
+
`,
|
|
125
|
+
[params.projectId, normalizeEmail(params.email)]
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return row ?? null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function getUserByEmail(
|
|
132
|
+
sql: SQLClient,
|
|
133
|
+
params: { readonly projectId: string; readonly email: string }
|
|
134
|
+
): Promise<UserRecord | null> {
|
|
135
|
+
const row = await getUserRowByEmail(sql, params)
|
|
136
|
+
return row ? rowToUserRecord(row) : null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function requireUserById(
|
|
140
|
+
sql: SQLClient,
|
|
141
|
+
params: { readonly projectId: string; readonly id: string }
|
|
142
|
+
): Promise<UserRecord> {
|
|
143
|
+
const user = await getUserById(sql, params)
|
|
144
|
+
if (!user) {
|
|
145
|
+
throw new AuthStorageError(
|
|
146
|
+
"missing_user",
|
|
147
|
+
`[Sixb] User '${params.id}' not found for project '${params.projectId}'.`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
return user
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function getIdentityRowBySubject(
|
|
154
|
+
sql: SQLClient,
|
|
155
|
+
params: {
|
|
156
|
+
readonly projectId: string
|
|
157
|
+
readonly strategyId: string
|
|
158
|
+
readonly subject: string
|
|
159
|
+
},
|
|
160
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
161
|
+
): Promise<PgAuthUserIdentityRow | null> {
|
|
162
|
+
const [row] = await sql.unsafe<PgAuthUserIdentityRow[]>(
|
|
163
|
+
`
|
|
164
|
+
SELECT *
|
|
165
|
+
FROM auth_user_identities
|
|
166
|
+
WHERE project_id = $1
|
|
167
|
+
AND strategy_id = $2
|
|
168
|
+
AND subject = $3
|
|
169
|
+
${options.forUpdate ? "FOR UPDATE" : ""}
|
|
170
|
+
`,
|
|
171
|
+
[params.projectId, params.strategyId, params.subject]
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return row ?? null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function getSessionRowById(
|
|
178
|
+
sql: SQLClient,
|
|
179
|
+
params: { readonly projectId: string; readonly id: string },
|
|
180
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
181
|
+
): Promise<PgAuthSessionRow | null> {
|
|
182
|
+
const [row] = await sql.unsafe<PgAuthSessionRow[]>(
|
|
183
|
+
`
|
|
184
|
+
SELECT *
|
|
185
|
+
FROM auth_sessions
|
|
186
|
+
WHERE project_id = $1
|
|
187
|
+
AND id = $2
|
|
188
|
+
${options.forUpdate ? "FOR UPDATE" : ""}
|
|
189
|
+
`,
|
|
190
|
+
[params.projectId, params.id]
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return row ?? null
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function getSessionById(
|
|
197
|
+
sql: SQLClient,
|
|
198
|
+
params: { readonly projectId: string; readonly id: string }
|
|
199
|
+
): Promise<ReturnType<typeof rowToSessionRecord> | null> {
|
|
200
|
+
const row = await getSessionRowById(sql, params)
|
|
201
|
+
return row ? rowToSessionRecord(row) : null
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function requireSessionById(
|
|
205
|
+
sql: SQLClient,
|
|
206
|
+
params: { readonly projectId: string; readonly id: string }
|
|
207
|
+
): Promise<ReturnType<typeof rowToSessionRecord>> {
|
|
208
|
+
const session = await getSessionById(sql, params)
|
|
209
|
+
if (!session) {
|
|
210
|
+
throw new AuthStorageError(
|
|
211
|
+
"missing_session",
|
|
212
|
+
`[Sixb] Session '${params.id}' not found for project '${params.projectId}'.`
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
return session
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function getInvitationGroupIds(
|
|
219
|
+
sql: SQLClient,
|
|
220
|
+
params: { readonly projectId: string; readonly invitationId: string }
|
|
221
|
+
): Promise<readonly string[]> {
|
|
222
|
+
const rows = (await sql`
|
|
223
|
+
SELECT group_id
|
|
224
|
+
FROM auth_invitation_groups
|
|
225
|
+
WHERE project_id = ${params.projectId}
|
|
226
|
+
AND invitation_id = ${params.invitationId}
|
|
227
|
+
ORDER BY position ASC, group_id ASC
|
|
228
|
+
`) as Array<{ readonly group_id: string }>
|
|
229
|
+
|
|
230
|
+
return rows.map((row) => row.group_id)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function getInvitationById(
|
|
234
|
+
sql: SQLClient,
|
|
235
|
+
params: { readonly projectId: string; readonly id: string },
|
|
236
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
237
|
+
): Promise<InvitationRecord | null> {
|
|
238
|
+
const [row] = await sql.unsafe<PgAuthInvitationRow[]>(
|
|
239
|
+
`
|
|
240
|
+
SELECT *
|
|
241
|
+
FROM auth_invitations
|
|
242
|
+
WHERE project_id = $1
|
|
243
|
+
AND id = $2
|
|
244
|
+
${options.forUpdate ? "FOR UPDATE" : ""}
|
|
245
|
+
`,
|
|
246
|
+
[params.projectId, params.id]
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return row ? invitationRowToRecord(sql, row) : null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function requireInvitationById(
|
|
253
|
+
sql: SQLClient,
|
|
254
|
+
params: { readonly projectId: string; readonly id: string },
|
|
255
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
256
|
+
): Promise<InvitationRecord> {
|
|
257
|
+
const invitation = await getInvitationById(sql, params, options)
|
|
258
|
+
if (!invitation) {
|
|
259
|
+
throw new AuthStorageError(
|
|
260
|
+
"missing_invitation",
|
|
261
|
+
`[Sixb] Invitation '${params.id}' not found for project '${params.projectId}'.`
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
return invitation
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function getActiveInvitationByEmail(
|
|
268
|
+
sql: SQLClient,
|
|
269
|
+
params: { readonly projectId: string; readonly email: string; readonly now: Date },
|
|
270
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
271
|
+
): Promise<InvitationRecord | null> {
|
|
272
|
+
const [row] = await sql.unsafe<PgAuthInvitationRow[]>(
|
|
273
|
+
`
|
|
274
|
+
SELECT *
|
|
275
|
+
FROM auth_invitations
|
|
276
|
+
WHERE project_id = $1
|
|
277
|
+
AND email = $2
|
|
278
|
+
AND status = 'pending'
|
|
279
|
+
AND expires_at > $3
|
|
280
|
+
ORDER BY created_at DESC, id DESC
|
|
281
|
+
LIMIT 1
|
|
282
|
+
${options.forUpdate ? "FOR UPDATE" : ""}
|
|
283
|
+
`,
|
|
284
|
+
[params.projectId, normalizeEmail(params.email), params.now]
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return row ? invitationRowToRecord(sql, row) : null
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function replaceInvitationGroups(
|
|
291
|
+
sql: SQLClient,
|
|
292
|
+
params: {
|
|
293
|
+
readonly projectId: string
|
|
294
|
+
readonly invitationId: string
|
|
295
|
+
readonly groupIds: readonly string[]
|
|
296
|
+
}
|
|
297
|
+
): Promise<void> {
|
|
298
|
+
await sql`
|
|
299
|
+
DELETE FROM auth_invitation_groups
|
|
300
|
+
WHERE project_id = ${params.projectId}
|
|
301
|
+
AND invitation_id = ${params.invitationId}
|
|
302
|
+
`
|
|
303
|
+
|
|
304
|
+
for (const [index, groupId] of params.groupIds.entries()) {
|
|
305
|
+
await sql`
|
|
306
|
+
INSERT INTO auth_invitation_groups (
|
|
307
|
+
project_id,
|
|
308
|
+
invitation_id,
|
|
309
|
+
group_id,
|
|
310
|
+
position
|
|
311
|
+
) VALUES (
|
|
312
|
+
${params.projectId},
|
|
313
|
+
${params.invitationId},
|
|
314
|
+
${groupId},
|
|
315
|
+
${index}
|
|
316
|
+
)
|
|
317
|
+
`
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export async function getMagicLinkRowById(
|
|
322
|
+
sql: SQLClient,
|
|
323
|
+
params: { readonly projectId: string; readonly id: string },
|
|
324
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
325
|
+
): Promise<PgAuthMagicLinkRow | null> {
|
|
326
|
+
const [row] = await sql.unsafe<PgAuthMagicLinkRow[]>(
|
|
327
|
+
`
|
|
328
|
+
SELECT *
|
|
329
|
+
FROM auth_magic_links
|
|
330
|
+
WHERE project_id = $1
|
|
331
|
+
AND id = $2
|
|
332
|
+
${options.forUpdate ? "FOR UPDATE" : ""}
|
|
333
|
+
`,
|
|
334
|
+
[params.projectId, params.id]
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return row ?? null
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export async function getMagicLinkById(
|
|
341
|
+
sql: SQLClient,
|
|
342
|
+
params: { readonly projectId: string; readonly id: string }
|
|
343
|
+
): Promise<MagicLinkRecord | null> {
|
|
344
|
+
const row = await getMagicLinkRowById(sql, params)
|
|
345
|
+
return row ? rowToMagicLinkRecord(row) : null
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function consumeMagicLink(
|
|
349
|
+
sql: SQLClient,
|
|
350
|
+
params: {
|
|
351
|
+
readonly projectId: string
|
|
352
|
+
readonly id: string
|
|
353
|
+
readonly tokenHash: string
|
|
354
|
+
readonly consumedAt: Date
|
|
355
|
+
}
|
|
356
|
+
): Promise<MagicLinkRecord> {
|
|
357
|
+
const projectId = assertNonEmpty(params.projectId, "Project id")
|
|
358
|
+
const id = assertNonEmpty(params.id, "Magic link id")
|
|
359
|
+
const tokenHash = assertNonEmpty(params.tokenHash, "Magic link token hash")
|
|
360
|
+
const row = await getMagicLinkRowById(sql, { projectId, id }, { forUpdate: true })
|
|
361
|
+
|
|
362
|
+
if (!row) {
|
|
363
|
+
throw new AuthStorageError(
|
|
364
|
+
"missing_magic_link",
|
|
365
|
+
`[Sixb] Magic link '${id}' not found for project '${projectId}'.`
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
assertMagicLinkUsable(projectId, id, tokenHash, params.consumedAt, row)
|
|
370
|
+
|
|
371
|
+
await sql`
|
|
372
|
+
UPDATE auth_magic_links
|
|
373
|
+
SET consumed_at = ${params.consumedAt}
|
|
374
|
+
WHERE project_id = ${projectId}
|
|
375
|
+
AND id = ${id}
|
|
376
|
+
`
|
|
377
|
+
|
|
378
|
+
return rowToMagicLinkRecord({
|
|
379
|
+
...row,
|
|
380
|
+
consumed_at: params.consumedAt,
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export async function getOidcAttemptRowById(
|
|
385
|
+
sql: SQLClient,
|
|
386
|
+
params: { readonly projectId: string; readonly id: string },
|
|
387
|
+
options: { readonly forUpdate?: boolean } = {}
|
|
388
|
+
): Promise<PgAuthOidcAttemptRow | null> {
|
|
389
|
+
const [row] = await sql.unsafe<PgAuthOidcAttemptRow[]>(
|
|
390
|
+
`
|
|
391
|
+
SELECT *
|
|
392
|
+
FROM auth_oidc_authorization_attempts
|
|
393
|
+
WHERE project_id = $1
|
|
394
|
+
AND id = $2
|
|
395
|
+
${options.forUpdate ? "FOR UPDATE" : ""}
|
|
396
|
+
`,
|
|
397
|
+
[params.projectId, params.id]
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
return row ?? null
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export async function getOidcAttemptById(
|
|
404
|
+
sql: SQLClient,
|
|
405
|
+
params: { readonly projectId: string; readonly id: string }
|
|
406
|
+
): Promise<OidcAuthorizationAttemptRecord | null> {
|
|
407
|
+
const row = await getOidcAttemptRowById(sql, params)
|
|
408
|
+
return row ? rowToOidcAuthorizationAttemptRecord(row) : null
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export async function consumeOidcAttempt(
|
|
412
|
+
sql: SQLClient,
|
|
413
|
+
params: {
|
|
414
|
+
readonly projectId: string
|
|
415
|
+
readonly id: string
|
|
416
|
+
readonly stateHash: string
|
|
417
|
+
readonly consumedAt: Date
|
|
418
|
+
}
|
|
419
|
+
): Promise<OidcAuthorizationAttemptRecord> {
|
|
420
|
+
const projectId = assertNonEmpty(params.projectId, "Project id")
|
|
421
|
+
const id = assertNonEmpty(params.id, "OIDC authorization attempt id")
|
|
422
|
+
const stateHash = assertNonEmpty(params.stateHash, "OIDC state hash")
|
|
423
|
+
const row = await getOidcAttemptRowById(sql, { projectId, id }, { forUpdate: true })
|
|
424
|
+
|
|
425
|
+
if (!row) {
|
|
426
|
+
throw new AuthStorageError(
|
|
427
|
+
"missing_oidc_attempt",
|
|
428
|
+
`[Sixb] OIDC authorization attempt '${id}' not found for project '${projectId}'.`
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
assertOidcAttemptUsable(projectId, id, stateHash, params.consumedAt, row)
|
|
433
|
+
|
|
434
|
+
await sql`
|
|
435
|
+
UPDATE auth_oidc_authorization_attempts
|
|
436
|
+
SET consumed_at = ${params.consumedAt}
|
|
437
|
+
WHERE project_id = ${projectId}
|
|
438
|
+
AND id = ${id}
|
|
439
|
+
`
|
|
440
|
+
|
|
441
|
+
return rowToOidcAuthorizationAttemptRecord({
|
|
442
|
+
...row,
|
|
443
|
+
consumed_at: params.consumedAt,
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export async function validateCompleteSessionInput(
|
|
448
|
+
sql: SQLClient,
|
|
449
|
+
projectId: string,
|
|
450
|
+
session: CompleteAuthSessionInput
|
|
451
|
+
): Promise<void> {
|
|
452
|
+
const sessionId = assertNonEmpty(session.id, "Session id")
|
|
453
|
+
assertNonEmpty(session.audience, "Session audience")
|
|
454
|
+
assertNonEmpty(session.tokenHash, "Session token hash")
|
|
455
|
+
await assertSessionIdAvailable(sql, projectId, sessionId)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export async function assertSessionIdAvailable(
|
|
459
|
+
sql: SQLClient,
|
|
460
|
+
projectId: string,
|
|
461
|
+
sessionId: string
|
|
462
|
+
): Promise<void> {
|
|
463
|
+
if (await getSessionRowById(sql, { projectId, id: sessionId })) {
|
|
464
|
+
throw new AuthStorageError(
|
|
465
|
+
"duplicate_session",
|
|
466
|
+
`[Sixb] Session '${sessionId}' already exists for project '${projectId}'.`
|
|
467
|
+
)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export async function createSession(
|
|
472
|
+
sql: SQLClient,
|
|
473
|
+
input: CreateAuthSessionInput
|
|
474
|
+
): Promise<SessionRecord> {
|
|
475
|
+
const id = assertNonEmpty(input.id, "Session id")
|
|
476
|
+
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
477
|
+
const userId = assertNonEmpty(input.userId, "User id")
|
|
478
|
+
const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
|
|
479
|
+
const audience = assertNonEmpty(input.audience, "Session audience")
|
|
480
|
+
const tokenHash = assertNonEmpty(input.tokenHash, "Session token hash")
|
|
481
|
+
|
|
482
|
+
await requireUserById(sql, { projectId, id: userId })
|
|
483
|
+
await assertSessionIdAvailable(sql, projectId, id)
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
const [row] = await sql<PgAuthSessionRow[]>`
|
|
487
|
+
INSERT INTO auth_sessions (
|
|
488
|
+
project_id,
|
|
489
|
+
id,
|
|
490
|
+
user_id,
|
|
491
|
+
strategy_id,
|
|
492
|
+
audience,
|
|
493
|
+
token_hash,
|
|
494
|
+
created_at,
|
|
495
|
+
expires_at,
|
|
496
|
+
absolute_expires_at,
|
|
497
|
+
user_agent,
|
|
498
|
+
ip_address
|
|
499
|
+
) VALUES (
|
|
500
|
+
${projectId},
|
|
501
|
+
${id},
|
|
502
|
+
${userId},
|
|
503
|
+
${strategyId},
|
|
504
|
+
${audience},
|
|
505
|
+
${tokenHash},
|
|
506
|
+
${input.createdAt},
|
|
507
|
+
${input.expiresAt},
|
|
508
|
+
${input.absoluteExpiresAt ?? null},
|
|
509
|
+
${input.userAgent ?? null},
|
|
510
|
+
${input.ipAddress ?? null}
|
|
511
|
+
)
|
|
512
|
+
RETURNING *
|
|
513
|
+
`
|
|
514
|
+
|
|
515
|
+
return rowToSessionRecord(row)
|
|
516
|
+
} catch (error) {
|
|
517
|
+
mapUniqueConstraintError(
|
|
518
|
+
error,
|
|
519
|
+
"duplicate_session",
|
|
520
|
+
`[Sixb] Session '${id}' already exists for project '${projectId}'.`
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export async function revokeActiveSessionsForUser(
|
|
526
|
+
sql: SQLClient,
|
|
527
|
+
params: {
|
|
528
|
+
readonly projectId: string
|
|
529
|
+
readonly userId: string
|
|
530
|
+
readonly audience?: AuthSessionAudience
|
|
531
|
+
readonly revokedAt: Date
|
|
532
|
+
}
|
|
533
|
+
): Promise<readonly SessionRecord[]> {
|
|
534
|
+
const rows =
|
|
535
|
+
params.audience === undefined
|
|
536
|
+
? await sql<PgAuthSessionRow[]>`
|
|
537
|
+
SELECT *
|
|
538
|
+
FROM auth_sessions
|
|
539
|
+
WHERE project_id = ${params.projectId}
|
|
540
|
+
AND user_id = ${params.userId}
|
|
541
|
+
AND revoked_at IS NULL
|
|
542
|
+
AND expires_at > ${params.revokedAt}
|
|
543
|
+
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
|
|
544
|
+
ORDER BY created_at ASC, id ASC
|
|
545
|
+
FOR UPDATE
|
|
546
|
+
`
|
|
547
|
+
: await sql<PgAuthSessionRow[]>`
|
|
548
|
+
SELECT *
|
|
549
|
+
FROM auth_sessions
|
|
550
|
+
WHERE project_id = ${params.projectId}
|
|
551
|
+
AND user_id = ${params.userId}
|
|
552
|
+
AND audience = ${params.audience}
|
|
553
|
+
AND revoked_at IS NULL
|
|
554
|
+
AND expires_at > ${params.revokedAt}
|
|
555
|
+
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
|
|
556
|
+
ORDER BY created_at ASC, id ASC
|
|
557
|
+
FOR UPDATE
|
|
558
|
+
`
|
|
559
|
+
|
|
560
|
+
if (params.audience === undefined) {
|
|
561
|
+
await sql`
|
|
562
|
+
UPDATE auth_sessions
|
|
563
|
+
SET revoked_at = ${params.revokedAt}
|
|
564
|
+
WHERE project_id = ${params.projectId}
|
|
565
|
+
AND user_id = ${params.userId}
|
|
566
|
+
AND revoked_at IS NULL
|
|
567
|
+
AND expires_at > ${params.revokedAt}
|
|
568
|
+
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
|
|
569
|
+
`
|
|
570
|
+
} else {
|
|
571
|
+
await sql`
|
|
572
|
+
UPDATE auth_sessions
|
|
573
|
+
SET revoked_at = ${params.revokedAt}
|
|
574
|
+
WHERE project_id = ${params.projectId}
|
|
575
|
+
AND user_id = ${params.userId}
|
|
576
|
+
AND audience = ${params.audience}
|
|
577
|
+
AND revoked_at IS NULL
|
|
578
|
+
AND expires_at > ${params.revokedAt}
|
|
579
|
+
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
|
|
580
|
+
`
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return rows.map((row) =>
|
|
584
|
+
rowToSessionRecord({
|
|
585
|
+
...row,
|
|
586
|
+
revoked_at: params.revokedAt,
|
|
587
|
+
})
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export async function upsertGroupMembership(
|
|
592
|
+
sql: SQLClient,
|
|
593
|
+
input: UpsertAuthGroupMembershipInput
|
|
594
|
+
): Promise<GroupMembershipRecord> {
|
|
595
|
+
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
596
|
+
const userId = assertNonEmpty(input.userId, "User id")
|
|
597
|
+
const groupId = assertNonEmpty(input.groupId, "Group id")
|
|
598
|
+
|
|
599
|
+
await requireUserById(sql, { projectId, id: userId })
|
|
600
|
+
|
|
601
|
+
await sql`
|
|
602
|
+
INSERT INTO auth_group_memberships (
|
|
603
|
+
project_id,
|
|
604
|
+
user_id,
|
|
605
|
+
group_id,
|
|
606
|
+
source,
|
|
607
|
+
created_at
|
|
608
|
+
) VALUES (
|
|
609
|
+
${projectId},
|
|
610
|
+
${userId},
|
|
611
|
+
${groupId},
|
|
612
|
+
${input.source},
|
|
613
|
+
${dateOrNow(input.createdAt)}
|
|
614
|
+
)
|
|
615
|
+
ON CONFLICT (project_id, user_id, group_id) DO NOTHING
|
|
616
|
+
`
|
|
617
|
+
|
|
618
|
+
const membership = await getGroupMembership(sql, { projectId, userId, groupId })
|
|
619
|
+
if (!membership) {
|
|
620
|
+
throw new AuthStorageError(
|
|
621
|
+
"missing_user",
|
|
622
|
+
`[Sixb] Failed to load group membership '${groupId}' for user '${userId}'.`
|
|
623
|
+
)
|
|
624
|
+
}
|
|
625
|
+
return membership
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export async function removeGroupMembership(
|
|
629
|
+
sql: SQLClient,
|
|
630
|
+
params: { readonly projectId: string; readonly userId: string; readonly groupId: string }
|
|
631
|
+
): Promise<GroupMembershipRecord | null> {
|
|
632
|
+
const projectId = assertNonEmpty(params.projectId, "Project id")
|
|
633
|
+
const userId = assertNonEmpty(params.userId, "User id")
|
|
634
|
+
const groupId = assertNonEmpty(params.groupId, "Group id")
|
|
635
|
+
|
|
636
|
+
const [row] = await sql<PgAuthGroupMembershipRow[]>`
|
|
637
|
+
DELETE FROM auth_group_memberships
|
|
638
|
+
WHERE project_id = ${projectId}
|
|
639
|
+
AND user_id = ${userId}
|
|
640
|
+
AND group_id = ${groupId}
|
|
641
|
+
RETURNING *
|
|
642
|
+
`
|
|
643
|
+
|
|
644
|
+
return row ? rowToGroupMembershipRecord(row) : null
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export async function getGroupMembership(
|
|
648
|
+
sql: SQLClient,
|
|
649
|
+
params: { readonly projectId: string; readonly userId: string; readonly groupId: string }
|
|
650
|
+
): Promise<GroupMembershipRecord | null> {
|
|
651
|
+
const [row] = await sql<PgAuthGroupMembershipRow[]>`
|
|
652
|
+
SELECT *
|
|
653
|
+
FROM auth_group_memberships
|
|
654
|
+
WHERE project_id = ${params.projectId}
|
|
655
|
+
AND user_id = ${params.userId}
|
|
656
|
+
AND group_id = ${params.groupId}
|
|
657
|
+
`
|
|
658
|
+
|
|
659
|
+
return row ? rowToGroupMembershipRecord(row) : null
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export async function listMembershipsForUser(
|
|
663
|
+
sql: SQLClient,
|
|
664
|
+
params: { readonly projectId: string; readonly userId: string }
|
|
665
|
+
): Promise<readonly GroupMembershipRecord[]> {
|
|
666
|
+
const rows = await sql<PgAuthGroupMembershipRow[]>`
|
|
667
|
+
SELECT *
|
|
668
|
+
FROM auth_group_memberships
|
|
669
|
+
WHERE project_id = ${params.projectId}
|
|
670
|
+
AND user_id = ${params.userId}
|
|
671
|
+
ORDER BY group_id ASC
|
|
672
|
+
`
|
|
673
|
+
|
|
674
|
+
return rows.map(rowToGroupMembershipRecord)
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export async function revokeActiveMagicLinksForEmail(
|
|
678
|
+
sql: SQLClient,
|
|
679
|
+
params: { readonly projectId: string; readonly email: string; readonly revokedAt: Date }
|
|
680
|
+
): Promise<readonly MagicLinkRecord[]> {
|
|
681
|
+
const email = normalizeEmail(params.email)
|
|
682
|
+
const rows = await sql<PgAuthMagicLinkRow[]>`
|
|
683
|
+
SELECT *
|
|
684
|
+
FROM auth_magic_links
|
|
685
|
+
WHERE project_id = ${params.projectId}
|
|
686
|
+
AND email = ${email}
|
|
687
|
+
AND consumed_at IS NULL
|
|
688
|
+
AND revoked_at IS NULL
|
|
689
|
+
AND expires_at > ${params.revokedAt}
|
|
690
|
+
ORDER BY created_at ASC, id ASC
|
|
691
|
+
FOR UPDATE
|
|
692
|
+
`
|
|
693
|
+
|
|
694
|
+
await sql`
|
|
695
|
+
UPDATE auth_magic_links
|
|
696
|
+
SET revoked_at = ${params.revokedAt}
|
|
697
|
+
WHERE project_id = ${params.projectId}
|
|
698
|
+
AND email = ${email}
|
|
699
|
+
AND consumed_at IS NULL
|
|
700
|
+
AND revoked_at IS NULL
|
|
701
|
+
AND expires_at > ${params.revokedAt}
|
|
702
|
+
`
|
|
703
|
+
|
|
704
|
+
return rows.map((row) =>
|
|
705
|
+
rowToMagicLinkRecord({
|
|
706
|
+
...row,
|
|
707
|
+
revoked_at: params.revokedAt,
|
|
708
|
+
})
|
|
709
|
+
)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function invitationCreatorColumns(principal: Principal | undefined): {
|
|
713
|
+
readonly createdByUserId: string | null
|
|
714
|
+
readonly createdByServiceAccountId: string | null
|
|
715
|
+
readonly createdBySystemId: string | null
|
|
716
|
+
} {
|
|
717
|
+
return {
|
|
718
|
+
createdByUserId: principal?.type === "user" ? principal.id : null,
|
|
719
|
+
createdByServiceAccountId: principal?.type === "serviceAccount" ? principal.id : null,
|
|
720
|
+
createdBySystemId: principal?.type === "system" ? principal.id : null,
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
export function assertMagicLinkUsable(
|
|
725
|
+
projectId: string,
|
|
726
|
+
id: string,
|
|
727
|
+
tokenHash: string,
|
|
728
|
+
at: Date,
|
|
729
|
+
row: PgAuthMagicLinkRow
|
|
730
|
+
): void {
|
|
731
|
+
if (row.token_hash !== tokenHash || row.consumed_at || row.revoked_at) {
|
|
732
|
+
throw new AuthStorageError(
|
|
733
|
+
"invalid_magic_link",
|
|
734
|
+
`[Sixb] Magic link '${id}' is not valid for project '${projectId}'.`
|
|
735
|
+
)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (new Date(row.expires_at) <= at) {
|
|
739
|
+
throw new AuthStorageError(
|
|
740
|
+
"expired_magic_link",
|
|
741
|
+
`[Sixb] Magic link '${id}' is expired for project '${projectId}'.`
|
|
742
|
+
)
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export function assertOidcAttemptUsable(
|
|
747
|
+
projectId: string,
|
|
748
|
+
id: string,
|
|
749
|
+
stateHash: string,
|
|
750
|
+
at: Date,
|
|
751
|
+
row: PgAuthOidcAttemptRow
|
|
752
|
+
): void {
|
|
753
|
+
if (row.state_hash !== stateHash || row.consumed_at) {
|
|
754
|
+
throw new AuthStorageError(
|
|
755
|
+
"invalid_oidc_attempt",
|
|
756
|
+
`[Sixb] OIDC authorization attempt '${id}' is not valid for project '${projectId}'.`
|
|
757
|
+
)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (new Date(row.expires_at) <= at) {
|
|
761
|
+
throw new AuthStorageError(
|
|
762
|
+
"expired_oidc_attempt",
|
|
763
|
+
`[Sixb] OIDC authorization attempt '${id}' is expired for project '${projectId}'.`
|
|
764
|
+
)
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export function mapUniqueConstraintError(
|
|
769
|
+
error: unknown,
|
|
770
|
+
code:
|
|
771
|
+
| "duplicate_access_token"
|
|
772
|
+
| "duplicate_identity"
|
|
773
|
+
| "duplicate_invitation"
|
|
774
|
+
| "duplicate_magic_link"
|
|
775
|
+
| "duplicate_oidc_attempt"
|
|
776
|
+
| "duplicate_service_account"
|
|
777
|
+
| "duplicate_session"
|
|
778
|
+
| "duplicate_user",
|
|
779
|
+
message: string
|
|
780
|
+
): never {
|
|
781
|
+
if (isUniqueViolation(error)) {
|
|
782
|
+
throw new AuthStorageError(code, message)
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
throw error
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
async function invitationRowToRecord(
|
|
789
|
+
sql: SQLClient,
|
|
790
|
+
row: PgAuthInvitationRow
|
|
791
|
+
): Promise<InvitationRecord> {
|
|
792
|
+
return rowToInvitationRecord(
|
|
793
|
+
row,
|
|
794
|
+
await getInvitationGroupIds(sql, {
|
|
795
|
+
projectId: row.project_id,
|
|
796
|
+
invitationId: row.id,
|
|
797
|
+
})
|
|
798
|
+
)
|
|
799
|
+
}
|