From 72f11f0b02744792c84884c502926b68b215d55e Mon Sep 17 00:00:00 2001 From: gibbyb Date: Mon, 12 Jan 2026 10:10:03 -0600 Subject: [PATCH] authentik now working with convex auth --- AGENTS.md | 281 +++++++++++ apps/next/next.config.js | 7 +- apps/next/package.json | 1 - .../misc/auth/gibs_auth_wide_header.png | Bin 0 -> 74049 bytes apps/next/src/app/(auth)/sign-in/page.tsx | 460 ++++++++++++++++++ apps/next/src/app/layout.tsx | 82 ++-- apps/next/src/app/page.tsx | 46 +- apps/next/src/app/styles.css | 2 +- .../layout/auth/buttons/gibs-auth.tsx | 46 ++ .../components/layout/auth/buttons/index.tsx | 1 + .../providers/ConvexClientProvider.tsx | 16 + apps/next/src/components/providers/index.tsx | 1 + apps/next/src/{env.ts => env.js} | 0 apps/next/src/lib/metadata.ts | 129 +++++ apps/next/src/lib/middleware/ban-sus-ips.ts | 199 ++++++++ apps/next/src/middleware.ts | 34 ++ bun.lock | 14 +- package.json | 3 +- packages/backend/convex/_generated/api.d.ts | 32 +- packages/backend/convex/_generated/api.js | 3 +- .../backend/convex/_generated/dataModel.d.ts | 9 +- .../backend/convex/_generated/server.d.ts | 32 +- packages/backend/convex/_generated/server.js | 7 +- packages/backend/package.json | 13 +- packages/backend/scripts/generateKeys.mjs | 17 + packages/backend/types/auth.ts | 4 + packages/backend/types/index.ts | 5 + 27 files changed, 1310 insertions(+), 134 deletions(-) create mode 100644 AGENTS.md create mode 100644 apps/next/public/misc/auth/gibs_auth_wide_header.png create mode 100644 apps/next/src/app/(auth)/sign-in/page.tsx create mode 100644 apps/next/src/components/layout/auth/buttons/gibs-auth.tsx create mode 100644 apps/next/src/components/layout/auth/buttons/index.tsx create mode 100644 apps/next/src/components/providers/ConvexClientProvider.tsx create mode 100644 apps/next/src/components/providers/index.tsx rename apps/next/src/{env.ts => env.js} (100%) create mode 100644 apps/next/src/lib/metadata.ts create mode 100644 apps/next/src/lib/middleware/ban-sus-ips.ts create mode 100644 apps/next/src/middleware.ts create mode 100755 packages/backend/scripts/generateKeys.mjs create mode 100644 packages/backend/types/auth.ts create mode 100644 packages/backend/types/index.ts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..22a1190 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,281 @@ +# AGENTS.md - Convex Turbo Monorepo + +## Quick Reference + +### Build/Lint/Test Commands + +```bash +# Development +bun dev # Run all apps (Next.js + Expo + Backend) +bun dev:next # Run Next.js + Convex backend only +bun dev:expo # Run Expo + Convex backend only +bun dev:backend # Run Convex backend only +bun dev:expo:tunnel # Expo with tunnel for physical device + +# Quality +bun lint # Lint all packages +bun lint:fix # Lint and auto-fix +bun format # Check formatting +bun format:fix # Fix formatting +bun typecheck # TypeScript type checking + +# Build +bun build # Build all packages + +# Single Package Commands (use Turborepo filters) +bun turbo run dev -F @gib/next # Single app dev +bun turbo run lint -F @gib/backend # Lint single package +bun turbo run typecheck -F @gib/ui # Typecheck single package + +# Cleanup +bun clean # Clean all node_modules (git clean) +bun clean:ws # Clean workspace caches +``` + +## Project Structure + +``` +convex-monorepo/ +├── apps/ +│ ├── next/ # Next.js 16 web app (@gib/next) +│ └── expo/ # Expo 54 mobile app (@gib/expo) +├── packages/ +│ ├── backend/ # Convex backend (@gib/backend) +│ │ └── convex/ # Convex functions, schema, auth +│ └── ui/ # Shared shadcn/ui components (@gib/ui) +├── tools/ +│ ├── eslint/ # @gib/eslint-config +│ ├── prettier/ # @gib/prettier-config +│ ├── tailwind/ # @gib/tailwind-config +│ └── typescript/ # @gib/tsconfig +├── docker/ # Self-hosted Convex deployment +└── .env # Central environment variables +``` + +## Dependency Management + +### Catalogs (Single Source of Truth) + +All shared dependencies are defined in root `package.json` catalogs: + +```json +"catalog": { + "prettier": "^3.6.2", + "typescript": "^5.9.3", + "eslint": "^9.38.0" +}, +"catalogs": { + "convex": { "convex": "^1.28.0", "@convex-dev/auth": "^0.0.81" }, + "react19": { "react": "^19.1.4", "react-dom": "19.1.4" } +} +``` + +### Using Catalogs in Packages + +```json +"dependencies": { + "convex": "catalog:convex", + "react": "catalog:react19", + "typescript": "catalog:" +}, +"devDependencies": { + "@gib/eslint-config": "workspace:*", + "@gib/ui": "workspace:*" +} +``` + +### Updating Dependencies + +**IMPORTANT:** Do NOT use `bun update` directly - it may replace catalog: with versions. + +```bash +# Correct workflow: +1. Edit version in root package.json catalog section +2. Run: bun install +3. Verify with: bun lint:ws (runs sherif) +``` + +## Code Style Guidelines + +### Imports (via @gib/prettier-config) + +```typescript +// Order: Types → React → Next/Expo → Third-party → @gib/* → Local +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { api } from '@/convex/_generated/api'; +import { ConvexError } from 'convex/values'; + +import { cn } from '@gib/ui'; + +import type { User } from './types'; +``` + +### TypeScript + +- Strict mode enabled (`noUncheckedIndexedAccess: true`) +- Use `type` imports: `import type { X } from 'y'` +- Prefix unused vars with `_`: `const _unused = ...` +- Avoid `any` - use `unknown` with type guards + +### Naming Conventions + +- Components: `PascalCase` (`UserProfile.tsx`) +- Functions/variables: `camelCase` +- Constants: `SCREAMING_SNAKE_CASE` +- Files: `kebab-case.ts` (except components) + +### Error Handling + +```typescript +// Convex functions - use ConvexError +import { ConvexError } from 'convex/values'; +throw new ConvexError('User not found.'); + +// Client-side - handle gracefully +try { ... } catch (e) { + if (e instanceof ConvexError) { /* handle */ } +} +``` + +### Formatting + +- Single quotes, trailing commas +- 80 char line width, 2 space indent +- Tailwind classes sorted via prettier-plugin-tailwindcss +- Use `cn()` for conditional classes: `cn('base', condition && 'active')` + +## Environment Variables + +### Central .env (root) + +All env vars in `/.env`. Apps load via `with-env` script: + +```json +"scripts": { + "dev": "bun with-env next dev --turbo", + "with-env": "dotenv -e ../../.env --" +} +``` + +### Required Variables + +```bash +# Convex +CONVEX_SELF_HOSTED_URL=https://api.convex.example.com +CONVEX_SELF_HOSTED_ADMIN_KEY= +CONVEX_SITE_URL=https://convex.example.com +NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com + +# Auth (sync to Convex deployment) +AUTH_AUTHENTIK_ID= +AUTH_AUTHENTIK_SECRET= +AUTH_AUTHENTIK_ISSUER= +USESEND_API_KEY= +``` + +### Syncing to Convex Deployment + +```bash +# Upload env vars to self-hosted Convex +npx convex env set AUTH_AUTHENTIK_ID "value" +npx convex env set AUTH_AUTHENTIK_SECRET "value" +npx convex env set AUTH_AUTHENTIK_ISSUER "value" +npx convex env set USESEND_API_KEY "value" +npx convex env set CONVEX_SITE_URL "https://convex.example.com" +``` + +## Convex Backend Patterns + +### Schema (`packages/backend/convex/schema.ts`) + +```typescript +import { defineSchema, defineTable } from 'convex/server'; +import { authTables } from '@convex-dev/auth/server'; + +export default defineSchema({ + ...authTables, + users: defineTable({ ... }).index('email', ['email']), +}); +``` + +### Queries & Mutations + +```typescript +import { v } from 'convex/values'; + +import { mutation, query } from './_generated/server'; + +export const getUser = query({ + args: { userId: v.id('users') }, + handler: async (ctx, args) => { + return await ctx.db.get(args.userId); + }, +}); +``` + +### Auth + +```typescript +import { getAuthUserId } from '@convex-dev/auth/server'; + +// In any query/mutation: +const userId = await getAuthUserId(ctx); +if (!userId) throw new ConvexError('Not authenticated.'); +``` + +## Next.js Patterns + +### Convex Provider Setup + +```tsx +// src/components/providers/ConvexClientProvider.tsx +'use client'; + +import { ConvexAuthNextjsProvider } from '@convex-dev/auth/nextjs'; +import { ConvexReactClient } from 'convex/react'; + +const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); + +export const ConvexClientProvider = ({ children }) => ( + + {children} + +); +``` + +### Path Aliases + +- `@/*` → `./src/*` (Next.js) +- `~/*` → `./src/*` (Expo) + +## Expo Patterns + +### NativeWind (Tailwind for RN) + +```tsx +import '../styles.css'; + +// Use className like web: +``` + +### Secure Storage for Auth + +```typescript +import * as SecureStore from 'expo-secure-store'; + +// Tokens stored via SecureStore, not AsyncStorage +``` + +## Known Issues / Cleanup Needed + +1. **Apps reference @acme/_ instead of @gib/_** - Legacy T3 template imports +2. **TRPC imports exist but no TRPC** - Replace with Convex client +3. **Expo uses better-auth** - Should use @convex-dev/auth +4. **@gib/ui uses pnpm dlx** - Should use bunx for shadcn + +## Adding UI Components + +```bash +bun ui-add # Interactive shadcn/ui component addition +``` diff --git a/apps/next/next.config.js b/apps/next/next.config.js index 28980dc..eab557c 100644 --- a/apps/next/next.config.js +++ b/apps/next/next.config.js @@ -1,11 +1,6 @@ import { withSentryConfig } from '@sentry/nextjs'; -import { createJiti } from 'jiti'; import { withPlausibleProxy } from 'next-plausible'; - -const jiti = createJiti(import.meta.url); - -// Import env files to validate at build time. Use jiti so we can load .ts files in here. -await jiti.import('./src/env'); +import { env } from './src/env.js'; /** @type {import("next").NextConfig} */ const config = withPlausibleProxy({ diff --git a/apps/next/package.json b/apps/next/package.json index 998f42d..0bb7d72 100644 --- a/apps/next/package.json +++ b/apps/next/package.json @@ -38,7 +38,6 @@ "@types/react": "catalog:react19", "@types/react-dom": "catalog:react19", "eslint": "catalog:", - "jiti": "^2.5.1", "prettier": "catalog:", "tailwindcss": "catalog:", "tw-animate-css": "^1.4.0", diff --git a/apps/next/public/misc/auth/gibs_auth_wide_header.png b/apps/next/public/misc/auth/gibs_auth_wide_header.png new file mode 100644 index 0000000000000000000000000000000000000000..a28f7a926d72a7d14c963978a8aff7eee16757d7 GIT binary patch literal 74049 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 z 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 q-vK-+Jm*$$a_mI<0T`Xx=4 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�NYYE;o)E`dug%iU++|?PVby!-78;${%Xdd&~ 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 literal 0 HcmV?d00001 diff --git a/apps/next/src/app/(auth)/sign-in/page.tsx b/apps/next/src/app/(auth)/sign-in/page.tsx new file mode 100644 index 0000000..fce0a5e --- /dev/null +++ b/apps/next/src/app/(auth)/sign-in/page.tsx @@ -0,0 +1,460 @@ +'use client'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import Link from 'next/link'; +import { useAuthActions } from '@convex-dev/auth/react'; +import { useRouter } from 'next/navigation'; +import { ConvexError } from 'convex/values'; +import { useState } from 'react'; +import { + Card, + CardContent, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, + InputOTP, + InputOTPGroup, + InputOTPSlot, + InputOTPSeparator, + Separator, + SubmitButton, + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from '@gib/ui'; +import { toast } from 'sonner'; +import { + GibsAuthSignInButton, +} from '@/components/layout/auth/buttons'; +import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@gib/backend/types'; + +const signInFormSchema = z.object({ + email: z.email({ + message: 'Please enter a valid email address.', + }), + password: z.string().regex(PASSWORD_REGEX, { + message: 'Incorrect password. Does not meet requirements.', + }), +}); + +const signUpFormSchema = z + .object({ + name: z.string().min(2, { + message: 'Name must be at least 2 characters.', + }), + email: z.email({ + message: 'Please enter a valid email address.', + }), + password: z + .string() + .min(PASSWORD_MIN, { + message: `Password must be at least ${PASSWORD_MIN} characters.`, + }) + .max(PASSWORD_MAX, { + message: `Password must be no more than ${PASSWORD_MAX} characters.`, + }) + .regex(/^\S+$/, { + message: 'Password must not contain whitespace.', + }) + .regex(/[0-9]/, { + message: 'Password must contain at least one digit.', + }) + .regex(/[a-z]/, { + message: 'Password must contain at least one lowercase letter.', + }) + .regex(/[A-Z]/, { + message: 'Password must contain at least one uppercase letter.', + }) + .regex(/[\p{P}\p{S}]/u, { + message: 'Password must contain at least one symbol.', + }), + confirmPassword: z.string().min(PASSWORD_MIN, { + message: `Password must be at least ${PASSWORD_MIN} characters.`, + }), + }) + .refine((data) => data.password === data.confirmPassword, { + message: 'Passwords do not match!', + path: ['confirmPassword'], + }); + +const verifyEmailFormSchema = z.object({ + code: z.string({ message: 'Invalid code.' }), +}); + +const SignIn = () => { + const { signIn } = useAuthActions(); + const [flow, setFlow] = useState<'signIn' | 'signUp' | 'email-verification'>( + 'signIn', + ); + const [email, setEmail] = useState(''); + const [code, setCode] = useState(''); + const [loading, setLoading] = useState(false); + const router = useRouter(); + + const signInForm = useForm>({ + resolver: zodResolver(signInFormSchema), + defaultValues: { email: '', password: '' }, + }); + + const signUpForm = useForm>({ + resolver: zodResolver(signUpFormSchema), + defaultValues: { + name: '', + email, + password: '', + confirmPassword: '', + }, + }); + + const verifyEmailForm = useForm>({ + resolver: zodResolver(verifyEmailFormSchema), + defaultValues: { code }, + }); + + const handleSignIn = async (values: z.infer) => { + const formData = new FormData(); + formData.append('email', values.email); + formData.append('password', values.password); + formData.append('flow', flow); + setLoading(true); + try { + await signIn('password', formData).then(() => router.push('/')); + } catch (error) { + console.error('Error signing in:', error); + toast.error('Error signing in.'); + } finally { + signInForm.reset(); + setLoading(false); + } + }; + + const handleSignUp = async (values: z.infer) => { + const formData = new FormData(); + formData.append('email', values.email); + formData.append('password', values.password); + formData.append('flow', flow); + formData.append('name', values.name); + setLoading(true); + try { + if (values.confirmPassword !== values.password) + throw new ConvexError('Passwords do not match.'); + await signIn('password', formData).then(() => { + setEmail(values.email); + setFlow('email-verification'); + }); + } catch (error) { + console.error('Error signing up:', error); + toast.error('Error signing up.'); + } finally { + signUpForm.reset(); + setLoading(false); + } + }; + + const handleVerifyEmail = async ( + values: z.infer, + ) => { + const formData = new FormData(); + formData.append('code', code); + formData.append('flow', flow); + formData.append('email', email); + setLoading(true); + try { + await signIn('password', formData).then(() => router.push('/')); + } catch (error) { + console.error('Error verifying email:', error); + toast.error('Error verifying email.'); + } finally { + verifyEmailForm.reset(); + setLoading(false); + } + }; + + if (flow === 'email-verification') { + return ( +
+ + +
+

Verify Your Email

+

We sent a code to {email}

+
+
+ + ( + + Code + + setCode(value)} + > + + + + + + + + + + + + + Please enter the one-time password sent to your email. + +
+ +
+
+ )} + /> + + Verify Email + + + +
+ +
+
+
+
+ ); + } + + return ( +
+ + setFlow(value as 'signIn' | 'signUp')} + className='items-center' + > + + + Sign In + + + Sign Up + + + + + +
+ + ( + + Email + + + +
+ +
+
+ )} + /> + ( + +
+ Password + + Forgot Password? + +
+ + + +
+ +
+
+ )} + /> + + Sign In + + + +
+
+ + or + +
+
+
+ +
+
+
+
+ + + +
+ + ( + + Name + + + +
+ +
+
+ )} + /> + ( + + Email + + + +
+ +
+
+ )} + /> + ( + + Password + + + +
+ +
+
+ )} + /> + ( + + + Confirm Passsword + + + + +
+ +
+
+ )} + /> + + Sign Up + + + +
+
+ + or + +
+
+
+ +
+
+
+
+
+
+
+ ); +}; +export default SignIn; diff --git a/apps/next/src/app/layout.tsx b/apps/next/src/app/layout.tsx index 872bcd4..1502b40 100644 --- a/apps/next/src/app/layout.tsx +++ b/apps/next/src/app/layout.tsx @@ -1,34 +1,16 @@ import type { Metadata, Viewport } from 'next'; import { Geist, Geist_Mono } from 'next/font/google'; -import { cn } from '@acme/ui'; -import { ThemeProvider, ThemeToggle } from '@acme/ui/theme'; -import { Toaster } from '@acme/ui/toast'; -import { env } from '~/env'; -import { TRPCReactProvider } from '~/trpc/react'; +import '@/app/styles.css'; -import '~/app/styles.css'; +import { ConvexClientProvider } from '@/components/providers'; +import { generateMetadata } from '@/lib/metadata'; +import { ConvexAuthNextjsServerProvider } from '@convex-dev/auth/nextjs/server'; +import PlausibleProvider from 'next-plausible'; -export const metadata: Metadata = { - metadataBase: new URL( - env.VERCEL_ENV === 'production' - ? 'https://turbo.t3.gg' - : 'http://localhost:3000', - ), - title: 'Create T3 Turbo', - description: 'Simple monorepo with shared backend for web & mobile apps', - openGraph: { - title: 'Create T3 Turbo', - description: 'Simple monorepo with shared backend for web & mobile apps', - url: 'https://create-t3-turbo.vercel.app', - siteName: 'Create T3 Turbo', - }, - twitter: { - card: 'summary_large_image', - site: '@jullerino', - creator: '@jullerino', - }, -}; +import { ThemeProvider, Toaster } from '@gib/ui'; + +export const metadata: Metadata = generateMetadata(); export const viewport: Viewport = { themeColor: [ @@ -46,24 +28,36 @@ const geistMono = Geist_Mono({ variable: '--font-geist-mono', }); -export default function RootLayout(props: { children: React.ReactNode }) { +const RootLayout = ({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) => { return ( - - + - - {props.children} -
- -
- -
- - + + + + + {children} + + + + + +
+ ); -} +}; +export default RootLayout; diff --git a/apps/next/src/app/page.tsx b/apps/next/src/app/page.tsx index 106c875..c1aacb6 100644 --- a/apps/next/src/app/page.tsx +++ b/apps/next/src/app/page.tsx @@ -1,41 +1,11 @@ -import { Suspense } from 'react'; - -import { HydrateClient, prefetch, trpc } from '~/trpc/server'; -import { AuthShowcase } from './_components/auth-showcase'; -import { - CreatePostForm, - PostCardSkeleton, - PostList, -} from './_components/posts'; - -export default function HomePage() { - prefetch(trpc.post.all.queryOptions()); +'use client'; +const Home = () => { return ( - -
-
-

- Create T3 Turbo -

- - - -
- - - - -
- } - > - - -
- -
-
+
+ Hello! +
); -} +}; + +export default Home; diff --git a/apps/next/src/app/styles.css b/apps/next/src/app/styles.css index 42afca7..552d3f4 100644 --- a/apps/next/src/app/styles.css +++ b/apps/next/src/app/styles.css @@ -1,6 +1,6 @@ @import 'tailwindcss'; @import 'tw-animate-css'; -@import '@acme/tailwind-config/theme'; +@import '@gib/tailwind-config/theme'; @source '../../../../packages/ui/src/*.{ts,tsx}'; diff --git a/apps/next/src/components/layout/auth/buttons/gibs-auth.tsx b/apps/next/src/components/layout/auth/buttons/gibs-auth.tsx new file mode 100644 index 0000000..590cdb6 --- /dev/null +++ b/apps/next/src/components/layout/auth/buttons/gibs-auth.tsx @@ -0,0 +1,46 @@ +import Image from 'next/image'; +import { useAuthActions } from '@convex-dev/auth/react'; +import type { buttonVariants } from '@gib/ui'; +import { Button } from '@gib/ui'; +import type { ComponentProps } from 'react'; +import type { VariantProps } from 'class-variance-authority'; + +interface Props { + buttonProps?: Omit, 'onClick'> & + VariantProps & { + asChild?: boolean; + }; + type?: 'signIn' | 'signUp'; +}; + +export const GibsAuthSignInButton = ({ + buttonProps, + type = 'signIn', +}: Props) => { + const { signIn } = useAuthActions(); + return ( + + ); +}; diff --git a/apps/next/src/components/layout/auth/buttons/index.tsx b/apps/next/src/components/layout/auth/buttons/index.tsx new file mode 100644 index 0000000..c67c893 --- /dev/null +++ b/apps/next/src/components/layout/auth/buttons/index.tsx @@ -0,0 +1 @@ +export { GibsAuthSignInButton } from './gibs-auth'; diff --git a/apps/next/src/components/providers/ConvexClientProvider.tsx b/apps/next/src/components/providers/ConvexClientProvider.tsx new file mode 100644 index 0000000..0b9c22f --- /dev/null +++ b/apps/next/src/components/providers/ConvexClientProvider.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { ConvexAuthNextjsProvider } from "@convex-dev/auth/nextjs"; +import { ConvexReactClient } from "convex/react"; +import type { ReactNode } from "react"; +import { env } from '@/env'; + +const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL); + +export function ConvexClientProvider({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/next/src/components/providers/index.tsx b/apps/next/src/components/providers/index.tsx new file mode 100644 index 0000000..809a7c6 --- /dev/null +++ b/apps/next/src/components/providers/index.tsx @@ -0,0 +1 @@ +export { ConvexClientProvider } from './ConvexClientProvider'; diff --git a/apps/next/src/env.ts b/apps/next/src/env.js similarity index 100% rename from apps/next/src/env.ts rename to apps/next/src/env.js diff --git a/apps/next/src/lib/metadata.ts b/apps/next/src/lib/metadata.ts new file mode 100644 index 0000000..955a984 --- /dev/null +++ b/apps/next/src/lib/metadata.ts @@ -0,0 +1,129 @@ +import type { Metadata } from 'next'; +import * as Sentry from '@sentry/nextjs'; + +export const generateMetadata = (): Metadata => { + return { + title: { + template: '%s | Convex Monorepo', + default: 'Convex Monorepo', + }, + description: 'A Convex Monorepo with Next.js & Expo', + applicationName: 'Convex Monorepo', + keywords: + 'Convex Monorepo,T3 Template, Nextjs, ' + + 'Tailwind, TypeScript, React, T3, Gib', + authors: [{ name: 'Gib', url: 'https://gbrown.org' }], + creator: 'Gib Brown', + publisher: 'Gib Brown', + formatDetection: { + email: false, + address: false, + telephone: false, + }, + robots: { + index: true, + follow: true, + nocache: false, + googleBot: { + index: true, + follow: true, + noimageindex: false, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, + }, + icons: { + icon: [ + { url: '/favicon.ico', type: 'image/x-icon', sizes: 'any' }, + { + url: '/favicon.ico', + type: 'image/x-icon', + sizes: 'any', + media: '(prefers-color-scheme: dark)', + }, + //{ + //url: '/appicon/icon.png', + //type: 'image/png', + //sizes: '192x192', + //}, + //{ + //url: '/appicon/icon.png', + //type: 'image/png', + //sizes: '192x192', + //media: '(prefers-color-scheme: dark)', + //}, + ], + //shortcut: [ + //{ + //url: '/appicon/icon.png', + //type: 'image/png', + //sizes: '192x192', + //}, + //{ + //url: '/appicon/icon.png', + //type: 'image/png', + //sizes: '192x192', + //media: '(prefers-color-scheme: dark)', + //}, + //], + //apple: [ + //{ + //url: 'appicon/icon.png', + //type: 'image/png', + //sizes: '192x192', + //}, + //{ + //url: 'appicon/icon.png', + //type: 'image/png', + //sizes: '192x192', + //media: '(prefers-color-scheme: dark)', + //}, + //], + //other: [ + //{ + //rel: 'apple-touch-icon-precomposed', + //url: '/appicon/icon-precomposed.png', + //type: 'image/png', + //sizes: '180x180', + //}, + //], + }, + other: { + ...Sentry.getTraceData(), + }, + //appleWebApp: { + //title: 'Convex Monorepo', + //statusBarStyle: 'black-translucent', + //startupImage: [ + //'/icons/apple/splash-768x1004.png', + //{ + //url: '/icons/apple/splash-1536x2008.png', + //media: '(device-width: 768px) and (device-height: 1024px)', + //}, + //], + //}, + verification: { + google: 'google', + yandex: 'yandex', + yahoo: 'yahoo', + }, + category: 'technology', + /* + appLinks: { + ios: { + url: 'https://techtracker.gbrown.org/ios', + app_store_id: 'com.gbrown.techtracker', + }, + android: { + package: 'https://techtracker.gbrown.org/android', + app_name: 'app_t3_template', + }, + web: { + url: 'https://techtracker.gbrown.org', + should_fallback: true, + }, + }, + */ + }; +}; diff --git a/apps/next/src/lib/middleware/ban-sus-ips.ts b/apps/next/src/lib/middleware/ban-sus-ips.ts new file mode 100644 index 0000000..72f6e98 --- /dev/null +++ b/apps/next/src/lib/middleware/ban-sus-ips.ts @@ -0,0 +1,199 @@ +import type { NextRequest } from 'next/server'; +import { NextResponse } from 'next/server'; + +// In-memory stores for tracking IPs (use Redis in production) +const ipAttempts = new Map(); +const ip404Attempts = new Map(); +const bannedIPs = new Set(); + +// Suspicious patterns that indicate malicious activity +const MALICIOUS_PATTERNS = [ + // Your existing patterns + /web-inf/i, + /\.jsp/i, + /\.php/i, + /puttest/i, + /WEB-INF/i, + /\.xml$/i, + /perl/i, + /xampp/i, + /phpwebgallery/i, + /FileManager/i, + /standalonemanager/i, + /h2console/i, + /WebAdmin/i, + /login_form\.php/i, + /%2e/i, + /%u002e/i, + /\.%00/i, + /\.\./, + /lcgi/i, + + // New patterns from your logs + /\/appliance\//i, + /bomgar/i, + /netburner-logo/i, + /\/ui\/images\//i, + /logon_merge/i, + /logon_t\.gif/i, + /login_top\.gif/i, + /theme1\/images/i, + /\.well-known\/acme-challenge\/.*\.jpg$/i, + /\.well-known\/pki-validation\/.*\.jpg$/i, + + // Path traversal and system file access patterns + /\/etc\/passwd/i, + /\/etc%2fpasswd/i, + /\/etc%5cpasswd/i, + /\/\/+etc/i, + /\\\\+.*etc/i, + /%2f%2f/i, + /%5c%5c/i, + /\/\/+/, + /\\\\+/, + /%00/i, + /%23/i, + + // Encoded path traversal attempts + /%2e%2e/i, + /%252e/i, + /%c0%ae/i, + /%c1%9c/i, +]; + +// Suspicious HTTP methods +const SUSPICIOUS_METHODS = ['TRACE', 'PUT', 'DELETE', 'PATCH']; + +const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute +const MAX_ATTEMPTS = 10; // Max suspicious requests per window +const BAN_DURATION = 30 * 60 * 1000; // 30 minutes + +// 404 rate limiting settings +const RATE_404_WINDOW = 2 * 60 * 1000; // 2 minutes +const MAX_404_ATTEMPTS = 10; // Max 404s before ban + +const getClientIP = (request: NextRequest): string => { + const forwarded = request.headers.get('x-forwarded-for'); + const realIP = request.headers.get('x-real-ip'); + const cfConnectingIP = request.headers.get('cf-connecting-ip'); + + if (forwarded) return (forwarded.split(',')[0] ?? '').trim(); + if (realIP) return realIP; + if (cfConnectingIP) return cfConnectingIP; + return request.headers.get('host') ?? 'unknown'; +}; + +const isPathSuspicious = (pathname: string): boolean => { + return MALICIOUS_PATTERNS.some((pattern) => pattern.test(pathname)); +}; + +const isMethodSuspicious = (method: string): boolean => { + return SUSPICIOUS_METHODS.includes(method); +}; + +const updateIPAttempts = (ip: string): boolean => { + const now = Date.now(); + const attempts = ipAttempts.get(ip); + + if (!attempts || now - attempts.lastAttempt > RATE_LIMIT_WINDOW) { + ipAttempts.set(ip, { count: 1, lastAttempt: now }); + return false; + } + + attempts.count++; + attempts.lastAttempt = now; + + if (attempts.count > MAX_ATTEMPTS) { + bannedIPs.add(ip); + ipAttempts.delete(ip); + + setTimeout(() => { + bannedIPs.delete(ip); + }, BAN_DURATION); + + return true; + } + + return false; +}; + +const update404Attempts = (ip: string): boolean => { + const now = Date.now(); + const attempts = ip404Attempts.get(ip); + + if (!attempts || now - attempts.lastAttempt > RATE_404_WINDOW) { + ip404Attempts.set(ip, { count: 1, lastAttempt: now }); + return false; + } + + attempts.count++; + attempts.lastAttempt = now; + + if (attempts.count > MAX_404_ATTEMPTS) { + bannedIPs.add(ip); + ip404Attempts.delete(ip); + + console.log( + `🔨 IP ${ip} banned for excessive 404 requests (${attempts.count} in ${RATE_404_WINDOW / 1000}s)`, + ); + + setTimeout(() => { + bannedIPs.delete(ip); + }, BAN_DURATION); + + return true; + } + + return false; +}; + +export const banSuspiciousIPs = (request: NextRequest): NextResponse | null => { + const { pathname } = request.nextUrl; + const method = request.method; + const ip = getClientIP(request); + + // Check if IP is already banned + if (bannedIPs.has(ip)) { + return new NextResponse('Access denied.', { status: 403 }); + } + + const isSuspiciousPath = isPathSuspicious(pathname); + const isSuspiciousMethod = isMethodSuspicious(method); + + // Handle suspicious activity + if (isSuspiciousPath || isSuspiciousMethod) { + const shouldBan = updateIPAttempts(ip); + + if (shouldBan) { + console.log(`🔨 IP ${ip} has been banned for suspicious activity`); + return new NextResponse('Access denied - IP banned. Please fuck off.', { + status: 403, + }); + } + + return new NextResponse('Not Found', { status: 404 }); + } + + return null; +}; + +// Call this function when you detect a 404 response +export const handle404Response = ( + request: NextRequest, +): NextResponse | null => { + const ip = getClientIP(request); + + if (bannedIPs.has(ip)) { + return new NextResponse('Access denied.', { status: 403 }); + } + + const shouldBan = update404Attempts(ip); + + if (shouldBan) { + return new NextResponse('Access denied - IP banned for excessive 404s.', { + status: 403, + }); + } + + return null; +}; diff --git a/apps/next/src/middleware.ts b/apps/next/src/middleware.ts new file mode 100644 index 0000000..a44a019 --- /dev/null +++ b/apps/next/src/middleware.ts @@ -0,0 +1,34 @@ +import { + convexAuthNextjsMiddleware, + createRouteMatcher, + nextjsMiddlewareRedirect, +} from '@convex-dev/auth/nextjs/server'; +import { banSuspiciousIPs } from '@/lib/middleware/ban-sus-ips'; + +const isSignInPage = createRouteMatcher(['/sign-in']); +const isProtectedRoute = createRouteMatcher(['/', '/profile']); + +export default convexAuthNextjsMiddleware( + async (request, { convexAuth }) => { + const banResponse = banSuspiciousIPs(request); + if (banResponse) return banResponse; + if (isSignInPage(request) && (await convexAuth.isAuthenticated())) { + return nextjsMiddlewareRedirect(request, '/'); + } + if (isProtectedRoute(request) && !(await convexAuth.isAuthenticated())) { + return nextjsMiddlewareRedirect(request, '/sign-in'); + } + }, + { cookieConfig: { maxAge: 60 * 60 * 24 * 30 } }, +); + +export const config = { + // The following matcher runs middleware on all routes + // except static assets. + matcher: [ + '/((?!_next/static|_next/image|favicon.ico|monitoring-tunnel|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', + '/((?!.*\\..*|_next).*)', + '/', + '/(api|trpc)(.*)', + ], +}; diff --git a/bun.lock b/bun.lock index 0bd3575..129a43c 100644 --- a/bun.lock +++ b/bun.lock @@ -1,12 +1,12 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "convex-turbo", "devDependencies": { "@gib/prettier-config": "workspace:", "@turbo/gen": "^2.7.3", + "baseline-browser-mapping": "^2.9.14", "dotenv-cli": "^10.0.0", "prettier": "catalog:", "turbo": "^2.7.3", @@ -91,8 +91,8 @@ "@types/node": "catalog:", "@types/react": "catalog:react19", "@types/react-dom": "catalog:react19", + "baseline-browser-mapping": "^2.9.14", "eslint": "catalog:", - "jiti": "^2.5.1", "prettier": "catalog:", "tailwindcss": "catalog:", "tw-animate-css": "^1.4.0", @@ -240,7 +240,7 @@ "react19": { "@types/react": "~19.1.0", "@types/react-dom": "~19.1.0", - "react": "^19.1.4", + "react": "19.1.4", "react-dom": "19.1.4", }, }, @@ -1605,7 +1605,7 @@ "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.8.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg=="], "basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="], @@ -2689,7 +2689,7 @@ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + "react": ["react@19.1.4", "", {}, "sha512-DHINL3PAmPUiK1uszfbKiXqfE03eszdt5BpVSuEAHb5nfmNPwnsy7g39h2t8aXFc/Bv99GH81s+j8dobtD+jOw=="], "react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="], @@ -3397,6 +3397,8 @@ "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + "browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q=="], + "builtins/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "chalk-template/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -3623,6 +3625,8 @@ "update-browserslist-db/picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "usesend-js/react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + "webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], "webpack/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], diff --git a/package.json b/package.json index b891391..ec3dfc4 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "react19": { "@types/react": "~19.1.0", "@types/react-dom": "~19.1.0", - "react": "^19.1.4", + "react": "19.1.4", "react-dom": "19.1.4" } }, @@ -56,6 +56,7 @@ "devDependencies": { "@gib/prettier-config": "workspace:", "@turbo/gen": "^2.7.3", + "baseline-browser-mapping": "^2.9.14", "dotenv-cli": "^10.0.0", "prettier": "catalog:", "turbo": "^2.7.3", diff --git a/packages/backend/convex/_generated/api.d.ts b/packages/backend/convex/_generated/api.d.ts index b74dda9..6440f2c 100644 --- a/packages/backend/convex/_generated/api.d.ts +++ b/packages/backend/convex/_generated/api.d.ts @@ -8,15 +8,18 @@ * @module */ +import type * as auth from "../auth.js"; +import type * as custom_auth_index from "../custom/auth/index.js"; +import type * as custom_auth_providers_password from "../custom/auth/providers/password.js"; +import type * as custom_auth_providers_usesend from "../custom/auth/providers/usesend.js"; +import type * as http from "../http.js"; +import type * as utils from "../utils.js"; + import type { ApiFromModules, FilterApi, FunctionReference, -} from 'convex/server'; - -import type * as notes from '../notes.js'; -import type * as openai from '../openai.js'; -import type * as utils from '../utils.js'; +} from "convex/server"; /** * A utility for referencing Convex functions in your app's API. @@ -27,15 +30,22 @@ import type * as utils from '../utils.js'; * ``` */ declare const fullApi: ApiFromModules<{ - notes: typeof notes; - openai: typeof openai; + auth: typeof auth; + "custom/auth/index": typeof custom_auth_index; + "custom/auth/providers/password": typeof custom_auth_providers_password; + "custom/auth/providers/usesend": typeof custom_auth_providers_usesend; + http: typeof http; utils: typeof utils; }>; +declare const fullApiWithMounts: typeof fullApi; + export declare const api: FilterApi< - typeof fullApi, - FunctionReference + typeof fullApiWithMounts, + FunctionReference >; export declare const internal: FilterApi< - typeof fullApi, - FunctionReference + typeof fullApiWithMounts, + FunctionReference >; + +export declare const components: {}; diff --git a/packages/backend/convex/_generated/api.js b/packages/backend/convex/_generated/api.js index 2e31a22..44bf985 100644 --- a/packages/backend/convex/_generated/api.js +++ b/packages/backend/convex/_generated/api.js @@ -8,7 +8,7 @@ * @module */ -import { anyApi } from 'convex/server'; +import { anyApi, componentsGeneric } from "convex/server"; /** * A utility for referencing Convex functions in your app's API. @@ -20,3 +20,4 @@ import { anyApi } from 'convex/server'; */ export const api = anyApi; export const internal = anyApi; +export const components = componentsGeneric(); diff --git a/packages/backend/convex/_generated/dataModel.d.ts b/packages/backend/convex/_generated/dataModel.d.ts index bebbb31..8541f31 100644 --- a/packages/backend/convex/_generated/dataModel.d.ts +++ b/packages/backend/convex/_generated/dataModel.d.ts @@ -11,12 +11,11 @@ import type { DataModelFromSchemaDefinition, DocumentByName, - SystemTableNames, TableNamesInDataModel, -} from 'convex/server'; -import type { GenericId } from 'convex/values'; - -import schema from '../schema.js'; + SystemTableNames, +} from "convex/server"; +import type { GenericId } from "convex/values"; +import schema from "../schema.js"; /** * The names of all of your Convex tables. diff --git a/packages/backend/convex/_generated/server.d.ts b/packages/backend/convex/_generated/server.d.ts index 2403186..b5c6828 100644 --- a/packages/backend/convex/_generated/server.d.ts +++ b/packages/backend/convex/_generated/server.d.ts @@ -10,17 +10,23 @@ import { ActionBuilder, - GenericActionCtx, - GenericDatabaseReader, - GenericDatabaseWriter, - GenericMutationCtx, - GenericQueryCtx, + AnyComponents, HttpActionBuilder, MutationBuilder, QueryBuilder, -} from 'convex/server'; + GenericActionCtx, + GenericMutationCtx, + GenericQueryCtx, + GenericDatabaseReader, + GenericDatabaseWriter, + FunctionReference, +} from "convex/server"; +import type { DataModel } from "./dataModel.js"; -import type { DataModel } from './dataModel.js'; +type GenericCtx = + | GenericActionCtx + | GenericMutationCtx + | GenericQueryCtx; /** * Define a query in this Convex app's public API. @@ -30,7 +36,7 @@ import type { DataModel } from './dataModel.js'; * @param func - The query function. It receives a {@link QueryCtx} as its first argument. * @returns The wrapped query. Include this as an `export` to name it and make it accessible. */ -export declare const query: QueryBuilder; +export declare const query: QueryBuilder; /** * Define a query that is only accessible from other Convex functions (but not from the client). @@ -40,7 +46,7 @@ export declare const query: QueryBuilder; * @param func - The query function. It receives a {@link QueryCtx} as its first argument. * @returns The wrapped query. Include this as an `export` to name it and make it accessible. */ -export declare const internalQuery: QueryBuilder; +export declare const internalQuery: QueryBuilder; /** * Define a mutation in this Convex app's public API. @@ -50,7 +56,7 @@ export declare const internalQuery: QueryBuilder; * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. */ -export declare const mutation: MutationBuilder; +export declare const mutation: MutationBuilder; /** * Define a mutation that is only accessible from other Convex functions (but not from the client). @@ -60,7 +66,7 @@ export declare const mutation: MutationBuilder; * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. */ -export declare const internalMutation: MutationBuilder; +export declare const internalMutation: MutationBuilder; /** * Define an action in this Convex app's public API. @@ -73,7 +79,7 @@ export declare const internalMutation: MutationBuilder; * @param func - The action. It receives an {@link ActionCtx} as its first argument. * @returns The wrapped action. Include this as an `export` to name it and make it accessible. */ -export declare const action: ActionBuilder; +export declare const action: ActionBuilder; /** * Define an action that is only accessible from other Convex functions (but not from the client). @@ -81,7 +87,7 @@ export declare const action: ActionBuilder; * @param func - The function. It receives an {@link ActionCtx} as its first argument. * @returns The wrapped function. Include this as an `export` to name it and make it accessible. */ -export declare const internalAction: ActionBuilder; +export declare const internalAction: ActionBuilder; /** * Define an HTTP action. diff --git a/packages/backend/convex/_generated/server.js b/packages/backend/convex/_generated/server.js index e81ccfd..4a21df4 100644 --- a/packages/backend/convex/_generated/server.js +++ b/packages/backend/convex/_generated/server.js @@ -11,12 +11,13 @@ import { actionGeneric, httpActionGeneric, + queryGeneric, + mutationGeneric, internalActionGeneric, internalMutationGeneric, internalQueryGeneric, - mutationGeneric, - queryGeneric, -} from 'convex/server'; + componentsGeneric, +} from "convex/server"; /** * Define a query in this Convex app's public API. diff --git a/packages/backend/package.json b/packages/backend/package.json index 6cca95d..7fb0193 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -6,15 +6,18 @@ "description": "Convex Backend for Monorepo", "author": "Gib", "license": "MIT", - "exports": {}, + "exports": { + "./types" : "./types/index.ts" + }, "scripts": { - "dev": "convex dev", - "dev:tunnel": "convex dev", - "setup": "convex dev --until-success", + "dev": "bun with-env convex dev", + "dev:tunnel": "bun with-env convex dev", + "setup": "bun with-env convex dev --until-success", "clean": "git clean -xdf .cache .turbo dist node_modules", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint --flag unstable_native_nodejs_ts_config", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "with-env": "dotenv -e ../../.env --" }, "dependencies": { "@oslojs/crypto": "^1.0.1", diff --git a/packages/backend/scripts/generateKeys.mjs b/packages/backend/scripts/generateKeys.mjs new file mode 100755 index 0000000..2116061 --- /dev/null +++ b/packages/backend/scripts/generateKeys.mjs @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +import { exportJWK, exportPKCS8, generateKeyPair } from "jose"; + +const keys = await generateKeyPair("RS256", { + extractable: true, +}); +const privateKey = await exportPKCS8(keys.privateKey); +const publicKey = await exportJWK(keys.publicKey); +const jwks = JSON.stringify({ keys: [{ use: "sig", ...publicKey }] }); + +process.stdout.write( + `JWT_PRIVATE_KEY="${privateKey.trimEnd().replace(/\n/g, " ")}"`, +); +process.stdout.write("\n"); +process.stdout.write(`JWKS=${jwks}`); +process.stdout.write("\n"); diff --git a/packages/backend/types/auth.ts b/packages/backend/types/auth.ts new file mode 100644 index 0000000..287aa01 --- /dev/null +++ b/packages/backend/types/auth.ts @@ -0,0 +1,4 @@ +export const PASSWORD_MIN = 8; +export const PASSWORD_MAX = 100; +export const PASSWORD_REGEX = + /^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u; diff --git a/packages/backend/types/index.ts b/packages/backend/types/index.ts new file mode 100644 index 0000000..0ac8378 --- /dev/null +++ b/packages/backend/types/index.ts @@ -0,0 +1,5 @@ +export { + PASSWORD_MIN, + PASSWORD_MAX, + PASSWORD_REGEX, +} from './auth';