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

@@ -7,7 +7,12 @@ export class AuthService {
constructor(private userRepo: UserRepository) {}
async login(data: LoginDTO): Promise<AuthResponse> {
const user = await this.userRepo.findByEmail(data.email);
// Try email first, then userName
let user = await this.userRepo.findByEmail(data.identifier);
if (!user) {
user = await this.userRepo.findByUserName(data.identifier);
}
if (!user || !user.passwordHash) {
throw new Error("Invalid credentials");
}
@@ -21,15 +26,20 @@ export class AuthService {
}
async register(data: CreateUserDTO): Promise<AuthResponse> {
const existingUser = await this.userRepo.findByEmail(data.email);
if (existingUser) {
const existingEmail = await this.userRepo.findByEmail(data.email);
if (existingEmail) {
throw new Error("Email already exists");
}
const existingUserName = await this.userRepo.findByUserName(data.userName);
if (existingUserName) {
throw new Error("Username already exists");
}
const passwordHash = await password.hash(data.password);
const user = await this.userRepo.create({
email: data.email,
displayName: data.displayName,
userName: data.userName,
passwordHash,
});

View File

@@ -2,12 +2,13 @@ import { User } from "@caldav/shared";
export interface CreateUserData {
email: string;
displayName: string;
userName: string;
passwordHash: string;
}
export interface UserRepository {
findById(id: string): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
findByUserName(userName: string): Promise<User | null>;
create(data: CreateUserData): Promise<User>;
}