Skip to content

Commit

Permalink
feat(app/sveltekit-example-app): add sign-up flow (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpellegrini authored Apr 8, 2024
1 parent 1bb686d commit 8c3a578
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 17 deletions.
8 changes: 6 additions & 2 deletions apps/sveltekit-example-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,24 @@
"typecheck": "svelte-check --tsconfig ./tsconfig.json"
},
"dependencies": {
"@packages/example-pkg": "workspace:*"
"@packages/example-pkg": "workspace:*",
"@packages/lucia-auth": "workspace:*"
},
"devDependencies": {
"@sveltejs/adapter-auto": "3.2.0",
"@sveltejs/kit": "2.5.5",
"@sveltejs/vite-plugin-svelte": "3.0.2",
"@tailwindcss/forms": "0.5.7",
"@toolchain/eslint-config": "workspace:*",
"@toolchain/vitest-config": "workspace:*",
"autoprefixer": "10.4.19",
"postcss": "8.4.38",
"svelte": "4.2.12",
"svelte-check": "3.6.9",
"sveltekit-superforms": "2.12.2",
"tailwindcss": "3.4.3",
"tslib": "2.6.2",
"vite": "5.2.8"
"vite": "5.2.8",
"zod": "3.22.4"
}
}
7 changes: 6 additions & 1 deletion apps/sveltekit-example-app/src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
import type { Session, User } from '@packages/lucia-auth'

declare global {
namespace App {
// interface Error {}
// interface Locals {}
interface Locals {
user: User | null
session: Session | null
}
// interface PageData {}
// interface PageState {}
// interface Platform {}
Expand Down
35 changes: 35 additions & 0 deletions apps/sveltekit-example-app/src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { lucia } from '@packages/lucia-auth'

import type { Handle } from '@sveltejs/kit'

export const handle: Handle = async ({ event, resolve }) => {
const sessionId = event.cookies.get(lucia.sessionCookieName)
if (!sessionId) {
event.locals.user = null
event.locals.session = null
return resolve(event)
}

const { user, session } = await lucia.validateSession(sessionId)

if (session?.fresh) {
const sessionCookie = lucia.createSessionCookie(session.id)
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes,
})
}

if (!session) {
const sessionCookie = lucia.createBlankSessionCookie()
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes,
})
}

event.locals.user = user
event.locals.session = session

return resolve(event)
}
15 changes: 14 additions & 1 deletion apps/sveltekit-example-app/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
// place files you want to import through the `$lib` alias in this folder.
export {}
import { z } from 'zod'

const password = z.string().min(6).max(255)

export const schema = z
.object({
email: z.string().email(),
password: password,
password_confirm: password,
})
.refine((data) => data.password === data.password_confirm, {
message: 'Passwords do not match',
path: ['password_confirm'],
})
41 changes: 41 additions & 0 deletions apps/sveltekit-example-app/src/routes/sign-up/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { fail } from '@sveltejs/kit'
import { message, setError, superValidate } from 'sveltekit-superforms'
import { zod } from 'sveltekit-superforms/adapters'

import { schema } from '$lib'
import { lucia } from '@packages/lucia-auth'
import { createUser } from '@packages/lucia-auth/repository'

import type { Actions, PageServerLoad } from './$types.js'

export const load = (async () => {
const form = await superValidate(zod(schema))
return { form }
}) satisfies PageServerLoad

export const actions = {
default: async ({ request, cookies }) => {
const form = await superValidate(request, zod(schema))

if (!form.valid) {
return fail(400, { form })
}

try {
const userId = await createUser({ username: form.data.email, password: form.data.password })
if (userId) {
const session = await lucia.createSession(userId, {})
const sessionCookie = lucia.createSessionCookie(session.id)
cookies.set(sessionCookie.name, sessionCookie.value, {
path: '.',
...sessionCookie.attributes,
})
return message(form, 'Form posted successfully!')
}
} catch (err) {
if (err instanceof Error) {
return setError(form, 'email', 'E-mail already exists.')
}
}
},
} satisfies Actions
56 changes: 56 additions & 0 deletions apps/sveltekit-example-app/src/routes/sign-up/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script lang="ts">
import SuperDebug, { superForm } from 'sveltekit-superforms'
import { zodClient } from 'sveltekit-superforms/adapters'
import { schema } from '$lib'
import type { PageData } from './$types.js'
export let data: PageData
const { form, message, errors, constraints, enhance } = superForm(data.form, {
validators: zodClient(schema),
validationMethod: 'auto',
})
</script>

{#if $message}<h3>{$message}</h3>{/if}

<form method="POST" novalidate use:enhance>
<label for="email">Email</label>
<input
name="email"
type="email"
aria-invalid={$errors.email ? 'true' : undefined}
bind:value={$form.email}
{...$constraints.email} />

{#if $errors.email}<span class="text-red-500">{$errors.email}</span>{/if}

<label for="password">Password</label>
<input
name="password"
type="password"
aria-invalid={$errors.password ? 'true' : undefined}
bind:value={$form.password}
{...$constraints.password} />
{#if $errors.password}<span class="text-red-500">{$errors.password}</span>{/if}

<label for="password-confirm">Password</label>
<input
name="password_confirm"
type="password"
aria-invalid={$errors.password_confirm ? 'true' : undefined}
bind:value={$form.password_confirm}
{...$constraints.password_confirm} />
{#if $errors.password_confirm}
<span class="text-red-500">{$errors.password_confirm}</span>
{/if}
<div>
<button>Submit</button>
</div>
</form>

<div class="w-1/2 pt-10">
<SuperDebug data={$form} collapsible={true} />
</div>
4 changes: 3 additions & 1 deletion apps/sveltekit-example-app/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import forms from '@tailwindcss/forms'

/** @type {import('tailwindcss').Config} */
const config = {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {},
},
plugins: [],
plugins: [forms],
}

export default config
2 changes: 1 addition & 1 deletion packages/drizzledb-pg/src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const authSchema = pgSchema('auth')

export const users = authSchema.table('users', {
id: uuid('id').primaryKey().defaultRandom(),
username: citext('user_name').notNull(),
username: citext('username').notNull().unique(),
hashedPassword: text('hashed_password').notNull(),
})

Expand Down
5 changes: 4 additions & 1 deletion packages/lucia-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
"description": "Lucia Auth Client",
"license": "UNLICENSED",
"type": "module",
"exports": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./repository": "./src/repository/index.ts"
},
"scripts": {
"clean": "del .turbo coverage",
"lint": "eslint .",
Expand Down
23 changes: 23 additions & 0 deletions packages/lucia-auth/src/repository/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Argon2id } from 'oslo/password'

import { db } from '@packages/drizzledb-pg/client'
import * as schema from '@packages/drizzledb-pg/schema'
import { users } from '@packages/drizzledb-pg/schema'

export type NewUser = Omit<typeof schema.users.$inferInsert, 'id' | 'hashedPassword'> & {
password: string
}

export type User = typeof schema.users.$inferSelect

export const createUser = async (user: NewUser): Promise<string | undefined> => {
const hashedPassword = await new Argon2id().hash(user.password)
return db
.insert(schema.users)
.values({ username: user.username, hashedPassword })
.returning({ id: users.id })
.then((res) => res[0]?.id)
.catch((err) => {
throw err
})
}
Loading

0 comments on commit 8c3a578

Please sign in to comment.