signup is now complete

This commit is contained in:
Chad Freeman 2024-08-17 13:04:03 -04:00
parent 4f38e3bbd1
commit bcdad3442c
8 changed files with 108 additions and 21 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -14,14 +14,17 @@
"devDependencies": { "devDependencies": {
"@deno/kv": "^0.8.1", "@deno/kv": "^0.8.1",
"@olli/kvdex": "npm:@jsr/olli__kvdex", "@olli/kvdex": "npm:@jsr/olli__kvdex",
"@oxi/result": "npm:@jsr/oxi__result",
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@ts-rex/argon2": "npm:@jsr/ts-rex__argon2", "@ts-rex/argon2": "npm:@jsr/ts-rex__argon2",
"arctic": "^1.9.2",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"daisyui": "^4.12.10", "daisyui": "^4.12.10",
"hono": "^4.5.5", "hono": "^4.5.5",
"lucia": "^3.2.0", "nanoid": "^5.0.7",
"oslo": "^1.2.1",
"postcss": "^8.4.41", "postcss": "^8.4.41",
"prettier": "^3.3.2", "prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.5", "prettier-plugin-svelte": "^3.2.5",
@ -36,5 +39,8 @@
"type": "module", "type": "module",
"trustedDependencies": [ "trustedDependencies": [
"svelte-preprocess" "svelte-preprocess"
] ],
"dependencies": {
"@petamoriken/float16": "^3.8.7"
}
} }

View file

@ -0,0 +1,30 @@
import type { z } from "zod"
import { db, session } from "./db"
import { Err, Ok, Result } from "@oxi/result"
import type { FlatDocumentData } from "@olli/kvdex"
import { nanoid } from "nanoid"
import { TimeSpan, createDate } from "oslo"
import { alphabet, generateRandomString } from "oslo/crypto"
const sessionTimeSpan = new TimeSpan(1, 'w')
async function createSessionForUser(userId: string): Promise<Result<FlatDocumentData<z.infer<typeof session>, string>, null>> {
const user = (await db.user.find(userId))?.flat()
if(!user) return Err(null)
const sessionId = generateRandomString(10, alphabet("0-9", "a-z"))
const createdSession = (await db.session.set(sessionId, {
expiresAt: createDate(sessionTimeSpan),
userId
}, { expireIn: sessionTimeSpan.milliseconds() }))
if(!createdSession.ok) return Err(null)
return Ok((await db.session.find(sessionId))?.flat()!)
}
async function deleteSession(sessionId: string): Promise<void> {
await db.session.delete(sessionId)
}
export {
createSessionForUser,
deleteSession
}

View file

@ -2,20 +2,34 @@ import { kvdex as kvdex, collection, model } from "@olli/kvdex"
import { openKv } from "@deno/kv" import { openKv } from "@deno/kv"
import { z } from "zod" import { z } from "zod"
const user = z.object({ export const user = z.object({
displayName: z.string(),
username: z.string(), username: z.string(),
password: z.string(),
id: z.string(), id: z.string(),
password: z.optional(z.string()),
oauth_github_id: z.optional(z.string()),
oauth_google_id: z.optional(z.string()),
oauth_discord_id: z.optional(z.string())
}) })
const session = z.object({ export const publicUser = user.pick({ displayName: true, id: true })
export const session = z.object({
expiresAt: z.date(), expiresAt: z.date(),
userId: z.string(), userId: z.string(),
}) })
export const kv = await openKv() export const kv = await openKv()
export const db = kvdex(kv, { export const db = kvdex(kv, {
user: collection(user), user: collection(user, {
idGenerator: ({ id }) => id,
indices: {
username: 'primary',
oauth_github_id: 'primary',
oauth_google_id: 'primary',
oauth_discord_id: 'primary'
}
}),
session: collection(session, { session: collection(session, {
indices: { indices: {
userId: 'secondary' userId: 'secondary'
@ -24,6 +38,6 @@ export const db = kvdex(kv, {
pfp: collection(model<Uint8Array>()), pfp: collection(model<Uint8Array>()),
chat: { chat: {
boxes: collection(model<{ userID: string, text: string }>()), boxes: collection(model<{ userID: string, text: string }>()),
users: collection(user.pick({ username: true, id: true })) users: collection(publicUser)
} }
}) })

5
src/lib/oauth.ts Normal file
View file

@ -0,0 +1,5 @@
import { Google, Discord, GitHub } from "arctic"
export const google = new Google();
export const discord = new Discord();
export const github = new GitHub();

View file

@ -1,10 +1,13 @@
import { fail, message, superValidate } from 'sveltekit-superforms'; import { alphabet, generateRandomString } from 'oslo/crypto';
import { fail, message, setError, superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters'; import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod'; import { z } from 'zod';
import { hash } from "@ts-rex/argon2"
import { db } from '$lib/db.js';
const schema = z.object({ const schema = z.object({
username: z.string().min(4, "must be atleast 4 characters").max(32, "must be less than 32 characters").regex(/^[a-z0-9_\-]{4,32}$/i, `must be alphanumeric, with the exception of "_" and "-"`), username: z.string().min(4, "must be atleast 4 characters").max(32, "must be less than 32 characters").regex(/^[a-z0-9_\-]+$/i, `must be alphanumeric, with the exception of "_" and "-"`),
password: z.string().min(8, "must be atleast 8 characters") password: z.string().min(8, "must be atleast 8 characters").max(255)
}); });
export async function load() { export async function load() {
@ -17,17 +20,30 @@ export const actions = {
const form = await superValidate(request, zod(schema)); const form = await superValidate(request, zod(schema));
if (!form.valid) return fail(400, { form }); if (!form.valid) return fail(400, { form });
const { username, password } = form.data
// TODO: Login user // TODO: Login user
return message(form, 'Login form submitted'); return message(form, 'Login form submitted');
}, },
signup: async ({ request }) => { signup: async ({ request }) => {
const form = await superValidate(request, zod(schema)); const form = await superValidate(request, zod(schema));
console.log(form)
if (!form.valid) return fail(400, { form }); if (!form.valid) return fail(400, { form });
const { username, password } = form.data
const userId = generateRandomString(10, alphabet("0-9", "a-z"))
const passwordHash = hash(password)
const user = (await db.user.findByPrimaryIndex('username', username))?.flat()
if(user) return setError(form, 'Username already exists')
console.log(userId, password, passwordHash);
console.log(await db.user.set(userId, {
displayName: username,
username,
id: userId,
password: passwordHash
}))
// TODO: Login user
return message(form, 'Signup form submitted'); return message(form, 'Signup form submitted');
} }
} }

View file

@ -3,6 +3,16 @@
import { page } from "$app/stores" import { page } from "$app/stores"
const { data } = $props(); const { data } = $props();
const { enhance, message, constraints, errors, form } = superForm(data.form); const { enhance, message, constraints, errors, form } = superForm(data.form);
let password = $state('')
let username = $state('')
$effect(() => {
form.update(f => {
f.username = username
f.password = password
console.log(f)
return f
})
})
</script> </script>
<div class="h-[100vh] flex items-center justify-center"> <div class="h-[100vh] flex items-center justify-center">
@ -56,7 +66,7 @@
</div> </div>
<span class="divider">or</span> <span class="divider">or</span>
<form class="flex-col flex gap-y-4" method="post" use:enhance> <form class="flex-col flex gap-y-4" method="post" use:enhance>
<label class="input input-bordered flex items-center gap-2"> <label class="input input-bordered flex items-center gap-2" class:input-error={!!$errors.username}>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" viewBox="0 0 16 16"
@ -69,16 +79,17 @@
</svg> </svg>
<input <input
bind:value={$form.username} bind:value={username}
aria-invalid={$errors.username ? 'true' : undefined} aria-invalid={$errors.username ? 'true' : undefined}
name="username"
type="text" type="text"
class="grow placeholder:text-base-content/20" class="grow placeholder:text-base-content/20"
placeholder="kaii" /> placeholder="kaii" />
</label> </label>
<span <span
class="opacity-0 hidden transition-opacity duration-1000 text-error" class="opacity-0 hidden transition-opacity duration-1000 text-error"
class:showerror={$errors.username}>^ {$errors.username?.join(' & ')}</span> class:showerror={$errors.username}>{$errors.username?.join(' & ')}</span>
<label class="input input-bordered flex items-center gap-2"> <label class="input input-bordered flex items-center gap-2" class:input-error={!!$errors.password}>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" viewBox="0 0 16 16"
@ -90,15 +101,16 @@
clip-rule="evenodd"></path> clip-rule="evenodd"></path>
</svg> </svg>
<input <input
bind:value={$form.password} bind:value={password}
aria-invalid={$errors.password ? 'true' : undefined} aria-invalid={$errors.password ? 'true' : undefined}
name="password"
type="password" type="password"
class="grow placeholder:text-base-content/20" class="grow placeholder:text-base-content/20"
placeholder="verygoodpassword" /> placeholder="verygoodpassword" />
</label> </label>
<span <span
class="opacity-0 hidden transition-opacity duration-1000 text-error" class="opacity-0 hidden transition-opacity duration-1000 text-error"
class:showerror={$errors.password}>^ {$errors.password}</span> class:showerror={$errors.password}>{$errors.password}</span>
<div class="flex flex-row gap-x-4"> <div class="flex flex-row gap-x-4">
<button formaction="?/signup" type="submit" class="btn btn-secondary flex-grow"> <button formaction="?/signup" type="submit" class="btn btn-secondary flex-grow">
sign up sign up

View file

@ -1,6 +1,10 @@
import adapter from '@sveltejs/adapter-auto'; import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { Float16Array } from "@petamoriken/float16"
// kvdex uses float16array under the hood (doesn't exist in node), filling that in here so it works during dev
globalThis.Float16Array = Float16Array
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors // Consult https://kit.svelte.dev/docs/integrations#preprocessors