signup is now complete
This commit is contained in:
parent
4f38e3bbd1
commit
bcdad3442c
8 changed files with 108 additions and 21 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
10
package.json
10
package.json
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
5
src/lib/oauth.ts
Normal 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();
|
|
@ -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');
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue