Skip to content

Commit e71de80

Browse files
feedback
1 parent 3c87196 commit e71de80

File tree

9 files changed

+193
-230
lines changed

9 files changed

+193
-230
lines changed

docs/docs/configuration/audit-logs.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,6 @@ curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
122122
| `chat.shared_with_users` | `user` | `chat` |
123123
| `chat.unshared_with_user` | `user` | `chat` |
124124
| `chat.visibility_updated` | `user` | `chat` |
125-
| `org.ownership_transfer_failed` | `user` | `org` |
126-
| `org.ownership_transferred` | `user` | `org` |
127125
| `user.created_ask_chat` | `user` | `org` |
128126
| `user.creation_failed` | `user` | `user` |
129127
| `user.delete` | `user` | `user` |
@@ -144,8 +142,6 @@ curl --request GET '$SOURCEBOT_URL/api/ee/audit' \
144142
| `user.read` | `user` | `user` |
145143
| `user.signed_in` | `user` | `user` |
146144
| `user.signed_out` | `user` | `user` |
147-
| `org.ownership_transfer_failed` | `user` | `org` |
148-
| `org.ownership_transferred` | `user` | `org` |
149145
| `org.member_promoted_to_owner` | `user` | `user` |
150146
| `org.owner_demoted_to_member` | `user` | `user` |
151147

docs/docs/configuration/auth/roles-and-permissions.mdx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,30 @@ import LicenseKeyRequired from '/snippets/license-key-required.mdx'
2121

2222
Organizations support multiple owners, allowing you to share administrative responsibilities across your team. Owners can promote members to owner and demote other owners back to member from **Settings -> Members**.
2323

24+
<Frame>
25+
<img src="/images/managing_owners.png" alt="Members settings page showing team members and their roles" />
26+
</Frame>
27+
2428
### Promoting a member to owner
2529

2630
To promote a member, click the action menu (three dots) next to their name in the members list and select **Promote to owner**. The member will immediately gain full administrative access.
2731

32+
<Frame>
33+
<img src="/images/promote_to_owner.png" alt="Dropdown menu showing Promote to owner option for a member" />
34+
</Frame>
35+
2836
### Demoting an owner to member
2937

3038
To demote an owner, click the action menu next to their name and select **Demote to member**. Owners can also demote themselves to step down from the role. The last remaining owner of an organization cannot be demoted — at least one owner must exist at all times.
3139

40+
<Frame>
41+
<img src="/images/demote_to_member.png" alt="Dropdown menu showing Demote to member option for an owner" />
42+
</Frame>
43+
3244
### Leaving an organization as an owner
3345

34-
An owner can leave the organization as long as at least one other owner exists. If you are the last owner, you must promote another member to owner before leaving.
46+
An owner can leave the organization as long as at least one other owner exists. If you are the last owner, you must promote another member to owner before leaving.
47+
48+
<Frame>
49+
<img src="/images/owner_leave_org.png" alt="Dropdown menu showing Leave organization option for an owner" />
50+
</Frame>

docs/images/demote_to_member.png

56.7 KB
Loading

docs/images/managing_owners.png

256 KB
Loading

docs/images/owner_leave_org.png

60.5 KB
Loading

docs/images/promote_to_owner.png

55.3 KB
Loading

packages/web/src/actions.ts

Lines changed: 1 addition & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { createTransport } from "nodemailer";
2222
import { Octokit } from "octokit";
2323
import { auth } from "./auth";
2424
import { getOrgFromDomain } from "./data/org";
25-
import { decrementOrgSeatCount, getSubscriptionForOrg } from "./ee/features/billing/serverUtils";
25+
import { getSubscriptionForOrg } from "./ee/features/billing/serverUtils";
2626
import { IS_BILLING_ENABLED } from "./ee/features/billing/stripe";
2727
import InviteUserEmail from "./emails/inviteUserEmail";
2828
import JoinRequestApprovedEmail from "./emails/joinRequestApprovedEmail";
@@ -1150,107 +1150,6 @@ export const getInviteInfo = async (inviteId: string) => sew(() =>
11501150
}
11511151
}));
11521152

1153-
/**
1154-
* @deprecated Use `promoteToOwner` and `demoteToMember` from `@/ee/features/userManagement/actions` instead.
1155-
* This function atomically promotes a member to OWNER and demotes the caller to MEMBER.
1156-
* In a multi-owner model, use promoteToOwner (and optionally leaveOrg) for equivalent behavior.
1157-
*/
1158-
export const transferOwnership = async (newOwnerId: string, domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
1159-
withAuth((userId) =>
1160-
withOrgMembership(userId, domain, async ({ org }) => {
1161-
const currentUserId = userId;
1162-
1163-
const failAuditCallback = async (error: string) => {
1164-
await auditService.createAudit({
1165-
action: "org.ownership_transfer_failed",
1166-
actor: {
1167-
id: currentUserId,
1168-
type: "user"
1169-
},
1170-
target: {
1171-
id: org.id.toString(),
1172-
type: "org"
1173-
},
1174-
orgId: org.id,
1175-
metadata: {
1176-
message: error
1177-
}
1178-
})
1179-
}
1180-
if (newOwnerId === currentUserId) {
1181-
await failAuditCallback("User is already the owner of this org");
1182-
return {
1183-
statusCode: StatusCodes.BAD_REQUEST,
1184-
errorCode: ErrorCode.INVALID_REQUEST_BODY,
1185-
message: "You're already the owner of this org",
1186-
} satisfies ServiceError;
1187-
}
1188-
1189-
const newOwner = await prisma.userToOrg.findUnique({
1190-
where: {
1191-
orgId_userId: {
1192-
userId: newOwnerId,
1193-
orgId: org.id,
1194-
},
1195-
},
1196-
});
1197-
1198-
if (!newOwner) {
1199-
await failAuditCallback("The user you're trying to make the owner doesn't exist");
1200-
return {
1201-
statusCode: StatusCodes.BAD_REQUEST,
1202-
errorCode: ErrorCode.INVALID_REQUEST_BODY,
1203-
message: "The user you're trying to make the owner doesn't exist",
1204-
} satisfies ServiceError;
1205-
}
1206-
1207-
await prisma.$transaction([
1208-
prisma.userToOrg.update({
1209-
where: {
1210-
orgId_userId: {
1211-
userId: newOwnerId,
1212-
orgId: org.id,
1213-
},
1214-
},
1215-
data: {
1216-
role: "OWNER",
1217-
}
1218-
}),
1219-
prisma.userToOrg.update({
1220-
where: {
1221-
orgId_userId: {
1222-
userId: currentUserId,
1223-
orgId: org.id,
1224-
},
1225-
},
1226-
data: {
1227-
role: "MEMBER",
1228-
}
1229-
})
1230-
]);
1231-
1232-
await auditService.createAudit({
1233-
action: "org.ownership_transferred",
1234-
actor: {
1235-
id: currentUserId,
1236-
type: "user"
1237-
},
1238-
target: {
1239-
id: org.id.toString(),
1240-
type: "org"
1241-
},
1242-
orgId: org.id,
1243-
metadata: {
1244-
message: `Ownership transferred from ${currentUserId} to ${newOwnerId}`
1245-
}
1246-
});
1247-
1248-
return {
1249-
success: true,
1250-
}
1251-
}, /* minRequiredRole = */ OrgRole.OWNER)
1252-
));
1253-
12541153
export const checkIfOrgDomainExists = async (domain: string): Promise<boolean | ServiceError> => sew(() =>
12551154
withAuth(async () => {
12561155
const org = await prisma.org.findFirst({
@@ -1262,89 +1161,6 @@ export const checkIfOrgDomainExists = async (domain: string): Promise<boolean |
12621161
return !!org;
12631162
}));
12641163

1265-
export const removeMemberFromOrg = async (memberId: string, domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
1266-
withAuth(async (userId) =>
1267-
withOrgMembership(userId, domain, async ({ org }) => {
1268-
const targetMember = await prisma.userToOrg.findUnique({
1269-
where: {
1270-
orgId_userId: {
1271-
orgId: org.id,
1272-
userId: memberId,
1273-
}
1274-
}
1275-
});
1276-
1277-
if (!targetMember) {
1278-
return notFound();
1279-
}
1280-
1281-
await prisma.$transaction(async (tx) => {
1282-
await tx.userToOrg.delete({
1283-
where: {
1284-
orgId_userId: {
1285-
orgId: org.id,
1286-
userId: memberId,
1287-
}
1288-
}
1289-
});
1290-
1291-
if (IS_BILLING_ENABLED) {
1292-
const result = await decrementOrgSeatCount(org.id, tx);
1293-
if (isServiceError(result)) {
1294-
throw result;
1295-
}
1296-
}
1297-
});
1298-
1299-
return {
1300-
success: true,
1301-
}
1302-
}, /* minRequiredRole = */ OrgRole.OWNER)
1303-
));
1304-
1305-
export const leaveOrg = async (domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
1306-
withAuth(async (userId) =>
1307-
withOrgMembership(userId, domain, async ({ org, userRole }) => {
1308-
if (userRole === OrgRole.OWNER) {
1309-
const ownerCount = await prisma.userToOrg.count({
1310-
where: {
1311-
orgId: org.id,
1312-
role: OrgRole.OWNER,
1313-
},
1314-
});
1315-
1316-
if (ownerCount <= 1) {
1317-
return {
1318-
statusCode: StatusCodes.FORBIDDEN,
1319-
errorCode: ErrorCode.OWNER_CANNOT_LEAVE_ORG,
1320-
message: "You are the last owner of this organization. Promote another member to owner before leaving.",
1321-
} satisfies ServiceError;
1322-
}
1323-
}
1324-
1325-
await prisma.$transaction(async (tx) => {
1326-
await tx.userToOrg.delete({
1327-
where: {
1328-
orgId_userId: {
1329-
orgId: org.id,
1330-
userId: userId,
1331-
}
1332-
}
1333-
});
1334-
1335-
if (IS_BILLING_ENABLED) {
1336-
const result = await decrementOrgSeatCount(org.id, tx);
1337-
if (isServiceError(result)) {
1338-
throw result;
1339-
}
1340-
}
1341-
});
1342-
1343-
return {
1344-
success: true,
1345-
}
1346-
})
1347-
));
13481164

13491165
export const getOrgMembers = async (domain: string) => sew(() =>
13501166
withAuth(async (userId) =>

0 commit comments

Comments
 (0)