Add auth bootstrap and database safeguards
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
import PendingForm from '$lib/components/PendingForm.svelte';
|
||||
let { data, form } = $props();
|
||||
const user = $derived(data.user!);
|
||||
let discordLinkPending = $state(false);
|
||||
function beginDiscordLink(event: MouseEvent) {
|
||||
if (discordLinkPending) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
discordLinkPending = true;
|
||||
}
|
||||
</script>
|
||||
<svelte:head><title>{user.username} — SHD.build</title></svelte:head>
|
||||
<main class="page profile-page">
|
||||
@@ -19,6 +27,6 @@
|
||||
</div>
|
||||
<div class="profile-columns"><div class="profile-module"><p class="eyebrow">SAVED INTELLIGENCE</p><h2>FAVORITES</h2><strong class="large-count">{data.account.favorites.length}</strong><span class="count-label">SAVED LOADOUTS</span></div><div class="profile-module"><p class="eyebrow">FIELD FEEDBACK</p><h2>RATINGS</h2><strong class="large-count">{data.account.ratings.length}</strong><span class="count-label">RATED LOADOUTS</span></div></div>
|
||||
</section>
|
||||
<aside class="account-console"><div class="console-head"><span>ACCOUNT CONTROL</span><i></i></div><p class="eyebrow">PUBLIC IDENTITY</p><h2>USERNAME</h2><PendingForm action="?/changeUsername"><label class="field">PUBLIC USERNAME<input name="username" value={user.username} required /></label>{#if form?.usernameError}<p class="error">{form.usernameError}</p>{/if}<button class="primary console-action">UPDATE IDENTITY →</button></PendingForm><hr/><p class="eyebrow">CONNECTIONS</p><h2>SIGN-IN METHODS</h2><div class="connection-card"><span class="discord-node">◉</span><div><b>DISCORD</b><small>OAuth identity provider</small></div><a href="/auth/discord?mode=link">LINK →</a></div><hr/><p class="eyebrow">SECURITY</p><h2>CHANGE PASSWORD</h2><PendingForm action="?/changePassword"><label class="field">CURRENT PASSWORD<input name="currentPassword" type="password" required /></label><label class="field">NEW PASSWORD<input name="newPassword" type="password" minlength="12" required /></label><label class="field">CONFIRM PASSWORD<input name="confirmPassword" type="password" minlength="12" required /></label>{#if form?.passwordError}<p class="error">{form.passwordError}</p>{/if}<button class="primary console-action">UPDATE PASSWORD →</button></PendingForm></aside>
|
||||
<aside class="account-console"><div class="console-head"><span>ACCOUNT CONTROL</span><i></i></div><p class="eyebrow">PUBLIC IDENTITY</p><h2>USERNAME</h2><PendingForm action="?/changeUsername"><label class="field">PUBLIC USERNAME<input name="username" value={user.username} required /></label>{#if form?.usernameError}<p class="error">{form.usernameError}</p>{/if}<button class="primary console-action">UPDATE IDENTITY →</button></PendingForm><hr/><p class="eyebrow">CONNECTIONS</p><h2>SIGN-IN METHODS</h2><div class="connection-card discord-connection"><div class="connection-copy"><img src="/discord-symbol.svg" alt="" aria-hidden="true" /><div><b>DISCORD</b><small>OAuth identity provider</small></div></div><a class="discord-link-button" class:interaction-pending={discordLinkPending} aria-busy={discordLinkPending} href="/auth/discord?mode=link" onclick={beginDiscordLink}><img src="/discord-symbol.svg" alt="" aria-hidden="true" /><span>Link Discord</span></a></div><hr/><p class="eyebrow">SECURITY</p><h2>CHANGE PASSWORD</h2><PendingForm action="?/changePassword"><label class="field">CURRENT PASSWORD<input name="currentPassword" type="password" required /></label><label class="field">NEW PASSWORD<input name="newPassword" type="password" minlength="12" required /></label><label class="field">CONFIRM PASSWORD<input name="confirmPassword" type="password" minlength="12" required /></label>{#if form?.passwordError}<p class="error">{form.passwordError}</p>{/if}<button class="primary console-action">UPDATE PASSWORD →</button></PendingForm></aside>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { dev } from '$app/environment';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { createSession, getSessionUser } from '$lib/server/auth/session';
|
||||
import { ensureBootstrapAdmin } from '$lib/server/auth/bootstrap';
|
||||
import { getDatabase } from '$lib/server/database';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
||||
@@ -118,6 +119,7 @@ export const GET: RequestHandler = async ({ cookies, url, fetch }) => {
|
||||
created = true;
|
||||
}
|
||||
|
||||
await ensureBootstrapAdmin(database, String(user.id));
|
||||
await createSession(cookies, String(user.id));
|
||||
redirect(303, created ? '/account?choose_username=1' : '/account');
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createSession, destroySession } from '$lib/server/auth/session';
|
||||
import { verifyPassword } from '$lib/server/auth/password';
|
||||
import { getDatabase } from '$lib/server/database';
|
||||
import { ensureBootstrapAdmin } from '$lib/server/auth/bootstrap';
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import type { Actions } from './$types';
|
||||
|
||||
@@ -14,6 +15,7 @@ export const actions: Actions = {
|
||||
const user = await (await database.prepare('SELECT id, password_hash FROM users WHERE username = ? COLLATE NOCASE')).get(username);
|
||||
const valid = typeof user?.password_hash === 'string' && await verifyPassword(user.password_hash, password);
|
||||
if (!valid) return fail(400, { loginError: 'Invalid username or password.', username });
|
||||
await ensureBootstrapAdmin(database, String(user.id));
|
||||
await createSession(cookies, String(user.id));
|
||||
redirect(303, '/account');
|
||||
},
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
<script lang="ts">import PendingForm from '$lib/components/PendingForm.svelte'; let { data, form } = $props();</script>
|
||||
<script lang="ts">
|
||||
import PendingForm from '$lib/components/PendingForm.svelte';
|
||||
let { data, form } = $props();
|
||||
let discordPending = $state(false);
|
||||
function beginDiscordSignIn(event: MouseEvent) {
|
||||
if (discordPending) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
discordPending = true;
|
||||
}
|
||||
</script>
|
||||
<svelte:head><title>Sign in — SHD.build</title></svelte:head>
|
||||
<main class="auth-page"><div class="auth-frame"><section class="auth-intel"><p class="eyebrow"><span></span> SHD IDENTITY NETWORK</p><h1>AGENT<br/><em>ACCESS.</em></h1><p>Authenticate to publish builds, join field discussions, and keep your loadouts synchronized across devices.</p><div class="auth-status"><span><i></i> NETWORK ONLINE</span><span>NODE NYC-01</span></div></section><section class="auth-console"><div class="console-head"><span>SECURE SIGN-IN</span><b>SHD // AUTH</b></div><a class="discord" href="/auth/discord">◉ CONTINUE WITH DISCORD</a><div class="divider"><span>OR USE LOCAL CREDENTIALS</span></div><PendingForm action="?/login"><label class="field">USERNAME<input name="username" value={form?.username ?? ''} autocomplete="username" placeholder="Agent handle" required /></label><label class="field">PASSWORD<input name="password" type="password" autocomplete="current-password" placeholder="••••••••••••" required /></label>{#if form?.loginError}<p class="error">{form.loginError}</p>{/if}{#if data.oauthError}<p class="error">{data.oauthError}</p>{/if}<button class="primary full">AUTHENTICATE →</button></PendingForm><small>Credentials are verified securely on the server. Discord shares only the basic identity information required to link your account.</small></section></div></main>
|
||||
<main class="auth-page"><div class="auth-frame"><section class="auth-intel"><p class="eyebrow"><span></span> SHD IDENTITY NETWORK</p><h1>AGENT<br/><em>ACCESS.</em></h1><p>Authenticate to publish builds, join field discussions, and keep your loadouts synchronized across devices.</p><div class="auth-status"><span><i></i> NETWORK ONLINE</span><span>NODE NYC-01</span></div></section><section class="auth-console"><div class="console-head"><span>SECURE SIGN-IN</span><b>SHD // AUTH</b></div><a class="discord" class:interaction-pending={discordPending} href="/auth/discord" aria-busy={discordPending} onclick={beginDiscordSignIn}><img src="/discord-symbol.svg" alt="" aria-hidden="true" /><span>Continue with Discord</span></a><div class="divider"><span>OR USE LOCAL CREDENTIALS</span></div><PendingForm action="?/login"><label class="field">USERNAME<input name="username" value={form?.username ?? ''} autocomplete="username" placeholder="Agent handle" required /></label><label class="field">PASSWORD<input name="password" type="password" autocomplete="current-password" placeholder="••••••••••••" required /></label>{#if form?.loginError}<p class="error">{form.loginError}</p>{/if}{#if data.oauthError}<p class="error">{data.oauthError}</p>{/if}<button class="primary full">AUTHENTICATE →</button></PendingForm><small>Credentials are verified securely on the server. Discord shares only the basic identity information required to link your account.</small></section></div></main>
|
||||
|
||||
Reference in New Issue
Block a user