implement auth login and register with MongoDB

- Add AuthController login/register endpoints with error handling
- Implement AuthService with password validation and user creation
- Add MongoUserRepository with findByEmail and create methods
- Implement password hashing with bcrypt
- Add dotenv for environment variable support
- Add Docker Compose setup for MongoDB + Mongo Express
- Stub AuthMiddleware with fake user for testing
- Update CLAUDE.md with implementation status
This commit is contained in:
2026-01-03 16:47:11 +01:00
parent 9cc6d17607
commit 105a9a4980
12 changed files with 177 additions and 10 deletions

View File

@@ -1 +1,2 @@
dist
.env

View File

@@ -0,0 +1,33 @@
services:
mongo:
image: mongo:8
restart: always
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: mongoose
volumes:
- mongo-data:/data/db
healthcheck:
test: mongosh --eval "db.adminCommand('ping')"
interval: 10s
timeout: 5s
retries: 5
mongo-express:
image: mongo-express:latest
restart: always
ports:
- "8081:8081"
environment:
ME_CONFIG_MONGODB_URL: mongodb://root:mongoose@mongo:27017/
ME_CONFIG_BASICAUTH_ENABLED: true
ME_CONFIG_BASICAUTH_USERNAME: admin
ME_CONFIG_BASICAUTH_PASSWORD: admin
depends_on:
mongo:
condition: service_healthy
volumes:
mongo-data:

View File

@@ -11,6 +11,7 @@
"@anthropic-ai/sdk": "^0.71.2",
"@caldav/shared": "*",
"bcrypt": "^6.0.0",
"dotenv": "^16.4.7",
"express": "^5.2.1",
"jsonwebtoken": "^9.0.3",
"mongoose": "^9.1.1"

View File

@@ -1,5 +1,6 @@
import express from 'express';
import mongoose from 'mongoose';
import 'dotenv/config'
import { createRoutes } from './routes';
import { AuthController, ChatController, EventController } from './controllers';

View File

@@ -5,11 +5,21 @@ export class AuthController {
constructor(private authService: AuthService) {}
async login(req: Request, res: Response): Promise<void> {
throw new Error('Not implemented');
try {
const result = await this.authService.login(req.body);
res.json(result);
} catch (error) {
res.status(401).json({ error: (error as Error).message });
}
}
async register(req: Request, res: Response): Promise<void> {
throw new Error('Not implemented');
try {
const result = await this.authService.register(req.body);
res.status(201).json(result);
} catch (error) {
res.status(400).json({ error: (error as Error).message });
}
}
async refresh(req: Request, res: Response): Promise<void> {

View File

@@ -6,5 +6,11 @@ export interface AuthenticatedRequest extends Request {
}
export function authenticate(req: AuthenticatedRequest, res: Response, next: NextFunction): void {
throw new Error('Not implemented');
// TODO: Implement real JWT verification
// Fake user for testing purposes
req.user = {
userId: 'fake-user-id',
email: 'test@example.com',
};
next();
}

View File

@@ -8,10 +8,12 @@ export class MongoUserRepository implements UserRepository {
}
async findByEmail(email: string): Promise<User | null> {
throw new Error('Not implemented');
const user = await UserModel.findOne({ email: email.toLowerCase() });
return user ? user.toJSON() as User : null;
}
async create(data: CreateUserData): Promise<User> {
throw new Error('Not implemented');
const user = await UserModel.create(data);
return user.toJSON() as User;
}
}

View File

@@ -7,11 +7,33 @@ export class AuthService {
constructor(private userRepo: UserRepository) {}
async login(data: LoginDTO): Promise<AuthResponse> {
throw new Error('Not implemented');
const user = await this.userRepo.findByEmail(data.email);
if (!user || !user.passwordHash) {
throw new Error('Invalid credentials');
}
const isValid = await password.compare(data.password, user.passwordHash);
if (!isValid) {
throw new Error('Invalid credentials');
}
return { user, accessToken: '' };
}
async register(data: CreateUserDTO): Promise<AuthResponse> {
throw new Error('Not implemented');
const existingUser = await this.userRepo.findByEmail(data.email);
if (existingUser) {
throw new Error('Email already exists');
}
const passwordHash = await password.hash(data.password);
const user = await this.userRepo.create({
email: data.email,
displayName: data.displayName,
passwordHash,
});
return { user, accessToken: '' };
}
async refreshToken(refreshToken: string): Promise<AuthResponse> {

View File

@@ -3,9 +3,9 @@ import bcrypt from 'bcrypt';
const SALT_ROUNDS = 10;
export async function hash(password: string): Promise<string> {
throw new Error('Not implemented');
return bcrypt.hash(password, SALT_ROUNDS);
}
export async function compare(password: string, hash: string): Promise<boolean> {
throw new Error('Not implemented');
return bcrypt.compare(password, hash);
}