mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-14 21:35:25 +00:00
Some checks failed
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Has been cancelled
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Has been cancelled
Update Roadmap Released / update (push) Has been cancelled
Publish to ghcr / merge (push) Has been cancelled
* generate protobuf types * stream poc over SSE * wip: make stream search api follow existing schema. Modify UI to support streaming * fix scrolling issue * Dockerfile * wip on lezer parser grammar for query language * add lezer tree -> grpc transformer * remove spammy log message * fix syntax highlighting by adding a module resolution for @lezer/common * further wip on query language * Add case sensitivity and regexp toggles * Improved type safety / cleanup for query lang * support search contexts * update Dockerfile with query langauge package * fix filter * Add skeletons to filter panel when search is streaming * add client side caching * improved cancelation handling * add isSearchExausted flag for flagging when a search captured all results * Add back posthog search_finished event * remove zoekt tenant enforcement * migrate blocking search over to grpc. Centralize everything in searchApi * branch handling * plumb file weburl * add repo_sets filter for repositories a user has access to * refactor a bunch of stuff + add support for passing in Query IR to search api * refactor * dev README * wip on better error handling * error handling for stream path * update mcp * changelog wip * type fix * style * Support rev:* wildcard * changelog * changelog nit * feedback * fix build * update docs and remove uneeded test file
207 lines
5.4 KiB
TypeScript
207 lines
5.4 KiB
TypeScript
import { prisma as __unsafePrisma, userScopedPrismaClientExtension } from "@/prisma";
|
|
import { hashSecret } from "@sourcebot/shared";
|
|
import { ApiKey, Org, OrgRole, PrismaClient, UserWithAccounts } from "@sourcebot/db";
|
|
import { headers } from "next/headers";
|
|
import { auth } from "./auth";
|
|
import { notAuthenticated, notFound, ServiceError } from "./lib/serviceError";
|
|
import { SINGLE_TENANT_ORG_ID } from "./lib/constants";
|
|
import { StatusCodes } from "http-status-codes";
|
|
import { ErrorCode } from "./lib/errorCodes";
|
|
import { getOrgMetadata, isServiceError } from "./lib/utils";
|
|
import { hasEntitlement } from "@sourcebot/shared";
|
|
|
|
interface OptionalAuthContext {
|
|
user?: UserWithAccounts;
|
|
org: Org;
|
|
role: OrgRole;
|
|
prisma: PrismaClient;
|
|
}
|
|
|
|
interface RequiredAuthContext {
|
|
user: UserWithAccounts;
|
|
org: Org;
|
|
role: Exclude<OrgRole, 'GUEST'>;
|
|
prisma: PrismaClient;
|
|
}
|
|
|
|
export const withAuthV2 = async <T>(fn: (params: RequiredAuthContext) => Promise<T>) => {
|
|
const authContext = await getAuthContext();
|
|
|
|
if (isServiceError(authContext)) {
|
|
return authContext;
|
|
}
|
|
|
|
const { user, org, role, prisma } = authContext;
|
|
|
|
if (!user || role === OrgRole.GUEST) {
|
|
return notAuthenticated();
|
|
}
|
|
|
|
return fn({ user, org, role, prisma });
|
|
};
|
|
|
|
export const withOptionalAuthV2 = async <T>(fn: (params: OptionalAuthContext) => Promise<T>) => {
|
|
const authContext = await getAuthContext();
|
|
if (isServiceError(authContext)) {
|
|
return authContext;
|
|
}
|
|
|
|
const { user, org, role, prisma } = authContext;
|
|
|
|
const hasAnonymousAccessEntitlement = hasEntitlement("anonymous-access");
|
|
const orgMetadata = getOrgMetadata(org);
|
|
|
|
if (
|
|
(
|
|
!user ||
|
|
role === OrgRole.GUEST
|
|
) && (
|
|
!hasAnonymousAccessEntitlement ||
|
|
!orgMetadata?.anonymousAccessEnabled
|
|
)
|
|
) {
|
|
return notAuthenticated();
|
|
}
|
|
|
|
return fn({ user, org, role, prisma });
|
|
};
|
|
|
|
export const getAuthContext = async (): Promise<OptionalAuthContext | ServiceError> => {
|
|
const user = await getAuthenticatedUser();
|
|
|
|
const org = await __unsafePrisma.org.findUnique({
|
|
where: {
|
|
id: SINGLE_TENANT_ORG_ID,
|
|
}
|
|
});
|
|
|
|
if (!org) {
|
|
return notFound("Organization not found");
|
|
}
|
|
|
|
const membership = user ? await __unsafePrisma.userToOrg.findUnique({
|
|
where: {
|
|
orgId_userId: {
|
|
orgId: org.id,
|
|
userId: user.id,
|
|
},
|
|
},
|
|
}) : null;
|
|
|
|
const prisma = __unsafePrisma.$extends(userScopedPrismaClientExtension(user)) as PrismaClient;
|
|
|
|
return {
|
|
user: user ?? undefined,
|
|
org,
|
|
role: membership?.role ?? OrgRole.GUEST,
|
|
prisma,
|
|
};
|
|
};
|
|
|
|
export const getAuthenticatedUser = async () => {
|
|
// First, check if we have a valid JWT session.
|
|
const session = await auth();
|
|
if (session) {
|
|
const userId = session.user.id;
|
|
const user = await __unsafePrisma.user.findUnique({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
include: {
|
|
accounts: true,
|
|
}
|
|
});
|
|
|
|
return user ?? undefined;
|
|
}
|
|
|
|
// If not, check if we have a valid API key.
|
|
const apiKeyString = (await headers()).get("X-Sourcebot-Api-Key") ?? undefined;
|
|
if (apiKeyString) {
|
|
const apiKey = await getVerifiedApiObject(apiKeyString);
|
|
if (!apiKey) {
|
|
return undefined;
|
|
}
|
|
|
|
// Attempt to find the user associated with this api key.
|
|
const user = await __unsafePrisma.user.findUnique({
|
|
where: {
|
|
id: apiKey.createdById,
|
|
},
|
|
include: {
|
|
accounts: true,
|
|
}
|
|
});
|
|
|
|
if (!user) {
|
|
return undefined;
|
|
}
|
|
|
|
// Update the last used at timestamp for this api key.
|
|
await __unsafePrisma.apiKey.update({
|
|
where: {
|
|
hash: apiKey.hash,
|
|
},
|
|
data: {
|
|
lastUsedAt: new Date(),
|
|
},
|
|
});
|
|
|
|
return user;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Returns a API key object if the API key string is valid, otherwise returns undefined.
|
|
*/
|
|
const getVerifiedApiObject = async (apiKeyString: string): Promise<ApiKey | undefined> => {
|
|
const parts = apiKeyString.split("-");
|
|
if (parts.length !== 2 || parts[0] !== "sourcebot") {
|
|
return undefined;
|
|
}
|
|
|
|
const hash = hashSecret(parts[1]);
|
|
const apiKey = await __unsafePrisma.apiKey.findUnique({
|
|
where: {
|
|
hash,
|
|
},
|
|
});
|
|
|
|
if (!apiKey) {
|
|
return undefined;
|
|
}
|
|
|
|
return apiKey;
|
|
}
|
|
|
|
export const withMinimumOrgRole = async <T>(
|
|
userRole: OrgRole,
|
|
minRequiredRole: OrgRole = OrgRole.MEMBER,
|
|
fn: () => Promise<T>,
|
|
): Promise<T | ServiceError> => {
|
|
|
|
const getAuthorizationPrecedence = (role: OrgRole): number => {
|
|
switch (role) {
|
|
case OrgRole.GUEST:
|
|
return 0;
|
|
case OrgRole.MEMBER:
|
|
return 1;
|
|
case OrgRole.OWNER:
|
|
return 2;
|
|
}
|
|
};
|
|
|
|
if (
|
|
getAuthorizationPrecedence(userRole) < getAuthorizationPrecedence(minRequiredRole)
|
|
) {
|
|
return {
|
|
statusCode: StatusCodes.FORBIDDEN,
|
|
errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS,
|
|
message: "You do not have sufficient permissions to perform this action.",
|
|
} satisfies ServiceError;
|
|
}
|
|
|
|
return fn();
|
|
}
|