diff --git a/web/.woodpecker.yml b/web/.woodpecker.yml index 1fd98ba..40966cc 100644 --- a/web/.woodpecker.yml +++ b/web/.woodpecker.yml @@ -29,7 +29,7 @@ steps: from_secret: registry_password build_args: - PUBLIC_API_BASE_URL=https://api.marktvogt.de - - PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAAACjLCV-78Q1loTPz + - PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAACjLCV-78Ql1oTPz when: - event: push branch: main diff --git a/web/deploy/helm/values.yaml b/web/deploy/helm/values.yaml index 5efe563..c50f78d 100644 --- a/web/deploy/helm/values.yaml +++ b/web/deploy/helm/values.yaml @@ -62,7 +62,7 @@ config: PORT: "3000" HOST: "0.0.0.0" # Cloudflare Turnstile — read at runtime via $env/dynamic/public - PUBLIC_TURNSTILE_SITE_KEY: "0x4AAAAAAACjLCV-78Q1loTPz" + PUBLIC_TURNSTILE_SITE_KEY: "0x4AAAAAACjLCV-78Ql1oTPz" nodeSelector: {} tolerations: [] diff --git a/web/src/lib/api/types.ts b/web/src/lib/api/types.ts index a1ec59f..40d3b99 100644 --- a/web/src/lib/api/types.ts +++ b/web/src/lib/api/types.ts @@ -94,6 +94,7 @@ export interface ProfileData { display_name: string; avatar_url: string; role: string; + has_password: boolean; created_at: string; } diff --git a/web/src/lib/components/layout/Header.svelte b/web/src/lib/components/layout/Header.svelte index a0d4886..46a7474 100644 --- a/web/src/lib/components/layout/Header.svelte +++ b/web/src/lib/components/layout/Header.svelte @@ -1,6 +1,7 @@ + + +
+ + + {#if open} + + {/if} +
diff --git a/web/src/routes/profile/+page.server.ts b/web/src/routes/profile/+page.server.ts index b5974fd..32a42bd 100644 --- a/web/src/routes/profile/+page.server.ts +++ b/web/src/routes/profile/+page.server.ts @@ -38,6 +38,38 @@ export const actions: Actions = { } }, + password: async ({ request, cookies, fetch }) => { + const form = await request.formData(); + const currentPassword = form.get('current_password') as string; + const newPassword = form.get('new_password') as string; + const confirmPassword = form.get('confirm_password') as string; + + if (!newPassword || newPassword.length < 8) { + return fail(400, { error: 'Passwort muss mindestens 8 Zeichen lang sein.' }); + } + + if (newPassword !== confirmPassword) { + return fail(400, { error: 'Passwörter stimmen nicht überein.' }); + } + + const body: Record = { new_password: newPassword }; + if (currentPassword) body.current_password = currentPassword; + + try { + await serverFetch('/auth/password', cookies, { + method: 'PUT', + body: JSON.stringify(body), + fetch + }); + return { success: 'Passwort aktualisiert.' }; + } catch (e) { + if (e instanceof ApiClientError) { + return fail(e.status, { error: e.message }); + } + return fail(500, { error: 'Ein Fehler ist aufgetreten.' }); + } + }, + delete: async ({ cookies, fetch }) => { try { await serverFetch('/users/me', cookies, { diff --git a/web/src/routes/profile/+page.svelte b/web/src/routes/profile/+page.svelte index 98a1fc1..c571d17 100644 --- a/web/src/routes/profile/+page.svelte +++ b/web/src/routes/profile/+page.svelte @@ -8,6 +8,7 @@ let showDeleteConfirm = $state(false); let updateLoading = $state(false); + let passwordLoading = $state(false); let deleteLoading = $state(false); @@ -66,12 +67,49 @@

Sicherheit

- - Zwei-Faktor-Authentifizierung verwalten - + +
+ +
+

+ {data.profile.has_password ? 'Passwort ändern' : 'Passwort festlegen'} +

+
{ + passwordLoading = true; + return async ({ update }) => { + passwordLoading = false; + await update(); + }; + }} + class="space-y-4" + > + {#if data.profile.has_password} + + {/if} + + + + + + +
+
+ + + +