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": {
"@deno/kv": "^0.8.1",
"@olli/kvdex": "npm:@jsr/olli__kvdex",
"@oxi/result": "npm:@jsr/oxi__result",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@ts-rex/argon2": "npm:@jsr/ts-rex__argon2",
"arctic": "^1.9.2",
"autoprefixer": "^10.4.20",
"daisyui": "^4.12.10",
"hono": "^4.5.5",
"lucia": "^3.2.0",
"nanoid": "^5.0.7",
"oslo": "^1.2.1",
"postcss": "^8.4.41",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.5",
@ -36,5 +39,8 @@
"type": "module",
"trustedDependencies": [
"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 { z } from "zod"
const user = z.object({
export const user = z.object({
displayName: z.string(),
username: z.string(),
password: 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(),
userId: z.string(),
})
export const kv = await openKv()
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, {
indices: {
userId: 'secondary'
@ -24,6 +38,6 @@ export const db = kvdex(kv, {
pfp: collection(model<Uint8Array>()),
chat: {
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 { z } from 'zod';
import { hash } from "@ts-rex/argon2"
import { db } from '$lib/db.js';
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 "-"`),
password: z.string().min(8, "must be atleast 8 characters")
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").max(255)
});
export async function load() {
@ -17,17 +20,30 @@ export const actions = {
const form = await superValidate(request, zod(schema));
if (!form.valid) return fail(400, { form });
const { username, password } = form.data
// TODO: Login user
return message(form, 'Login form submitted');
},
signup: async ({ request }) => {
const form = await superValidate(request, zod(schema));
console.log(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');
}
}

View file

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

View file

@ -1,6 +1,10 @@
import adapter from '@sveltejs/adapter-auto';
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} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors