style changes and stuff
This commit is contained in:
@@ -50,7 +50,8 @@ async function seed() {
|
||||
|
||||
console.log('Database reset complete. Seeding...');
|
||||
|
||||
let passwordHash = await Bun.password.hash('password');
|
||||
const { hash } = await import('../src/lib/server/password.js');
|
||||
let passwordHash = await hash('password');
|
||||
await db
|
||||
.insert(schema.scorers)
|
||||
.values({
|
||||
|
||||
24
src/lib/server/password.ts
Normal file
24
src/lib/server/password.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { scrypt, randomBytes, timingSafeEqual } from 'node:crypto';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
const scryptAsync = promisify(scrypt);
|
||||
const SALT_LEN = 32;
|
||||
const KEY_LEN = 64;
|
||||
|
||||
export async function hash(password: string): Promise<string> {
|
||||
const salt = randomBytes(SALT_LEN);
|
||||
const derivedKey = (await scryptAsync(password, salt, KEY_LEN)) as Buffer;
|
||||
return `${salt.toString('hex')}:${derivedKey.toString('hex')}`;
|
||||
}
|
||||
|
||||
export async function verify(password: string, stored: string): Promise<boolean> {
|
||||
const [saltHex, keyHex] = stored.split(':');
|
||||
if (!saltHex || !keyHex) return false;
|
||||
|
||||
const salt = Buffer.from(saltHex, 'hex');
|
||||
const key = Buffer.from(keyHex, 'hex');
|
||||
const derivedKey = (await scryptAsync(password, salt, KEY_LEN)) as Buffer;
|
||||
|
||||
if (key.length !== derivedKey.length) return false;
|
||||
return timingSafeEqual(key, derivedKey);
|
||||
}
|
||||
@@ -101,6 +101,10 @@
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
.runner .score-fg {
|
||||
padding: 0 8px;
|
||||
}
|
||||
.score-meta {
|
||||
display: flex;
|
||||
@@ -120,20 +124,13 @@
|
||||
text-shadow: 0 0 6px rgb(0 0 0 / 50%);
|
||||
}
|
||||
.score-name {
|
||||
font-size: clamp(20px, 5.5vw, 40px);
|
||||
font-size: clamp(18px, 3.5vw, 22px);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
.score-pts {
|
||||
font-size: clamp(20px, 5.5vw, 40px);
|
||||
letter-spacing: 2px;
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
text-shadow: 0 0 8px rgb(0 0 0 / 80%), 0 0 4px rgb(0 0 0 / 60%);
|
||||
}
|
||||
.winner .score-meta {
|
||||
@@ -147,7 +144,6 @@
|
||||
}
|
||||
.winner .score-name {
|
||||
font-size: clamp(34px, 9vw, 64px);
|
||||
max-width: 70%;
|
||||
}
|
||||
.winner .score-pts {
|
||||
font-size: clamp(34px, 9vw, 64px);
|
||||
@@ -163,42 +159,52 @@
|
||||
.score-rank {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.score-name {
|
||||
font-size: clamp(18px, 5vw, 28px);
|
||||
.runner .score-rank {
|
||||
font-size: clamp(16px, 3.5vw, 20px);
|
||||
}
|
||||
.runner .score-name {
|
||||
font-size: clamp(20px, 5vw, 26px);
|
||||
}
|
||||
.score-pts {
|
||||
font-size: clamp(22px, 6vw, 34px);
|
||||
}
|
||||
.winner .score-name {
|
||||
font-size: clamp(26px, 7vw, 42px);
|
||||
max-width: 60%;
|
||||
}
|
||||
.winner .score-pts {
|
||||
font-size: clamp(28px, 8vw, 48px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive runners-up: 1 col <480px, 2 cols 480-699px, 4 cols ≥700px */
|
||||
/* Responsive runners-up: 1 col <480px, 2 cols 480-950px, 4 cols >950px */
|
||||
.runners-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
grid-template-columns: 1fr;
|
||||
transition: gap 0.3s ease;
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
@media (min-width: 480px) and (max-width: 950px) {
|
||||
.runners-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.winner {
|
||||
aspect-ratio: 2 / 1;
|
||||
aspect-ratio: 12 / 5;
|
||||
}
|
||||
|
||||
.runner {
|
||||
aspect-ratio: 2 / 1;
|
||||
}
|
||||
|
||||
.runner .score-rank {
|
||||
font-size: clamp(18px, 3vw, 22px);
|
||||
}
|
||||
|
||||
.runner .score-name {
|
||||
font-size: clamp(24px, 4vw, 30px);
|
||||
}
|
||||
}
|
||||
@media (min-width: 700px) {
|
||||
@media (min-width: 951px) {
|
||||
.runners-grid {
|
||||
/* Use --runner-count so fewer teams fill the row evenly */
|
||||
grid-template-columns: repeat(min(var(--runner-count), 4), 1fr);
|
||||
@@ -378,7 +384,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
@media (min-width: 951px) {
|
||||
.player-box {
|
||||
max-width: calc(25% - 6px);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// src/routes/login/+page.server.ts
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { scorers } from '$lib/server/db/schema';
|
||||
import { generateSessionToken, createSession, setSessionTokenCookie } from '$lib/server/auth';
|
||||
import type { Actions } from '../login/$types';
|
||||
|
||||
import type { PageServerLoad } from '../login/$types';
|
||||
import { verify } from '$lib/server/password';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
return { user: locals.user };
|
||||
@@ -19,7 +17,7 @@ export const actions: Actions = {
|
||||
const password = data.get('password') as string;
|
||||
|
||||
const [user] = await db.select().from(scorers).where(eq(scorers.username, username));
|
||||
if (!user || !(await Bun.password.verify(password, user.passwordHash))) {
|
||||
if (!user || !(await verify(password, user.passwordHash))) {
|
||||
return fail(400, { message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// src/routes/signup/+page.server.ts
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db } from '$lib/server/db';
|
||||
import { scorers } from '$lib/server/db/schema';
|
||||
import { generateSessionToken, createSession, setSessionTokenCookie } from '$lib/server/auth';
|
||||
import { hash } from '$lib/server/password';
|
||||
import { getSetting } from '$lib/server/databaseManager';
|
||||
import type { PageServerLoad, Actions } from '../signup/$types';
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const disabled = (await getSetting('disableRegistration')) === 'true';
|
||||
@@ -32,7 +32,7 @@ export const actions: Actions = {
|
||||
return fail(400, { message: 'Username already taken' });
|
||||
}
|
||||
|
||||
const passwordHash = await Bun.password.hash(password);
|
||||
const passwordHash = await hash(password);
|
||||
const userId = crypto.randomUUID();
|
||||
|
||||
await db.insert(scorers).values({ id: userId, username, passwordHash });
|
||||
|
||||
Reference in New Issue
Block a user