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": {
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 { 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
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 { 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');
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue