-
Notifications
You must be signed in to change notification settings - Fork 309
Expand file tree
/
Copy pathactions.ts
More file actions
163 lines (142 loc) · 5.18 KB
/
Copy pathactions.ts
File metadata and controls
163 lines (142 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
'use server';
import { sew } from "@/middleware/sew";
import { ErrorCode } from "@/lib/errorCodes";
import { notFound, ServiceError } from "@/lib/serviceError";
import { withAuth } from "@/middleware/withAuth";
import { withMinimumOrgRole } from "@/middleware/withMinimumOrgRole";
import { OrgRole, Prisma } from "@sourcebot/db";
import { StatusCodes } from "http-status-codes";
export const removeMemberFromOrg = async (memberId: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
withAuth(async ({ org, role, prisma }) =>
withMinimumOrgRole(role, OrgRole.OWNER, async () => {
const guardError = await prisma.$transaction(async (tx) => {
const targetMember = await tx.userToOrg.findUnique({
where: {
orgId_userId: {
orgId: org.id,
userId: memberId,
}
}
});
if (!targetMember) {
return notFound("Member not found in this organization");
}
if (targetMember.role === OrgRole.OWNER) {
const ownerCount = await tx.userToOrg.count({
where: {
orgId: org.id,
role: OrgRole.OWNER,
},
});
if (ownerCount <= 1) {
return {
statusCode: StatusCodes.FORBIDDEN,
errorCode: ErrorCode.LAST_OWNER_CANNOT_BE_REMOVED,
message: "Cannot remove the last owner of the organization.",
} satisfies ServiceError;
}
}
await invalidateAllSessionsForUser(tx, memberId);
await revokeUserOAuthTokens(tx, memberId);
await revokeUserApiKeysInOrg(tx, memberId, org.id);
await tx.userToOrg.delete({
where: {
orgId_userId: {
orgId: org.id,
userId: memberId,
}
}
});
return null;
}, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
if (guardError) {
return guardError;
}
return { success: true };
}))
);
export const leaveOrg = async (): Promise<{ success: boolean } | ServiceError> => sew(() =>
withAuth(async ({ user, org, role, prisma }) => {
const guardError = await prisma.$transaction(async (tx) => {
if (role === OrgRole.OWNER) {
const ownerCount = await tx.userToOrg.count({
where: {
orgId: org.id,
role: OrgRole.OWNER,
},
});
if (ownerCount <= 1) {
return {
statusCode: StatusCodes.FORBIDDEN,
errorCode: ErrorCode.LAST_OWNER_CANNOT_BE_REMOVED,
message: "You are the last owner of this organization. Promote another member to owner before leaving.",
} satisfies ServiceError;
}
}
await invalidateAllSessionsForUser(tx, user.id);
await revokeUserOAuthTokens(tx, user.id);
await revokeUserApiKeysInOrg(tx, user.id, org.id);
await tx.userToOrg.delete({
where: {
orgId_userId: {
orgId: org.id,
userId: user.id,
}
}
});
return null;
}, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
if (guardError) {
return guardError;
}
return {
success: true,
}
}));
/**
* Invalidates every active JWT cookie for the given user by incrementing
* their `sessionVersion`. The next request from any of their active
* sessions will compare the cookie's baked-in version against the
* (now-bumped) value on the User row, fail, and be treated as logged out.
*/
const invalidateAllSessionsForUser = async (
prisma: Prisma.TransactionClient,
userId: string,
): Promise<void> => {
await prisma.user.update({
where: { id: userId },
data: { sessionVersion: { increment: 1 } },
});
};
const revokeUserApiKeysInOrg = async (
prisma: Prisma.TransactionClient,
userId: string,
orgId: number,
): Promise<void> => {
await prisma.apiKey.deleteMany({
where: {
createdById: userId,
orgId,
}
});
};
const revokeUserOAuthTokens = async (
prisma: Prisma.TransactionClient,
userId: string,
): Promise<void> => {
await prisma.oAuthToken.deleteMany({
where: {
userId
}
});
await prisma.oAuthRefreshToken.deleteMany({
where: {
userId
}
});
await prisma.oAuthAuthorizationCode.deleteMany({
where: {
userId
}
});
};