Authentication (#164)

This commit is contained in:
Brendan Kellam 2025-01-16 15:24:13 -08:00 committed by GitHub
parent 7029aa70c1
commit 6cf10b4988
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 634 additions and 132 deletions

View file

@ -1,6 +1,7 @@
{ {
"recommendations": [ "recommendations": [
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss" "bradlc.vscode-tailwindcss",
"prisma.prisma"
] ]
} }

View file

@ -10,6 +10,14 @@ RUN go mod download
COPY vendor/zoekt ./ COPY vendor/zoekt ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /cmd/ ./cmd/... RUN CGO_ENABLED=0 GOOS=linux go build -o /cmd/ ./cmd/...
# ------ Build Database ------
FROM node-alpine AS database-builder
WORKDIR /app
COPY package.json yarn.lock* ./
COPY ./packages/db ./packages/db
RUN yarn workspace @sourcebot/db install --frozen-lockfile
# ------ Build Web ------ # ------ Build Web ------
FROM node-alpine AS web-builder FROM node-alpine AS web-builder
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
@ -17,6 +25,8 @@ WORKDIR /app
COPY package.json yarn.lock* ./ COPY package.json yarn.lock* ./
COPY ./packages/web ./packages/web COPY ./packages/web ./packages/web
COPY --from=database-builder /app/node_modules ./node_modules
COPY --from=database-builder /app/packages/db ./packages/db
# Fixes arm64 timeouts # Fixes arm64 timeouts
RUN yarn config set registry https://registry.npmjs.org/ RUN yarn config set registry https://registry.npmjs.org/
@ -27,18 +37,16 @@ ENV NEXT_TELEMETRY_DISABLED=1
ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
# why exactly this was happening, but it's likely due to a bad replacement happening
# in the `sed` command.
# @note: leading "/" is required for the basePath property. @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath # @note: leading "/" is required for the basePath property. @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
ARG NEXT_PUBLIC_DOMAIN_SUB_PATH=/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH # ARG NEXT_PUBLIC_DOMAIN_SUB_PATH=/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH
RUN yarn workspace @sourcebot/web build RUN yarn workspace @sourcebot/web build
# ------ Build Database ------
FROM node-alpine AS database-builder
WORKDIR /app
COPY package.json yarn.lock* ./
COPY ./packages/db ./packages/db
RUN yarn workspace @sourcebot/db install --frozen-lockfile
# ------ Build Backend ------ # ------ Build Backend ------
FROM node-alpine AS backend-builder FROM node-alpine AS backend-builder

View file

@ -107,46 +107,50 @@ echo -e "\e[34m[Info] Using config file at: '$CONFIG_PATH'.\e[0m"
done done
} }
# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# Update specifically NEXT_PUBLIC_DOMAIN_SUB_PATH w/o requiring a rebuild. # causing regular expressions parsing errors when making a request. It's unclear
# Ultimately, the DOMAIN_SUB_PATH sets the `basePath` param in the next.config.mjs. # why exactly this was happening, but it's likely due to a bad replacement happening
# Similar to above, we pass in a `BAKED_` sentinal value into next.config.mjs at build # in the `sed` command.
# time. Unlike above, the `basePath` configuration is set in files other than just javascript
# code (e.g., manifest files, css files, etc.), so this section has subtle differences.
# #
# @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath # # Update specifically NEXT_PUBLIC_DOMAIN_SUB_PATH w/o requiring a rebuild.
# @see: https://phase.dev/blog/nextjs-public-runtime-variables/ # # Ultimately, the DOMAIN_SUB_PATH sets the `basePath` param in the next.config.mjs.
{ # # Similar to above, we pass in a `BAKED_` sentinal value into next.config.mjs at build
if [ ! -z "$DOMAIN_SUB_PATH" ]; then # # time. Unlike above, the `basePath` configuration is set in files other than just javascript
# If the sub-path is "/", this creates problems with certain replacements. For example: # # code (e.g., manifest files, css files, etc.), so this section has subtle differences.
# /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> //_next/image (notice the double slash...) # #
# To get around this, we default to an empty sub-path, which is the default when no sub-path is defined. # # @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
if [ "$DOMAIN_SUB_PATH" = "/" ]; then # # @see: https://phase.dev/blog/nextjs-public-runtime-variables/
DOMAIN_SUB_PATH="" # {
# if [ ! -z "$DOMAIN_SUB_PATH" ]; then
# # If the sub-path is "/", this creates problems with certain replacements. For example:
# # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> //_next/image (notice the double slash...)
# # To get around this, we default to an empty sub-path, which is the default when no sub-path is defined.
# if [ "$DOMAIN_SUB_PATH" = "/" ]; then
# DOMAIN_SUB_PATH=""
# Otherwise, we need to ensure that the sub-path starts with a slash, since this is a requirement # # Otherwise, we need to ensure that the sub-path starts with a slash, since this is a requirement
# for the basePath property. For example, assume DOMAIN_SUB_PATH=/bot, then: # # for the basePath property. For example, assume DOMAIN_SUB_PATH=/bot, then:
# /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> /bot/_next/image # # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> /bot/_next/image
elif [[ ! "$DOMAIN_SUB_PATH" =~ ^/ ]]; then # elif [[ ! "$DOMAIN_SUB_PATH" =~ ^/ ]]; then
DOMAIN_SUB_PATH="/$DOMAIN_SUB_PATH" # DOMAIN_SUB_PATH="/$DOMAIN_SUB_PATH"
fi # fi
fi # fi
if [ ! -z "$DOMAIN_SUB_PATH" ]; then # if [ ! -z "$DOMAIN_SUB_PATH" ]; then
echo -e "\e[34m[Info] DOMAIN_SUB_PATH was set to "$DOMAIN_SUB_PATH". Overriding default path.\e[0m" # echo -e "\e[34m[Info] DOMAIN_SUB_PATH was set to "$DOMAIN_SUB_PATH". Overriding default path.\e[0m"
fi # fi
# Always set NEXT_PUBLIC_DOMAIN_SUB_PATH to DOMAIN_SUB_PATH (even if it is empty!!) # # Always set NEXT_PUBLIC_DOMAIN_SUB_PATH to DOMAIN_SUB_PATH (even if it is empty!!)
export NEXT_PUBLIC_DOMAIN_SUB_PATH="$DOMAIN_SUB_PATH" # export NEXT_PUBLIC_DOMAIN_SUB_PATH="$DOMAIN_SUB_PATH"
# Iterate over _all_ files in the web directory, making substitutions for the `BAKED_` sentinal values # # Iterate over _all_ files in the web directory, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value. # # with their actual desired runtime value.
find /app/packages/web -type f | # find /app/packages/web -type f |
while read file; do # while read file; do
# @note: the leading "/" is required here as it is included at build time. See Dockerfile. # # @note: the leading "/" is required here as it is included at build time. See Dockerfile.
sed -i "s|/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH|${NEXT_PUBLIC_DOMAIN_SUB_PATH}|g" "$file" # sed -i "s|/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH|${NEXT_PUBLIC_DOMAIN_SUB_PATH}|g" "$file"
done # done
} # }
# Run supervisord # Run supervisord

View file

@ -6,8 +6,8 @@
"scripts": { "scripts": {
"build": "yarn workspaces run build", "build": "yarn workspaces run build",
"test": "yarn workspaces run test", "test": "yarn workspaces run test",
"dev": "npm-run-all --print-label --parallel dev:zoekt dev:backend dev:web dev:redis", "dev": "yarn workspace @sourcebot/db prisma:migrate:dev && npm-run-all --print-label --parallel dev:zoekt dev:backend dev:web dev:redis",
"dev:mt": "npm-run-all --print-label --parallel dev:zoekt:mt dev:backend dev:web dev:redis", "dev:mt": "yarn workspace @sourcebot/db prisma:migrate:dev && npm-run-all --print-label --parallel dev:zoekt:mt dev:backend dev:web dev:redis",
"dev:zoekt": "export PATH=\"$PWD/bin:$PATH\" && export SRC_TENANT_ENFORCEMENT_MODE=none && zoekt-webserver -index .sourcebot/index -rpc", "dev:zoekt": "export PATH=\"$PWD/bin:$PATH\" && export SRC_TENANT_ENFORCEMENT_MODE=none && zoekt-webserver -index .sourcebot/index -rpc",
"dev:zoekt:mt": "export PATH=\"$PWD/bin:$PATH\" && export SRC_TENANT_ENFORCEMENT_MODE=strict && zoekt-webserver -index .sourcebot/index -rpc", "dev:zoekt:mt": "export PATH=\"$PWD/bin:$PATH\" && export SRC_TENANT_ENFORCEMENT_MODE=strict && zoekt-webserver -index .sourcebot/index -rpc",
"dev:backend": "yarn workspace @sourcebot/backend dev:watch", "dev:backend": "yarn workspace @sourcebot/backend dev:watch",

View file

@ -32,6 +32,7 @@
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"micromatch": "^4.0.8", "micromatch": "^4.0.8",
"posthog-node": "^4.2.1", "posthog-node": "^4.2.1",
"@sourcebot/db": "^0.1.0",
"simple-git": "^3.27.0", "simple-git": "^3.27.0",
"strip-json-comments": "^5.0.1", "strip-json-comments": "^5.0.1",
"winston": "^3.15.0", "winston": "^3.15.0",

View file

@ -105,7 +105,7 @@ export const syncConfig = async (configPath: string, db: PrismaClient, signal: A
name: repoName, name: repoName,
tenantId: 0, // TODO: add support for tenantId in GitLab config tenantId: 0, // TODO: add support for tenantId in GitLab config
isFork, isFork,
isArchived: project.archived, isArchived: !!project.archived,
metadata: { metadata: {
'zoekt.web-url-type': 'gitlab', 'zoekt.web-url-type': 'gitlab',
'zoekt.web-url': project.web_url, 'zoekt.web-url': project.web_url,

View file

@ -3,7 +3,7 @@ import micromatch from "micromatch";
import { createLogger } from "./logger.js"; import { createLogger } from "./logger.js";
import { GitLabConfig } from "./schemas/v2.js"; import { GitLabConfig } from "./schemas/v2.js";
import { AppContext } from "./types.js"; import { AppContext } from "./types.js";
import { getTokenFromConfig, marshalBool, measure } from "./utils.js"; import { getTokenFromConfig, measure } from "./utils.js";
const logger = createLogger("GitLab"); const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com"; export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

View file

@ -0,0 +1,45 @@
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT,
"email" TEXT,
"emailVerified" DATETIME,
"image" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" DATETIME NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");

View file

@ -41,3 +41,48 @@ model Repo {
@@unique([external_id, external_codeHostUrl]) @@unique([external_id, external_codeHostUrl])
} }
// @see : https://authjs.dev/concepts/database-models#user
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// @see : https://authjs.dev/concepts/database-models#account
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
// @see : https://authjs.dev/concepts/database-models#verificationtoken
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
}

View file

@ -22,10 +22,14 @@ const nextConfig = {
// This is required to support PostHog trailing slash API requests // This is required to support PostHog trailing slash API requests
skipTrailingSlashRedirect: true, skipTrailingSlashRedirect: true,
// @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
// causing regular expressions parsing errors when making a request. It's unclear
// why exactly this was happening, but it's likely due to a bad replacement happening
// in the `sed` command.
// @note: this is evaluated at build time. // @note: this is evaluated at build time.
...(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH ? { // ...(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH ? {
basePath: process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH, // basePath: process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH,
} : {}) // } : {})
}; };
export default nextConfig; export default nextConfig;

View file

@ -10,6 +10,7 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@auth/prisma-adapter": "^2.7.4",
"@codemirror/commands": "^6.6.0", "@codemirror/commands": "^6.6.0",
"@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-cpp": "^6.0.2",
"@codemirror/lang-css": "^6.3.0", "@codemirror/lang-css": "^6.3.0",
@ -39,6 +40,7 @@
"@hookform/resolvers": "^3.9.0", "@hookform/resolvers": "^3.9.0",
"@iconify/react": "^5.1.0", "@iconify/react": "^5.1.0",
"@iizukak/codemirror-lang-wgsl": "^0.3.0", "@iizukak/codemirror-lang-wgsl": "^0.3.0",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
@ -89,6 +91,7 @@
"http-status-codes": "^2.3.0", "http-status-codes": "^2.3.0",
"lucide-react": "^0.435.0", "lucide-react": "^0.435.0",
"next": "14.2.21", "next": "14.2.21",
"next-auth": "^5.0.0-beta.25",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"posthog-js": "^1.161.5", "posthog-js": "^1.161.5",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
@ -119,6 +122,7 @@
"jsdom": "^25.0.1", "jsdom": "^25.0.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8", "postcss": "^8",
"@sourcebot/db": "^0.1.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5", "typescript": "^5",
"vite-tsconfig-paths": "^5.1.3", "vite-tsconfig-paths": "^5.1.3",

View file

@ -0,0 +1,2 @@
import { handlers } from "@/auth";
export const { GET, POST } = handlers;

View file

@ -8,13 +8,15 @@ import { NextRequest } from "next/server";
export const POST = async (request: NextRequest) => { export const POST = async (request: NextRequest) => {
const body = await request.json(); const body = await request.json();
const tenantId = await request.headers.get("X-Tenant-ID"); const tenantId = request.headers.get("X-Tenant-ID");
console.log(`Search request received. Tenant ID: ${tenantId}`); console.log(`Search request received. Tenant ID: ${tenantId}`);
const parsed = await searchRequestSchema.safeParseAsync({ const parsed = await searchRequestSchema.safeParseAsync({
...body, ...body,
...(tenantId && { tenantId: parseInt(tenantId) }), ...(tenantId ? {
tenantId: parseInt(tenantId)
} : {}),
}); });
if (!parsed.success) { if (!parsed.success) {
return serviceErrorResponse( return serviceErrorResponse(

View file

@ -1,31 +1,28 @@
'use client';
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { NavigationMenu as NavigationMenuBase, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; import { NavigationMenu as NavigationMenuBase, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
import Link from "next/link"; import Link from "next/link";
import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons";
import { SettingsDropdown } from "./settingsDropdown";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import Image from "next/image"; import Image from "next/image";
import logoDark from "../../../public/sb_logo_dark_small.png"; import logoDark from "../../../public/sb_logo_dark_small.png";
import logoLight from "../../../public/sb_logo_light_small.png"; import logoLight from "../../../public/sb_logo_light_small.png";
import { useRouter } from "next/navigation"; import { ProfilePicture } from "./profilePicture";
import { signOut } from "@/auth";
import { SettingsDropdown } from "./settingsDropdown";
import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons";
import { redirect } from "next/navigation";
const SOURCEBOT_DISCORD_URL = "https://discord.gg/6Fhp27x7Pb"; const SOURCEBOT_DISCORD_URL = "https://discord.gg/6Fhp27x7Pb";
const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot"; const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot";
export const NavigationMenu = () => { export const NavigationMenu = async () => {
const router = useRouter();
return ( return (
<div className="flex flex-col w-screen h-fit"> <div className="flex flex-col w-screen h-fit">
<div className="flex flex-row justify-between items-center py-1.5 px-3"> <div className="flex flex-row justify-between items-center py-1.5 px-3">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<div <Link
href="/"
className="mr-3 cursor-pointer" className="mr-3 cursor-pointer"
onClick={() => {
router.push("/");
}}
> >
<Image <Image
src={logoDark} src={logoDark}
@ -39,7 +36,7 @@ export const NavigationMenu = () => {
alt={"Sourcebot logo"} alt={"Sourcebot logo"}
priority={true} priority={true}
/> />
</div> </Link>
<NavigationMenuBase> <NavigationMenuBase>
<NavigationMenuList> <NavigationMenuList>
@ -62,25 +59,50 @@ export const NavigationMenu = () => {
</div> </div>
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<form
action={async () => {
"use server";
redirect(SOURCEBOT_DISCORD_URL);
}}
>
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
onClick={() => { type="submit"
window.open(SOURCEBOT_DISCORD_URL, "_blank");
}}
> >
<DiscordLogoIcon className="w-4 h-4" /> <DiscordLogoIcon className="w-4 h-4" />
</Button> </Button>
</form>
<form
action={async () => {
"use server";
redirect(SOURCEBOT_GITHUB_URL);
}}
>
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
onClick={() => { type="submit"
window.open(SOURCEBOT_GITHUB_URL, "_blank");
}}
> >
<GitHubLogoIcon className="w-4 h-4" /> <GitHubLogoIcon className="w-4 h-4" />
</Button> </Button>
</form>
<SettingsDropdown /> <SettingsDropdown />
<form
action={async () => {
"use server";
await signOut();
}}
>
<Button
type="submit"
variant="outline"
size="default"
>
Logout
</Button>
</form>
<ProfilePicture />
</div> </div>
</div> </div>
<Separator /> <Separator />

View file

@ -0,0 +1,20 @@
import { auth } from "@/auth"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar"
export const ProfilePicture = async () => {
const session = await auth()
return (
<Avatar>
<AvatarImage
src={session?.user?.image ?? ""}
alt="@shadcn"
/>
<AvatarFallback>U</AvatarFallback>
</Avatar>
)
}

View file

@ -0,0 +1,87 @@
import { providerMap, signIn } from "@/auth"
import { AuthError } from "next-auth"
import { redirect } from "next/navigation"
import logoDark from "@/public/sb_logo_dark_large.png";
import logoLight from "@/public/sb_logo_light_large.png";
import githubLogo from "@/public/github.svg";
import Image from "next/image";
import { Button } from "@/components/ui/button";
const SIGNIN_ERROR_URL = "/login";
export default async function Login(props: {
searchParams: { callbackUrl: string | undefined }
}) {
return (
<div className="flex flex-col justify-center items-center h-screen">
<div className="flex flex-col items-center border p-16 rounded-lg gap-6">
<div>
<Image
src={logoDark}
className="h-16 w-auto hidden dark:block"
alt={"Sourcebot logo"}
priority={true}
/>
<Image
src={logoLight}
className="h-16 w-auto block dark:hidden"
alt={"Sourcebot logo"}
priority={true}
/>
</div>
{
Object.values(providerMap)
.map((provider) => {
if (provider.id === "github") {
return {
provider,
logo: githubLogo,
}
}
return { provider }
})
.map(({ provider, logo }) => (
<form
key={provider.id}
action={async () => {
"use server"
try {
await signIn(provider.id, {
redirectTo: props.searchParams?.callbackUrl ?? "",
})
} catch (error) {
// Signin can fail for a number of reasons, such as the user
// not existing, or the user not having the correct role.
// In some cases, you may want to redirect to a custom error
if (error instanceof AuthError) {
return redirect(`${SIGNIN_ERROR_URL}?error=${error.type}`)
}
// Otherwise if a redirects happens Next.js can handle it
// so you can just re-thrown the error and let Next.js handle it.
// Docs:
// https://nextjs.org/docs/app/api-reference/functions/redirect#server-component
throw error
}
}}
>
<Button
type="submit"
>
{logo && (
<Image
src={logo}
alt={provider.name}
className="w-5 h-5 invert dark:invert-0 mr-2"
/>
)}
Sign in with {provider.name}
</Button>
</form>
))
}
</div>
</div>
)
}

View file

@ -2,8 +2,8 @@ import { listRepositories } from "@/lib/server/searchService";
import { isServiceError } from "@/lib/utils"; import { isServiceError } from "@/lib/utils";
import Image from "next/image"; import Image from "next/image";
import { Suspense } from "react"; import { Suspense } from "react";
import logoDark from "../../public/sb_logo_dark_large.png"; import logoDark from "@/public/sb_logo_dark_large.png";
import logoLight from "../../public/sb_logo_light_large.png"; import logoLight from "@/public/sb_logo_light_large.png";
import { NavigationMenu } from "./components/navigationMenu"; import { NavigationMenu } from "./components/navigationMenu";
import { RepositoryCarousel } from "./components/repositoryCarousel"; import { RepositoryCarousel } from "./components/repositoryCarousel";
import { SearchBar } from "./components/searchBar"; import { SearchBar } from "./components/searchBar";

38
packages/web/src/auth.ts Normal file
View file

@ -0,0 +1,38 @@
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma";
import type { Provider } from "next-auth/providers"
import { AUTH_GITHUB_CLIENT_ID, AUTH_GITHUB_CLIENT_SECRET, AUTH_SECRET } from "./lib/environment";
const providers: Provider[] = [
GitHub({
clientId: AUTH_GITHUB_CLIENT_ID,
clientSecret: AUTH_GITHUB_CLIENT_SECRET,
}),
];
// @see: https://authjs.dev/guides/pages/signin
export const providerMap = providers
.map((provider) => {
if (typeof provider === "function") {
const providerData = provider()
return { id: providerData.id, name: providerData.name }
} else {
return { id: provider.id, name: provider.name }
}
})
.filter((provider) => provider.id !== "credentials");
export const { handlers, signIn, signOut, auth } = NextAuth({
secret: AUTH_SECRET,
adapter: PrismaAdapter(prisma),
session: {
strategy: "jwt",
},
providers: providers,
pages: {
signIn: "/login"
}
})

View file

@ -0,0 +1,50 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View file

@ -6,3 +6,7 @@ export const ZOEKT_WEBSERVER_URL = getEnv(process.env.ZOEKT_WEBSERVER_URL, "http
export const SHARD_MAX_MATCH_COUNT = getEnvNumber(process.env.SHARD_MAX_MATCH_COUNT, 10000); export const SHARD_MAX_MATCH_COUNT = getEnvNumber(process.env.SHARD_MAX_MATCH_COUNT, 10000);
export const TOTAL_MAX_MATCH_COUNT = getEnvNumber(process.env.TOTAL_MAX_MATCH_COUNT, 100000); export const TOTAL_MAX_MATCH_COUNT = getEnvNumber(process.env.TOTAL_MAX_MATCH_COUNT, 100000);
export const NODE_ENV = process.env.NODE_ENV; export const NODE_ENV = process.env.NODE_ENV;
export const AUTH_SECRET = getEnv(process.env.AUTH_SECRET); // Generate using `npx auth secret`
export const AUTH_GITHUB_CLIENT_ID = getEnv(process.env.AUTH_GITHUB_CLIENT_ID);
export const AUTH_GITHUB_CLIENT_SECRET = getEnv(process.env.AUTH_GITHUB_CLIENT_SECRET);

View file

@ -5,4 +5,5 @@ export enum ErrorCode {
REPOSITORY_NOT_FOUND = 'REPOSITORY_NOT_FOUND', REPOSITORY_NOT_FOUND = 'REPOSITORY_NOT_FOUND',
FILE_NOT_FOUND = 'FILE_NOT_FOUND', FILE_NOT_FOUND = 'FILE_NOT_FOUND',
INVALID_REQUEST_BODY = 'INVALID_REQUEST_BODY', INVALID_REQUEST_BODY = 'INVALID_REQUEST_BODY',
NOT_AUTHENTICATED = 'NOT_AUTHENTICATED',
} }

View file

@ -68,3 +68,11 @@ export const unexpectedError = (message: string): ServiceError => {
message: `Unexpected error: ${message}`, message: `Unexpected error: ${message}`,
}; };
} }
export const notAuthenticated = (): ServiceError => {
return {
statusCode: StatusCodes.UNAUTHORIZED,
errorCode: ErrorCode.NOT_AUTHENTICATED,
message: "Not authenticated",
}
}

View file

@ -0,0 +1,46 @@
import { auth } from "@/auth";
import { Session } from "next-auth";
import { NextRequest, NextResponse } from "next/server";
import { notAuthenticated, serviceErrorResponse } from "./lib/serviceError";
interface NextAuthRequest extends NextRequest {
auth: Session | null;
}
const apiMiddleware = (req: NextAuthRequest) => {
if (req.nextUrl.pathname.startsWith("/api/auth")) {
return NextResponse.next();
}
if (!req.auth) {
return serviceErrorResponse(
notAuthenticated(),
);
}
return NextResponse.next();
}
const defaultMiddleware = (req: NextAuthRequest) => {
if (!req.auth && req.nextUrl.pathname !== "/login") {
const newUrl = new URL("/login", req.nextUrl.origin);
return NextResponse.redirect(newUrl);
}
return NextResponse.next();
}
export default auth(async (req) => {
if (req.nextUrl.pathname.startsWith("/api")) {
return apiMiddleware(req);
}
return defaultMiddleware(req);
})
export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ['/((?!_next/static|ingest|_next/image|favicon.ico|sitemap.xml|robots.txt).*)'],
}

View file

@ -0,0 +1,6 @@
import { PrismaClient } from "@sourcebot/db";
// @see: https://authjs.dev/getting-started/adapters/prisma
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
export const prisma = globalForPrisma.prisma || new PrismaClient()
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma

140
yarn.lock
View file

@ -16,6 +16,37 @@
"@types/json-schema" "^7.0.15" "@types/json-schema" "^7.0.15"
js-yaml "^4.1.0" js-yaml "^4.1.0"
"@auth/core@0.37.2":
version "0.37.2"
resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.37.2.tgz#0db8a94a076846bd88eb7f9273618513e2285cb2"
integrity sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==
dependencies:
"@panva/hkdf" "^1.2.1"
"@types/cookie" "0.6.0"
cookie "0.7.1"
jose "^5.9.3"
oauth4webapi "^3.0.0"
preact "10.11.3"
preact-render-to-string "5.2.3"
"@auth/core@0.37.4":
version "0.37.4"
resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.37.4.tgz#c51410aa7d0997fa22a07a196d2c21c8b1bca71b"
integrity sha512-HOXJwXWXQRhbBDHlMU0K/6FT1v+wjtzdKhsNg0ZN7/gne6XPsIrjZ4daMcFnbq0Z/vsAbYBinQhhua0d77v7qw==
dependencies:
"@panva/hkdf" "^1.2.1"
jose "^5.9.6"
oauth4webapi "^3.1.1"
preact "10.24.3"
preact-render-to-string "6.5.11"
"@auth/prisma-adapter@^2.7.4":
version "2.7.4"
resolved "https://registry.yarnpkg.com/@auth/prisma-adapter/-/prisma-adapter-2.7.4.tgz#4890be47a9f227f449832302d955c565c02879ee"
integrity sha512-3T/X94R9J1sxOLQtsD3ijIZ0JGHPXlZQxRr/8NpnZBJ3KGxun/mNsZ1MwMRhTxy0mmn9JWXk7u9+xCcVn0pu3A==
dependencies:
"@auth/core" "0.37.4"
"@babel/runtime@^7.18.6": "@babel/runtime@^7.18.6":
version "7.25.7" version "7.25.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6"
@ -1270,6 +1301,11 @@
dependencies: dependencies:
"@octokit/openapi-types" "^22.2.0" "@octokit/openapi-types" "^22.2.0"
"@panva/hkdf@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.2.1.tgz#cb0d111ef700136f4580349ff0226bf25c853f23"
integrity sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==
"@pkgjs/parseargs@^0.11.0": "@pkgjs/parseargs@^0.11.0":
version "0.11.0" version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@ -1333,6 +1369,16 @@
dependencies: dependencies:
"@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-avatar@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.2.tgz#24af4c66bb5271460a4a6b74c4f4f9d4789d3d90"
integrity sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==
dependencies:
"@radix-ui/react-context" "1.1.1"
"@radix-ui/react-primitive" "2.0.1"
"@radix-ui/react-use-callback-ref" "1.1.0"
"@radix-ui/react-use-layout-effect" "1.1.0"
"@radix-ui/react-collection@1.1.0": "@radix-ui/react-collection@1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed"
@ -1348,6 +1394,11 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74"
integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw== integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==
"@radix-ui/react-compose-refs@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz#6f766faa975f8738269ebb8a23bad4f5a8d2faec"
integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==
"@radix-ui/react-context@1.1.0": "@radix-ui/react-context@1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8"
@ -1503,6 +1554,13 @@
dependencies: dependencies:
"@radix-ui/react-slot" "1.1.0" "@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-primitive@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz#6d9efc550f7520135366f333d1e820cf225fad9e"
integrity sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==
dependencies:
"@radix-ui/react-slot" "1.1.1"
"@radix-ui/react-roving-focus@1.1.0": "@radix-ui/react-roving-focus@1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e" resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e"
@ -1547,6 +1605,13 @@
dependencies: dependencies:
"@radix-ui/react-compose-refs" "1.1.0" "@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-slot@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.1.tgz#ab9a0ffae4027db7dc2af503c223c978706affc3"
integrity sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==
dependencies:
"@radix-ui/react-compose-refs" "1.1.1"
"@radix-ui/react-toast@^1.2.2": "@radix-ui/react-toast@^1.2.2":
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.2.2.tgz#fdd8ed0b80f47d6631dfd90278fee6debc06bf33" resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.2.2.tgz#fdd8ed0b80f47d6631dfd90278fee6debc06bf33"
@ -1850,6 +1915,11 @@
resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.4.tgz#403488dc1c8d0db288270d3bbf0ce5f9c45678b4" resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.4.tgz#403488dc1c8d0db288270d3bbf0ce5f9c45678b4"
integrity sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA== integrity sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==
"@types/cookie@0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==
"@types/estree@1.0.6", "@types/estree@^1.0.0": "@types/estree@1.0.6", "@types/estree@^1.0.0":
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
@ -2851,6 +2921,11 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
cookie@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
crelt@^1.0.5: crelt@^1.0.5:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
@ -4338,6 +4413,11 @@ jiti@^1.21.0:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
jose@^5.9.3, jose@^5.9.6:
version "5.9.6"
resolved "https://registry.yarnpkg.com/jose/-/jose-5.9.6.tgz#77f1f901d88ebdc405e57cce08d2a91f47521883"
integrity sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==
"js-tokens@^3.0.0 || ^4.0.0": "js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -4710,6 +4790,13 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
next-auth@^5.0.0-beta.25:
version "5.0.0-beta.25"
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-5.0.0-beta.25.tgz#3a9f9734e1d8fa5ced545360f1afc24862cb92d5"
integrity sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==
dependencies:
"@auth/core" "0.37.2"
next-themes@^0.3.0: next-themes@^0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.3.0.tgz#b4d2a866137a67d42564b07f3a3e720e2ff3871a" resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.3.0.tgz#b4d2a866137a67d42564b07f3a3e720e2ff3871a"
@ -4807,6 +4894,11 @@ nwsapi@^2.2.12:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655"
integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ== integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==
oauth4webapi@^3.0.0, oauth4webapi@^3.1.1:
version "3.1.4"
resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-3.1.4.tgz#50695385cea8e7a43f3e2e23bc33ea27faece4a7"
integrity sha512-eVfN3nZNbok2s/ROifO0UAc5G8nRoLSbrcKJ09OqmucgnhXEfdIQOR4gq1eJH1rN3gV7rNw62bDEgftsgFtBEg==
object-assign@^4.0.1, object-assign@^4.1.1: object-assign@^4.0.1, object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -5143,6 +5235,28 @@ posthog-node@^4.2.1:
axios "^1.7.4" axios "^1.7.4"
rusha "^0.8.14" rusha "^0.8.14"
preact-render-to-string@5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz#23d17376182af720b1060d5a4099843c7fe92fe4"
integrity sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==
dependencies:
pretty-format "^3.8.0"
preact-render-to-string@6.5.11:
version "6.5.11"
resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz#467e69908a453497bb93d4d1fc35fb749a78e027"
integrity sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==
preact@10.11.3:
version "10.11.3"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.11.3.tgz#8a7e4ba19d3992c488b0785afcc0f8aa13c78d19"
integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==
preact@10.24.3:
version "10.24.3"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.3.tgz#086386bd47071e3b45410ef20844c21e23828f64"
integrity sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==
preact@^10.19.3: preact@^10.19.3:
version "10.24.2" version "10.24.2"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.2.tgz#42179771d3b06e7adb884e3f8127ddd3d99b78f6" resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.2.tgz#42179771d3b06e7adb884e3f8127ddd3d99b78f6"
@ -5163,6 +5277,11 @@ pretty-bytes@^6.1.1:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz#38cd6bb46f47afbf667c202cfc754bffd2016a3b" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz#38cd6bb46f47afbf667c202cfc754bffd2016a3b"
integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ== integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==
pretty-format@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==
prisma@^6.2.1: prisma@^6.2.1:
version "6.2.1" version "6.2.1"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.2.1.tgz#457b210326d66d0e6f583cc6f9cd2819b984408f" resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.2.1.tgz#457b210326d66d0e6f583cc6f9cd2819b984408f"
@ -5731,16 +5850,8 @@ string-argv@^0.3.1:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
"string-width-cjs@npm:string-width@^4.2.0": "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
version "4.2.3" name string-width-cjs
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -5837,14 +5948,7 @@ string_decoder@^1.1.1, string_decoder@^1.3.0:
dependencies: dependencies:
safe-buffer "~5.2.0" safe-buffer "~5.2.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==