parent
d6ad0d2ccd
commit
f45deb8680
7 changed files with 153 additions and 22 deletions
|
@ -1,7 +1,7 @@
|
|||
import { config } from "$lib/configuration";
|
||||
import { generateRandomToken } from "$lib/randomToken";
|
||||
import type { Email, Password, VerificationCode } from "$lib/types";
|
||||
import { hash } from "@node-rs/argon2";
|
||||
import { hash, verify } from "@node-rs/argon2";
|
||||
import { db } from ".";
|
||||
import { usersTable } from "./schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
@ -36,6 +36,22 @@ export async function createNewUser(email: Email, password: Password): Promise<{
|
|||
}
|
||||
}
|
||||
|
||||
export async function loadUser(id: number) {
|
||||
const user = await db.select().from(usersTable).where(eq(usersTable.id, id)).limit(1);
|
||||
if (user.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: user[0].id,
|
||||
email: user[0].email as Email,
|
||||
password_hash: user[0].password_hash
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyPassword(password: Password, password_hash: string) {
|
||||
return await verify(password_hash, password);
|
||||
}
|
||||
|
||||
export enum VerificationError {
|
||||
InvalidVerificationCode = 'Invalid verification code',
|
||||
VerificationCodeExpired = 'Verification code expired'
|
||||
|
|
27
src/lib/components/PasswordSetterFormPart.svelte
Normal file
27
src/lib/components/PasswordSetterFormPart.svelte
Normal file
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
type Props = {
|
||||
password: string;
|
||||
password_repeat: string;
|
||||
};
|
||||
|
||||
let { password = $bindable(), password_repeat = $bindable() } = $props();
|
||||
</script>
|
||||
|
||||
<label for="password" class="justify-self-end">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
class="justify-self-start"
|
||||
required
|
||||
bind:value={password}
|
||||
/>
|
||||
<label for="password_repeat" class="justify-self-end">Password (repeat)</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password_repeat"
|
||||
id="password_repeat"
|
||||
class="justify-self-start"
|
||||
required
|
||||
bind:value={password_repeat}
|
||||
/>
|
14
src/lib/components/icons/ProfileIcon.svelte
Normal file
14
src/lib/components/icons/ProfileIcon.svelte
Normal file
|
@ -0,0 +1,14 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 411 B |
|
@ -6,9 +6,30 @@
|
|||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import CheckIcon from '$lib/components/icons/CheckIcon.svelte';
|
||||
import DuplicateIcon from '$lib/components/icons/DuplicateIcon.svelte';
|
||||
import { page } from '$app/state';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { goto } from '$app/navigation';
|
||||
import ProfileIcon from '$lib/components/icons/ProfileIcon.svelte';
|
||||
|
||||
$effect(() => {
|
||||
if (page.url.searchParams.get('pwd_updated') === 'true') {
|
||||
toast.push('Password update successful', {
|
||||
theme: {
|
||||
'--toastProgressBackground': 'green'
|
||||
},
|
||||
onpop: () => {
|
||||
goto('/');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Navbar title="Dashboard" />
|
||||
<Navbar title="Dashboard">
|
||||
<div class="w-30 flex flex-row justify-self-end">
|
||||
<a href="passwordChange" title="Change password"><ProfileIcon /></a>
|
||||
</div>
|
||||
</Navbar>
|
||||
|
||||
<div class="p-4">
|
||||
<h2 class="text-2xl">Surveys you own</h2>
|
||||
|
|
33
src/routes/(app)/passwordChange/+page.server.ts
Normal file
33
src/routes/(app)/passwordChange/+page.server.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import type { Email, Password } from "$lib/types";
|
||||
import { error, redirect, type Actions } from "@sveltejs/kit";
|
||||
import { db } from "../../../db";
|
||||
import { usersTable } from "../../../db/schema";
|
||||
import { loadUser, verifyPassword } from "../../../db/users";
|
||||
import { hash } from "@node-rs/argon2";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const actions = {
|
||||
default: async (event) => {
|
||||
const formData = await event.request.formData();
|
||||
const old_password = formData.get('old_password')?.toString() as Password | undefined;
|
||||
const password = formData.get('password')?.toString() as Password | undefined;
|
||||
|
||||
if (!password || !old_password) {
|
||||
error(400, 'Old and new password is required');
|
||||
}
|
||||
|
||||
if (event.locals.userId === null) {
|
||||
error(403, 'User is not logged in');
|
||||
}
|
||||
// load and verify the old credentials and update the password hash
|
||||
const user = await loadUser(event.locals.userId);
|
||||
if (!user) {
|
||||
error(403, 'User does not exist');
|
||||
}
|
||||
if (await verifyPassword(old_password, user.password_hash)) {
|
||||
await db.update(usersTable).set({ password_hash: await hash(password) }).where(eq(usersTable.id, event.locals.userId));
|
||||
}
|
||||
|
||||
redirect(303, '/?pwd_updated=true');
|
||||
}
|
||||
} satisfies Actions;
|
36
src/routes/(app)/passwordChange/+page.svelte
Normal file
36
src/routes/(app)/passwordChange/+page.svelte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script>
|
||||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import PasswordSetterFormPart from '$lib/components/PasswordSetterFormPart.svelte';
|
||||
|
||||
let password = $state();
|
||||
let password_repeat = $state();
|
||||
</script>
|
||||
|
||||
<Navbar title="Update password" />
|
||||
|
||||
<p class="m-4 text-center">
|
||||
Please provide your old password and the new password twice to update your password.
|
||||
</p>
|
||||
<form method="post" class="grid grid-cols-2 gap-1">
|
||||
<label for="old_password" class="justify-self-end">Old password</label>
|
||||
<input
|
||||
type="password"
|
||||
name="old_password"
|
||||
id="old_password"
|
||||
class="justify-self-start"
|
||||
required
|
||||
/>
|
||||
|
||||
<PasswordSetterFormPart bind:password bind:password_repeat />
|
||||
<button
|
||||
type="submit"
|
||||
class="col-span-2 w-40 justify-self-center bg-slate-200"
|
||||
disabled={password !== password_repeat || password === ''}
|
||||
>
|
||||
{#if password !== password_repeat}
|
||||
New passwords don't match
|
||||
{:else}
|
||||
Update password
|
||||
{/if}
|
||||
</button>
|
||||
</form>
|
|
@ -1,8 +1,9 @@
|
|||
<script lang="ts">
|
||||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import PasswordSetterFormPart from '$lib/components/PasswordSetterFormPart.svelte';
|
||||
|
||||
let password = $state('');
|
||||
let password_repeat = $state('');
|
||||
let password = $state();
|
||||
let password_repeat = $state();
|
||||
|
||||
const { data } = $props();
|
||||
</script>
|
||||
|
@ -19,24 +20,7 @@
|
|||
{/if}
|
||||
</label>
|
||||
<input type="email" name="email" id="email" class="justify-self-start" required />
|
||||
<label for="password" class="justify-self-end">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
class="justify-self-start"
|
||||
required
|
||||
bind:value={password}
|
||||
/>
|
||||
<label for="password_repeat" class="justify-self-end">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password_repeat"
|
||||
id="password_repeat"
|
||||
class="justify-self-start"
|
||||
required
|
||||
bind:value={password_repeat}
|
||||
/>
|
||||
<PasswordSetterFormPart bind:password bind:password_repeat />
|
||||
<button
|
||||
type="submit"
|
||||
class="col-span-2 w-40 justify-self-center bg-slate-200"
|
||||
|
|
Loading…
Add table
Reference in a new issue