Skip to content

Commit 8abde2d

Browse files
msukkariclaudegithub-actions[bot]
authored
feat(web): add GET /api/ee/user endpoint for owner user info (#940)
* feat(web): add GET /api/ee/user endpoint for owner user info Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add CHANGELOG entry for GET /api/ee/user endpoint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(web): add audit trail to GET /api/ee/user handler Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com> * fix(web): accept userId query param in GET /api/ee/user Look up the specified user by ID instead of returning the authenticated user's own info. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(web): fix audit target to use userId param and destructure user Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add org-management entitlement to ee/user and ee/users APIs Adds a new "org-management" entitlement to both enterprise plan tiers and gates the GET /api/ee/user and GET /api/ee/users endpoints behind it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com>
1 parent b42dc33 commit 8abde2d

File tree

4 files changed

+80
-3
lines changed

4 files changed

+80
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Added `wa_user_created` PostHog event fired on successful user sign-up. [#933](https://github.com/sourcebot-dev/sourcebot/pull/933)
1515
- Added `wa_askgh_login_wall_prompted` PostHog event fired when an unauthenticated user attempts to ask a question on Ask GitHub. [#933](https://github.com/sourcebot-dev/sourcebot/pull/933)
1616
- Added Bitbucket Server (Data Center) OAuth 2.0 SSO identity provider support (`provider: "bitbucket-server"`). [#934](https://github.com/sourcebot-dev/sourcebot/pull/934)
17+
- Added `GET /api/ee/user` endpoint that returns the authenticated owner's user info (name, email, createdAt, updatedAt). [#940](https://github.com/sourcebot-dev/sourcebot/pull/940)
1718
- Added `selectedReposCount` to the `wa_chat_message_sent` PostHog event to track the number of selected repositories when users ask questions. [#941](https://github.com/sourcebot-dev/sourcebot/pull/941)
1819

1920
### Changed

packages/shared/src/entitlements.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const entitlements = [
3939
"analytics",
4040
"permission-syncing",
4141
"github-app",
42-
"chat-sharing"
42+
"chat-sharing",
43+
"org-management"
4344
] as const;
4445
export type Entitlement = (typeof entitlements)[number];
4546

@@ -56,6 +57,7 @@ const entitlementsByPlan: Record<Plan, Entitlement[]> = {
5657
"permission-syncing",
5758
"github-app",
5859
"chat-sharing",
60+
"org-management",
5961
],
6062
"self-hosted:enterprise-unlimited": [
6163
"anonymous-access",
@@ -67,6 +69,7 @@ const entitlementsByPlan: Record<Plan, Entitlement[]> = {
6769
"permission-syncing",
6870
"github-app",
6971
"chat-sharing",
72+
"org-management",
7073
],
7174
} as const;
7275

packages/web/src/app/api/(server)/ee/user/route.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,76 @@ import { serviceErrorResponse, missingQueryParam, notFound } from "@/lib/service
77
import { isServiceError } from "@/lib/utils";
88
import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2";
99
import { OrgRole } from "@sourcebot/db";
10-
import { createLogger } from "@sourcebot/shared";
10+
import { createLogger, hasEntitlement } from "@sourcebot/shared";
1111
import { StatusCodes } from "http-status-codes";
1212
import { NextRequest } from "next/server";
1313

1414
const logger = createLogger('ee-user-api');
1515
const auditService = getAuditService();
1616

17+
export const GET = apiHandler(async (request: NextRequest) => {
18+
if (!hasEntitlement('org-management')) {
19+
return serviceErrorResponse({
20+
statusCode: StatusCodes.FORBIDDEN,
21+
errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS,
22+
message: "Organization management is not enabled for your license",
23+
});
24+
}
25+
26+
const url = new URL(request.url);
27+
const userId = url.searchParams.get('userId');
28+
29+
if (!userId) {
30+
return serviceErrorResponse(missingQueryParam('userId'));
31+
}
32+
33+
const result = await withAuthV2(async ({ org, role, user, prisma }) => {
34+
return withMinimumOrgRole(role, OrgRole.OWNER, async () => {
35+
try {
36+
const userData = await prisma.user.findUnique({
37+
where: {
38+
id: userId,
39+
},
40+
select: {
41+
name: true,
42+
email: true,
43+
createdAt: true,
44+
updatedAt: true,
45+
},
46+
});
47+
48+
if (!userData) {
49+
return notFound('User not found');
50+
}
51+
52+
await auditService.createAudit({
53+
action: "user.read",
54+
actor: {
55+
id: user.id,
56+
type: "user"
57+
},
58+
target: {
59+
id: userId,
60+
type: "user"
61+
},
62+
orgId: org.id,
63+
});
64+
65+
return userData;
66+
} catch (error) {
67+
logger.error('Error fetching user info', { error, userId });
68+
throw error;
69+
}
70+
});
71+
});
72+
73+
if (isServiceError(result)) {
74+
return serviceErrorResponse(result);
75+
}
76+
77+
return Response.json(result, { status: StatusCodes.OK });
78+
});
79+
1780
export const DELETE = apiHandler(async (request: NextRequest) => {
1881
const url = new URL(request.url);
1982
const userId = url.searchParams.get('userId');

packages/web/src/app/api/(server)/ee/users/route.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@ import { serviceErrorResponse } from "@/lib/serviceError";
66
import { isServiceError } from "@/lib/utils";
77
import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2";
88
import { OrgRole } from "@sourcebot/db";
9-
import { createLogger } from "@sourcebot/shared";
9+
import { createLogger, hasEntitlement } from "@sourcebot/shared";
10+
import { StatusCodes } from "http-status-codes";
11+
import { ErrorCode } from "@/lib/errorCodes";
1012

1113
const logger = createLogger('ee-users-api');
1214
const auditService = getAuditService();
1315

1416
export const GET = apiHandler(async () => {
17+
if (!hasEntitlement('org-management')) {
18+
return serviceErrorResponse({
19+
statusCode: StatusCodes.FORBIDDEN,
20+
errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS,
21+
message: "Organization management is not enabled for your license",
22+
});
23+
}
24+
1525
const result = await withAuthV2(async ({ prisma, org, role, user }) => {
1626
return withMinimumOrgRole(role, OrgRole.OWNER, async () => {
1727
try {

0 commit comments

Comments
 (0)