From 37d13fd42b2d598e540aff3a969b7a1bc5cc4dc0 Mon Sep 17 00:00:00 2001 From: April Hall Date: Sun, 9 Feb 2025 19:58:50 -0500 Subject: [PATCH] feat: Partial user auth --- .env.example | 2 + .gitignore | 3 + .prettierrc | 2 +- bun.lockb | Bin 194153 -> 216472 bytes package.json | 4 ++ src/lib/functions/auth/auth-client.ts | 5 ++ src/lib/hooks.server.ts | 13 +++- src/lib/server/db/auth.ts | 10 +++ src/lib/server/db/index.ts | 1 - src/lib/types/schema.ts | 35 +++++++++++ src/routes/login/+page.server.ts | 42 +++++++++++++ src/routes/login/+page.svelte | 62 ++++++++++++++++++ src/routes/signup/+page.server.ts | 44 +++++++++++++ src/routes/signup/+page.svelte | 87 ++++++++++++++++++++++++++ 14 files changed, 305 insertions(+), 5 deletions(-) create mode 100644 .env.example create mode 100644 src/lib/functions/auth/auth-client.ts create mode 100644 src/lib/server/db/auth.ts create mode 100644 src/routes/login/+page.server.ts create mode 100644 src/routes/login/+page.svelte create mode 100644 src/routes/signup/+page.server.ts create mode 100644 src/routes/signup/+page.svelte diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e20558f --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +BETTER_AUTH_SECRET=XXX # Generate your BetterAuth secret at https://better-auth.vercel.app/docs/installation#set-environment-variables +BETTER_AUTH_URL=http://localhost:3005 #Base URL of your app \ No newline at end of file diff --git a/.gitignore b/.gitignore index ada5560..cd41439 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Sqlite +src/lib/server/db/users.db diff --git a/.prettierrc b/.prettierrc index fdd63a3..6afb3c3 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,7 +5,7 @@ "semi": true, "singleQuote": true, "trailingComma": "all", - "bracketSameLine": true, + "bracketSameLine": false, "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], "overrides": [ { diff --git a/bun.lockb b/bun.lockb index faadb5daebabab27fa36d5b781d6142d018e19ce..d876216004b9ce9bb7321dcbc907e8405c01d126 100755 GIT binary patch delta 46825 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

<4VT+<2Ai^J#RN) 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 b@Dbs*zw{J-m~8)lP7C9+`xoC4-?sc8N(=qk delta 33367 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 { + // Initialize WebSocket server if not building if (!building) { + // @ts-expect-error hides incorrect error startupSocketIOServer(event.locals.httpServer); + // @ts-expect-error hides incorrect error event.locals.io = io; } - return resolve(event, { - filterSerializedResponseHeaders: (name) => name === 'content-type', - }); + + // Handle authentication + const response = await svelteKitHandler({ event, resolve, auth }); + return response; }) satisfies Handle; diff --git a/src/lib/server/db/auth.ts b/src/lib/server/db/auth.ts new file mode 100644 index 0000000..5d17dbd --- /dev/null +++ b/src/lib/server/db/auth.ts @@ -0,0 +1,10 @@ +import { betterAuth } from 'better-auth'; +import Database from 'better-sqlite3'; + +export const auth = betterAuth({ + database: new Database('./src/lib/server/db/users.db'), + emailAndPassword: { + enabled: true, + autoSignIn: true, + }, +}); diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index 6986542..84d5f2e 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -29,7 +29,6 @@ class Db { } try { - await this.client.execute(`CREATE KEYSPACE IF NOT EXISTS users WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1};`); await this.client.execute(`CREATE KEYSPACE IF NOT EXISTS channels WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1};`); } catch (e) { console.log(`Error generating keyspaces: ${e as Error}`); diff --git a/src/lib/types/schema.ts b/src/lib/types/schema.ts index cb6e77b..de5d456 100644 --- a/src/lib/types/schema.ts +++ b/src/lib/types/schema.ts @@ -4,4 +4,39 @@ export const newChannelSchema = z.object({ channelName: z.string().min(1, 'Channel name is required'), }); +export const signupSchema = z + .object({ + email: z.string().nonempty('An email is required').email('Please enter a valid email.'), + username: z + .string() + .min(3, 'Username must be at least 3 characters.') + .regex(/^(?![A-Z])/gm, 'Username cannot contain uppercase letters') + .regex(/^(?=[a-z0-9-_]+$)/gm, 'Username cannot contain special characters'), + password: z + .string() + .min(8, 'Password must be at least 8 characters.') + .regex(/(?=.*[A-Z])/gm, 'Password must contain at uppercase letter.') + .regex(/(?=.*[a-z])/gm, 'Password must contain at lowercase letter.') + .regex(/(?=.*\d)/gm, 'Password must contain at least one number.') + .regex(/(?=.*\W)/gm, 'Password must contain at least one special character'), + verify: z.string().nonempty('Passwords do not match.'), + }) + .refine((schema) => schema.verify === schema.password, { + message: "Passwords don't match", + path: ['verify'], + }); + +export const loginSchema = z.object({ + email: z.string().nonempty('An email is required').email('Please enter a valid email.'), + password: z + .string() + .min(8, 'Password must be at least 8 characters.') + .regex(/(?=.*[A-Z])/gm, 'Password must contain at uppercase letter.') + .regex(/(?=.*[a-z])/gm, 'Password must contain at lowercase letter.') + .regex(/(?=.*\d)/gm, 'Password must contain at least one number.') + .regex(/(?=.*\W)/gm, 'Password must contain at least one special character'), +}); + export type NewChannelSchema = typeof newChannelSchema; +export type SignUpSchema = typeof signupSchema; +export type LogInSchema = typeof loginSchema; diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts new file mode 100644 index 0000000..9131f67 --- /dev/null +++ b/src/routes/login/+page.server.ts @@ -0,0 +1,42 @@ +import { loginSchema } from '$lib/types/schema'; +import { message, setError, superValidate, fail } from 'sveltekit-superforms'; +import { zod } from 'sveltekit-superforms/adapters'; +import type { Actions } from './$types'; +import { auth } from '$lib/server/db/auth'; +import { APIError } from 'better-auth/api'; + +export const load = async () => { + const form = await superValidate(zod(loginSchema)); + return { form }; +}; + +export const actions = { + login: async ({ request }) => { + const form = await superValidate(request, zod(loginSchema)); + const email = form.data.email; + const password = form.data.password; + + if (!form.valid) { + return fail(400, { form }); + } + + try { + await auth.api.signInEmail({ + body: { + email, + password, + }, + }); + } catch (e) { + if (e instanceof APIError) { + if (e.message === 'API Error: UNAUTHORIZED Invalid email or password') { + return setError(form, 'password', 'Invalid email or password', { + status: 401, + }); + } + } + } + + return message(form, 'Successfuly signed in.'); + }, +} satisfies Actions; diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte new file mode 100644 index 0000000..eded4c3 --- /dev/null +++ b/src/routes/login/+page.svelte @@ -0,0 +1,62 @@ + + + + SVChat | Log In + + +
    + + + Log In + Enter your email below to login to your account + + +
    +
    + + + {#if $errors.email}{$errors.email[0]}{/if} +
    +
    + + + {#if $errors.password}{$errors.password[0]}{/if} +
    + +
    +

    + {#if $message}{$message}{/if} +

    +
    + Don't have an account? + Sign up +
    +
    +
    +
    diff --git a/src/routes/signup/+page.server.ts b/src/routes/signup/+page.server.ts new file mode 100644 index 0000000..8141d07 --- /dev/null +++ b/src/routes/signup/+page.server.ts @@ -0,0 +1,44 @@ +import { signupSchema } from '$lib/types/schema'; +import { message, setError, superValidate, fail } from 'sveltekit-superforms'; +import { zod } from 'sveltekit-superforms/adapters'; +import type { Actions } from './$types'; +import { auth } from '$lib/server/db/auth'; +import { APIError } from 'better-auth/api'; + +export const load = async () => { + const form = await superValidate(zod(signupSchema)); + return { form }; +}; + +export const actions = { + signup: async ({ request }) => { + const form = await superValidate(request, zod(signupSchema)); + const email = form.data.email; + const password = form.data.password; + const name = form.data.username; + + if (!form.valid) { + return fail(400, { form }); + } + + try { + await auth.api.signUpEmail({ + body: { + name, + email, + password, + }, + }); + } catch (e) { + if (e instanceof APIError) { + if (e.message === 'API Error: UNAUTHORIZED Invalid email or password') { + return setError(form, 'verify', 'Invalid email or password', { + status: 401, + }); + } + } + } + + return message(form, 'Successfuly signed in.'); + }, +} satisfies Actions; diff --git a/src/routes/signup/+page.svelte b/src/routes/signup/+page.svelte new file mode 100644 index 0000000..d331e2a --- /dev/null +++ b/src/routes/signup/+page.svelte @@ -0,0 +1,87 @@ + + + + SVChat | Sign Up + + +
    + + + Sign Up + Enter your email below to create an account + + +
    +
    + + + {#if $errors.username}{$errors.username[0]}{/if} +
    +
    + + + {#if $errors.email}{$errors.email[0]}{/if} +
    +
    +
    + +
    + + {#if $errors.password}{$errors.password[0]}{/if} +
    +
    +
    + +
    + + {#if $errors.verify}{$errors.verify[0]}{/if} +
    + +
    +

    + {#if $message}{$message}{/if} +

    +
    + Already have an account? + Log in +
    +
    +
    +