@sixb/pg 0.1.12 → 0.1.14
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/auth-storage/access-tokens.d.ts.map +1 -1
- package/dist/auth-storage/auth-storage.d.ts +2 -1
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/device-authorizations.d.ts.map +1 -1
- package/dist/auth-storage/rows.d.ts +7 -4
- package/dist/auth-storage/rows.d.ts.map +1 -1
- package/dist/auth-storage/sessions.d.ts +8 -1
- package/dist/auth-storage/sessions.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +403 -115
- package/dist/index.js.map +16 -15
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +0 -2
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +2 -1
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/pg-file-upload-session-storage.d.ts +6 -0
- package/dist/pg-file-upload-session-storage.d.ts.map +1 -0
- package/dist/pg-workflow-intervention-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/auth-storage/access-tokens.ts +10 -14
- package/src/auth-storage/auth-storage.ts +47 -81
- package/src/auth-storage/device-authorizations.ts +3 -4
- package/src/auth-storage/rows.ts +18 -8
- package/src/auth-storage/sessions.ts +82 -4
- package/src/auth-storage/shared.ts +8 -2
- package/src/index.ts +5 -0
- package/src/migrations/046-workflow-intervention-principals.sql +15 -0
- package/src/migrations/047-ontology-commit-attribution.sql +50 -0
- package/src/migrations/048-file-upload-sessions.sql +34 -0
- package/src/migrations/049-native-sessions.sql +19 -0
- package/src/migrations/050-directory-group-memberships.sql +5 -0
- package/src/migrations.ts +16 -0
- package/src/objects/storage.ts +2 -0
- package/src/ontology-storage/materializations.ts +15 -58
- package/src/ontology-storage/shared.ts +7 -3
- package/src/pg-file-upload-session-storage.ts +143 -0
- package/src/pg-workflow-intervention-storage.ts +11 -15
package/dist/index.js
CHANGED
|
@@ -1162,7 +1162,14 @@ function rowToSessionRecord(row) {
|
|
|
1162
1162
|
revokedAt: row.revoked_at ? toDate(row.revoked_at) : undefined,
|
|
1163
1163
|
lastSeenAt: row.last_seen_at ? toDate(row.last_seen_at) : undefined,
|
|
1164
1164
|
userAgent: row.user_agent ?? undefined,
|
|
1165
|
-
ipAddress: row.ip_address ?? undefined
|
|
1165
|
+
ipAddress: row.ip_address ?? undefined,
|
|
1166
|
+
bearer: row.refresh_token_hash !== null && row.client_name !== null && row.access_expires_at !== null ? {
|
|
1167
|
+
clientName: row.client_name,
|
|
1168
|
+
accessExpiresAt: toDate(row.access_expires_at),
|
|
1169
|
+
refreshTokenHash: row.refresh_token_hash,
|
|
1170
|
+
previousRefreshTokenHash: row.previous_refresh_token_hash ?? undefined,
|
|
1171
|
+
refreshedAt: row.refreshed_at ? toDate(row.refreshed_at) : undefined
|
|
1172
|
+
} : undefined
|
|
1166
1173
|
};
|
|
1167
1174
|
}
|
|
1168
1175
|
function rowToAccessTokenRecord(row) {
|
|
@@ -1171,8 +1178,7 @@ function rowToAccessTokenRecord(row) {
|
|
|
1171
1178
|
projectId: row.project_id,
|
|
1172
1179
|
name: row.name,
|
|
1173
1180
|
kind: row.kind,
|
|
1174
|
-
|
|
1175
|
-
subjectId: row.subject_id,
|
|
1181
|
+
subject: { type: row.subject_type, id: row.subject_id },
|
|
1176
1182
|
tokenHash: row.token_hash,
|
|
1177
1183
|
groupIds: parseOptionalStringArray(row.group_ids),
|
|
1178
1184
|
createdByPrincipal: principalFromColumns2(row),
|
|
@@ -1247,8 +1253,6 @@ function rowToDeviceAuthorizationRecord(row) {
|
|
|
1247
1253
|
deviceCodeHash: row.device_code_hash,
|
|
1248
1254
|
userCode: row.user_code,
|
|
1249
1255
|
clientName: row.client_name,
|
|
1250
|
-
tokenName: row.token_name,
|
|
1251
|
-
tokenExpiresAt: toDate(row.token_expires_at),
|
|
1252
1256
|
status: row.status,
|
|
1253
1257
|
approvedUserId: row.approved_user_id ?? undefined,
|
|
1254
1258
|
approvedSessionId: row.approved_session_id ?? undefined,
|
|
@@ -1559,7 +1563,10 @@ async function createSession(sql, input) {
|
|
|
1559
1563
|
expires_at,
|
|
1560
1564
|
absolute_expires_at,
|
|
1561
1565
|
user_agent,
|
|
1562
|
-
ip_address
|
|
1566
|
+
ip_address,
|
|
1567
|
+
client_name,
|
|
1568
|
+
access_expires_at,
|
|
1569
|
+
refresh_token_hash
|
|
1563
1570
|
) VALUES (
|
|
1564
1571
|
${projectId},
|
|
1565
1572
|
${id},
|
|
@@ -1571,7 +1578,10 @@ async function createSession(sql, input) {
|
|
|
1571
1578
|
${input.expiresAt},
|
|
1572
1579
|
${input.absoluteExpiresAt ?? null},
|
|
1573
1580
|
${input.userAgent ?? null},
|
|
1574
|
-
${input.ipAddress ?? null}
|
|
1581
|
+
${input.ipAddress ?? null},
|
|
1582
|
+
${input.bearer ? assertNonEmpty(input.bearer.clientName, "Session client name") : null},
|
|
1583
|
+
${input.bearer?.accessExpiresAt ?? null},
|
|
1584
|
+
${input.bearer ? assertNonEmpty(input.bearer.refreshTokenHash, "Refresh token hash") : null}
|
|
1575
1585
|
)
|
|
1576
1586
|
RETURNING *
|
|
1577
1587
|
`;
|
|
@@ -1763,10 +1773,10 @@ class PgAuthAccessTokenStore {
|
|
|
1763
1773
|
const id = assertNonEmpty(input.id, "Access token id");
|
|
1764
1774
|
const projectId = assertNonEmpty(input.projectId, "Project id");
|
|
1765
1775
|
const name = assertNonEmpty(input.name, "Access token name");
|
|
1766
|
-
const subjectId = assertNonEmpty(input.
|
|
1776
|
+
const subjectId = assertNonEmpty(input.subject.id, "Access token subject id");
|
|
1767
1777
|
const tokenHash = assertNonEmpty(input.tokenHash, "Access token hash");
|
|
1768
1778
|
assertKindMatchesSubject(input);
|
|
1769
|
-
await this.assertSubjectExists(projectId, input.
|
|
1779
|
+
await this.assertSubjectExists(projectId, input.subject.type, subjectId);
|
|
1770
1780
|
const groupIds = input.groupIds === undefined ? undefined : normalizeGroupIds(input.groupIds);
|
|
1771
1781
|
try {
|
|
1772
1782
|
const [row] = await this.sql`
|
|
@@ -1790,7 +1800,7 @@ class PgAuthAccessTokenStore {
|
|
|
1790
1800
|
${id},
|
|
1791
1801
|
${name},
|
|
1792
1802
|
${input.kind},
|
|
1793
|
-
${input.
|
|
1803
|
+
${input.subject.type},
|
|
1794
1804
|
${subjectId},
|
|
1795
1805
|
${tokenHash},
|
|
1796
1806
|
${serializeOptionalStringArray(groupIds)},
|
|
@@ -1825,13 +1835,9 @@ class PgAuthAccessTokenStore {
|
|
|
1825
1835
|
whereClauses.push(`kind = $${nextIndex++}`);
|
|
1826
1836
|
params.push(input.kind);
|
|
1827
1837
|
}
|
|
1828
|
-
if (input.
|
|
1829
|
-
whereClauses.push(`subject_type = $${nextIndex++}`);
|
|
1830
|
-
params.push(input.
|
|
1831
|
-
}
|
|
1832
|
-
if (input.subjectId) {
|
|
1833
|
-
whereClauses.push(`subject_id = $${nextIndex++}`);
|
|
1834
|
-
params.push(input.subjectId);
|
|
1838
|
+
if (input.subject) {
|
|
1839
|
+
whereClauses.push(`subject_type = $${nextIndex++}`, `subject_id = $${nextIndex++}`);
|
|
1840
|
+
params.push(input.subject.type, input.subject.id);
|
|
1835
1841
|
}
|
|
1836
1842
|
if (!input.includeRevoked) {
|
|
1837
1843
|
whereClauses.push("revoked_at IS NULL");
|
|
@@ -1919,10 +1925,10 @@ class PgAuthAccessTokenStore {
|
|
|
1919
1925
|
}
|
|
1920
1926
|
}
|
|
1921
1927
|
function assertKindMatchesSubject(input) {
|
|
1922
|
-
if (input.kind === "personal" && input.
|
|
1928
|
+
if (input.kind === "personal" && input.subject.type === "user" || input.kind === "serviceAccount" && input.subject.type === "serviceAccount") {
|
|
1923
1929
|
return;
|
|
1924
1930
|
}
|
|
1925
|
-
throw new AuthStorageError2("invalid_input", `[Sixb] Access token kind '${input.kind}' cannot target subject type '${input.
|
|
1931
|
+
throw new AuthStorageError2("invalid_input", `[Sixb] Access token kind '${input.kind}' cannot target subject type '${input.subject.type}'.`);
|
|
1926
1932
|
}
|
|
1927
1933
|
|
|
1928
1934
|
// src/auth-storage/device-authorizations.ts
|
|
@@ -1949,16 +1955,15 @@ class PgAuthDeviceAuthorizationStore {
|
|
|
1949
1955
|
try {
|
|
1950
1956
|
const [row] = await tx`
|
|
1951
1957
|
INSERT INTO auth_device_authorizations (
|
|
1952
|
-
project_id, id, device_code_hash, user_code, client_name,
|
|
1953
|
-
|
|
1958
|
+
project_id, id, device_code_hash, user_code, client_name, status, created_at,
|
|
1959
|
+
expires_at
|
|
1954
1960
|
) VALUES (
|
|
1955
1961
|
${assertNonEmpty(input.projectId, "Project id")},
|
|
1956
1962
|
${assertNonEmpty(input.id, "Device authorization id")},
|
|
1957
1963
|
${assertNonEmpty(input.deviceCodeHash, "Device code hash")},
|
|
1958
1964
|
${assertNonEmpty(input.userCode, "User code")},
|
|
1959
1965
|
${assertNonEmpty(input.clientName, "Client name")},
|
|
1960
|
-
${
|
|
1961
|
-
${input.tokenExpiresAt}, 'pending', ${input.createdAt}, ${input.expiresAt}
|
|
1966
|
+
'pending', ${input.createdAt}, ${input.expiresAt}
|
|
1962
1967
|
) RETURNING *
|
|
1963
1968
|
`;
|
|
1964
1969
|
return rowToDeviceAuthorizationRecord(row);
|
|
@@ -2700,11 +2705,53 @@ class PgAuthSessionStore {
|
|
|
2700
2705
|
}
|
|
2701
2706
|
async findValidByTokenHash(params) {
|
|
2702
2707
|
const row = await getSessionRowById(this.sql, params);
|
|
2703
|
-
if (!row || row.
|
|
2708
|
+
if (!row || row.refresh_token_hash !== null || row.audience !== params.audience || row.token_hash !== params.tokenHash || !isLiveSessionRow(row, params.now)) {
|
|
2709
|
+
return null;
|
|
2710
|
+
}
|
|
2711
|
+
return rowToSessionRecord(row);
|
|
2712
|
+
}
|
|
2713
|
+
async findValidByAccessTokenHash(params) {
|
|
2714
|
+
const row = await getSessionRowById(this.sql, params);
|
|
2715
|
+
if (!row || row.access_expires_at === null || row.token_hash !== params.tokenHash || new Date(row.access_expires_at) <= params.now || !isLiveSessionRow(row, params.now)) {
|
|
2704
2716
|
return null;
|
|
2705
2717
|
}
|
|
2706
2718
|
return rowToSessionRecord(row);
|
|
2707
2719
|
}
|
|
2720
|
+
async rotateRefreshToken(params) {
|
|
2721
|
+
return runPgTransaction(this.sql, async (tx) => {
|
|
2722
|
+
const row = await getSessionRowById(tx, params, { forUpdate: true });
|
|
2723
|
+
if (!row?.refresh_token_hash || !isLiveSessionRow(row, params.now)) {
|
|
2724
|
+
return { status: "invalid" };
|
|
2725
|
+
}
|
|
2726
|
+
const replacedRecently = row.previous_refresh_token_hash === params.refreshTokenHash && row.refreshed_at !== null && params.now.getTime() - new Date(row.refreshed_at).getTime() <= params.reuseGraceMs;
|
|
2727
|
+
if (row.refresh_token_hash !== params.refreshTokenHash && !replacedRecently) {
|
|
2728
|
+
if (row.previous_refresh_token_hash !== params.refreshTokenHash) {
|
|
2729
|
+
return { status: "invalid" };
|
|
2730
|
+
}
|
|
2731
|
+
await tx`
|
|
2732
|
+
UPDATE auth_sessions SET revoked_at = ${params.now}
|
|
2733
|
+
WHERE project_id = ${params.projectId} AND id = ${params.id}
|
|
2734
|
+
`;
|
|
2735
|
+
return { status: "reused" };
|
|
2736
|
+
}
|
|
2737
|
+
const [rotated] = await tx`
|
|
2738
|
+
UPDATE auth_sessions
|
|
2739
|
+
SET token_hash = ${params.next.tokenHash},
|
|
2740
|
+
access_expires_at = ${params.next.accessExpiresAt},
|
|
2741
|
+
previous_refresh_token_hash = refresh_token_hash,
|
|
2742
|
+
refresh_token_hash = ${params.next.refreshTokenHash},
|
|
2743
|
+
refreshed_at = ${params.now},
|
|
2744
|
+
last_seen_at = ${params.now},
|
|
2745
|
+
expires_at = GREATEST(
|
|
2746
|
+
expires_at,
|
|
2747
|
+
LEAST(${params.next.expiresAt}, COALESCE(absolute_expires_at, ${params.next.expiresAt}))
|
|
2748
|
+
)
|
|
2749
|
+
WHERE project_id = ${params.projectId} AND id = ${params.id}
|
|
2750
|
+
RETURNING *
|
|
2751
|
+
`;
|
|
2752
|
+
return rotated ? { status: "rotated", session: rowToSessionRecord(rotated) } : { status: "invalid" };
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2708
2755
|
async renewIfValid(params) {
|
|
2709
2756
|
const [row] = await this.sql`
|
|
2710
2757
|
UPDATE auth_sessions
|
|
@@ -2720,6 +2767,7 @@ class PgAuthSessionStore {
|
|
|
2720
2767
|
AND id = ${params.id}
|
|
2721
2768
|
AND audience = ${params.audience}
|
|
2722
2769
|
AND token_hash = ${params.tokenHash}
|
|
2770
|
+
AND refresh_token_hash IS NULL
|
|
2723
2771
|
AND revoked_at IS NULL
|
|
2724
2772
|
AND expires_at > ${params.now}
|
|
2725
2773
|
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.now})
|
|
@@ -2767,6 +2815,9 @@ class PgAuthSessionStore {
|
|
|
2767
2815
|
});
|
|
2768
2816
|
}
|
|
2769
2817
|
}
|
|
2818
|
+
function isLiveSessionRow(row, now) {
|
|
2819
|
+
return row.revoked_at === null && new Date(row.expires_at) > now && (row.absolute_expires_at === null || new Date(row.absolute_expires_at) > now);
|
|
2820
|
+
}
|
|
2770
2821
|
|
|
2771
2822
|
// src/auth-storage/users.ts
|
|
2772
2823
|
import { AuthStorageError as AuthStorageError10 } from "@sixb/core/storage";
|
|
@@ -2928,7 +2979,7 @@ class PgAuthStorage {
|
|
|
2928
2979
|
throw new AuthStorageError11("missing_device_authorization", `[Sixb] Device authorization '${input.id}' not found for project '${input.projectId}'.`);
|
|
2929
2980
|
}
|
|
2930
2981
|
assertCompletableDeviceAuthorization(authorization, input);
|
|
2931
|
-
const
|
|
2982
|
+
const session = await createSession(tx, input.session);
|
|
2932
2983
|
const [consumed] = await tx`
|
|
2933
2984
|
UPDATE auth_device_authorizations
|
|
2934
2985
|
SET status = 'consumed', consumed_at = ${input.completedAt}
|
|
@@ -2944,7 +2995,7 @@ class PgAuthStorage {
|
|
|
2944
2995
|
status: "consumed",
|
|
2945
2996
|
consumedAt: new Date(input.completedAt)
|
|
2946
2997
|
},
|
|
2947
|
-
|
|
2998
|
+
session
|
|
2948
2999
|
};
|
|
2949
3000
|
});
|
|
2950
3001
|
}
|
|
@@ -3004,17 +3055,6 @@ class PgAuthStorage {
|
|
|
3004
3055
|
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
|
|
3005
3056
|
};
|
|
3006
3057
|
}
|
|
3007
|
-
if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && await hasActiveUsers(tx, projectId)) {
|
|
3008
|
-
await consumeMagicLink(tx, {
|
|
3009
|
-
projectId,
|
|
3010
|
-
id: input.magicLinkId,
|
|
3011
|
-
tokenHash: input.tokenHash,
|
|
3012
|
-
consumedAt: completedAt
|
|
3013
|
-
});
|
|
3014
|
-
return {
|
|
3015
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
|
|
3016
|
-
};
|
|
3017
|
-
}
|
|
3018
3058
|
await validateCompleteSessionInput(tx, projectId, input.session);
|
|
3019
3059
|
assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience);
|
|
3020
3060
|
let newUserId;
|
|
@@ -3046,8 +3086,9 @@ class PgAuthStorage {
|
|
|
3046
3086
|
projectId,
|
|
3047
3087
|
user
|
|
3048
3088
|
});
|
|
3049
|
-
const groupMemberships = await this.
|
|
3089
|
+
const groupMemberships = await this.applyGroups(tx, {
|
|
3050
3090
|
completedAt,
|
|
3091
|
+
source: "manual",
|
|
3051
3092
|
existing: invitation.groupMemberships,
|
|
3052
3093
|
groupIds: manualGroupIds,
|
|
3053
3094
|
projectId,
|
|
@@ -3107,36 +3148,18 @@ class PgAuthStorage {
|
|
|
3107
3148
|
error: new AuthStorageError11("missing_user", `[Sixb] User '${identity.user_id}' not found for linked OIDC identity.`)
|
|
3108
3149
|
};
|
|
3109
3150
|
}
|
|
3110
|
-
if (!identity && userRow && (!input.autoLinkByVerifiedEmail || !input.emailVerified)) {
|
|
3111
|
-
await this.consumeOidcAttempt(input, completedAt, projectId, tx);
|
|
3112
|
-
return {
|
|
3113
|
-
error: new AuthStorageError11("email_link_not_allowed", `[Sixb] OIDC identity cannot auto-link to user '${userRow.id}' for project '${projectId}'.`)
|
|
3114
|
-
};
|
|
3115
|
-
}
|
|
3116
3151
|
if (userRow?.status === "suspended") {
|
|
3117
3152
|
await this.consumeOidcAttempt(input, completedAt, projectId, tx);
|
|
3118
3153
|
return {
|
|
3119
3154
|
error: new AuthStorageError11("suspended_user", `[Sixb] User '${userRow.id}' is suspended for project '${projectId}'.`)
|
|
3120
3155
|
};
|
|
3121
3156
|
}
|
|
3122
|
-
if (shouldCreateUser && !input.emailVerified) {
|
|
3123
|
-
await this.consumeOidcAttempt(input, completedAt, projectId, tx);
|
|
3124
|
-
return {
|
|
3125
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
3126
|
-
};
|
|
3127
|
-
}
|
|
3128
3157
|
if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
|
|
3129
3158
|
await this.consumeOidcAttempt(input, completedAt, projectId, tx);
|
|
3130
3159
|
return {
|
|
3131
3160
|
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
3132
3161
|
};
|
|
3133
3162
|
}
|
|
3134
|
-
if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && await hasActiveUsers(tx, projectId)) {
|
|
3135
|
-
await this.consumeOidcAttempt(input, completedAt, projectId, tx);
|
|
3136
|
-
return {
|
|
3137
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
3138
|
-
};
|
|
3139
|
-
}
|
|
3140
3163
|
await validateCompleteSessionInput(tx, projectId, input.session);
|
|
3141
3164
|
assertSignInSessionAudience(projectId, input.session.audience, attempt.audience);
|
|
3142
3165
|
let newUserId;
|
|
@@ -3179,10 +3202,17 @@ class PgAuthStorage {
|
|
|
3179
3202
|
projectId,
|
|
3180
3203
|
user
|
|
3181
3204
|
});
|
|
3182
|
-
const groupMemberships = await this.
|
|
3205
|
+
const groupMemberships = await this.syncDirectoryGroups(tx, {
|
|
3183
3206
|
completedAt,
|
|
3184
|
-
existing:
|
|
3185
|
-
|
|
3207
|
+
existing: await this.applyGroups(tx, {
|
|
3208
|
+
completedAt,
|
|
3209
|
+
source: "manual",
|
|
3210
|
+
existing: invitation.groupMemberships,
|
|
3211
|
+
groupIds: manualGroupIds,
|
|
3212
|
+
projectId,
|
|
3213
|
+
userId: user.id
|
|
3214
|
+
}),
|
|
3215
|
+
groupIds: input.directoryGroupIds,
|
|
3186
3216
|
projectId,
|
|
3187
3217
|
userId: user.id
|
|
3188
3218
|
});
|
|
@@ -3311,19 +3341,33 @@ class PgAuthStorage {
|
|
|
3311
3341
|
}
|
|
3312
3342
|
return { invitation, groupMemberships };
|
|
3313
3343
|
}
|
|
3314
|
-
async
|
|
3344
|
+
async applyGroups(sql, input) {
|
|
3315
3345
|
const groupMemberships = [...input.existing];
|
|
3316
3346
|
for (const groupId of input.groupIds) {
|
|
3317
3347
|
groupMemberships.push(await upsertGroupMembership(sql, {
|
|
3318
3348
|
projectId: input.projectId,
|
|
3319
3349
|
userId: input.userId,
|
|
3320
3350
|
groupId,
|
|
3321
|
-
source:
|
|
3351
|
+
source: input.source,
|
|
3322
3352
|
createdAt: input.completedAt
|
|
3323
3353
|
}));
|
|
3324
3354
|
}
|
|
3325
3355
|
return groupMemberships;
|
|
3326
3356
|
}
|
|
3357
|
+
async syncDirectoryGroups(sql, input) {
|
|
3358
|
+
if (!input.groupIds) {
|
|
3359
|
+
return input.existing;
|
|
3360
|
+
}
|
|
3361
|
+
const groupIds = normalizeGroupIds(input.groupIds);
|
|
3362
|
+
await sql`
|
|
3363
|
+
DELETE FROM auth_group_memberships
|
|
3364
|
+
WHERE project_id = ${input.projectId}
|
|
3365
|
+
AND user_id = ${input.userId}
|
|
3366
|
+
AND source = 'directory'
|
|
3367
|
+
${groupIds.length > 0 ? sql`AND group_id NOT IN ${sql(groupIds)}` : sql``}
|
|
3368
|
+
`;
|
|
3369
|
+
return this.applyGroups(sql, { ...input, groupIds, source: "directory" });
|
|
3370
|
+
}
|
|
3327
3371
|
async createSignInSession(sql, input) {
|
|
3328
3372
|
await lockAdvisoryKeys(sql, [
|
|
3329
3373
|
authLockKey("sessions", input.projectId, input.userId),
|
|
@@ -3380,16 +3424,6 @@ class PgAuthStorage {
|
|
|
3380
3424
|
return rowToIdentityRecord(row);
|
|
3381
3425
|
}
|
|
3382
3426
|
}
|
|
3383
|
-
async function hasActiveUsers(sql, projectId) {
|
|
3384
|
-
const rows = await sql`
|
|
3385
|
-
SELECT 1 AS active
|
|
3386
|
-
FROM auth_users
|
|
3387
|
-
WHERE project_id = ${projectId}
|
|
3388
|
-
AND status = 'active'
|
|
3389
|
-
LIMIT 1
|
|
3390
|
-
`;
|
|
3391
|
-
return rows.length > 0;
|
|
3392
|
-
}
|
|
3393
3427
|
function assertSignInSessionAudience(projectId, sessionAudience, storedAudience) {
|
|
3394
3428
|
if (sessionAudience === storedAudience) {
|
|
3395
3429
|
return;
|
|
@@ -3406,7 +3440,7 @@ function assertCompletableDeviceAuthorization(authorization, input) {
|
|
|
3406
3440
|
if (authorization.status === "denied") {
|
|
3407
3441
|
throw new AuthStorageError11("device_authorization_denied", "[Sixb] Device authorization was denied.");
|
|
3408
3442
|
}
|
|
3409
|
-
if (authorization.status !== "approved" || authorization.expiresAt <= input.completedAt || !authorization.approvedUserId || !authorization.approvedSessionId || input.
|
|
3443
|
+
if (authorization.status !== "approved" || authorization.expiresAt <= input.completedAt || !authorization.approvedUserId || !authorization.approvedSessionId || input.session.projectId !== input.projectId || input.session.userId !== authorization.approvedUserId) {
|
|
3410
3444
|
throw new AuthStorageError11("invalid_device_authorization", "[Sixb] Device authorization is invalid.");
|
|
3411
3445
|
}
|
|
3412
3446
|
}
|
|
@@ -7244,6 +7278,107 @@ var _045_agent_thread_sandbox_state_default = `ALTER TABLE agent_threads ADD COL
|
|
|
7244
7278
|
CHECK (sandbox_state IS NULL OR jsonb_typeof(sandbox_state) = 'object');
|
|
7245
7279
|
`;
|
|
7246
7280
|
|
|
7281
|
+
// src/migrations/046-workflow-intervention-principals.sql
|
|
7282
|
+
var _046_workflow_intervention_principals_default = `-- Intervention actors were stored as { principalType, principalId }. Rewrite them to the
|
|
7283
|
+
-- canonical Principal shape { type, id } used by every other stored principal.
|
|
7284
|
+
UPDATE workflow_interventions
|
|
7285
|
+
SET submitted_by = jsonb_build_object(
|
|
7286
|
+
'type', submitted_by->>'principalType',
|
|
7287
|
+
'id', submitted_by->>'principalId'
|
|
7288
|
+
)
|
|
7289
|
+
WHERE submitted_by->>'principalType' IS NOT NULL;
|
|
7290
|
+
|
|
7291
|
+
UPDATE workflow_interventions
|
|
7292
|
+
SET cancelled_by = jsonb_build_object(
|
|
7293
|
+
'type', cancelled_by->>'principalType',
|
|
7294
|
+
'id', cancelled_by->>'principalId'
|
|
7295
|
+
)
|
|
7296
|
+
WHERE cancelled_by->>'principalType' IS NOT NULL;
|
|
7297
|
+
`;
|
|
7298
|
+
|
|
7299
|
+
// src/migrations/047-ontology-commit-attribution.sql
|
|
7300
|
+
var _047_ontology_commit_attribution_default = `-- Commits and their events name who requested the write and what ran it, replacing the
|
|
7301
|
+
-- authority-only \`actor\`. Both are copied from the commit's execution, which stays canonical and
|
|
7302
|
+
-- is immutable, so the backfill is exact.
|
|
7303
|
+
ALTER TABLE ontology_commits
|
|
7304
|
+
ADD COLUMN requested_by JSONB,
|
|
7305
|
+
ADD COLUMN executor JSONB;
|
|
7306
|
+
|
|
7307
|
+
UPDATE ontology_commits c SET
|
|
7308
|
+
requested_by = CASE
|
|
7309
|
+
WHEN e.requested_by_user_id IS NOT NULL
|
|
7310
|
+
THEN jsonb_build_object('type', 'user', 'id', e.requested_by_user_id)
|
|
7311
|
+
WHEN e.requested_by_service_account_id IS NOT NULL
|
|
7312
|
+
THEN jsonb_build_object('type', 'serviceAccount', 'id', e.requested_by_service_account_id)
|
|
7313
|
+
END,
|
|
7314
|
+
executor = CASE e.executor_kind
|
|
7315
|
+
WHEN 'request' THEN jsonb_build_object('type', 'request', 'requestId', e.executor_id)
|
|
7316
|
+
WHEN 'agent' THEN jsonb_build_object('type', 'agent', 'runId', e.executor_id)
|
|
7317
|
+
WHEN 'kernel' THEN jsonb_build_object(
|
|
7318
|
+
'type', 'kernel',
|
|
7319
|
+
'operation',
|
|
7320
|
+
CASE e.authority_kernel_operation
|
|
7321
|
+
WHEN 'ontology.recover'
|
|
7322
|
+
THEN jsonb_build_object('type', 'ontology.recover', 'recoveryId', e.executor_id)
|
|
7323
|
+
ELSE jsonb_build_object('type', 'ontology.indexVectors', 'indexingId', e.executor_id)
|
|
7324
|
+
END
|
|
7325
|
+
)
|
|
7326
|
+
ELSE jsonb_build_object(
|
|
7327
|
+
'type', 'primitive',
|
|
7328
|
+
'kind', e.executor_kind,
|
|
7329
|
+
'id', e.authority_primitive_id,
|
|
7330
|
+
'runId', e.executor_id
|
|
7331
|
+
)
|
|
7332
|
+
END
|
|
7333
|
+
FROM executions e
|
|
7334
|
+
WHERE e.project_id = c.project_id AND e.id = c.execution_id;
|
|
7335
|
+
|
|
7336
|
+
ALTER TABLE ontology_commits ALTER COLUMN executor SET NOT NULL;
|
|
7337
|
+
|
|
7338
|
+
-- Events not yet published would otherwise reach consumers in the retired shape.
|
|
7339
|
+
UPDATE ontology_outbox o SET envelope =
|
|
7340
|
+
(o.envelope - 'actor')
|
|
7341
|
+
|| jsonb_build_object('executor', c.executor)
|
|
7342
|
+
|| CASE
|
|
7343
|
+
WHEN c.requested_by IS NULL THEN '{}'::jsonb
|
|
7344
|
+
ELSE jsonb_build_object('requestedBy', c.requested_by)
|
|
7345
|
+
END
|
|
7346
|
+
FROM ontology_commits c
|
|
7347
|
+
WHERE c.project_id = o.project_id AND c.id = o.commit_id;
|
|
7348
|
+
|
|
7349
|
+
ALTER TABLE ontology_commits DROP COLUMN actor;
|
|
7350
|
+
`;
|
|
7351
|
+
|
|
7352
|
+
// src/migrations/049-native-sessions.sql
|
|
7353
|
+
var _049_native_sessions_default = `-- Native clients, such as the CLI, hold their session as bearer tokens: a
|
|
7354
|
+
-- short-lived access token, whose hash is \`token_hash\`, and a rotating refresh token. Browser
|
|
7355
|
+
-- sessions leave these columns NULL; \`refresh_token_hash IS NULL\` is what marks a cookie session.
|
|
7356
|
+
ALTER TABLE auth_sessions
|
|
7357
|
+
ADD COLUMN client_name TEXT,
|
|
7358
|
+
ADD COLUMN access_expires_at TIMESTAMPTZ,
|
|
7359
|
+
ADD COLUMN refresh_token_hash TEXT,
|
|
7360
|
+
ADD COLUMN previous_refresh_token_hash TEXT,
|
|
7361
|
+
ADD COLUMN refreshed_at TIMESTAMPTZ,
|
|
7362
|
+
ADD CONSTRAINT auth_sessions_bearer_state CHECK (
|
|
7363
|
+
(refresh_token_hash IS NULL) = (client_name IS NULL)
|
|
7364
|
+
AND (refresh_token_hash IS NULL) = (access_expires_at IS NULL)
|
|
7365
|
+
AND (refresh_token_hash IS NOT NULL OR previous_refresh_token_hash IS NULL)
|
|
7366
|
+
);
|
|
7367
|
+
|
|
7368
|
+
-- The device flow now starts a session instead of issuing a personal access token.
|
|
7369
|
+
ALTER TABLE auth_device_authorizations
|
|
7370
|
+
DROP COLUMN token_name,
|
|
7371
|
+
DROP COLUMN token_expires_at;
|
|
7372
|
+
`;
|
|
7373
|
+
|
|
7374
|
+
// src/migrations/050-directory-group-memberships.sql
|
|
7375
|
+
var _050_directory_group_memberships_default = `-- A membership the identity provider owns is recorded with source 'directory' and synced to the
|
|
7376
|
+
-- provider's groups on every sign-in. Service accounts never sign in, so their table is unchanged.
|
|
7377
|
+
ALTER TABLE auth_group_memberships DROP CONSTRAINT auth_group_memberships_source_check;
|
|
7378
|
+
ALTER TABLE auth_group_memberships ADD CONSTRAINT auth_group_memberships_source_check
|
|
7379
|
+
CHECK (source IN ('invitation', 'manual', 'agent', 'directory'));
|
|
7380
|
+
`;
|
|
7381
|
+
|
|
7247
7382
|
// src/migrations/037-execution-requester-groups.sql
|
|
7248
7383
|
var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids JSONB NOT NULL DEFAULT '[]'::jsonb;
|
|
7249
7384
|
|
|
@@ -7282,6 +7417,43 @@ ALTER TABLE connector_authorization_attempts
|
|
|
7282
7417
|
ALTER COLUMN code_verifier DROP NOT NULL;
|
|
7283
7418
|
`;
|
|
7284
7419
|
|
|
7420
|
+
// src/migrations/048-file-upload-sessions.sql
|
|
7421
|
+
var _048_file_upload_sessions_default = `-- Durable staged-upload sessions, so an upload survives restarts and spans API replicas.
|
|
7422
|
+
CREATE TABLE file_upload_sessions (
|
|
7423
|
+
id TEXT PRIMARY KEY CHECK (length(trim(id)) > 0),
|
|
7424
|
+
project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
|
|
7425
|
+
owner_principal_type TEXT NOT NULL
|
|
7426
|
+
CHECK (owner_principal_type IN ('user', 'serviceAccount', 'system')),
|
|
7427
|
+
owner_principal_id TEXT NOT NULL CHECK (length(owner_principal_id) > 0),
|
|
7428
|
+
strategy TEXT NOT NULL CHECK (strategy IN ('server', 'direct-put', 'multipart')),
|
|
7429
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'aborted')),
|
|
7430
|
+
file_name TEXT,
|
|
7431
|
+
media_type TEXT,
|
|
7432
|
+
logical_path TEXT,
|
|
7433
|
+
expected_size_bytes BIGINT CHECK (expected_size_bytes IS NULL OR expected_size_bytes >= 0),
|
|
7434
|
+
expected_digest TEXT,
|
|
7435
|
+
provider_upload JSONB CHECK (provider_upload IS NULL OR jsonb_typeof(provider_upload) = 'object'),
|
|
7436
|
+
signed_parts JSONB NOT NULL CHECK (jsonb_typeof(signed_parts) = 'array'),
|
|
7437
|
+
file_ref JSONB CHECK (file_ref IS NULL OR jsonb_typeof(file_ref) = 'object'),
|
|
7438
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
7439
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
7440
|
+
completed_at TIMESTAMPTZ,
|
|
7441
|
+
aborted_at TIMESTAMPTZ,
|
|
7442
|
+
-- Derived by core (fileUploadSessionReapAt); NULL while an abandoned upload awaits its abort.
|
|
7443
|
+
reap_at TIMESTAMPTZ,
|
|
7444
|
+
CHECK ((status = 'completed') = (completed_at IS NOT NULL)),
|
|
7445
|
+
CHECK ((status = 'aborted') = (aborted_at IS NOT NULL))
|
|
7446
|
+
);
|
|
7447
|
+
|
|
7448
|
+
CREATE INDEX idx_file_upload_sessions_reap
|
|
7449
|
+
ON file_upload_sessions (reap_at)
|
|
7450
|
+
WHERE reap_at IS NOT NULL;
|
|
7451
|
+
|
|
7452
|
+
CREATE INDEX idx_file_upload_sessions_abandoned
|
|
7453
|
+
ON file_upload_sessions (project_id, expires_at)
|
|
7454
|
+
WHERE status = 'pending' AND provider_upload IS NOT NULL;
|
|
7455
|
+
`;
|
|
7456
|
+
|
|
7285
7457
|
// src/migrations.ts
|
|
7286
7458
|
var POSTGRES_STORAGE_ADAPTER_ID = "SixbPostgresStorage";
|
|
7287
7459
|
function pgSql(id, sqlText) {
|
|
@@ -7501,7 +7673,12 @@ var postgresStorageMigrations = defineMigrations({
|
|
|
7501
7673
|
pgSql("042-vector-profiles", _042_vector_profiles_default),
|
|
7502
7674
|
pgSql("043-vector-indexing", _043_vector_indexing_default),
|
|
7503
7675
|
pgSql("044-vector-batching", _044_vector_batching_default),
|
|
7504
|
-
pgSql("045-agent-thread-sandbox-state", _045_agent_thread_sandbox_state_default)
|
|
7676
|
+
pgSql("045-agent-thread-sandbox-state", _045_agent_thread_sandbox_state_default),
|
|
7677
|
+
pgSql("046-workflow-intervention-principals", _046_workflow_intervention_principals_default),
|
|
7678
|
+
pgSql("047-ontology-commit-attribution", _047_ontology_commit_attribution_default),
|
|
7679
|
+
pgSql("048-file-upload-sessions", _048_file_upload_sessions_default),
|
|
7680
|
+
pgSql("049-native-sessions", _049_native_sessions_default),
|
|
7681
|
+
pgSql("050-directory-group-memberships", _050_directory_group_memberships_default)
|
|
7505
7682
|
]
|
|
7506
7683
|
});
|
|
7507
7684
|
|
|
@@ -9625,7 +9802,8 @@ var PG_OBJECT_QUERY_CAPABILITIES = {
|
|
|
9625
9802
|
double: { equality: true, ordering: true },
|
|
9626
9803
|
decimal: { equality: true, ordering: true },
|
|
9627
9804
|
date: { equality: true, ordering: true },
|
|
9628
|
-
timestamp: { equality: true, ordering: true }
|
|
9805
|
+
timestamp: { equality: true, ordering: true },
|
|
9806
|
+
userRef: { equality: true }
|
|
9629
9807
|
},
|
|
9630
9808
|
limits: {
|
|
9631
9809
|
totalCount: true,
|
|
@@ -9828,9 +10006,10 @@ function commitRecord(row) {
|
|
|
9828
10006
|
requestHash: row.request_hash,
|
|
9829
10007
|
executionId: row.execution_id,
|
|
9830
10008
|
origin: structuredClone(row.origin),
|
|
9831
|
-
...row.
|
|
9832
|
-
|
|
10009
|
+
...row.requested_by === null ? {} : {
|
|
10010
|
+
requestedBy: structuredClone(row.requested_by)
|
|
9833
10011
|
},
|
|
10012
|
+
executor: structuredClone(row.executor),
|
|
9834
10013
|
ontologyRevision: row.ontology_revision,
|
|
9835
10014
|
...row.projection_revision === null ? {} : { projectionRevision: row.projection_revision },
|
|
9836
10015
|
...row.ownership_hash === null ? {} : { ownershipHash: row.ownership_hash },
|
|
@@ -9979,6 +10158,9 @@ import {
|
|
|
9979
10158
|
telemetryPointSortKey as telemetryPointSortKey2
|
|
9980
10159
|
} from "@sixb/core/internal/materialization";
|
|
9981
10160
|
import {
|
|
10161
|
+
assertExpectedLinkRevision,
|
|
10162
|
+
assertExpectedLinkScopeRevision,
|
|
10163
|
+
assertExpectedObjectRevision,
|
|
9982
10164
|
assertMaterializationFinalizationCorrelation,
|
|
9983
10165
|
assertMaterializationHeader,
|
|
9984
10166
|
assertMaterializationLaneCompletion,
|
|
@@ -11915,17 +12097,15 @@ class PgOntologyMaterializationStorage {
|
|
|
11915
12097
|
await this.assertSources(input.expected.sources, input.commit.projectId);
|
|
11916
12098
|
const objectRevisions = await reader.effectiveObjectRevisions(input.expected.objects.map((expected) => expected.ref), true);
|
|
11917
12099
|
for (const expected of input.expected.objects) {
|
|
11918
|
-
|
|
12100
|
+
assertExpectedObjectRevision(objectRevisions.get(objectRefKey3(expected.ref)) ?? null, expected);
|
|
11919
12101
|
}
|
|
11920
12102
|
const linkRevisions = await reader.effectiveLinkLastCommits(input.expected.links.map((expected) => expected.ref), true);
|
|
11921
12103
|
for (const expected of input.expected.links) {
|
|
11922
|
-
|
|
12104
|
+
assertExpectedLinkRevision(linkRevisions.get(linkRefKey3(expected.ref)), expected);
|
|
11923
12105
|
}
|
|
11924
12106
|
const linkScopeRevisions = await reader.linkScopeRevisions(input.expected.linkScopes);
|
|
11925
12107
|
for (const [index, expected] of input.expected.linkScopes.entries()) {
|
|
11926
|
-
|
|
11927
|
-
throw new MaterializationConflictError6("effective-state", `Expected link scope changed for ${expected.source.objectTypeId}:${expected.source.primaryId}.${expected.linkId}.`);
|
|
11928
|
-
}
|
|
12108
|
+
assertExpectedLinkScopeRevision(linkScopeRevisions[index]?.fingerprint, expected);
|
|
11929
12109
|
}
|
|
11930
12110
|
const points = await reader.exactPoints(input.expected.points, true);
|
|
11931
12111
|
const pointRevisions = new Map(points.map((point) => [telemetryPointKey3(point.series, point.at), point.lastCommitId]));
|
|
@@ -12185,28 +12365,6 @@ class PgOntologyMaterializationStorage {
|
|
|
12185
12365
|
throw new MaterializationConflictError6("projection-fence", `Source '${expected.source.projectionId}' changed.`);
|
|
12186
12366
|
}
|
|
12187
12367
|
}
|
|
12188
|
-
assertObject(row, expected) {
|
|
12189
|
-
if (!expected.exists) {
|
|
12190
|
-
if (row) {
|
|
12191
|
-
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} to be absent.`);
|
|
12192
|
-
}
|
|
12193
|
-
return;
|
|
12194
|
-
}
|
|
12195
|
-
if (!row || row.version !== expected.version || row.lastCommitId !== expected.lastCommitId) {
|
|
12196
|
-
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} changed.`);
|
|
12197
|
-
}
|
|
12198
|
-
}
|
|
12199
|
-
assertLink(lastCommitId, expected) {
|
|
12200
|
-
if (!expected.exists) {
|
|
12201
|
-
if (lastCommitId !== undefined) {
|
|
12202
|
-
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} to be absent.`);
|
|
12203
|
-
}
|
|
12204
|
-
return;
|
|
12205
|
-
}
|
|
12206
|
-
if (lastCommitId === undefined || lastCommitId !== expected.lastCommitId) {
|
|
12207
|
-
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} changed.`);
|
|
12208
|
-
}
|
|
12209
|
-
}
|
|
12210
12368
|
async assertFinalization(session, input) {
|
|
12211
12369
|
const { commit } = session.header;
|
|
12212
12370
|
const { result } = input.finalization;
|
|
@@ -12442,18 +12600,19 @@ class PgOntologyMaterializationStorage {
|
|
|
12442
12600
|
async insertCommit(header, input) {
|
|
12443
12601
|
const { commit } = header;
|
|
12444
12602
|
const origin = originColumns(commit.origin);
|
|
12445
|
-
const
|
|
12603
|
+
const requestedBy = commit.requestedBy === undefined ? null : jsonParameter2(this.sql, commit.requestedBy);
|
|
12446
12604
|
const rows = await this.sql`
|
|
12447
12605
|
INSERT INTO ontology_commits (
|
|
12448
12606
|
project_id, id, idempotency_key, request_hash, execution_id,
|
|
12449
|
-
origin_kind, origin_run_id, origin_batch_ordinal, origin,
|
|
12607
|
+
origin_kind, origin_run_id, origin_batch_ordinal, origin, requested_by, executor,
|
|
12450
12608
|
ontology_revision, projection_revision, ownership_hash,
|
|
12451
12609
|
intent, result, committed_at
|
|
12452
12610
|
) VALUES (
|
|
12453
12611
|
${commit.projectId}, ${commit.id}, ${commit.idempotencyKey}, ${commit.requestHash},
|
|
12454
12612
|
${commit.executionId},
|
|
12455
12613
|
${origin.kind}, ${origin.runId}, ${origin.batchOrdinal},
|
|
12456
|
-
${jsonParameter2(this.sql, commit.origin)}, ${
|
|
12614
|
+
${jsonParameter2(this.sql, commit.origin)}, ${requestedBy},
|
|
12615
|
+
${jsonParameter2(this.sql, commit.executor)}, ${commit.ontologyRevision},
|
|
12457
12616
|
${commit.projectionRevision ?? null}, ${commit.ownershipHash ?? null},
|
|
12458
12617
|
${jsonParameter2(this.sql, commit.intent)},
|
|
12459
12618
|
${jsonParameter2(this.sql, input.finalization.result)}, ${commit.committedAt}
|
|
@@ -15547,6 +15706,132 @@ function toStorageRow(row) {
|
|
|
15547
15706
|
};
|
|
15548
15707
|
}
|
|
15549
15708
|
|
|
15709
|
+
// src/pg-file-upload-session-storage.ts
|
|
15710
|
+
import {
|
|
15711
|
+
DurableFileUploadSessions,
|
|
15712
|
+
fileUploadSessionReapAt,
|
|
15713
|
+
parseFileUploadSessionRow
|
|
15714
|
+
} from "@sixb/core/internal/file-upload-session-storage-provider";
|
|
15715
|
+
class PgFileUploadSessionStorage extends DurableFileUploadSessions {
|
|
15716
|
+
constructor(sql) {
|
|
15717
|
+
super(new PgFileUploadSessionBackend(sql));
|
|
15718
|
+
}
|
|
15719
|
+
}
|
|
15720
|
+
|
|
15721
|
+
class PgFileUploadSessionBackend {
|
|
15722
|
+
sql;
|
|
15723
|
+
constructor(sql) {
|
|
15724
|
+
this.sql = sql;
|
|
15725
|
+
}
|
|
15726
|
+
read(run) {
|
|
15727
|
+
return run(new PgFileUploadSessionPersistence(this.sql, false));
|
|
15728
|
+
}
|
|
15729
|
+
transaction(run) {
|
|
15730
|
+
return runPgTransaction(this.sql, (tx) => run(new PgFileUploadSessionPersistence(tx, true)));
|
|
15731
|
+
}
|
|
15732
|
+
}
|
|
15733
|
+
|
|
15734
|
+
class PgFileUploadSessionPersistence {
|
|
15735
|
+
sql;
|
|
15736
|
+
locking;
|
|
15737
|
+
constructor(sql, locking) {
|
|
15738
|
+
this.sql = sql;
|
|
15739
|
+
this.locking = locking;
|
|
15740
|
+
}
|
|
15741
|
+
async now() {
|
|
15742
|
+
const [row] = await this.sql`SELECT clock_timestamp() AS now`;
|
|
15743
|
+
if (!row)
|
|
15744
|
+
throw new Error("[SixbPg] Database clock returned no row.");
|
|
15745
|
+
return new Date(row.now);
|
|
15746
|
+
}
|
|
15747
|
+
async insert(session) {
|
|
15748
|
+
const rows = await this.sql`
|
|
15749
|
+
INSERT INTO file_upload_sessions (
|
|
15750
|
+
id, project_id, owner_principal_type, owner_principal_id, strategy, status, file_name,
|
|
15751
|
+
media_type, logical_path, expected_size_bytes, expected_digest, provider_upload,
|
|
15752
|
+
signed_parts, file_ref, created_at, expires_at, completed_at, aborted_at, reap_at
|
|
15753
|
+
) VALUES (
|
|
15754
|
+
${session.id}, ${session.projectId}, ${session.principal.type}, ${session.principal.id},
|
|
15755
|
+
${session.strategy}, ${session.status}, ${session.fileName ?? null}, ${session.mediaType ?? null},
|
|
15756
|
+
${session.logicalPath ?? null}, ${session.expectedSizeBytes ?? null},
|
|
15757
|
+
${session.expectedDigest ?? null}, ${jsonText(session.providerUpload)}::text::jsonb,
|
|
15758
|
+
${JSON.stringify(session.signedParts)}::text::jsonb, ${jsonText(session.fileRef)}::text::jsonb,
|
|
15759
|
+
${session.createdAt}, ${session.expiresAt}, ${session.completedAt ?? null},
|
|
15760
|
+
${session.abortedAt ?? null}, ${fileUploadSessionReapAt(session)}
|
|
15761
|
+
)
|
|
15762
|
+
ON CONFLICT (id) DO NOTHING
|
|
15763
|
+
RETURNING id
|
|
15764
|
+
`;
|
|
15765
|
+
return rows.length === 1;
|
|
15766
|
+
}
|
|
15767
|
+
async get(uploadId) {
|
|
15768
|
+
const [row] = this.locking ? await this.sql`
|
|
15769
|
+
SELECT * FROM file_upload_sessions WHERE id = ${uploadId} FOR UPDATE
|
|
15770
|
+
` : await this.sql`
|
|
15771
|
+
SELECT * FROM file_upload_sessions WHERE id = ${uploadId}
|
|
15772
|
+
`;
|
|
15773
|
+
return row ? sessionFromRow(row) : null;
|
|
15774
|
+
}
|
|
15775
|
+
async update(session) {
|
|
15776
|
+
await this.sql`
|
|
15777
|
+
UPDATE file_upload_sessions
|
|
15778
|
+
SET status = ${session.status},
|
|
15779
|
+
provider_upload = ${jsonText(session.providerUpload)}::text::jsonb,
|
|
15780
|
+
signed_parts = ${JSON.stringify(session.signedParts)}::text::jsonb,
|
|
15781
|
+
file_ref = ${jsonText(session.fileRef)}::text::jsonb,
|
|
15782
|
+
created_at = ${session.createdAt},
|
|
15783
|
+
expires_at = ${session.expiresAt},
|
|
15784
|
+
completed_at = ${session.completedAt ?? null},
|
|
15785
|
+
aborted_at = ${session.abortedAt ?? null},
|
|
15786
|
+
reap_at = ${fileUploadSessionReapAt(session)}
|
|
15787
|
+
WHERE id = ${session.id}
|
|
15788
|
+
`;
|
|
15789
|
+
}
|
|
15790
|
+
async listAbandoned(input) {
|
|
15791
|
+
const rows = await this.sql`
|
|
15792
|
+
SELECT * FROM file_upload_sessions
|
|
15793
|
+
WHERE project_id = ${input.projectId} AND status = 'pending'
|
|
15794
|
+
AND provider_upload IS NOT NULL AND expires_at <= ${input.now}
|
|
15795
|
+
ORDER BY expires_at, id COLLATE "C"
|
|
15796
|
+
LIMIT ${input.limit}
|
|
15797
|
+
`;
|
|
15798
|
+
return rows.map(sessionFromRow);
|
|
15799
|
+
}
|
|
15800
|
+
async deleteReapable(now) {
|
|
15801
|
+
const rows = await this.sql`
|
|
15802
|
+
DELETE FROM file_upload_sessions
|
|
15803
|
+
WHERE reap_at IS NOT NULL AND reap_at <= ${now}
|
|
15804
|
+
RETURNING id
|
|
15805
|
+
`;
|
|
15806
|
+
return rows.length;
|
|
15807
|
+
}
|
|
15808
|
+
}
|
|
15809
|
+
function sessionFromRow(row) {
|
|
15810
|
+
return parseFileUploadSessionRow({
|
|
15811
|
+
id: row.id,
|
|
15812
|
+
projectId: row.project_id,
|
|
15813
|
+
ownerPrincipalType: row.owner_principal_type,
|
|
15814
|
+
ownerPrincipalId: row.owner_principal_id,
|
|
15815
|
+
strategy: row.strategy,
|
|
15816
|
+
status: row.status,
|
|
15817
|
+
fileName: row.file_name,
|
|
15818
|
+
mediaType: row.media_type,
|
|
15819
|
+
logicalPath: row.logical_path,
|
|
15820
|
+
expectedSizeBytes: row.expected_size_bytes,
|
|
15821
|
+
expectedDigest: row.expected_digest,
|
|
15822
|
+
providerUpload: row.provider_upload,
|
|
15823
|
+
signedParts: row.signed_parts,
|
|
15824
|
+
fileRef: row.file_ref,
|
|
15825
|
+
createdAt: row.created_at,
|
|
15826
|
+
expiresAt: row.expires_at,
|
|
15827
|
+
completedAt: row.completed_at,
|
|
15828
|
+
abortedAt: row.aborted_at
|
|
15829
|
+
});
|
|
15830
|
+
}
|
|
15831
|
+
function jsonText(value) {
|
|
15832
|
+
return value === undefined ? null : JSON.stringify(value);
|
|
15833
|
+
}
|
|
15834
|
+
|
|
15550
15835
|
// src/pg-pipeline-run-storage.ts
|
|
15551
15836
|
import { parseSixbFailure as parseSixbFailure4, serializeSixbFailure as serializeSixbFailure3 } from "@sixb/core/internal/errors";
|
|
15552
15837
|
import { assertPipelineRunExecution } from "@sixb/core/internal/pipeline-run-storage-provider";
|
|
@@ -17218,7 +17503,7 @@ class PgWorkflowInterventionStorage {
|
|
|
17218
17503
|
SET
|
|
17219
17504
|
status = ${"submitted"},
|
|
17220
17505
|
submitted_at = ${input.submittedAt ?? new Date},
|
|
17221
|
-
submitted_by = ${
|
|
17506
|
+
submitted_by = ${serializePrincipal(input.submittedBy)}::text::jsonb,
|
|
17222
17507
|
response = ${serializeRecord(input.response)}::text::jsonb
|
|
17223
17508
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
17224
17509
|
RETURNING *
|
|
@@ -17234,7 +17519,7 @@ class PgWorkflowInterventionStorage {
|
|
|
17234
17519
|
SET
|
|
17235
17520
|
status = ${"cancelled"},
|
|
17236
17521
|
cancelled_at = ${input.cancelledAt ?? new Date},
|
|
17237
|
-
cancelled_by = ${
|
|
17522
|
+
cancelled_by = ${serializePrincipal(input.cancelledBy)}::text::jsonb
|
|
17238
17523
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
17239
17524
|
RETURNING *
|
|
17240
17525
|
`;
|
|
@@ -17361,10 +17646,10 @@ function serializeRecord(value) {
|
|
|
17361
17646
|
function parseRecord(value) {
|
|
17362
17647
|
return typeof value === "string" ? JSON.parse(value) : value;
|
|
17363
17648
|
}
|
|
17364
|
-
function
|
|
17365
|
-
return
|
|
17649
|
+
function serializePrincipal(principal) {
|
|
17650
|
+
return principal ? JSON.stringify(principal) : null;
|
|
17366
17651
|
}
|
|
17367
|
-
function
|
|
17652
|
+
function parsePrincipal(value) {
|
|
17368
17653
|
return typeof value === "string" ? JSON.parse(value) : value ?? undefined;
|
|
17369
17654
|
}
|
|
17370
17655
|
function rowToWorkflowInterventionRecord(row) {
|
|
@@ -17384,10 +17669,10 @@ function rowToWorkflowInterventionRecord(row) {
|
|
|
17384
17669
|
requestedAt: new Date(row.requested_at),
|
|
17385
17670
|
expiresAt: row.expires_at ? new Date(row.expires_at) : undefined,
|
|
17386
17671
|
submittedAt: row.submitted_at ? new Date(row.submitted_at) : undefined,
|
|
17387
|
-
submittedBy:
|
|
17672
|
+
submittedBy: parsePrincipal(row.submitted_by),
|
|
17388
17673
|
response: row.response ? parseRecord(row.response) : undefined,
|
|
17389
17674
|
cancelledAt: row.cancelled_at ? new Date(row.cancelled_at) : undefined,
|
|
17390
|
-
cancelledBy:
|
|
17675
|
+
cancelledBy: parsePrincipal(row.cancelled_by),
|
|
17391
17676
|
expiredAt: row.expired_at ? new Date(row.expired_at) : undefined
|
|
17392
17677
|
};
|
|
17393
17678
|
}
|
|
@@ -18455,6 +18740,7 @@ class PostgresStorage {
|
|
|
18455
18740
|
connectorConnections;
|
|
18456
18741
|
shareGrants;
|
|
18457
18742
|
shareSessions;
|
|
18743
|
+
fileUploadSessions;
|
|
18458
18744
|
migrators;
|
|
18459
18745
|
sql;
|
|
18460
18746
|
schemaName;
|
|
@@ -18508,6 +18794,7 @@ class PostgresStorage {
|
|
|
18508
18794
|
this.connectorConnections = createOperationScopedFacade(stores.connectorConnections, scope);
|
|
18509
18795
|
this.shareGrants = createOperationScopedFacade(stores.shareGrants, scope);
|
|
18510
18796
|
this.shareSessions = createOperationScopedFacade(stores.shareSessions, scope);
|
|
18797
|
+
this.fileUploadSessions = createOperationScopedFacade(stores.fileUploadSessions, scope);
|
|
18511
18798
|
registerPostgresStorageTestingAdapter(this, (durationMs) => stores.connectorConnections.advanceTimeForTesting(durationMs));
|
|
18512
18799
|
}
|
|
18513
18800
|
async ping() {
|
|
@@ -18602,7 +18889,8 @@ function createPostgresStores(sql, options) {
|
|
|
18602
18889
|
rules: new PgRulesStorage(sql),
|
|
18603
18890
|
connectorConnections: new PgConnectorConnectionStorage(sql),
|
|
18604
18891
|
shareGrants,
|
|
18605
|
-
shareSessions
|
|
18892
|
+
shareSessions,
|
|
18893
|
+
fileUploadSessions: new PgFileUploadSessionStorage(sql)
|
|
18606
18894
|
};
|
|
18607
18895
|
}
|
|
18608
18896
|
function resolveTimeoutMillis(value, label) {
|
|
@@ -18619,4 +18907,4 @@ export {
|
|
|
18619
18907
|
migratePostgresStorage
|
|
18620
18908
|
};
|
|
18621
18909
|
|
|
18622
|
-
//# debugId=
|
|
18910
|
+
//# debugId=5427C0EF9727207C64756E2164756E21
|