From 5cca85735847eb81cc0fbc06b0b9b5c62a747281 Mon Sep 17 00:00:00 2001 From: Moritz Utcke Date: Thu, 16 Oct 2025 10:24:35 -0400 Subject: [PATCH] Vereinfachter Registrierungsprozess MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nicht-eingeloggte Nutzer sollen sich einfacher registrieren/authentifizieren können. Speichern oder Bestellen nicht-eingeloggte Nutzer auf der Kundendaten-Seite, so soll ein Popup aufgehen, wo sie einen Bestätigungscode eingeben können. Dieser Code wird umgehend an ihre eingegene Email gesendet. Wird der Code efolgreich eingegeben gibt es zwei Fälle: Unter der Email existiert bereits ein Account → Der Ausweis wird dann diesem User zugewiesen Unter der Email existiet noch kein Account → Ein neuer Account wird angelegt und der Ausweise dem neuen Account zugewiesen Wird ein neuner Account automatisch angelegt, so benötigen wir auch noch einen Prozess, wie der Nutzer dann sein Passwort vergeben kann. Idealerweise erhält er in seiner Willkommensmail einen Link zur Passwort setzung. Alternativ nutzt er einfach die bestehende Passwortrücksetzen-Funktion auf der Webseite. Um bei der Erstregistrierung soll ein Zahlencode an die eingegebene E-Mail verschickt werden. Dieser muss dann vom User eingegeben werden um die Registrierung bzw. meistens ja dann die Erstbestellung abzuschließen. Der Zahlencode kann ja dann das Passwort sein. Wir weisen den Kunden darauf hin sich ein eigenes Passwort zu vergeben. --- .env | 2 + src/astro-typesafe-api-caller.ts | 19 +-- src/lib/pin.ts | 59 +++++++ src/lib/server/constants.ts | 1 + src/lib/server/mail/registrierung.ts | 16 ++ src/modules/EmbeddedAuthFlowModule.svelte | 14 +- src/modules/EmbeddedRegisterModule.svelte | 2 +- src/modules/EmbeddedVerifyModule.svelte | 130 ++++++++++++++++ src/modules/PINVerifyModule.svelte | 145 ++++++++++++++++++ src/modules/RegisterModule.svelte | 4 +- src/pages/api/auth/verify.ts | 65 ++++++++ src/pages/auth/request-pin.astro | 95 ++++++++++++ src/pages/auth/verify-pin.astro | 39 +++++ src/pages/auth/verify.astro | 4 +- .../[ausweisart]/index.astro | 19 ++- 15 files changed, 588 insertions(+), 26 deletions(-) create mode 100644 src/lib/pin.ts create mode 100644 src/modules/EmbeddedVerifyModule.svelte create mode 100644 src/modules/PINVerifyModule.svelte create mode 100644 src/pages/api/auth/verify.ts create mode 100644 src/pages/auth/request-pin.astro create mode 100644 src/pages/auth/verify-pin.astro diff --git a/.env b/.env index 29afc948..12f7be79 100644 --- a/.env +++ b/.env @@ -4,6 +4,8 @@ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # See the documentation for all the connection string options: https://pris.ly/d/connection-strings +SECRET="jg57cya4mrNkGlnb2R85X1gI8LdY7iNZ9MF0Jbn0K5zQBshOxv" + POSTGRES_DB=main POSTGRES_HOST=localhost POSTGRES_PORT=5432 diff --git a/src/astro-typesafe-api-caller.ts b/src/astro-typesafe-api-caller.ts index 0432563b..1d1730fb 100644 --- a/src/astro-typesafe-api-caller.ts +++ b/src/astro-typesafe-api-caller.ts @@ -12,31 +12,32 @@ export const createCaller = createCallerFactory({ "admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"), "admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"), "admin/stornieren": await import("../src/pages/api/admin/stornieren.ts"), - "ausweise": await import("../src/pages/api/ausweise/index.ts"), - "bedarfsausweis-gewerbe/[id]": await import("../src/pages/api/bedarfsausweis-gewerbe/[id].ts"), - "bedarfsausweis-gewerbe": await import("../src/pages/api/bedarfsausweis-gewerbe/index.ts"), "auth/access-token": await import("../src/pages/api/auth/access-token.ts"), "auth/passwort-vergessen": await import("../src/pages/api/auth/passwort-vergessen.ts"), "auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"), + "auth/verify": await import("../src/pages/api/auth/verify.ts"), + "ausweise": await import("../src/pages/api/ausweise/index.ts"), + "bedarfsausweis-gewerbe/[id]": await import("../src/pages/api/bedarfsausweis-gewerbe/[id].ts"), + "bedarfsausweis-gewerbe": await import("../src/pages/api/bedarfsausweis-gewerbe/index.ts"), + "aufnahme": await import("../src/pages/api/aufnahme/index.ts"), "bedarfsausweis-wohnen/[id]": await import("../src/pages/api/bedarfsausweis-wohnen/[id].ts"), "bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"), - "aufnahme": await import("../src/pages/api/aufnahme/index.ts"), "bilder/[id]": await import("../src/pages/api/bilder/[id].ts"), "geg-nachweis-gewerbe/[id]": await import("../src/pages/api/geg-nachweis-gewerbe/[id].ts"), "geg-nachweis-gewerbe": await import("../src/pages/api/geg-nachweis-gewerbe/index.ts"), "geg-nachweis-wohnen/[id]": await import("../src/pages/api/geg-nachweis-wohnen/[id].ts"), "geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"), "objekt": await import("../src/pages/api/objekt/index.ts"), - "ticket": await import("../src/pages/api/ticket/index.ts"), - "user": await import("../src/pages/api/user/index.ts"), - "user/self": await import("../src/pages/api/user/self.ts"), "rechnung/[id]": await import("../src/pages/api/rechnung/[id].ts"), "rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.ts"), "rechnung": await import("../src/pages/api/rechnung/index.ts"), - "verbrauchsausweis-wohnen/[id]": await import("../src/pages/api/verbrauchsausweis-wohnen/[id].ts"), - "verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"), + "ticket": await import("../src/pages/api/ticket/index.ts"), "verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"), "verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"), + "user": await import("../src/pages/api/user/index.ts"), + "user/self": await import("../src/pages/api/user/self.ts"), + "verbrauchsausweis-wohnen/[id]": await import("../src/pages/api/verbrauchsausweis-wohnen/[id].ts"), + "verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"), "webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"), "aufnahme/[id]/bilder": await import("../src/pages/api/aufnahme/[id]/bilder.ts"), "aufnahme/[id]": await import("../src/pages/api/aufnahme/[id]/index.ts"), diff --git a/src/lib/pin.ts b/src/lib/pin.ts new file mode 100644 index 00000000..66ef666a --- /dev/null +++ b/src/lib/pin.ts @@ -0,0 +1,59 @@ +import crypto from "crypto"; +import { SECRET } from "./server/constants.js"; + +// Convert integer to 8-byte Buffer (big-endian) +function intToBuffer(i: number): Buffer { + const buf = Buffer.alloc(8); + buf.writeBigUInt64BE(BigInt(i)); + return buf; +} + +// Derive a stable key per email +function deriveKey(email: string): Buffer { + return crypto.createHmac("sha256", Buffer.from(SECRET, "utf8")) + .update(email) + .digest(); +} + +/** + * Erstellt eine 4-stellige PIN. + * @param email - Die Email-Adresse, für die die PIN generiert wird + * @param forTime - Der Zeitpunkt, für den die PIN generiert wird (Standard: aktueller Zeitpunkt) + * @param validSeconds - Die Anzahl der Sekunden, für die die PIN gültig ist (Standard: 1800 Sekunden = 30 Minuten) + * @returns Die generierte PIN + */ +export function generatePinCode(email: string, forTime: number = Math.floor(Date.now() / 1000), validSeconds: number = 1800): string { + const timestep = Math.floor(forTime / validSeconds); + console.log(timestep, forTime, validSeconds, Date.now()); + + const key = deriveKey(email); + const msg = intToBuffer(timestep); + + const mac = crypto.createHmac("sha256", key).update(msg).digest(); + + // Dynamic truncation (RFC4226-style) + const offset = mac[mac.length - 1] & 0x0f; + const codeInt = (mac.readUInt32BE(offset) & 0x7fffffff) % 10000; + return codeInt.toString().padStart(4, "0"); +} + +/** + * Verifiziert eine PIN für eine Email-Adresse. + * @param email - Die Email-Adresse, für die die PIN verifiziert wird + * @param code - Die zu verifizierende PIN + * @param validSeconds - Die Anzahl der Sekunden, für die die PIN gültig ist (Standard: 1800 Sekunden = 30 Minuten) + * @param allowedWindows - Die Anzahl der erlaubten Zeitfenster für Zeitabweichungen (Standard: 1, erlaubt ±1 Fenster) + * @returns true, wenn die PIN gültig ist, sonst false + */ +export function verifyCode(email: string, code: string, validSeconds: number = 1800, allowedWindows: number = 1): boolean { + const now = Math.floor(Date.now() / 1000); + + for (let w = -allowedWindows; w <= allowedWindows; w++) { + const time = now + w * validSeconds; + if (generatePinCode(email, time, validSeconds) === code) { + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/src/lib/server/constants.ts b/src/lib/server/constants.ts index 343b3c96..f0562041 100644 --- a/src/lib/server/constants.ts +++ b/src/lib/server/constants.ts @@ -2,6 +2,7 @@ import os from "os" import fs from "fs" export const PERSISTENT_DIR = `${os.homedir()}/persistent/online-energieausweis` +export const SECRET = process.env.SECRET as string if (!fs.existsSync(PERSISTENT_DIR)) { fs.mkdirSync(PERSISTENT_DIR, {recursive: true}) diff --git a/src/lib/server/mail/registrierung.ts b/src/lib/server/mail/registrierung.ts index 5eb476ae..f92da0b5 100644 --- a/src/lib/server/mail/registrierung.ts +++ b/src/lib/server/mail/registrierung.ts @@ -5,6 +5,8 @@ import { } from "#lib/client/prisma.js"; import { encodeToken } from "#lib/auth/token.js"; import { TokenType } from "#lib/auth/types.js"; +import { generatePinCode } from "#lib/pin.js"; +import { logger } from "#lib/logger.js"; export async function sendRegisterMail( user: Benutzer @@ -15,6 +17,13 @@ export async function sendRegisterMail( id: user.id }) + // Bei der Registrierung soll ein Code an die Email des Benutzers geschickt werden. + // Der Benutzer muss diesen Code dann eingeben, um seine Email zu verifizieren. + // Bis dahin kann er sich nicht einloggen. + // Diese Pin soll nach 30 Minuten ablaufen, wir können sie ganz einfach erstellen indem wir einen zeitbasierten Hash der Email generieren. + const pin = generatePinCode(user.email); + logger.info(`Generated PIN for ${user.email}: ${pin}`); + await transport.sendMail({ from: `"IBCornelsen" `, to: user.email, @@ -26,6 +35,13 @@ export async function sendRegisterMail( Um Ihre Registrierung abzuschließen, klicken Sie bitte auf den folgenden Link, um Ihre E-Mail-Adresse zu bestätigen:

E-Mail-Adresse bestätigen

+

Alternativ können Sie den folgenden Bestätigungscode verwenden:

+ ${pin}

+ Dieser Code ist 30 Minuten gültig.

+ + Sollten Sie diese Registrierung nicht vorgenommen haben, können Sie diese E-Mail einfach ignorieren. Ihr Benutzerkonto wird in diesem Fall nicht aktiviert.

+ + Falls Sie Fragen haben oder Unterstützung benötigen, stehen wir Ihnen gerne zur Verfügung. Kontaktieren Sie uns einfach unter support@online-energieausweis.org.

Mit freundlichen Grüßen,
diff --git a/src/modules/EmbeddedAuthFlowModule.svelte b/src/modules/EmbeddedAuthFlowModule.svelte index 7637e0d4..75878b07 100644 --- a/src/modules/EmbeddedAuthFlowModule.svelte +++ b/src/modules/EmbeddedAuthFlowModule.svelte @@ -2,27 +2,27 @@ import { loginClient } from "#lib/login.js"; import EmbeddedLoginModule from "./EmbeddedLoginModule.svelte" import EmbeddedRegisterModule from "./EmbeddedRegisterModule.svelte" + import EmbeddedVerifyModule from "./EmbeddedVerifyModule.svelte"; export let onLogin: (response: Awaited>) => any; export let email: string = ""; export let password: string = ""; - export let route: "login" | "signup" = "login" + export let route: "login" | "signup" | "verify" = "login" const navigate = (target: string) => { route = target as typeof route; } - - const loginData = { - email, - passwort: "", - } {#if route == "login"} -{:else} +{:else if route == "signup"} { email = response.email + navigate("verify") + }} {navigate} /> +{:else if route == "verify"} + { navigate("login") }} {navigate} /> {/if} \ No newline at end of file diff --git a/src/modules/EmbeddedRegisterModule.svelte b/src/modules/EmbeddedRegisterModule.svelte index 1d16a668..508d3c9d 100644 --- a/src/modules/EmbeddedRegisterModule.svelte +++ b/src/modules/EmbeddedRegisterModule.svelte @@ -122,7 +122,7 @@

- + Passwort Vergessen?
diff --git a/src/modules/EmbeddedVerifyModule.svelte b/src/modules/EmbeddedVerifyModule.svelte new file mode 100644 index 00000000..182c5276 --- /dev/null +++ b/src/modules/EmbeddedVerifyModule.svelte @@ -0,0 +1,130 @@ + + +
+

Verifizieren

+

Bitte geben Sie die 4-stellige PIN ein, den Sie per E-Mail erhalten haben.

+
+
+ { + handleInput(e, 0); + }} /> + { + handleInput(e, 1); + }} /> + { + handleInput(e, 2); + }} /> + { + handleInput(e, 3); + }} /> +
+ +
+ +
+ PIN erneut anfordern +
+
\ No newline at end of file diff --git a/src/modules/PINVerifyModule.svelte b/src/modules/PINVerifyModule.svelte new file mode 100644 index 00000000..f3e2c103 --- /dev/null +++ b/src/modules/PINVerifyModule.svelte @@ -0,0 +1,145 @@ + + +
+

Verifizieren

+

Bitte geben Sie die 4-stellige PIN ein, den Sie per E-Mail erhalten haben.

+
+
+ { + handleInput(e, 0); + }} /> + { + handleInput(e, 1); + }} /> + { + handleInput(e, 2); + }} /> + { + handleInput(e, 3); + }} /> +
+ +
+ +
+ PIN erneut anfordern +
+ +
\ No newline at end of file diff --git a/src/modules/RegisterModule.svelte b/src/modules/RegisterModule.svelte index d7fd0f4a..1e165562 100644 --- a/src/modules/RegisterModule.svelte +++ b/src/modules/RegisterModule.svelte @@ -42,11 +42,11 @@ }) if (redirect) { - window.location.href = redirect + window.location.href = `/auth/verify-pin?e=${encodeURIComponent(email)}&r=${redirect}` return } - window.location.href = "/auth/login"; + window.location.href = "/auth/verify-pin?e=" + encodeURIComponent(email); } catch (e) { errorHidden = false; } diff --git a/src/pages/api/auth/verify.ts b/src/pages/api/auth/verify.ts new file mode 100644 index 00000000..a0e1486d --- /dev/null +++ b/src/pages/api/auth/verify.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; +import { prisma } from "#lib/server/prisma.js"; +import { APIError, defineApiRoute } from "astro-typesafe-api/server"; +import { verifyCode } from "#lib/pin.js"; +import { logger } from "#lib/logger.js"; + +export const PUT = defineApiRoute({ + meta: { + description: + "Erstellt, basierend auf einem existierenden und gültigen Refresh Tokens, einen neuen Access Token, welcher zur Authentifizierung genutzt werden kann. Der resultierende Access Token ist nur 2 Tage gültig und muss danach neu generiert werden. Diese Funktion gibt ebenfalls einen neuen Refresh Token zurück, der alte wird dadurch invalidiert.", + tags: ["Benutzer"], + summary: "Access Token anfragen.", + }, + input: z.object({ + pin: z.string(), + email: z.string(), + }), + output: z.object({ + verified: z.boolean(), + }), + + async fetch(input, ctx) { + const valid = verifyCode(input.email, input.pin); + if (!valid) { + throw new APIError({ + code: "BAD_REQUEST", + message: "Die gegebene PIN ist nicht gültig.", + }); + } + + const user = await prisma.benutzer.findUnique({ + where: { + email: input.email, + }, + }); + + if (!user) { + throw new APIError({ + code: "BAD_REQUEST", + message: "Die gegebene PIN ist nicht gültig.", + }); + } + + logger.info(`Verified ${input.email}`); + + if (user.verified) { + return { + verified: true, + }; + } + + await prisma.benutzer.update({ + where: { + id: user.id, + }, + data: { + verified: true, + }, + }); + + return { + verified: true, + }; + }, +}); diff --git a/src/pages/auth/request-pin.astro b/src/pages/auth/request-pin.astro new file mode 100644 index 00000000..82a94684 --- /dev/null +++ b/src/pages/auth/request-pin.astro @@ -0,0 +1,95 @@ +--- +import MinimalLayout from "#layouts/MinimalLayout.astro"; +import { validateAccessTokenServer } from "#server/lib/validateAccessToken"; +import PINVerifyModule from "#modules/PINVerifyModule.svelte"; +import { generatePinCode } from "#lib/pin"; +import { logger } from "#lib/logger"; +import { transport } from "#lib/mail"; +import { prisma } from "#lib/server/prisma"; +import { TokenType } from "#lib/auth/types"; +import { encodeToken } from "#lib/auth/token"; +import { BASE_URI } from "#lib/constants"; + +const valid = await validateAccessTokenServer(Astro) + +if (valid) { + return Astro.redirect("/dashboard") +} + +const redirect = Astro.url.searchParams.get("r"); +const email = Astro.url.searchParams.get("e"); + +if (!email) { + return Astro.redirect("/auth/signup") +} + +const user = await prisma.benutzer.findUnique({ + where: { + email: email + } +}) + +if (!user) { + // Der Nutzer existiert nicht, weiter zur Registrierung + return Astro.redirect("/auth/signup") +} + +if (user?.verified) { + // Der Nutzer ist bereits verifiziert, wir können ihn auch direkt zum Login weiterleiten + return Astro.redirect(redirect ?? "/auth/login") +} + +const verificationJwt = encodeToken({ + typ: TokenType.Verify, + exp: Date.now() + (15 * 60 * 1000), + id: user.id +}) + +const pin = generatePinCode(email); // 30 Minuten +logger.info(`Generated PIN ${email}: ${pin}`); + +await transport.sendMail({ + from: `"IBCornelsen" `, + to: email, + subject: `Ihre Registrierung bei IBCornelsen - Bitte bestätigen Sie Ihre E-Mail-Adresse`, + bcc: "info@online-energieausweis.org", + html: `

Sehr geehrte*r ${user.vorname} ${user.name},

+

vielen Dank für Ihre Registrierung bei IBCornelsen. Ihr Benutzerkonto wurde erfolgreich erstellt.

+ + Um Ihre Registrierung abzuschließen, klicken Sie bitte auf den folgenden Link, um Ihre E-Mail-Adresse zu bestätigen:

+ + E-Mail-Adresse bestätigen

+

Alternativ können Sie den folgenden Bestätigungscode verwenden:

+ ${pin}

+ Dieser Code ist 30 Minuten gültig.

+ + Sollten Sie diese Registrierung nicht vorgenommen haben, können Sie diese E-Mail einfach ignorieren. Ihr Benutzerkonto wird in diesem Fall nicht aktiviert.

+ + Falls Sie Fragen haben oder Unterstützung benötigen, stehen wir Ihnen gerne zur Verfügung. Kontaktieren Sie uns einfach unter support@online-energieausweis.org. +

+ Mit freundlichen Grüßen, +
+ Dipl.-Ing. Jens Cornelsen +
+
+ IB Cornelsen +
+ Katendeich 5A +
+ 21035 Hamburg +
+ www.online-energieausweis.org +
+
+ fon 040 · 209339850 +
+ fax 040 · 209339859 +

` + }); + + +--- + + + + diff --git a/src/pages/auth/verify-pin.astro b/src/pages/auth/verify-pin.astro new file mode 100644 index 00000000..bd0ca1a6 --- /dev/null +++ b/src/pages/auth/verify-pin.astro @@ -0,0 +1,39 @@ +--- +import MinimalLayout from "#layouts/MinimalLayout.astro"; +import { validateAccessTokenServer } from "#server/lib/validateAccessToken"; +import PINVerifyModule from "#modules/PINVerifyModule.svelte"; +import { prisma } from "#lib/server/prisma"; + +const valid = await validateAccessTokenServer(Astro) + +if (valid) { + return Astro.redirect("/dashboard") +} + +const redirect = Astro.url.searchParams.get("r"); +const email = Astro.url.searchParams.get("e"); + +if (!email) { + return Astro.redirect("/auth/signup") +} + +const user = await prisma.benutzer.findUnique({ + where: { + email: email + } +}) + +if (!user) { + // Der Nutzer existiert nicht, weiter zur Registrierung + return Astro.redirect("/auth/signup") +} + +if (user?.verified) { + // Der Nutzer ist bereits verifiziert, wir können ihn auch direkt zum Login weiterleiten + return Astro.redirect(redirect ?? "/auth/login") +} +--- + + + + diff --git a/src/pages/auth/verify.astro b/src/pages/auth/verify.astro index ce009659..6838308f 100644 --- a/src/pages/auth/verify.astro +++ b/src/pages/auth/verify.astro @@ -12,13 +12,13 @@ if (!token) { const payload = decodeToken(token) -if (payload.typ !== TokenType.Verify || !payload.uid || !payload.exp || payload.exp < Date.now()) { +if (payload.typ !== TokenType.Verify || !payload.id || !payload.exp || payload.exp < Date.now()) { return Astro.redirect("/") } await prisma.benutzer.update({ where: { - uid: payload.uid + id: payload.id }, data: { verified: true diff --git a/src/pages/energieausweis-erstellen/[ausweisart]/index.astro b/src/pages/energieausweis-erstellen/[ausweisart]/index.astro index e8b5174e..b7a31947 100644 --- a/src/pages/energieausweis-erstellen/[ausweisart]/index.astro +++ b/src/pages/energieausweis-erstellen/[ausweisart]/index.astro @@ -59,10 +59,13 @@ let loadFromDatabase = false; if (typ === AusstellungsTyp.Neuausstellung) { if (!user) { + // Der Nutzer muss eingeloggt sein um eine Neuausstellung anzufordern, + // sonst können wir nicht sicher sein, dass der Nutzer berechtigt ist auf den Ausweis zuzugreifen. return Astro.redirect(`/auth/login?r=${Astro.url.toString()}`); } if (!ausweis_id) { + // Falls es keine Ausweis ID gibt können wir nicht fortfahren. return Astro.redirect("/400"); } @@ -97,9 +100,10 @@ if (typ === AusstellungsTyp.Neuausstellung) { return Astro.redirect("/405"); } - ausweis.id = null; - aufnahme.id = null; - delete aufnahme.erstellungsdatum; + // Wir setzen alle Daten vom Ausweis zurück, sonst könnte es passieren, dass der Ausweis als der alte Ausweis gespeichert wird. + ausweis.id = ""; + aufnahme.id = ""; + aufnahme.erstellungsdatum = null; ausweis.created_at = new Date() ausweis.updated_at = new Date(); ausweis.alte_ausweis_id = null; @@ -132,6 +136,11 @@ if (typ === AusstellungsTyp.Neuausstellung) { ausweis = await getBedarfsausweisWohnen(ausweis_id); } + if (!ausweis) { + // Falls der Ausweis nicht gefunden wurde, können wir nicht fortfahren. + return Astro.redirect("/404"); + } + ausweistyp = ausweis.ausweistyp; aufnahme = (await getAufnahme(ausweis.aufnahme_id)) as Aufnahme; @@ -192,8 +201,8 @@ if (typ === AusstellungsTyp.Neuausstellung) { return Astro.redirect("/405"); } - ausweis.id = null; - delete aufnahme.erstellungsdatum; + ausweis.id = ""; + aufnahme.erstellungsdatum = null; ausweis.created_at = new Date() ausweis.updated_at = new Date(); ausweis.alte_ausweis_id = null;