diff --git a/Makefile b/Makefile index 03a610e6..27d8d983 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,8 @@ clean: packages/db/dist \ packages/schemas/node_modules \ packages/schemas/dist \ + packages/crypto/node_modules \ + packages/crypto/dist \ .sourcebot .PHONY: bin diff --git a/packages/crypto/src/environment.ts b/packages/crypto/src/environment.ts index c1f72210..8efe296d 100644 --- a/packages/crypto/src/environment.ts +++ b/packages/crypto/src/environment.ts @@ -1,10 +1,6 @@ import dotenv from 'dotenv'; -export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => { - if (required && !env && !defaultValue) { - throw new Error(`Missing required environment variable`); - } - +export const getEnv = (env: string | undefined, defaultValue?: string) => { return env ?? defaultValue; } @@ -14,4 +10,4 @@ dotenv.config({ }); // @note: You can use https://generate-random.org/encryption-key-generator to create a new 32 byte key -export const SOURCEBOT_ENCRYPTION_KEY = getEnv(process.env.SOURCEBOT_ENCRYPTION_KEY, undefined, true)!; \ No newline at end of file +export const SOURCEBOT_ENCRYPTION_KEY = getEnv(process.env.SOURCEBOT_ENCRYPTION_KEY); \ No newline at end of file diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index fc63f764..f0128297 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -9,6 +9,10 @@ const generateIV = (): Buffer => { }; export function encrypt(text: string): { iv: string; encryptedData: string } { + if (!SOURCEBOT_ENCRYPTION_KEY) { + throw new Error('Encryption key is not set'); + } + const encryptionKey = Buffer.from(SOURCEBOT_ENCRYPTION_KEY, 'ascii'); const iv = generateIV(); @@ -21,6 +25,10 @@ export function encrypt(text: string): { iv: string; encryptedData: string } { } export function decrypt(iv: string, encryptedText: string): string { + if (!SOURCEBOT_ENCRYPTION_KEY) { + throw new Error('Encryption key is not set'); + } + const encryptionKey = Buffer.from(SOURCEBOT_ENCRYPTION_KEY, 'ascii'); const ivBuffer = Buffer.from(iv, 'hex'); diff --git a/packages/schemas/tools/generate.ts b/packages/schemas/tools/generate.ts index a5911e27..7ba1b467 100644 --- a/packages/schemas/tools/generate.ts +++ b/packages/schemas/tools/generate.ts @@ -6,20 +6,18 @@ import { glob } from "glob"; const BANNER_COMMENT = '// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!\n'; -// const SCHEMAS: string[] = ["github.json", "shared.json"]; (async () => { const cwd = process.cwd(); const schemasBasePath = path.resolve(`${cwd}/../../schemas`); - const schemas = await glob(`${schemasBasePath}/**/*.json`) + const outDirRoot = path.resolve(`${cwd}/src`); + const schemas = await glob(`${schemasBasePath}/**/*.json`); await Promise.all(schemas.map(async (schemaPath) => { const name = path.parse(schemaPath).name; const version = path.basename(path.dirname(schemaPath)); - const outDir = path.join(cwd, `src/${version}`); + const outDir = path.join(outDirRoot, version); - // Clean output directory first - await rm(outDir, { recursive: true, force: true }); await mkdir(outDir, { recursive: true }); // Generate schema diff --git a/packages/web/src/app/[domain]/layout.tsx b/packages/web/src/app/[domain]/layout.tsx index 740a640d..8e2a2858 100644 --- a/packages/web/src/app/[domain]/layout.tsx +++ b/packages/web/src/app/[domain]/layout.tsx @@ -47,9 +47,7 @@ export default async function Layout({ if (isServiceError(subscription) || (subscription.status !== "active" && subscription.status !== "trialing")) { return (
- +
diff --git a/packages/web/src/app/onboard/components/orgCreateForm.tsx b/packages/web/src/app/onboard/components/orgCreateForm.tsx index 94daf69c..dce4b014 100644 --- a/packages/web/src/app/onboard/components/orgCreateForm.tsx +++ b/packages/web/src/app/onboard/components/orgCreateForm.tsx @@ -20,8 +20,9 @@ const onboardingFormSchema = z.object({ domain: z.string() .min(2, { message: "Organization domain must be at least 3 characters long." }) .max(20, { message: "Organization domain must be at most 20 characters long." }) - .regex(/^[a-zA-Z-]+$/, { message: "Organization domain must contain only letters and hyphens." }) - .regex(/^[^-].*[^-]$/, { message: "Organization domain must not start or end with a hyphen." }), + .regex(/^[a-z-]+$/, { + message: "Domain can only contain lowercase letters and dashes.", + }), }) export type OnboardingFormValues = z.infer @@ -54,6 +55,12 @@ export function OrgCreateForm({ setOrgCreateData }: OrgCreateFormProps) { } } + const handleNameChange = (e: React.ChangeEvent) => { + const name = e.target.value + const domain = name.toLowerCase().replace(/\s+/g, "-") + form.setValue("domain", domain) + } + return (
@@ -80,7 +87,14 @@ export function OrgCreateForm({ setOrgCreateData }: OrgCreateFormProps) { Organization Name - + { + field.onChange(e) + handleNameChange(e) + }} + /> @@ -94,7 +108,7 @@ export function OrgCreateForm({ setOrgCreateData }: OrgCreateFormProps) { Organization Domain
- + .sourcebot.dev
diff --git a/packages/web/src/auth.ts b/packages/web/src/auth.ts index 859342a9..febd2a21 100644 --- a/packages/web/src/auth.ts +++ b/packages/web/src/auth.ts @@ -6,9 +6,7 @@ import { PrismaAdapter } from "@auth/prisma-adapter" import { prisma } from "@/prisma"; import { AUTH_GITHUB_CLIENT_ID, AUTH_GITHUB_CLIENT_SECRET, AUTH_GOOGLE_CLIENT_ID, AUTH_GOOGLE_CLIENT_SECRET, AUTH_SECRET, AUTH_URL } from "./lib/environment"; import { User } from '@sourcebot/db'; -import { notAuthenticated, notFound, unexpectedError } from "@/lib/serviceError"; -import { getUser } from "./data/user"; -import { LuToggleRight } from 'react-icons/lu'; +import 'next-auth/jwt'; import type { Provider } from "next-auth/providers"; declare module 'next-auth' { @@ -49,8 +47,8 @@ export const providerMap = providers .filter((provider) => provider.id !== "credentials"); -const useSecureCookies = AUTH_URL.startsWith("https://"); -const hostName = new URL(AUTH_URL).hostname; +const useSecureCookies = AUTH_URL?.startsWith("https://") ?? false; +const hostName = AUTH_URL ? new URL(AUTH_URL).hostname : "localhost"; export const { handlers, signIn, signOut, auth } = NextAuth({ secret: AUTH_SECRET, @@ -115,43 +113,3 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ signIn: "/login" } }); - -export const getCurrentUserOrg = async () => { - const session = await auth(); - if (!session) { - return notAuthenticated(); - } - - const user = await getUser(session.user.id); - if (!user) { - return unexpectedError("User not found"); - } - const orgId = user.activeOrgId; - if (!orgId) { - return unexpectedError("User has no active org"); - } - - const membership = await prisma.userToOrg.findUnique({ - where: { - orgId_userId: { - userId: session.user.id, - orgId, - } - }, - }); - if (!membership) { - return notFound(); - } - - return orgId; -} - -export const doesUserHaveOrg = async (userId: string) => { - const orgs = await prisma.userToOrg.findMany({ - where: { - userId, - }, - }); - - return orgs.length > 0; -} diff --git a/packages/web/src/lib/environment.ts b/packages/web/src/lib/environment.ts index 10f45b99..c8195cc6 100644 --- a/packages/web/src/lib/environment.ts +++ b/packages/web/src/lib/environment.ts @@ -15,4 +15,4 @@ export const AUTH_GOOGLE_CLIENT_SECRET = getEnv(process.env.AUTH_GOOGLE_CLIENT_S export const AUTH_URL = getEnv(process.env.AUTH_URL)!; export const STRIPE_SECRET_KEY = getEnv(process.env.STRIPE_SECRET_KEY); -export const STRIPE_PRODUCT_ID = getEnv(process.env.STRIPE_PRODUCT_ID); \ No newline at end of file +export const STRIPE_PRODUCT_ID = getEnv(process.env.STRIPE_PRODUCT_ID); diff --git a/packages/web/src/middleware.ts b/packages/web/src/middleware.ts index 2923e1de..a4e646af 100644 --- a/packages/web/src/middleware.ts +++ b/packages/web/src/middleware.ts @@ -1,18 +1,6 @@ import { NextResponse } from "next/server"; import { auth } from "./auth" -/* -// We're not able to check if the user doesn't belong to any orgs in the middleware, since we cannot call prisma. As a result, we do this check -// in the root layout. However, there are certain endpoints (ex. login, redeem, onboard) that we want the user to be able to hit even if they don't -// belong to an org. It seems like the easiest way to do this is to check for these paths here and pass in a flag to the root layout using the headers -// https://github.com/vercel/next.js/discussions/43657#discussioncomment-5981981 -const bypassOrgCheck = req.nextUrl.pathname === "/login" || req.nextUrl.pathname === "/redeem" || req.nextUrl.pathname.includes("onboard"); -const bypassPaywall = req.nextUrl.pathname === "/login" || req.nextUrl.pathname === "/redeem" || req.nextUrl.pathname.includes("onboard") || req.nextUrl.pathname.includes("settings"); -const requestheaders = new Headers(req.headers); -requestheaders.set("x-bypass-org-check", bypassOrgCheck.toString()); -requestheaders.set("x-bypass-paywall", bypassPaywall.toString()); -*/ - export default auth((request) => { const host = request.headers.get("host")!;