diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index dc0c637..8d695fd 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -31,13 +31,13 @@ function remove_duplicate_env() { mv "$temp_file" "$file" } -touch /app/.env.local -chmod 400 /app/.env.local +touch /app/.env +chmod 400 /app/.env -if ! grep -q "NEXTAUTH_SECRET" /app/.env.local; then - cat <>/app/.env.local -NEXTAUTH_SECRET=$(openssl rand -base64 32) +if ! grep -q "AUTH_SECRET" /app/.env; then + cat <>/app/.env +AUTH_SECRET=$(openssl rand -base64 32) EOF fi @@ -46,8 +46,8 @@ fi # the .env.local if [ -n "$UI_PASSWORD" ]; then ui_password_hex=$(echo -n "$UI_PASSWORD" | xxd -ps -u) - sed -e '/^HASHED_PASSWORD=/d' /app/.env.local - cat <>/app/.env.local + sed -e '/^HASHED_PASSWORD=/d' /app/.env + cat <>/app/.env HASHED_PASSWORD=$ui_password_hex EOF unset UI_PASSWORD diff --git a/web/.prettierrc b/web/.prettierrc index c8756c1..8ea8f6e 100644 --- a/web/.prettierrc +++ b/web/.prettierrc @@ -3,7 +3,7 @@ "semi": true, "singleQuote": true, "trailingComma": "all", - "printWidth": 100, + "printWidth": 120, "jsxBracketSameLine": true, "overrides": [ { diff --git a/web/bun.lockb b/web/bun.lockb index cbb3c04..1b00889 100644 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/src/app.css b/web/src/app.css index f8b695a..e5a4b47 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -1,73 +1,74 @@ @tailwind base; @tailwind components; @tailwind utilities; - + @layer base { :root { - --background: 0 0% 100%; + --background: 210 20% 98%; --foreground: 224 71.4% 4.1%; - + --muted: 220 14.3% 95.9%; --muted-foreground: 220 8.9% 46.1%; - + --popover: 0 0% 100%; --popover-foreground: 224 71.4% 4.1%; - + --card: 0 0% 100%; --card-foreground: 224 71.4% 4.1%; - + --border: 220 13% 91%; --input: 220 13% 91%; - - --primary: 220.9 39.3% 11%; - --primary-foreground: 210 20% 98%; - + + --primary: 358 72% 31%; + --primary-foreground: 0 0% 100%; + --secondary: 220 14.3% 95.9%; --secondary-foreground: 220.9 39.3% 11%; - + --accent: 220 14.3% 95.9%; --accent-foreground: 220.9 39.3% 11%; - + --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 20% 98%; - - --ring: 224 71.4% 4.1%; - + + /*--ring: 224 71.4% 4.1%;*/ + --ring: var(--primary); + --radius: 0.5rem; } - + .dark { --background: 224 71.4% 4.1%; --foreground: 210 20% 98%; - + --muted: 215 27.9% 16.9%; --muted-foreground: 217.9 10.6% 64.9%; - + --popover: 224 71.4% 4.1%; --popover-foreground: 210 20% 98%; - + --card: 224 71.4% 4.1%; --card-foreground: 210 20% 98%; - + --border: 215 27.9% 16.9%; --input: 215 27.9% 16.9%; - + --primary: 210 20% 98%; --primary-foreground: 220.9 39.3% 11%; - + --secondary: 215 27.9% 16.9%; --secondary-foreground: 210 20% 98%; - + --accent: 215 27.9% 16.9%; --accent-foreground: 210 20% 98%; - + --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 20% 98%; - + --ring: 216 12.2% 83.9%; } } - + @layer base { * { @apply border-border; @@ -75,4 +76,37 @@ body { @apply bg-background text-foreground; } +} + +@layer base { + + h1 { + @apply text-4xl font-bold; + } + + h2 { + @apply text-3xl font-bold; + } + + h3 { + @apply text-2xl font-semibold; + } + + h4 { + @apply text-xl font-semibold; + } + + h5 { + @apply text-lg font-medium; + } + + h6 { + @apply text-base font-medium; + } + + a { + text-decoration: none; + transition: color 0.2s ease-in-out; + } + } \ No newline at end of file diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts index 2448dcd..e5363cb 100644 --- a/web/src/hooks.server.ts +++ b/web/src/hooks.server.ts @@ -1,16 +1,10 @@ import type { Handle } from '@sveltejs/kit'; import { verifyToken } from '$lib/auth'; +import { HASHED_PASSWORD } from '$env/static/private'; export const handle: Handle = async ({ event, resolve }) => { - if (event.url.pathname.startsWith('/custom')) { - const resp = new Response('custom response'); - resp.headers.set('content-type', 'text/plain'); - return resp; - } - const auth_exception = ['/api/health', '/login']; - - if (!auth_exception.includes(event.url.pathname)) { + if (!!HASHED_PASSWORD && !AUTH_EXCEPTION.includes(event.url.pathname)) { const token = event.cookies.get('authorization'); const redirect = new Response(null, { status: 302, headers: { location: '/login' } }); @@ -26,9 +20,17 @@ export const handle: Handle = async ({ event, resolve }) => { } } + if (event.url.pathname === '/login') { + console.log('handle', 'already logged in'); + return new Response(null, { status: 302, headers: { location: '/' } }); + } + const resp = await resolve(event); console.log('handle', event.url.pathname, resp.status); return resp; }; + + +const AUTH_EXCEPTION = ['/api/health', '/login']; diff --git a/web/src/lib/auth.ts b/web/src/lib/auth.ts index a3b3f77..fdf998b 100644 --- a/web/src/lib/auth.ts +++ b/web/src/lib/auth.ts @@ -2,13 +2,22 @@ import jwt from 'jsonwebtoken'; import { AUTH_SECRET } from '$env/static/private'; export async function generateToken(): Promise { - return jwt.sign('OK', AUTH_SECRET, { expiresIn: '1d' }); + const now = Math.floor(Date.now() / 1000); + return jwt.sign( + { + ok: true, + iat: now, + exp: now + 60 * 60, + }, + AUTH_SECRET, + ); } export async function verifyToken(token: string): Promise { try { const decode = jwt.verify(token, AUTH_SECRET); - return !!(decode && decode === 'OK'); + console.log('decode', decode); + return !!decode; } catch (e) { return false; } diff --git a/web/src/lib/components/DotDivider.svelte b/web/src/lib/components/DotDivider.svelte new file mode 100644 index 0000000..9aee396 --- /dev/null +++ b/web/src/lib/components/DotDivider.svelte @@ -0,0 +1,5 @@ + + + ยท diff --git a/web/src/lib/components/page/PageFooter.svelte b/web/src/lib/components/page/PageFooter.svelte new file mode 100644 index 0000000..f5e4961 --- /dev/null +++ b/web/src/lib/components/page/PageFooter.svelte @@ -0,0 +1,21 @@ + + + diff --git a/web/src/routes/login/+page.server.ts b/web/src/routes/login/+page.server.ts new file mode 100644 index 0000000..25c7f4e --- /dev/null +++ b/web/src/routes/login/+page.server.ts @@ -0,0 +1,30 @@ +import { type Actions, fail } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { superValidate } from 'sveltekit-superforms/server'; +import { formSchema } from './schema'; +import { HASHED_PASSWORD } from '$env/static/private'; +import { generateToken } from '$lib/auth'; + +export const load: PageServerLoad = () => { + return { + form: superValidate(formSchema), + }; +}; + +export const actions: Actions = { + default: async ({ request, cookies }) => { + const data = await request.formData(); + const password = data.get('password') ?? ''; + + if (HASHED_PASSWORD.toLowerCase() !== Buffer.from(password.toString()).toString('hex').toLowerCase()) { + console.warn('auth failed'); + return fail(401, { message: 'Unauthorized' }); + } + + const token = await generateToken(); + cookies.set('authorization', token); + + console.info('logged in.'); + return { message: 'Success!' }; + }, +}; diff --git a/web/src/routes/login/+page.svelte b/web/src/routes/login/+page.svelte index ba7c290..86f8789 100644 --- a/web/src/routes/login/+page.svelte +++ b/web/src/routes/login/+page.svelte @@ -1 +1,40 @@ -

Hello World!

\ No newline at end of file + + +
+
+
+ WireAdmin +

WireAdmin

+
+
+
+ +
+
+ +
+
+ + + + Password + + + + + + Sign In +
+
+
+ +
+
diff --git a/web/src/routes/login/schema.ts b/web/src/routes/login/schema.ts new file mode 100644 index 0000000..2467b77 --- /dev/null +++ b/web/src/routes/login/schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const formSchema = z.object({ + password: z.string(), +}); +export type FormSchema = typeof formSchema;