Registrierungsprozess verschlankt
This commit is contained in:
@@ -12,6 +12,7 @@ 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"),
|
||||
"aufnahme": await import("../src/pages/api/aufnahme/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"),
|
||||
@@ -19,7 +20,6 @@ export const createCaller = createCallerFactory({
|
||||
"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"),
|
||||
"bilder/[id]": await import("../src/pages/api/bilder/[id].ts"),
|
||||
@@ -32,12 +32,13 @@ export const createCaller = createCallerFactory({
|
||||
"rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.ts"),
|
||||
"rechnung": await import("../src/pages/api/rechnung/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/autocreate": await import("../src/pages/api/user/autocreate.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"),
|
||||
"verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"),
|
||||
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/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"),
|
||||
|
||||
45
src/lib/server/mail/auto-registrierung.ts
Normal file
45
src/lib/server/mail/auto-registrierung.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { transport } from "#lib/mail.js";
|
||||
import {
|
||||
Benutzer,
|
||||
} from "#lib/client/prisma.js";
|
||||
|
||||
export async function sendAutoRegisterMail(
|
||||
user: Benutzer,
|
||||
password: string
|
||||
) {
|
||||
await transport.sendMail({
|
||||
from: `"IBCornelsen" <info@online-energieausweis.org>`,
|
||||
to: user.email,
|
||||
subject: `Ihre Registrierung bei IBCornelsen`,
|
||||
bcc: "info@online-energieausweis.org",
|
||||
html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>
|
||||
<p>vielen Dank für Ihre Registrierung bei IBCornelsen. Ihr Benutzerkonto wurde erfolgreich erstellt.<br><br>
|
||||
|
||||
Nachfolgend finden Sie Ihre Zugangsdaten:<br><br>
|
||||
E-Mail: ${user.email}<br>
|
||||
Passwort: ${password}<br><br>
|
||||
|
||||
Sollten Sie diese Registrierung nicht vorgenommen haben, können Sie diese E-Mail einfach ignorieren. Ihr Benutzerkonto wird in diesem Fall nicht aktiviert.<br><br>
|
||||
|
||||
Falls Sie Fragen haben oder Unterstützung benötigen, stehen wir Ihnen gerne zur Verfügung. Kontaktieren Sie uns einfach unter <a href="mailto:support@online-energieausweis.org">support@online-energieausweis.org</a>.
|
||||
<p>
|
||||
Mit freundlichen Grüßen,
|
||||
<br>
|
||||
Dipl.-Ing. Jens Cornelsen
|
||||
<br>
|
||||
<br>
|
||||
<strong>IB Cornelsen</strong>
|
||||
<br>
|
||||
Katendeich 5A
|
||||
<br>
|
||||
21035 Hamburg
|
||||
<br>
|
||||
www.online-energieausweis.org
|
||||
<br>
|
||||
<br>
|
||||
fon 040 · 209339850
|
||||
<br>
|
||||
fax 040 · 209339859
|
||||
</p>`
|
||||
});
|
||||
}
|
||||
@@ -2,12 +2,11 @@
|
||||
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<ReturnType<typeof loginClient>>) => any;
|
||||
export let email: string = "";
|
||||
export let password: string = "";
|
||||
export let route: "login" | "signup" | "verify" = "login"
|
||||
export let route: "login" | "signup" = "login"
|
||||
|
||||
const navigate = (target: string) => {
|
||||
route = target as typeof route;
|
||||
@@ -17,12 +16,8 @@
|
||||
{#if route == "login"}
|
||||
<EmbeddedLoginModule onLogin={onLogin} bind:email bind:password {navigate} />
|
||||
{:else if route == "signup"}
|
||||
<EmbeddedRegisterModule bind:email bind:password onRegister={(response) => {
|
||||
<EmbeddedRegisterModule bind:email onRegister={(response) => {
|
||||
email = response.email
|
||||
navigate("verify")
|
||||
}} {navigate} />
|
||||
{:else if route == "verify"}
|
||||
<EmbeddedVerifyModule bind:email onVerify={() => {
|
||||
navigate("login")
|
||||
}} {navigate} />
|
||||
{/if}
|
||||
@@ -26,7 +26,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<form style="width:50%;margin: 0 auto" on:submit={login} name="login">
|
||||
<form class="max-w-md mx-auto" on:submit={login} name="login">
|
||||
<h1 class="text-2xl font-semibold mb-6">Einloggen</h1>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
|
||||
@@ -3,26 +3,15 @@
|
||||
import { api } from "astro-typesafe-api/client";
|
||||
|
||||
export let navigate: (target: string) => void;
|
||||
export let onRegister: (response: { email: string, name: string, vorname: string }) => void;
|
||||
export let password: string;
|
||||
export let onRegister: (response: { email: string }) => void;
|
||||
export let email: string;
|
||||
|
||||
let vorname: string;
|
||||
let name: string;
|
||||
let repeatEmail: string;
|
||||
|
||||
async function signUp(e: SubmitEvent) {
|
||||
async function signup(e: SubmitEvent) {
|
||||
e.preventDefault()
|
||||
|
||||
if (password.length < 8) {
|
||||
addNotification({
|
||||
message: "Passwort muss mindestens 8 Zeichen enthalten.",
|
||||
dismissable: true,
|
||||
timeout: 3000,
|
||||
type: "error"
|
||||
})
|
||||
return;
|
||||
} else if (email !== repeatEmail) {
|
||||
if (email !== repeatEmail) {
|
||||
addNotification({
|
||||
message: "Die eingegebenen Email Adressen stimmen nicht überein.",
|
||||
dismissable: true,
|
||||
@@ -33,18 +22,15 @@
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.user.PUT.fetch({
|
||||
const response = await api.user.autocreate.PUT.fetch({
|
||||
email,
|
||||
passwort: password,
|
||||
vorname,
|
||||
name,
|
||||
});
|
||||
|
||||
onRegister({
|
||||
email,
|
||||
name,
|
||||
vorname
|
||||
})
|
||||
|
||||
navigate("login")
|
||||
} catch (e) {
|
||||
addNotification({
|
||||
message: "Ups...",
|
||||
@@ -58,33 +44,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<form style="width:50%;margin: 0 auto" name="signup" on:submit={signUp}>
|
||||
<form class="max-w-md mx-auto" name="signup" on:submit={signup}>
|
||||
<h1>Registrieren</h1>
|
||||
<p>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.</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-row gap-4 w-full">
|
||||
<div class="w-1/2">
|
||||
<h4>Vorname</h4>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Vorname"
|
||||
name="vorname"
|
||||
class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
|
||||
bind:value={vorname}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<h4>Nachname</h4>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nachname"
|
||||
name="nachname"
|
||||
class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
|
||||
bind:value={name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Email</h4>
|
||||
<input
|
||||
@@ -109,20 +72,9 @@
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Passwort</h4>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="********"
|
||||
name="passwort"
|
||||
class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
|
||||
bind:value={password}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button class="button" type="submit">Registrieren</button>
|
||||
<div class="flex flex-row justify-between" style="margin-top: 10px">
|
||||
<button on:click={() => navigate("verify")}>Einloggen</button>
|
||||
<button on:click={() => navigate("login")}>Einloggen</button>
|
||||
<a href="/auth/passwort-vergessen?r={window.location.href}">Passwort Vergessen?</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { addNotification } from "@ibcornelsen/ui";
|
||||
import { api } from "astro-typesafe-api/client";
|
||||
|
||||
export let navigate: (target: string) => void;
|
||||
export let onVerify: (response: { email: string, name: string, vorname: string }) => void;
|
||||
export let email: string;
|
||||
|
||||
let digits = new Array(4).fill(null);
|
||||
let inputs: HTMLInputElement[] = new Array(4);
|
||||
|
||||
|
||||
function focusInput(index: number) {
|
||||
if (inputs[index]) {
|
||||
inputs[index].focus();
|
||||
}
|
||||
}
|
||||
|
||||
function paste(e: ClipboardEvent) {
|
||||
e.preventDefault()
|
||||
if (!e.clipboardData) return;
|
||||
const paste = e.clipboardData.getData('text').slice(0, 4).split('');
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (inputs[i] && /^\d$/.test(paste[i])) {
|
||||
inputs[i].value = paste[i] || '';
|
||||
digits[i] = paste[i] || null;
|
||||
}
|
||||
}
|
||||
if (paste.length >= 4) {
|
||||
inputs.forEach(input => input.blur());
|
||||
} else {
|
||||
focusInput(paste.length);
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
}
|
||||
|
||||
function handleInput(e: InputEvent, index: number) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const value = input.value;
|
||||
|
||||
if (e.inputType === 'deleteContentBackward' || e.inputType === 'deleteContentForward') {
|
||||
if (index > 0) {
|
||||
focusInput(index - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (/^\d$/.test(value)) {
|
||||
digits[index] = value;
|
||||
if (index < 3) {
|
||||
focusInput(index + 1);
|
||||
} else {
|
||||
input.blur();
|
||||
}
|
||||
} else {
|
||||
input.value = '';
|
||||
digits[index] = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function verify(e: SubmitEvent) {
|
||||
e.preventDefault()
|
||||
const pin = digits.join('');
|
||||
|
||||
if (pin.length < 4) {
|
||||
addNotification({
|
||||
message: "PIN muss 4 Zeichen enthalten.",
|
||||
dismissable: true,
|
||||
timeout: 3000,
|
||||
type: "error"
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { verified } = await api.auth.verify.PUT.fetch({
|
||||
email,
|
||||
pin
|
||||
})
|
||||
|
||||
if (!verified) {
|
||||
addNotification({
|
||||
message: "Die eingegebene PIN ist ungültig oder abgelaufen.",
|
||||
dismissable: true,
|
||||
timeout: 3000,
|
||||
type: "error"
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
navigate("login");
|
||||
} catch (e) {
|
||||
addNotification({
|
||||
message: "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.",
|
||||
dismissable: true,
|
||||
timeout: 3000,
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form style="width:50%;margin: 0 auto" name="signup" on:submit={verify}>
|
||||
<h1>Verifizieren</h1>
|
||||
<p>Bitte geben Sie die 4-stellige PIN ein, den Sie per E-Mail erhalten haben.</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-row gap-4 justify-center my-8">
|
||||
<input type="text" bind:value={digits[0]} bind:this={inputs[0]} maxlength="1" class="input input-bordered w-12 text-center text-2xl" on:paste={paste} on:input={(e) => {
|
||||
handleInput(e, 0);
|
||||
}} />
|
||||
<input type="text" bind:value={digits[1]} bind:this={inputs[1]} maxlength="1" class="input input-bordered w-12 text-center text-2xl" on:input={(e) => {
|
||||
handleInput(e, 1);
|
||||
}} />
|
||||
<input type="text" bind:value={digits[2]} bind:this={inputs[2]} maxlength="1" class="input input-bordered w-12 text-center text-2xl" on:input={(e) => {
|
||||
handleInput(e, 2);
|
||||
}} />
|
||||
<input type="text" bind:value={digits[3]} bind:this={inputs[3]} maxlength="1" class="input input-bordered w-12 text-center text-2xl" on:input={(e) => {
|
||||
handleInput(e, 3);
|
||||
}} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-center mb-8">
|
||||
<button on:click={() => navigate("login")} class="button"
|
||||
>Verifizieren</button
|
||||
>
|
||||
</div>
|
||||
<a class="link link-hover" href="/auth/request-pin?e={email}">PIN erneut anfordern</a>
|
||||
</div>
|
||||
</form>
|
||||
61
src/pages/api/user/autocreate.ts
Normal file
61
src/pages/api/user/autocreate.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { IDWithPrefix } from "#components/Ausweis/types.js";
|
||||
import { VALID_UUID_PREFIXES } from "#lib/constants.js";
|
||||
import { generateIDWithPrefix } from "#lib/db.js";
|
||||
import { hashPassword } from "#lib/password.js";
|
||||
import { createLexOfficeCustomer } from "#lib/server/lexoffice.js";
|
||||
import { sendAutoRegisterMail } from "#lib/server/mail/auto-registrierung.js";
|
||||
import { prisma } from "#lib/server/prisma.js";
|
||||
import { defineApiRoute, APIError } from "astro-typesafe-api/server";
|
||||
import { z } from "astro:content";
|
||||
|
||||
export const PUT = defineApiRoute({
|
||||
input: z.object({
|
||||
email: z.string().email(),
|
||||
}),
|
||||
output: z.object({
|
||||
id: IDWithPrefix
|
||||
}),
|
||||
async fetch(input) {
|
||||
let { email } = input;
|
||||
email = email.toLowerCase();
|
||||
|
||||
const existingUser = await prisma.benutzer.findUnique({
|
||||
where: {
|
||||
email
|
||||
}
|
||||
})
|
||||
|
||||
if (existingUser) {
|
||||
throw new APIError({
|
||||
code: "CONFLICT",
|
||||
message: "Email Adresse ist bereits vergeben."
|
||||
})
|
||||
}
|
||||
|
||||
const id = generateIDWithPrefix(9, VALID_UUID_PREFIXES.User);
|
||||
const password = crypto.randomUUID().slice(0, 8);
|
||||
|
||||
const user = await prisma.benutzer.create({
|
||||
data: {
|
||||
email,
|
||||
passwort: hashPassword(password),
|
||||
id
|
||||
}
|
||||
})
|
||||
|
||||
const lex_office_id = await createLexOfficeCustomer(user);
|
||||
|
||||
await prisma.benutzer.update({
|
||||
where: {
|
||||
id: user.id
|
||||
},
|
||||
data: {
|
||||
lex_office_id
|
||||
}
|
||||
})
|
||||
|
||||
await sendAutoRegisterMail(user, password)
|
||||
|
||||
return { id }
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user