diff --git a/CHANGELOG.md b/CHANGELOG.md index 954efefe0..1262b27db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `wa_user_created` PostHog event fired on successful user sign-up. [#933](https://github.com/sourcebot-dev/sourcebot/pull/933) - 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) - Added Bitbucket Server (Data Center) OAuth 2.0 SSO identity provider support (`provider: "bitbucket-server"`). [#934](https://github.com/sourcebot-dev/sourcebot/pull/934) +- 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) - 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) ### Changed diff --git a/packages/shared/src/entitlements.ts b/packages/shared/src/entitlements.ts index 4d66fa2cb..18b2ae6b4 100644 --- a/packages/shared/src/entitlements.ts +++ b/packages/shared/src/entitlements.ts @@ -39,7 +39,8 @@ const entitlements = [ "analytics", "permission-syncing", "github-app", - "chat-sharing" + "chat-sharing", + "org-management" ] as const; export type Entitlement = (typeof entitlements)[number]; @@ -56,6 +57,7 @@ const entitlementsByPlan: Record = { "permission-syncing", "github-app", "chat-sharing", + "org-management", ], "self-hosted:enterprise-unlimited": [ "anonymous-access", @@ -67,6 +69,7 @@ const entitlementsByPlan: Record = { "permission-syncing", "github-app", "chat-sharing", + "org-management", ], } as const; diff --git a/packages/web/src/app/api/(server)/ee/user/route.ts b/packages/web/src/app/api/(server)/ee/user/route.ts index 8ca6ee807..79f3018c6 100644 --- a/packages/web/src/app/api/(server)/ee/user/route.ts +++ b/packages/web/src/app/api/(server)/ee/user/route.ts @@ -7,13 +7,76 @@ import { serviceErrorResponse, missingQueryParam, notFound } from "@/lib/service import { isServiceError } from "@/lib/utils"; import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2"; import { OrgRole } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/shared"; +import { createLogger, hasEntitlement } from "@sourcebot/shared"; import { StatusCodes } from "http-status-codes"; import { NextRequest } from "next/server"; const logger = createLogger('ee-user-api'); const auditService = getAuditService(); +export const GET = apiHandler(async (request: NextRequest) => { + if (!hasEntitlement('org-management')) { + return serviceErrorResponse({ + statusCode: StatusCodes.FORBIDDEN, + errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS, + message: "Organization management is not enabled for your license", + }); + } + + const url = new URL(request.url); + const userId = url.searchParams.get('userId'); + + if (!userId) { + return serviceErrorResponse(missingQueryParam('userId')); + } + + const result = await withAuthV2(async ({ org, role, user, prisma }) => { + return withMinimumOrgRole(role, OrgRole.OWNER, async () => { + try { + const userData = await prisma.user.findUnique({ + where: { + id: userId, + }, + select: { + name: true, + email: true, + createdAt: true, + updatedAt: true, + }, + }); + + if (!userData) { + return notFound('User not found'); + } + + await auditService.createAudit({ + action: "user.read", + actor: { + id: user.id, + type: "user" + }, + target: { + id: userId, + type: "user" + }, + orgId: org.id, + }); + + return userData; + } catch (error) { + logger.error('Error fetching user info', { error, userId }); + throw error; + } + }); + }); + + if (isServiceError(result)) { + return serviceErrorResponse(result); + } + + return Response.json(result, { status: StatusCodes.OK }); +}); + export const DELETE = apiHandler(async (request: NextRequest) => { const url = new URL(request.url); const userId = url.searchParams.get('userId'); diff --git a/packages/web/src/app/api/(server)/ee/users/route.ts b/packages/web/src/app/api/(server)/ee/users/route.ts index 02c74d968..cc9da236f 100644 --- a/packages/web/src/app/api/(server)/ee/users/route.ts +++ b/packages/web/src/app/api/(server)/ee/users/route.ts @@ -6,12 +6,22 @@ import { serviceErrorResponse } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2"; import { OrgRole } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/shared"; +import { createLogger, hasEntitlement } from "@sourcebot/shared"; +import { StatusCodes } from "http-status-codes"; +import { ErrorCode } from "@/lib/errorCodes"; const logger = createLogger('ee-users-api'); const auditService = getAuditService(); export const GET = apiHandler(async () => { + if (!hasEntitlement('org-management')) { + return serviceErrorResponse({ + statusCode: StatusCodes.FORBIDDEN, + errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS, + message: "Organization management is not enabled for your license", + }); + } + const result = await withAuthV2(async ({ prisma, org, role, user }) => { return withMinimumOrgRole(role, OrgRole.OWNER, async () => { try {