api login

This commit is contained in:
JakkrapartXD 2026-01-09 10:14:13 +00:00
parent bd0daf5858
commit 1a7473362b
8 changed files with 613 additions and 60 deletions

View file

@ -1,4 +1,4 @@
import { Body, Post, Route, Tags, SuccessResponse, Response, Example } from 'tsoa';
import { Body, Post, Route, Tags, SuccessResponse, Response, Example, Controller } from 'tsoa';
import { AuthService } from '../services/auth.service';
import {
LoginRequest,
@ -18,7 +18,7 @@ export class AuthController {
/**
* User login
* @summary Login with username and password
* @summary Login with email and password
* @param body Login credentials
* @returns JWT token and user information
*/
@ -52,7 +52,8 @@ export class AuthController {
})
public async login(@Body() body: LoginRequest): Promise<LoginResponse> {
// Validate input
const { error } = loginSchema.validate(body);
const { error, value } = loginSchema.validate(body);
if (error) {
throw new ValidationError(error.details[0].message);
}

View file

@ -19,34 +19,33 @@ export class AuthService {
* User login
*/
async login(data: LoginRequest): Promise<LoginResponse> {
const { username, password } = data;
const { email, password } = data;
// Find user with role and profile
const user = await prisma.user.findUnique({
where: { username },
where: { email },
include: {
role: true,
profile: true
}
});
if (!user) {
logger.warn('Login attempt with invalid username', { username });
throw new UnauthorizedError('Invalid username or password');
logger.warn('Login attempt with invalid email', { email });
throw new UnauthorizedError('Invalid email or password');
}
// Verify password
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
logger.warn('Login attempt with invalid password', { username });
throw new UnauthorizedError('Invalid username or password');
logger.warn('Login attempt with invalid password', { email });
throw new UnauthorizedError('Invalid email or password');
}
// Generate tokens
const token = this.generateAccessToken(user.id, user.username, user.email, user.role.code);
const token = this.generateAccessToken(user.id, user.email, user.email, user.role.code);
const refreshToken = this.generateRefreshToken(user.id);
logger.info('User logged in successfully', { userId: user.id, username: user.username });
logger.info('User logged in successfully', { userId: user.id, email: user.email });
return {
token,

View file

@ -3,7 +3,7 @@
*/
export interface LoginRequest {
username: string;
email: string;
password: string;
}
@ -17,6 +17,7 @@ export interface RegisterRequest {
th?: string;
en?: string;
};
phone: string;
}
export interface LoginResponse {

View file

@ -1,14 +1,12 @@
import Joi from 'joi';
export const loginSchema = Joi.object({
username: Joi.string()
.min(3)
.max(50)
email: Joi.string()
.email({ tlds: { allow: false } }) // Allow any TLD including .local
.required()
.messages({
'string.min': 'Username must be at least 3 characters',
'string.max': 'Username must not exceed 50 characters',
'any.required': 'Username is required'
'string.email': 'Please provide a valid email address',
'any.required': 'Email is required'
}),
password: Joi.string()
.min(6)
@ -47,6 +45,10 @@ export const registerSchema = Joi.object({
'string.max': 'Password must not exceed 100 characters',
'any.required': 'Password is required'
}),
prefix: Joi.object({
th: Joi.string().optional(),
en: Joi.string().optional()
}).optional(),
first_name: Joi.string()
.min(1)
.max(100)
@ -61,10 +63,13 @@ export const registerSchema = Joi.object({
.messages({
'any.required': 'Last name is required'
}),
prefix: Joi.object({
th: Joi.string().optional(),
en: Joi.string().optional()
}).optional()
phone: Joi.string()
.min(10)
.max(15)
.required()
.messages({
'any.required': 'Phone number is required'
})
});
export const refreshTokenSchema = Joi.object({