mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
fix: require reauthorization after owner removal
Deactivate surviving grants and shared connections whose credentials belonged to a suspended or archived member so dependent runs fail closed with an actionable reauthorization state. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
5ae50e9a71
commit
63ddbd43da
2 files changed
+32
-4
No files matched your search
@@ -275,6 +275,8 @@ describeEmbeddedPostgres("access service", () => {
|
||||
transport: "mcp_remote",
|
||||
authKind: "oauth",
|
||||
credentialPolicy: "per_user",
|
||||
status: "active",
|
||||
enabled: true,
|
||||
}).returning().then((rows) => rows[0]!);
|
||||
const definition = await db.insert(userSecretDefinitions).values({
|
||||
companyId: company.id,
|
||||
@@ -329,9 +331,19 @@ describeEmbeddedPostgres("access service", () => {
|
||||
expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, grant.id)))
|
||||
.toEqual([expect.objectContaining({ status: "revoked", credentialSecretRefs: [] })]);
|
||||
expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, organizationGrant.id)))
|
||||
.toEqual([expect.objectContaining({ credentialSecretRefs: [] })]);
|
||||
.toEqual([expect.objectContaining({
|
||||
status: "needs_reauthorization",
|
||||
isDefault: false,
|
||||
credentialSecretRefs: [],
|
||||
})]);
|
||||
expect(await db.select().from(toolConnections).where(eq(toolConnections.id, connection.id)))
|
||||
.toEqual([expect.objectContaining({ credentialSecretRefs: [] })]);
|
||||
.toEqual([expect.objectContaining({
|
||||
status: "draft",
|
||||
enabled: false,
|
||||
healthStatus: "missing_secret",
|
||||
lastError: "oauth_reauthorization_required",
|
||||
credentialSecretRefs: [],
|
||||
})]);
|
||||
expect(await db.select().from(toolAccessAuditEvents).where(eq(toolAccessAuditEvents.reasonCode, "membership_removed")))
|
||||
.toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -87,6 +87,7 @@ export function accessService(db: Db) {
|
||||
const [grantRefs, connectionRefs] = await Promise.all([
|
||||
tx.select({
|
||||
id: connectionGrants.id,
|
||||
status: connectionGrants.status,
|
||||
credentialSecretRefs: connectionGrants.credentialSecretRefs,
|
||||
}).from(connectionGrants).where(eq(connectionGrants.companyId, companyId)),
|
||||
tx.select({
|
||||
@@ -99,7 +100,14 @@ export function accessService(db: Db) {
|
||||
(ref) => !removedSecretIds.has(ref.secretId),
|
||||
);
|
||||
if (credentialSecretRefs.length !== grant.credentialSecretRefs.length) {
|
||||
await tx.update(connectionGrants).set({ credentialSecretRefs, updatedAt: now })
|
||||
await tx.update(connectionGrants).set({
|
||||
credentialSecretRefs,
|
||||
...(grant.status === "revoked" ? {} : {
|
||||
status: "needs_reauthorization" as const,
|
||||
isDefault: false,
|
||||
}),
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq(connectionGrants.id, grant.id));
|
||||
}
|
||||
}
|
||||
@@ -108,7 +116,15 @@ export function accessService(db: Db) {
|
||||
(ref) => !removedSecretIds.has(ref.secretId),
|
||||
);
|
||||
if (credentialSecretRefs.length !== connection.credentialSecretRefs.length) {
|
||||
await tx.update(toolConnections).set({ credentialSecretRefs, updatedAt: now })
|
||||
await tx.update(toolConnections).set({
|
||||
credentialSecretRefs,
|
||||
status: "draft",
|
||||
enabled: false,
|
||||
healthStatus: "missing_secret",
|
||||
healthMessage: "Personal credential owner no longer has company access. Reauthorize this connection.",
|
||||
lastError: "oauth_reauthorization_required",
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq(toolConnections.id, connection.id));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user