feat: implement user authentication with login and register

- Add login screen with email/username support
- Add register screen with email validation
- Implement AuthStore with expo-secure-store (native) / localStorage (web)
- Add X-User-Id header authentication (simple auth without JWT)
- Rename displayName to userName across codebase
- Add findByUserName() to UserRepository
- Check for existing email AND username on registration
- Add AuthButton component with shadow effect
- Add logout button to Header
- Add hash-password.js utility script for manual password resets
- Update CORS to allow X-User-Id header
This commit is contained in:
2026-01-10 20:07:35 +01:00
parent 71f84d1cc7
commit 8efe6c304e
20 changed files with 468 additions and 108 deletions

View File

@@ -1,42 +1,119 @@
import { useState } from "react";
import { View, Text, TextInput, Pressable } from "react-native";
import { Link, router } from "expo-router";
import BaseBackground from "../components/BaseBackground";
import AuthButton from "../components/AuthButton";
import { AuthService } from "../services";
import currentTheme from "../Themes";
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const RegisterScreen = () => {
// TODO: Email input field
// TODO: Display name input field
// TODO: Password input field
// TODO: Password confirmation field
// TODO: Register button -> AuthService.register()
// TODO: Link to LoginScreen
// TODO: Error handling and display
// TODO: Navigate to Calendar on success
throw new Error("Not implemented");
const [email, setEmail] = useState("");
const [userName, setUserName] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const handleRegister = async () => {
setError(null);
if (!email || !userName || !password) {
setError("Bitte alle Felder ausfüllen");
return;
}
if (!EMAIL_REGEX.test(email)) {
setError("Bitte eine gültige E-Mail-Adresse eingeben");
return;
}
setIsLoading(true);
try {
await AuthService.register({ email, userName, password });
router.replace("/(tabs)/chat");
} catch {
setError("Registrierung fehlgeschlagen. E-Mail bereits vergeben?");
} finally {
setIsLoading(false);
}
};
return (
<BaseBackground>
<View className="flex-1 justify-center items-center p-4">
<Text className="text-2xl mb-8">Register</Text>
<View className="flex-1 justify-center items-center p-8">
<Text
className="text-3xl font-bold mb-8"
style={{ color: currentTheme.textPrimary }}
>
Registrieren
</Text>
{error && (
<Text className="mb-4 text-center" style={{ color: currentTheme.rejectButton }}>
{error}
</Text>
)}
<TextInput
placeholder="Email"
className="w-full border rounded p-2 mb-4"
placeholder="E-Mail"
placeholderTextColor={currentTheme.textMuted}
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
className="w-full rounded-lg p-4 mb-4"
style={{
backgroundColor: currentTheme.secondaryBg,
color: currentTheme.textPrimary,
borderWidth: 1,
borderColor: currentTheme.borderPrimary,
}}
/>
<TextInput
placeholder="Display Name"
className="w-full border rounded p-2 mb-4"
placeholder="Benutzername"
placeholderTextColor={currentTheme.textMuted}
value={userName}
onChangeText={setUserName}
autoCapitalize="none"
className="w-full rounded-lg p-4 mb-4"
style={{
backgroundColor: currentTheme.secondaryBg,
color: currentTheme.textPrimary,
borderWidth: 1,
borderColor: currentTheme.borderPrimary,
}}
/>
<TextInput
placeholder="Password"
placeholder="Passwort"
placeholderTextColor={currentTheme.textMuted}
value={password}
onChangeText={setPassword}
secureTextEntry
className="w-full border rounded p-2 mb-4"
className="w-full rounded-lg p-4 mb-6"
style={{
backgroundColor: currentTheme.secondaryBg,
color: currentTheme.textPrimary,
borderWidth: 1,
borderColor: currentTheme.borderPrimary,
}}
/>
<TextInput
placeholder="Confirm Password"
secureTextEntry
className="w-full border rounded p-2 mb-4"
<AuthButton
title="Registrieren"
onPress={handleRegister}
isLoading={isLoading}
/>
<Pressable className="bg-blue-500 p-3 rounded w-full">
<Text className="text-white text-center">Register</Text>
</Pressable>
<Link href="/login" asChild>
<Pressable>
<Text style={{ color: currentTheme.chatBot }}>
Bereits ein Konto? Anmelden
</Text>
</Pressable>
</Link>
</View>
</BaseBackground>
);