From 0b01a7695353403953d135db717d451648896013 Mon Sep 17 00:00:00 2001 From: Moritz Utcke Date: Fri, 17 Oct 2025 10:05:07 -0400 Subject: [PATCH] Verbesserungen Registrierung --- src/astro-typesafe-api-caller.ts | 1 - .../Ausweis/ButtonWeiterHilfe.svelte | 10 +- src/components/PlzSuche.svelte | 2 +- src/lib/pin.ts | 59 ------- src/lib/server/mail/registrierung.ts | 30 +--- src/modules/EmbeddedAuthFlowModule.svelte | 12 +- src/modules/EmbeddedLoginModule.svelte | 6 +- src/modules/EmbeddedRegisterModule.svelte | 39 ++++- src/modules/KundendatenModule.svelte | 10 +- src/modules/PINVerifyModule.svelte | 145 ------------------ src/modules/RegisterModule.svelte | 99 ++++++------ src/pages/api/auth/verify.ts | 65 -------- src/pages/api/user/autocreate.ts | 11 +- src/pages/api/user/index.ts | 21 ++- src/pages/auth/request-pin.astro | 95 ------------ src/pages/auth/verify-pin.astro | 39 ----- src/pages/auth/verify.astro | 32 ---- 17 files changed, 146 insertions(+), 530 deletions(-) delete mode 100644 src/lib/pin.ts delete mode 100644 src/modules/PINVerifyModule.svelte delete mode 100644 src/pages/api/auth/verify.ts delete mode 100644 src/pages/auth/request-pin.astro delete mode 100644 src/pages/auth/verify-pin.astro delete mode 100644 src/pages/auth/verify.astro diff --git a/src/astro-typesafe-api-caller.ts b/src/astro-typesafe-api-caller.ts index fbbe1e56..8c735c38 100644 --- a/src/astro-typesafe-api-caller.ts +++ b/src/astro-typesafe-api-caller.ts @@ -16,7 +16,6 @@ export const createCaller = createCallerFactory({ "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"), diff --git a/src/components/Ausweis/ButtonWeiterHilfe.svelte b/src/components/Ausweis/ButtonWeiterHilfe.svelte index 2d70f7a6..2a4934c5 100644 --- a/src/components/Ausweis/ButtonWeiterHilfe.svelte +++ b/src/components/Ausweis/ButtonWeiterHilfe.svelte @@ -186,7 +186,15 @@ sm:grid-cols-[1fr_min-content_min-content_min-content] sm:justify-self-end">
- +
diff --git a/src/components/PlzSuche.svelte b/src/components/PlzSuche.svelte index 6c9019f0..a6a6015d 100644 --- a/src/components/PlzSuche.svelte +++ b/src/components/PlzSuche.svelte @@ -5,7 +5,7 @@ export let readonly: boolean = false; export let city: string | null | undefined; export let zip: string | null = ""; - export let onchange: (event: Event) => void; + export let onchange: (event: Event) => void = () => {}; let hideZipDropdown: boolean = true; let zipCodes: inferOutput = []; diff --git a/src/lib/pin.ts b/src/lib/pin.ts deleted file mode 100644 index 66ef666a..00000000 --- a/src/lib/pin.ts +++ /dev/null @@ -1,59 +0,0 @@ -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/mail/registrierung.ts b/src/lib/server/mail/registrierung.ts index f92da0b5..d2191f46 100644 --- a/src/lib/server/mail/registrierung.ts +++ b/src/lib/server/mail/registrierung.ts @@ -1,29 +1,12 @@ -import { BASE_URI } from "#lib/constants.js"; import { transport } from "#lib/mail.js"; import { Benutzer, } 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 + user: Benutzer, + passwort: string ) { - const verificationJwt = encodeToken({ - typ: TokenType.Verify, - exp: Date.now() + (15 * 60 * 1000), - 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, @@ -32,12 +15,9 @@ export async function sendRegisterMail( 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.

+ Nachfolgend finden Sie Ihre Zugangsdaten:

+ E-Mail: ${user.email}
+ Passwort: ${passwort}

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

diff --git a/src/modules/EmbeddedAuthFlowModule.svelte b/src/modules/EmbeddedAuthFlowModule.svelte index ea82080c..adb3c3fe 100644 --- a/src/modules/EmbeddedAuthFlowModule.svelte +++ b/src/modules/EmbeddedAuthFlowModule.svelte @@ -8,6 +8,14 @@ export let email: string = ""; export let password: string = ""; export let route: "login" | "signup" = "login" + export let title: Record = { + login: "Einloggen", + signup: "Registrieren" + } + export let buttonText: Record = { + login: "Einloggen", + signup: "Registrieren" + } const navigate = (target: string) => { route = target as typeof route; @@ -15,7 +23,7 @@ {#if route == "login"} - + {:else if route == "signup"} { addNotification({ @@ -26,5 +34,5 @@ dismissable: true }) onLogin(response) - }} {navigate} /> + }} {navigate} title={title.signup} buttonText={buttonText.signup} /> {/if} \ No newline at end of file diff --git a/src/modules/EmbeddedLoginModule.svelte b/src/modules/EmbeddedLoginModule.svelte index eef1f67d..fb3ca8a3 100644 --- a/src/modules/EmbeddedLoginModule.svelte +++ b/src/modules/EmbeddedLoginModule.svelte @@ -5,6 +5,8 @@ export let navigate: (target: string) => void; export let email: string; export let password: string; + export let title: string = "Einloggen"; + export let buttonText: string = "Einloggen"; export let onLogin: (response: Awaited>) => any; @@ -27,7 +29,7 @@

-

Einloggen

+

{title}

Email

@@ -51,7 +53,7 @@ required />
- +
navigate("signup")} class="cursor-pointer" data-cy="registrieren">Registrieren Passwort Vergessen? diff --git a/src/modules/EmbeddedRegisterModule.svelte b/src/modules/EmbeddedRegisterModule.svelte index 7690a7fe..184b3ce7 100644 --- a/src/modules/EmbeddedRegisterModule.svelte +++ b/src/modules/EmbeddedRegisterModule.svelte @@ -6,33 +6,48 @@ export let navigate: (target: string) => void; export let onRegister: (response: Awaited>) => void; export let email: string; + export let title: string = "Registrieren"; + export let buttonText: string = "Registrieren"; + let repeatEmail: string; async function signup(e: SubmitEvent) { e.preventDefault() + if (email !== repeatEmail) { + addNotification({ + message: "Die eingegebenen Email Adressen stimmen nicht überein.", + dismissable: true, + timeout: 3000, + type: "error" + }) + return; + } + try { - const { id, password } = await api.user.autocreate.PUT.fetch({ + const { id, passwort } = await api.user.autocreate.PUT.fetch({ email, }); - const response = await loginClient(email, password) + const response = await loginClient(email, passwort) onRegister(response); } catch (e) { addNotification({ message: "Ups...", subtext: - "Da ist wohl etwas schiefgelaufen. Diese Email Adresse ist bereits in Benutzung, haben sie vielleicht bereits ein Konto bei uns?", + `Sie besitzen bereits ein Konto bei IBC. Bitte loggen Sie sich mit Ihrem Passwort ein oder vergeben sich über “Passwort vergessen” ein neues.`, type: "error", timeout: 0, dismissable: true, }); + navigate("login"); } } -

Registrieren

-

Bitte geben sie ihre Email ein, um einen Account beim IBC zu erstellen, ein Passwort wird ihnen per Email zugesendet, dieses können sie im Nachhinein jederzeit ändern.

+

{title}

+

Ihre Ausweisdaten werden bei uns gespeichert und Ihnen wird ein vorläufiges Passwort erstellt. Sollte Ihre E-Mail bereits bei uns registriert sein, dann loggen Sie sich bitte mit Ihrem vorhandenen Passwort ein oder vergeben sich ein neues über “Passwort vergessen”.

+

Email

@@ -46,7 +61,19 @@ required />
- +
+

Email erneut eingeben

+ (repeatEmail = repeatEmail.toLowerCase())} + required + /> +
+
Passwort Vergessen? diff --git a/src/modules/KundendatenModule.svelte b/src/modules/KundendatenModule.svelte index 788b9831..34d28fa5 100644 --- a/src/modules/KundendatenModule.svelte +++ b/src/modules/KundendatenModule.svelte @@ -1214,7 +1214,15 @@ sm:grid-cols-[min-content_min-content_min-content] sm:justify-self-end sm:mt-8"
- +
diff --git a/src/modules/PINVerifyModule.svelte b/src/modules/PINVerifyModule.svelte deleted file mode 100644 index f3e2c103..00000000 --- a/src/modules/PINVerifyModule.svelte +++ /dev/null @@ -1,145 +0,0 @@ - - -
-

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 1e165562..63c36d3d 100644 --- a/src/modules/RegisterModule.svelte +++ b/src/modules/RegisterModule.svelte @@ -1,29 +1,24 @@

Registrieren

-
+

Bitte geben sie Email, Ansprechpartner und Adressdaten ein, um einen Account beim IBC zu erstellen, ein Passwort wird ihnen per Email zugesendet, dieses können sie im Nachhinein jederzeit ändern.

+

Vorname

@@ -83,6 +84,35 @@ />
+
+
+

Straße

+ +
+
+

PLZ

+ +
+
+

Ort

+ +
+ +

Email

-
-

Passwort

- -
- {#if !errorHidden} - - {/if} - +
Einloggen`, - 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 deleted file mode 100644 index bd0ca1a6..00000000 --- a/src/pages/auth/verify-pin.astro +++ /dev/null @@ -1,39 +0,0 @@ ---- -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 deleted file mode 100644 index 6838308f..00000000 --- a/src/pages/auth/verify.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -import Layout from "#layouts/Layout.astro"; -import { decodeToken } from "#lib/auth/token"; -import { TokenType } from "#lib/auth/types"; -import { prisma } from "#lib/server/prisma"; - -const token = Astro.url.searchParams.get("t"); - -if (!token) { - return Astro.redirect("/") -} - -const payload = decodeToken(token) - -if (payload.typ !== TokenType.Verify || !payload.id || !payload.exp || payload.exp < Date.now()) { - return Astro.redirect("/") -} - -await prisma.benutzer.update({ - where: { - id: payload.id - }, - data: { - verified: true - } -}) ---- - - -

Vielen Dank

-

Ihre Email Adresse wurde bestätigt, sie können diese Seite nun schließen.

-
\ No newline at end of file