Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions lib/auth/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { AuthService } from './service';
import { verifyToken } from './utils';

describe('Authentication Service', () => {
const testUser = {
email: 'test@example.com',
password: 'SecurePass123!'
};

beforeEach(() => {
// Reset the mock user database before each test
(AuthService as any).resetUsers();
});

it('should register a new user', async () => {
const token = await AuthService.register(testUser);
const decoded = verifyToken(token);

expect(decoded).not.toBeNull();
expect(decoded?.userId).toBeDefined();
});

it('should login with valid credentials', async () => {
// First register the user
await AuthService.register(testUser);

// Then login
const token = await AuthService.login(testUser);
const decoded = verifyToken(token);

expect(decoded).not.toBeNull();
expect(decoded?.userId).toBeDefined();
});

it('should fail login with invalid credentials', async () => {
await expect(AuthService.login({
email: 'wrong@example.com',
password: 'WrongPassword123!'
})).rejects.toThrow('Invalid credentials');
});

it('should prevent duplicate user registration', async () => {
await AuthService.register(testUser);

await expect(AuthService.register(testUser))
.rejects.toThrow('User already exists');
});
});
19 changes: 19 additions & 0 deletions lib/auth/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from 'zod';

// Authentication Configuration
export const AuthConfig = {
JWT_SECRET: process.env.JWT_SECRET || 'your_default_secret',
JWT_EXPIRATION: '1h',
};

// Email Validation Schema
export const EmailSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.max(100, 'Password must be less than 100 characters')
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
'Password must include uppercase, lowercase, number, and special character')
});

export type EmailLoginCredentials = z.infer<typeof EmailSchema>;
56 changes: 56 additions & 0 deletions lib/auth/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { EmailLoginCredentials } from './config';
import { hashPassword, verifyPassword, generateToken } from './utils';

// Mock User Database (In a real app, this would be a PostgreSQL database)
let users: Array<{ id: string; email: string; password: string }> = [];

export class AuthService {
static async register(credentials: EmailLoginCredentials): Promise<string> {
const { email, password } = credentials;

// Check if user already exists
const existingUser = users.find(user => user.email === email);
if (existingUser) {
throw new Error('User already exists');
}

// Hash password
const hashedPassword = await hashPassword(password);

// Create user
const newUser = {
id: Date.now().toString(), // Simple unique ID generation
email,
password: hashedPassword
};

users.push(newUser);

// Generate token
return generateToken(newUser.id);
}

static async login(credentials: EmailLoginCredentials): Promise<string> {
const { email, password } = credentials;

// Find user
const user = users.find(u => u.email === email);
if (!user) {
throw new Error('Invalid credentials');
}

// Verify password
const isValid = await verifyPassword(password, user.password);
if (!isValid) {
throw new Error('Invalid credentials');
}

// Generate token
return generateToken(user.id);
}

// For testing purposes: Reset the user database
static resetUsers(): void {
users = [];
}
}
33 changes: 33 additions & 0 deletions lib/auth/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { compare, hash } from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { AuthConfig } from './config';

// Password Hashing
export const hashPassword = async (password: string): Promise<string> => {
return await hash(password, 10);
};

// Password Verification
export const verifyPassword = async (
plainPassword: string,
hashedPassword: string
): Promise<boolean> => {
return await compare(plainPassword, hashedPassword);
};

// JWT Token Generation
export const generateToken = (userId: string): string => {
return jwt.sign({ userId }, AuthConfig.JWT_SECRET, {
expiresIn: AuthConfig.JWT_EXPIRATION
});
};

// JWT Token Verification
export const verifyToken = (token: string): { userId: string } | null => {
try {
const decoded = jwt.verify(token, AuthConfig.JWT_SECRET) as { userId: string };
return decoded;
} catch (error) {
return null;
}
};
Loading