Skip to content

Commit df31a0f

Browse files
stubbiclaude
andcommitted
feat: auto-approve human join requests on invite acceptance
When a user accepts a human invite link, they are now automatically approved and added as a company member. The manual approval step was designed for agent onboarding where review is needed — for human invites, the inviter already made the decision by sharing the link. Agent join requests still require manual approval as before. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 40b3451 commit df31a0f

1 file changed

Lines changed: 42 additions & 5 deletions

File tree

server/src/routes/access.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,7 +2358,7 @@ export function accessRoutes(
23582358

23592359
const actorEmail =
23602360
requestType === "human" ? await resolveActorEmail(db, req) : null;
2361-
const created = !inviteAlreadyAccepted
2361+
let created = !inviteAlreadyAccepted
23622362
? await db.transaction(async (tx) => {
23632363
await tx
23642364
.update(invites)
@@ -2530,6 +2530,40 @@ export function accessRoutes(
25302530
}
25312531
}
25322532

2533+
// Auto-approve human join requests — the inviter already made the decision.
2534+
let autoApproved = false;
2535+
if (requestType === "human" && !inviteAlreadyAccepted && created.requestingUserId) {
2536+
await access.ensureMembership(
2537+
companyId,
2538+
"user",
2539+
created.requestingUserId,
2540+
"member",
2541+
"active"
2542+
);
2543+
const grants = grantsFromDefaults(
2544+
invite.defaultsPayload as Record<string, unknown> | null,
2545+
"human"
2546+
);
2547+
await access.setPrincipalGrants(
2548+
companyId,
2549+
"user",
2550+
created.requestingUserId,
2551+
grants,
2552+
invite.invitedByUserId ?? null
2553+
);
2554+
await db
2555+
.update(joinRequests)
2556+
.set({
2557+
status: "approved",
2558+
approvedByUserId: invite.invitedByUserId ?? "system",
2559+
approvedAt: new Date(),
2560+
updatedAt: new Date()
2561+
})
2562+
.where(eq(joinRequests.id, created.id));
2563+
created = { ...created, status: "approved" as const, approvedAt: new Date() };
2564+
autoApproved = true;
2565+
}
2566+
25332567
await logActivity(db, {
25342568
companyId,
25352569
actorType: req.actor.type === "agent" ? "agent" : "user",
@@ -2538,15 +2572,18 @@ export function accessRoutes(
25382572
? req.actor.agentId ?? "invite-agent"
25392573
: req.actor.userId ??
25402574
(requestType === "agent" ? "invite-anon" : "board"),
2541-
action: inviteAlreadyAccepted
2542-
? "join.request_replayed"
2543-
: "join.requested",
2575+
action: autoApproved
2576+
? "join.auto_approved"
2577+
: inviteAlreadyAccepted
2578+
? "join.request_replayed"
2579+
: "join.requested",
25442580
entityType: "join_request",
25452581
entityId: created.id,
25462582
details: {
25472583
requestType,
25482584
requestIp: created.requestIp,
2549-
inviteReplay: inviteAlreadyAccepted
2585+
inviteReplay: inviteAlreadyAccepted,
2586+
autoApproved
25502587
}
25512588
});
25522589

0 commit comments

Comments
 (0)