@sixb/sqlite 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 +57 -0
- package/dist/action-run-storage.d.ts +29 -0
- package/dist/action-run-storage.d.ts.map +1 -0
- package/dist/agents/agent-storage.d.ts +25 -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 +14 -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 +26 -0
- package/dist/agents/runs.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +14 -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 +46 -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 +28 -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 +134 -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 +131 -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 +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10204 -0
- package/dist/index.js.map +54 -0
- package/dist/latest-run-query.d.ts +22 -0
- package/dist/latest-run-query.d.ts.map +1 -0
- package/dist/migrations.d.ts +18 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/object-query-compiler.d.ts +44 -0
- package/dist/object-query-compiler.d.ts.map +1 -0
- package/dist/object-storage.d.ts +93 -0
- package/dist/object-storage.d.ts.map +1 -0
- package/dist/ontology-storage/commits.d.ts +14 -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 +49 -0
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-state.d.ts +63 -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 +32 -0
- package/dist/ontology-storage/materializations.d.ts.map +1 -0
- package/dist/ontology-storage/outbox.d.ts +14 -0
- package/dist/ontology-storage/outbox.d.ts.map +1 -0
- package/dist/ontology-storage/shared.d.ts +91 -0
- package/dist/ontology-storage/shared.d.ts.map +1 -0
- package/dist/ontology-storage/sources.d.ts +26 -0
- package/dist/ontology-storage/sources.d.ts.map +1 -0
- package/dist/pipeline-run-storage.d.ts +26 -0
- package/dist/pipeline-run-storage.d.ts.map +1 -0
- package/dist/projection-run-storage.d.ts +32 -0
- package/dist/projection-run-storage.d.ts.map +1 -0
- package/dist/rules-storage.d.ts +33 -0
- package/dist/rules-storage.d.ts.map +1 -0
- package/dist/run-list-query.d.ts +27 -0
- package/dist/run-list-query.d.ts.map +1 -0
- package/dist/storage-errors.d.ts +2 -0
- package/dist/storage-errors.d.ts.map +1 -0
- package/dist/sync-run-storage.d.ts +23 -0
- package/dist/sync-run-storage.d.ts.map +1 -0
- package/dist/timeseries-storage.d.ts +41 -0
- package/dist/timeseries-storage.d.ts.map +1 -0
- package/dist/transactions.d.ts +15 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/tsconfig.json +5 -0
- package/dist/validation.d.ts +5 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/webhook-delivery-storage.d.ts +27 -0
- package/dist/webhook-delivery-storage.d.ts.map +1 -0
- package/dist/webhook-run-storage.d.ts +22 -0
- package/dist/webhook-run-storage.d.ts.map +1 -0
- package/dist/workflow-intervention-storage.d.ts +26 -0
- package/dist/workflow-intervention-storage.d.ts.map +1 -0
- package/dist/workflow-run-storage.d.ts +63 -0
- package/dist/workflow-run-storage.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/action-run-storage.ts +761 -0
- package/src/agents/agent-storage.ts +46 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/messages.ts +162 -0
- package/src/agents/rows.ts +205 -0
- package/src/agents/runs.ts +389 -0
- package/src/agents/threads.ts +133 -0
- package/src/auth-storage/access-tokens.ts +289 -0
- package/src/auth-storage/auth-storage.ts +784 -0
- package/src/auth-storage/group-memberships.ts +52 -0
- package/src/auth-storage/identities.ts +98 -0
- package/src/auth-storage/index.ts +2 -0
- package/src/auth-storage/invitations.ts +258 -0
- package/src/auth-storage/magic-links.ts +149 -0
- package/src/auth-storage/oidc-attempts.ts +109 -0
- package/src/auth-storage/rows.ts +338 -0
- package/src/auth-storage/service-account-group-memberships.ts +161 -0
- package/src/auth-storage/service-accounts.ts +168 -0
- package/src/auth-storage/sessions.ts +230 -0
- package/src/auth-storage/shared.ts +707 -0
- package/src/auth-storage/users.ts +226 -0
- package/src/index.ts +292 -0
- package/src/latest-run-query.ts +49 -0
- package/src/migrations/001-initial-schema.sql +1068 -0
- package/src/migrations.ts +245 -0
- package/src/object-query-compiler.ts +1198 -0
- package/src/object-storage.ts +605 -0
- package/src/ontology-storage/commits.ts +127 -0
- package/src/ontology-storage/index.ts +35 -0
- package/src/ontology-storage/materialization-session.ts +393 -0
- package/src/ontology-storage/materialization-state.ts +901 -0
- package/src/ontology-storage/materialization-writer.ts +394 -0
- package/src/ontology-storage/materializations.ts +838 -0
- package/src/ontology-storage/outbox.ts +237 -0
- package/src/ontology-storage/shared.ts +269 -0
- package/src/ontology-storage/sources.ts +653 -0
- package/src/pipeline-run-storage.ts +506 -0
- package/src/projection-run-storage.ts +592 -0
- package/src/rules-storage.ts +375 -0
- package/src/run-list-query.ts +98 -0
- package/src/sql.d.ts +4 -0
- package/src/storage-errors.ts +3 -0
- package/src/sync-run-storage.ts +299 -0
- package/src/timeseries-storage.ts +212 -0
- package/src/transactions.ts +96 -0
- package/src/validation.ts +30 -0
- package/src/webhook-delivery-storage.ts +272 -0
- package/src/webhook-run-storage.ts +240 -0
- package/src/workflow-intervention-storage.ts +398 -0
- package/src/workflow-run-storage.ts +1148 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import type {
|
|
3
|
+
AuthGroupMembershipStore,
|
|
4
|
+
GroupMembershipRecord,
|
|
5
|
+
UpsertAuthGroupMembershipInput,
|
|
6
|
+
} from "@sixb/core/storage"
|
|
7
|
+
import { runImmediateTransaction } from "../transactions"
|
|
8
|
+
import type { SqliteAuthGroupMembershipRow } from "./rows"
|
|
9
|
+
import { rowToGroupMembershipRecord } from "./rows"
|
|
10
|
+
import { listMembershipsForUser, removeGroupMembership, upsertGroupMembership } from "./shared"
|
|
11
|
+
|
|
12
|
+
export class SqliteAuthGroupMembershipStore implements AuthGroupMembershipStore {
|
|
13
|
+
constructor(private readonly db: Database) {}
|
|
14
|
+
|
|
15
|
+
async upsert(input: UpsertAuthGroupMembershipInput): Promise<GroupMembershipRecord> {
|
|
16
|
+
return runImmediateTransaction(this.db, () => upsertGroupMembership(this.db, input))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async remove(params: {
|
|
20
|
+
readonly projectId: string
|
|
21
|
+
readonly userId: string
|
|
22
|
+
readonly groupId: string
|
|
23
|
+
}): Promise<GroupMembershipRecord | null> {
|
|
24
|
+
return runImmediateTransaction(this.db, () => removeGroupMembership(this.db, params))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async listForUser(params: {
|
|
28
|
+
readonly projectId: string
|
|
29
|
+
readonly userId: string
|
|
30
|
+
}): Promise<readonly GroupMembershipRecord[]> {
|
|
31
|
+
return listMembershipsForUser(this.db, params)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async listForGroup(params: {
|
|
35
|
+
readonly projectId: string
|
|
36
|
+
readonly groupId: string
|
|
37
|
+
}): Promise<readonly GroupMembershipRecord[]> {
|
|
38
|
+
const rows = this.db
|
|
39
|
+
.query(
|
|
40
|
+
`
|
|
41
|
+
SELECT *
|
|
42
|
+
FROM auth_group_memberships
|
|
43
|
+
WHERE project_id = ?
|
|
44
|
+
AND group_id = ?
|
|
45
|
+
ORDER BY user_id ASC
|
|
46
|
+
`
|
|
47
|
+
)
|
|
48
|
+
.all(params.projectId, params.groupId) as SqliteAuthGroupMembershipRow[]
|
|
49
|
+
|
|
50
|
+
return rows.map(rowToGroupMembershipRecord)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import type {
|
|
3
|
+
AuthUserIdentityStore,
|
|
4
|
+
UpsertAuthUserIdentityInput,
|
|
5
|
+
UserIdentityRecord,
|
|
6
|
+
} from "@sixb/core/storage"
|
|
7
|
+
import type { SqliteAuthUserIdentityRow } from "./rows"
|
|
8
|
+
import { rowToIdentityRecord, serializeOptionalRecord } from "./rows"
|
|
9
|
+
import { assertNonEmpty, dateOrNow, getIdentityRowBySubject, toIso } from "./shared"
|
|
10
|
+
|
|
11
|
+
export class SqliteAuthUserIdentityStore implements AuthUserIdentityStore {
|
|
12
|
+
constructor(private readonly db: Database) {}
|
|
13
|
+
|
|
14
|
+
async upsert(input: UpsertAuthUserIdentityInput): Promise<UserIdentityRecord> {
|
|
15
|
+
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
16
|
+
const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
|
|
17
|
+
const subject = assertNonEmpty(input.subject, "Subject")
|
|
18
|
+
const userId = assertNonEmpty(input.userId, "User id")
|
|
19
|
+
const existing = getIdentityRowBySubject(this.db, {
|
|
20
|
+
projectId,
|
|
21
|
+
strategyId,
|
|
22
|
+
subject,
|
|
23
|
+
})
|
|
24
|
+
const createdAt = existing ? new Date(existing.created_at) : dateOrNow(input.createdAt)
|
|
25
|
+
const updatedAt = input.updatedAt
|
|
26
|
+
? new Date(input.updatedAt)
|
|
27
|
+
: existing
|
|
28
|
+
? new Date()
|
|
29
|
+
: createdAt
|
|
30
|
+
|
|
31
|
+
this.db
|
|
32
|
+
.query(
|
|
33
|
+
`
|
|
34
|
+
INSERT INTO auth_user_identities (
|
|
35
|
+
project_id,
|
|
36
|
+
strategy_id,
|
|
37
|
+
subject,
|
|
38
|
+
user_id,
|
|
39
|
+
claims,
|
|
40
|
+
created_at,
|
|
41
|
+
updated_at
|
|
42
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
43
|
+
ON CONFLICT(project_id, strategy_id, subject)
|
|
44
|
+
DO UPDATE SET
|
|
45
|
+
user_id = excluded.user_id,
|
|
46
|
+
claims = excluded.claims,
|
|
47
|
+
updated_at = excluded.updated_at
|
|
48
|
+
`
|
|
49
|
+
)
|
|
50
|
+
.run(
|
|
51
|
+
projectId,
|
|
52
|
+
strategyId,
|
|
53
|
+
subject,
|
|
54
|
+
userId,
|
|
55
|
+
serializeOptionalRecord(input.claims),
|
|
56
|
+
toIso(createdAt),
|
|
57
|
+
toIso(updatedAt)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
projectId,
|
|
62
|
+
strategyId,
|
|
63
|
+
subject,
|
|
64
|
+
userId,
|
|
65
|
+
claims: input.claims ? structuredClone(input.claims) : undefined,
|
|
66
|
+
createdAt,
|
|
67
|
+
updatedAt,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async getBySubject(params: {
|
|
72
|
+
readonly projectId: string
|
|
73
|
+
readonly strategyId: string
|
|
74
|
+
readonly subject: string
|
|
75
|
+
}): Promise<UserIdentityRecord | null> {
|
|
76
|
+
const row = getIdentityRowBySubject(this.db, params)
|
|
77
|
+
return row ? rowToIdentityRecord(row) : null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async listForUser(params: {
|
|
81
|
+
readonly projectId: string
|
|
82
|
+
readonly userId: string
|
|
83
|
+
}): Promise<readonly UserIdentityRecord[]> {
|
|
84
|
+
const rows = this.db
|
|
85
|
+
.query(
|
|
86
|
+
`
|
|
87
|
+
SELECT *
|
|
88
|
+
FROM auth_user_identities
|
|
89
|
+
WHERE project_id = ?
|
|
90
|
+
AND user_id = ?
|
|
91
|
+
ORDER BY created_at ASC, strategy_id ASC, subject ASC
|
|
92
|
+
`
|
|
93
|
+
)
|
|
94
|
+
.all(params.projectId, params.userId) as SqliteAuthUserIdentityRow[]
|
|
95
|
+
|
|
96
|
+
return rows.map(rowToIdentityRecord)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import type {
|
|
3
|
+
AuthInvitationStore,
|
|
4
|
+
CreateOrUpdateAuthInvitationInput,
|
|
5
|
+
InvitationRecord,
|
|
6
|
+
ListAuthInvitationsInput,
|
|
7
|
+
ListAuthInvitationsResult,
|
|
8
|
+
} from "@sixb/core/storage"
|
|
9
|
+
import { AuthStorageError } from "@sixb/core/storage"
|
|
10
|
+
import { runImmediateTransaction } from "../transactions"
|
|
11
|
+
import type { SqliteAuthInvitationRow } from "./rows"
|
|
12
|
+
import { rowToInvitationRecord } from "./rows"
|
|
13
|
+
import {
|
|
14
|
+
appendPagination,
|
|
15
|
+
assertNonEmpty,
|
|
16
|
+
dateOrNow,
|
|
17
|
+
getActiveInvitationByEmail,
|
|
18
|
+
getInvitationById,
|
|
19
|
+
getInvitationGroupIds,
|
|
20
|
+
hasEmptyFilter,
|
|
21
|
+
mapUniqueConstraintError,
|
|
22
|
+
normalizeEmail,
|
|
23
|
+
normalizeGroupIds,
|
|
24
|
+
replaceInvitationGroups,
|
|
25
|
+
requireInvitationById,
|
|
26
|
+
type SqliteValue,
|
|
27
|
+
toIso,
|
|
28
|
+
} from "./shared"
|
|
29
|
+
|
|
30
|
+
export class SqliteAuthInvitationStore implements AuthInvitationStore {
|
|
31
|
+
constructor(private readonly db: Database) {}
|
|
32
|
+
|
|
33
|
+
async createOrUpdateActive(input: CreateOrUpdateAuthInvitationInput): Promise<InvitationRecord> {
|
|
34
|
+
return runImmediateTransaction(this.db, () => {
|
|
35
|
+
const id = assertNonEmpty(input.id, "Invitation id")
|
|
36
|
+
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
37
|
+
const email = normalizeEmail(input.email)
|
|
38
|
+
const groupIds = normalizeGroupIds(input.groupIds)
|
|
39
|
+
const now = dateOrNow(input.updatedAt ?? input.createdAt)
|
|
40
|
+
const existingActive = getActiveInvitationByEmail(this.db, {
|
|
41
|
+
projectId,
|
|
42
|
+
email,
|
|
43
|
+
now,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (existingActive) {
|
|
47
|
+
this.db
|
|
48
|
+
.query(
|
|
49
|
+
`
|
|
50
|
+
UPDATE auth_invitations
|
|
51
|
+
SET created_by_principal_type = ?,
|
|
52
|
+
created_by_principal_id = ?,
|
|
53
|
+
created_by_session_id = ?,
|
|
54
|
+
expires_at = ?,
|
|
55
|
+
updated_at = ?
|
|
56
|
+
WHERE project_id = ?
|
|
57
|
+
AND id = ?
|
|
58
|
+
`
|
|
59
|
+
)
|
|
60
|
+
.run(
|
|
61
|
+
input.createdByPrincipal?.type ?? null,
|
|
62
|
+
input.createdByPrincipal?.id ?? null,
|
|
63
|
+
input.createdBySessionId ?? null,
|
|
64
|
+
toIso(input.expiresAt),
|
|
65
|
+
toIso(now),
|
|
66
|
+
projectId,
|
|
67
|
+
existingActive.id
|
|
68
|
+
)
|
|
69
|
+
replaceInvitationGroups(this.db, {
|
|
70
|
+
projectId,
|
|
71
|
+
invitationId: existingActive.id,
|
|
72
|
+
groupIds,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return requireInvitationById(this.db, { projectId, id: existingActive.id })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (getInvitationById(this.db, { projectId, id })) {
|
|
79
|
+
throw new AuthStorageError(
|
|
80
|
+
"duplicate_invitation",
|
|
81
|
+
`[Sixb] Invitation '${id}' already exists but is not active for project '${projectId}'.`
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const createdAt = dateOrNow(input.createdAt)
|
|
86
|
+
try {
|
|
87
|
+
this.db
|
|
88
|
+
.query(
|
|
89
|
+
`
|
|
90
|
+
INSERT INTO auth_invitations (
|
|
91
|
+
project_id,
|
|
92
|
+
id,
|
|
93
|
+
email,
|
|
94
|
+
status,
|
|
95
|
+
created_by_principal_type,
|
|
96
|
+
created_by_principal_id,
|
|
97
|
+
created_by_session_id,
|
|
98
|
+
created_at,
|
|
99
|
+
updated_at,
|
|
100
|
+
expires_at
|
|
101
|
+
) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?, ?)
|
|
102
|
+
`
|
|
103
|
+
)
|
|
104
|
+
.run(
|
|
105
|
+
projectId,
|
|
106
|
+
id,
|
|
107
|
+
email,
|
|
108
|
+
input.createdByPrincipal?.type ?? null,
|
|
109
|
+
input.createdByPrincipal?.id ?? null,
|
|
110
|
+
input.createdBySessionId ?? null,
|
|
111
|
+
toIso(createdAt),
|
|
112
|
+
toIso(now),
|
|
113
|
+
toIso(input.expiresAt)
|
|
114
|
+
)
|
|
115
|
+
} catch (error) {
|
|
116
|
+
mapUniqueConstraintError(
|
|
117
|
+
error,
|
|
118
|
+
"duplicate_invitation",
|
|
119
|
+
`[Sixb] Invitation '${id}' already exists for project '${projectId}'.`
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
replaceInvitationGroups(this.db, { projectId, invitationId: id, groupIds })
|
|
124
|
+
return requireInvitationById(this.db, { projectId, id })
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async getById(params: {
|
|
129
|
+
readonly projectId: string
|
|
130
|
+
readonly id: string
|
|
131
|
+
}): Promise<InvitationRecord | null> {
|
|
132
|
+
return getInvitationById(this.db, params)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async getActiveByEmail(params: {
|
|
136
|
+
readonly projectId: string
|
|
137
|
+
readonly email: string
|
|
138
|
+
readonly now: Date
|
|
139
|
+
}): Promise<InvitationRecord | null> {
|
|
140
|
+
return getActiveInvitationByEmail(this.db, params)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async list(input: ListAuthInvitationsInput): Promise<ListAuthInvitationsResult> {
|
|
144
|
+
if (hasEmptyFilter(input.statuses) || hasEmptyFilter(input.groupIds)) {
|
|
145
|
+
return { invitations: [], hasMore: false, total: 0 }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const whereClauses = ["project_id = ?"]
|
|
149
|
+
const args: SqliteValue[] = [input.projectId]
|
|
150
|
+
|
|
151
|
+
if (input.email) {
|
|
152
|
+
whereClauses.push("email = ?")
|
|
153
|
+
args.push(normalizeEmail(input.email))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (input.statuses) {
|
|
157
|
+
whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`)
|
|
158
|
+
args.push(...input.statuses)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const groupIds = input.groupIds ? normalizeGroupIds(input.groupIds) : null
|
|
162
|
+
if (groupIds) {
|
|
163
|
+
whereClauses.push(`
|
|
164
|
+
EXISTS (
|
|
165
|
+
SELECT 1
|
|
166
|
+
FROM auth_invitation_groups invitation_groups
|
|
167
|
+
WHERE invitation_groups.project_id = auth_invitations.project_id
|
|
168
|
+
AND invitation_groups.invitation_id = auth_invitations.id
|
|
169
|
+
AND invitation_groups.group_id IN (${groupIds.map(() => "?").join(", ")})
|
|
170
|
+
)
|
|
171
|
+
`)
|
|
172
|
+
args.push(...groupIds)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const where = `WHERE ${whereClauses.join(" AND ")}`
|
|
176
|
+
const order = input.order === "asc" ? "ASC" : "DESC"
|
|
177
|
+
const totalRow = this.db
|
|
178
|
+
.query(`SELECT COUNT(*) AS count FROM auth_invitations ${where}`)
|
|
179
|
+
.get(...args) as { readonly count: number }
|
|
180
|
+
|
|
181
|
+
const queryArgs = [...args]
|
|
182
|
+
const query = appendPagination(
|
|
183
|
+
`
|
|
184
|
+
SELECT *
|
|
185
|
+
FROM auth_invitations
|
|
186
|
+
${where}
|
|
187
|
+
ORDER BY created_at ${order}, id ${order}
|
|
188
|
+
`,
|
|
189
|
+
queryArgs,
|
|
190
|
+
input
|
|
191
|
+
)
|
|
192
|
+
const rows = this.db.query(query).all(...queryArgs) as SqliteAuthInvitationRow[]
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
invitations: rows.map((row) =>
|
|
196
|
+
rowToInvitationRecord(
|
|
197
|
+
row,
|
|
198
|
+
getInvitationGroupIds(this.db, {
|
|
199
|
+
projectId: row.project_id,
|
|
200
|
+
invitationId: row.id,
|
|
201
|
+
})
|
|
202
|
+
)
|
|
203
|
+
),
|
|
204
|
+
hasMore: (input.offset ?? 0) + rows.length < totalRow.count,
|
|
205
|
+
total: totalRow.count,
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async accept(params: {
|
|
210
|
+
readonly projectId: string
|
|
211
|
+
readonly id: string
|
|
212
|
+
readonly acceptedAt: Date
|
|
213
|
+
}): Promise<InvitationRecord> {
|
|
214
|
+
return runImmediateTransaction(this.db, () => {
|
|
215
|
+
requireInvitationById(this.db, params)
|
|
216
|
+
|
|
217
|
+
this.db
|
|
218
|
+
.query(
|
|
219
|
+
`
|
|
220
|
+
UPDATE auth_invitations
|
|
221
|
+
SET status = 'accepted',
|
|
222
|
+
accepted_at = ?,
|
|
223
|
+
updated_at = ?
|
|
224
|
+
WHERE project_id = ?
|
|
225
|
+
AND id = ?
|
|
226
|
+
`
|
|
227
|
+
)
|
|
228
|
+
.run(toIso(params.acceptedAt), toIso(params.acceptedAt), params.projectId, params.id)
|
|
229
|
+
|
|
230
|
+
return requireInvitationById(this.db, params)
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async revoke(params: {
|
|
235
|
+
readonly projectId: string
|
|
236
|
+
readonly id: string
|
|
237
|
+
readonly revokedAt: Date
|
|
238
|
+
}): Promise<InvitationRecord> {
|
|
239
|
+
return runImmediateTransaction(this.db, () => {
|
|
240
|
+
requireInvitationById(this.db, params)
|
|
241
|
+
|
|
242
|
+
this.db
|
|
243
|
+
.query(
|
|
244
|
+
`
|
|
245
|
+
UPDATE auth_invitations
|
|
246
|
+
SET status = 'revoked',
|
|
247
|
+
revoked_at = ?,
|
|
248
|
+
updated_at = ?
|
|
249
|
+
WHERE project_id = ?
|
|
250
|
+
AND id = ?
|
|
251
|
+
`
|
|
252
|
+
)
|
|
253
|
+
.run(toIso(params.revokedAt), toIso(params.revokedAt), params.projectId, params.id)
|
|
254
|
+
|
|
255
|
+
return requireInvitationById(this.db, params)
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import { resolveAuthSessionAudience } from "@sixb/core"
|
|
3
|
+
import type {
|
|
4
|
+
AuthMagicLinkStore,
|
|
5
|
+
CreateAuthMagicLinkInput,
|
|
6
|
+
MagicLinkRecord,
|
|
7
|
+
} from "@sixb/core/storage"
|
|
8
|
+
import { AuthStorageError } from "@sixb/core/storage"
|
|
9
|
+
import { runImmediateTransaction } from "../transactions"
|
|
10
|
+
import type { SqliteAuthMagicLinkRow } from "./rows"
|
|
11
|
+
import { rowToMagicLinkRecord } from "./rows"
|
|
12
|
+
import {
|
|
13
|
+
assertNonEmpty,
|
|
14
|
+
consumeMagicLink,
|
|
15
|
+
getMagicLinkById,
|
|
16
|
+
getMagicLinkRowById,
|
|
17
|
+
mapUniqueConstraintError,
|
|
18
|
+
normalizeEmail,
|
|
19
|
+
revokeActiveMagicLinksForEmail,
|
|
20
|
+
toIso,
|
|
21
|
+
} from "./shared"
|
|
22
|
+
|
|
23
|
+
export class SqliteAuthMagicLinkStore implements AuthMagicLinkStore {
|
|
24
|
+
constructor(private readonly db: Database) {}
|
|
25
|
+
|
|
26
|
+
async create(input: CreateAuthMagicLinkInput): Promise<MagicLinkRecord> {
|
|
27
|
+
return runImmediateTransaction(this.db, () => {
|
|
28
|
+
const id = assertNonEmpty(input.id, "Magic link id")
|
|
29
|
+
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
30
|
+
const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
|
|
31
|
+
const audience = resolveAuthSessionAudience(input.audience)
|
|
32
|
+
const email = normalizeEmail(input.email)
|
|
33
|
+
const tokenHash = assertNonEmpty(input.tokenHash, "Magic link token hash")
|
|
34
|
+
|
|
35
|
+
if (getMagicLinkRowById(this.db, { projectId, id })) {
|
|
36
|
+
throw new AuthStorageError(
|
|
37
|
+
"duplicate_magic_link",
|
|
38
|
+
`[Sixb] Magic link '${id}' already exists for project '${projectId}'.`
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
revokeActiveMagicLinksForEmail(this.db, {
|
|
43
|
+
projectId,
|
|
44
|
+
email,
|
|
45
|
+
revokedAt: input.createdAt,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
this.db
|
|
50
|
+
.query(
|
|
51
|
+
`
|
|
52
|
+
INSERT INTO auth_magic_links (
|
|
53
|
+
project_id,
|
|
54
|
+
id,
|
|
55
|
+
strategy_id,
|
|
56
|
+
audience,
|
|
57
|
+
email,
|
|
58
|
+
token_hash,
|
|
59
|
+
return_to,
|
|
60
|
+
created_at,
|
|
61
|
+
expires_at
|
|
62
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
63
|
+
`
|
|
64
|
+
)
|
|
65
|
+
.run(
|
|
66
|
+
projectId,
|
|
67
|
+
id,
|
|
68
|
+
strategyId,
|
|
69
|
+
audience,
|
|
70
|
+
email,
|
|
71
|
+
tokenHash,
|
|
72
|
+
input.returnTo ?? null,
|
|
73
|
+
toIso(input.createdAt),
|
|
74
|
+
toIso(input.expiresAt)
|
|
75
|
+
)
|
|
76
|
+
} catch (error) {
|
|
77
|
+
mapUniqueConstraintError(
|
|
78
|
+
error,
|
|
79
|
+
"duplicate_magic_link",
|
|
80
|
+
`[Sixb] Magic link '${id}' already exists for project '${projectId}'.`
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
id,
|
|
86
|
+
projectId,
|
|
87
|
+
strategyId,
|
|
88
|
+
audience,
|
|
89
|
+
email,
|
|
90
|
+
tokenHash,
|
|
91
|
+
returnTo: input.returnTo,
|
|
92
|
+
createdAt: new Date(input.createdAt),
|
|
93
|
+
expiresAt: new Date(input.expiresAt),
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getById(params: {
|
|
99
|
+
readonly projectId: string
|
|
100
|
+
readonly id: string
|
|
101
|
+
}): Promise<MagicLinkRecord | null> {
|
|
102
|
+
return getMagicLinkById(this.db, params)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async getActiveByEmail(params: {
|
|
106
|
+
readonly projectId: string
|
|
107
|
+
readonly email: string
|
|
108
|
+
readonly now: Date
|
|
109
|
+
}): Promise<MagicLinkRecord | null> {
|
|
110
|
+
const row = this.db
|
|
111
|
+
.query(
|
|
112
|
+
`
|
|
113
|
+
SELECT *
|
|
114
|
+
FROM auth_magic_links
|
|
115
|
+
WHERE project_id = ?
|
|
116
|
+
AND email = ?
|
|
117
|
+
AND consumed_at IS NULL
|
|
118
|
+
AND revoked_at IS NULL
|
|
119
|
+
AND expires_at > ?
|
|
120
|
+
ORDER BY created_at DESC, id DESC
|
|
121
|
+
LIMIT 1
|
|
122
|
+
`
|
|
123
|
+
)
|
|
124
|
+
.get(
|
|
125
|
+
params.projectId,
|
|
126
|
+
normalizeEmail(params.email),
|
|
127
|
+
toIso(params.now)
|
|
128
|
+
) as SqliteAuthMagicLinkRow | null
|
|
129
|
+
|
|
130
|
+
return row ? rowToMagicLinkRecord(row) : null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async consume(params: {
|
|
134
|
+
readonly projectId: string
|
|
135
|
+
readonly id: string
|
|
136
|
+
readonly tokenHash: string
|
|
137
|
+
readonly consumedAt: Date
|
|
138
|
+
}): Promise<MagicLinkRecord> {
|
|
139
|
+
return runImmediateTransaction(this.db, () => consumeMagicLink(this.db, params))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async revokeActiveForEmail(params: {
|
|
143
|
+
readonly projectId: string
|
|
144
|
+
readonly email: string
|
|
145
|
+
readonly revokedAt: Date
|
|
146
|
+
}): Promise<readonly MagicLinkRecord[]> {
|
|
147
|
+
return runImmediateTransaction(this.db, () => revokeActiveMagicLinksForEmail(this.db, params))
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import { resolveAuthSessionAudience } from "@sixb/core"
|
|
3
|
+
import type {
|
|
4
|
+
AuthOidcAuthorizationAttemptStore,
|
|
5
|
+
CreateOidcAuthorizationAttemptInput,
|
|
6
|
+
OidcAuthorizationAttemptRecord,
|
|
7
|
+
} from "@sixb/core/storage"
|
|
8
|
+
import { AuthStorageError } from "@sixb/core/storage"
|
|
9
|
+
import { runImmediateTransaction } from "../transactions"
|
|
10
|
+
import {
|
|
11
|
+
assertNonEmpty,
|
|
12
|
+
consumeOidcAttempt,
|
|
13
|
+
getOidcAttemptById,
|
|
14
|
+
getOidcAttemptRowById,
|
|
15
|
+
mapUniqueConstraintError,
|
|
16
|
+
toIso,
|
|
17
|
+
} from "./shared"
|
|
18
|
+
|
|
19
|
+
export class SqliteAuthOidcAuthorizationAttemptStore implements AuthOidcAuthorizationAttemptStore {
|
|
20
|
+
constructor(private readonly db: Database) {}
|
|
21
|
+
|
|
22
|
+
async create(
|
|
23
|
+
input: CreateOidcAuthorizationAttemptInput
|
|
24
|
+
): Promise<OidcAuthorizationAttemptRecord> {
|
|
25
|
+
return runImmediateTransaction(this.db, () => {
|
|
26
|
+
const id = assertNonEmpty(input.id, "OIDC authorization attempt id")
|
|
27
|
+
const projectId = assertNonEmpty(input.projectId, "Project id")
|
|
28
|
+
const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
|
|
29
|
+
const audience = resolveAuthSessionAudience(input.audience)
|
|
30
|
+
const stateHash = assertNonEmpty(input.stateHash, "OIDC state hash")
|
|
31
|
+
const nonceHash = assertNonEmpty(input.nonceHash, "OIDC nonce hash")
|
|
32
|
+
const codeVerifier = assertNonEmpty(input.codeVerifier, "OIDC code verifier")
|
|
33
|
+
|
|
34
|
+
if (getOidcAttemptRowById(this.db, { projectId, id })) {
|
|
35
|
+
throw new AuthStorageError(
|
|
36
|
+
"duplicate_oidc_attempt",
|
|
37
|
+
`[Sixb] OIDC authorization attempt '${id}' already exists for project '${projectId}'.`
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
this.db
|
|
43
|
+
.query(
|
|
44
|
+
`
|
|
45
|
+
INSERT INTO auth_oidc_authorization_attempts (
|
|
46
|
+
project_id,
|
|
47
|
+
id,
|
|
48
|
+
strategy_id,
|
|
49
|
+
audience,
|
|
50
|
+
state_hash,
|
|
51
|
+
nonce_hash,
|
|
52
|
+
code_verifier,
|
|
53
|
+
return_to,
|
|
54
|
+
created_at,
|
|
55
|
+
expires_at
|
|
56
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
57
|
+
`
|
|
58
|
+
)
|
|
59
|
+
.run(
|
|
60
|
+
projectId,
|
|
61
|
+
id,
|
|
62
|
+
strategyId,
|
|
63
|
+
audience,
|
|
64
|
+
stateHash,
|
|
65
|
+
nonceHash,
|
|
66
|
+
codeVerifier,
|
|
67
|
+
input.returnTo ?? null,
|
|
68
|
+
toIso(input.createdAt),
|
|
69
|
+
toIso(input.expiresAt)
|
|
70
|
+
)
|
|
71
|
+
} catch (error) {
|
|
72
|
+
mapUniqueConstraintError(
|
|
73
|
+
error,
|
|
74
|
+
"duplicate_oidc_attempt",
|
|
75
|
+
`[Sixb] OIDC authorization attempt '${id}' already exists for project '${projectId}'.`
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
id,
|
|
81
|
+
projectId,
|
|
82
|
+
strategyId,
|
|
83
|
+
audience,
|
|
84
|
+
stateHash,
|
|
85
|
+
nonceHash,
|
|
86
|
+
codeVerifier,
|
|
87
|
+
returnTo: input.returnTo,
|
|
88
|
+
createdAt: new Date(input.createdAt),
|
|
89
|
+
expiresAt: new Date(input.expiresAt),
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async getById(params: {
|
|
95
|
+
readonly projectId: string
|
|
96
|
+
readonly id: string
|
|
97
|
+
}): Promise<OidcAuthorizationAttemptRecord | null> {
|
|
98
|
+
return getOidcAttemptById(this.db, params)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async consume(params: {
|
|
102
|
+
readonly projectId: string
|
|
103
|
+
readonly id: string
|
|
104
|
+
readonly stateHash: string
|
|
105
|
+
readonly consumedAt: Date
|
|
106
|
+
}): Promise<OidcAuthorizationAttemptRecord> {
|
|
107
|
+
return runImmediateTransaction(this.db, () => consumeOidcAttempt(this.db, params))
|
|
108
|
+
}
|
|
109
|
+
}
|