PRIMEIRO ENVIO
This commit is contained in:
@@ -0,0 +1,507 @@
|
||||
import express from 'express';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import { authenticateToken, blacklistToken } from '../middleware/auth';
|
||||
import config from '../config';
|
||||
|
||||
// Temporary mock implementations
|
||||
const prisma = {
|
||||
user: {
|
||||
findUnique: async (params: any): Promise<any> => {
|
||||
return {
|
||||
id: 'mock-user-id',
|
||||
email: 'mock@example.com',
|
||||
firstName: 'Mock',
|
||||
lastName: 'User',
|
||||
subscription: 'FREE',
|
||||
isActive: true,
|
||||
passwordHash: '$2b$12$mockhashedpassword',
|
||||
createdAt: new Date(),
|
||||
usageMetrics: {
|
||||
pagesCloned: 0,
|
||||
storageUsed: 0,
|
||||
apiCalls: 0,
|
||||
lastReset: new Date()
|
||||
}
|
||||
};
|
||||
},
|
||||
create: async (params: any): Promise<any> => ({
|
||||
id: 'mock-user-id',
|
||||
email: params.data.email,
|
||||
firstName: params.data.firstName,
|
||||
lastName: params.data.lastName,
|
||||
subscription: 'FREE',
|
||||
createdAt: new Date()
|
||||
}),
|
||||
update: async (params: any): Promise<any> => ({
|
||||
id: 'mock-user-id',
|
||||
email: 'mock@example.com',
|
||||
firstName: 'Mock',
|
||||
lastName: 'User',
|
||||
subscription: 'FREE',
|
||||
updatedAt: new Date()
|
||||
})
|
||||
},
|
||||
userSession: {
|
||||
create: async (params: any): Promise<any> => ({ id: 'mock-session' }),
|
||||
deleteMany: async (params: any): Promise<any> => ({ count: 0 })
|
||||
}
|
||||
};
|
||||
|
||||
const validateUserRegistration = (data: any) => {
|
||||
if (!data.email || !data.password) {
|
||||
throw new Error('Email and password are required');
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const validateUserLogin = (data: any) => {
|
||||
if (!data.email || !data.password) {
|
||||
throw new Error('Email and password are required');
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Register new user
|
||||
router.post('/register', async (req, res, next) => {
|
||||
try {
|
||||
// Validate input
|
||||
const validatedData = validateUserRegistration(req.body);
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { email: validatedData.email }
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'User with this email already exists',
|
||||
code: 'USER_EXISTS'
|
||||
});
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const passwordHash = await AuthService.hashPassword(validatedData.password);
|
||||
|
||||
// Create user
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: validatedData.email,
|
||||
passwordHash,
|
||||
firstName: validatedData.firstName,
|
||||
lastName: validatedData.lastName,
|
||||
subscription: 'FREE',
|
||||
usageMetrics: {
|
||||
create: {
|
||||
pagesCloned: 0,
|
||||
storageUsed: 0,
|
||||
apiCalls: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
subscription: true,
|
||||
createdAt: true
|
||||
}
|
||||
});
|
||||
|
||||
// Generate JWT token pair
|
||||
const tokenPair = AuthService.generateTokenPair(
|
||||
user.id,
|
||||
user.email,
|
||||
user.subscription
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: {
|
||||
user,
|
||||
...tokenPair
|
||||
},
|
||||
message: 'User registered successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Login user
|
||||
router.post('/login', async (req, res, next) => {
|
||||
try {
|
||||
// Validate input
|
||||
const validatedData = validateUserLogin(req.body);
|
||||
|
||||
// Find user
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: validatedData.email },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
passwordHash: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
subscription: true,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid email or password',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValidPassword = await AuthService.verifyPassword(validatedData.password, user.passwordHash);
|
||||
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid email or password',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
}
|
||||
|
||||
// Generate JWT token pair
|
||||
const tokenPair = AuthService.generateTokenPair(
|
||||
user.id,
|
||||
user.email,
|
||||
user.subscription
|
||||
);
|
||||
|
||||
// Create session record
|
||||
await prisma.userSession.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
token: tokenPair.accessToken,
|
||||
expiresAt: new Date(Date.now() + tokenPair.expiresIn * 1000)
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
subscription: user.subscription
|
||||
},
|
||||
...tokenPair
|
||||
},
|
||||
message: 'Login successful'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Logout user
|
||||
router.post('/logout', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (token) {
|
||||
// Blacklist the access token
|
||||
blacklistToken(token);
|
||||
|
||||
// Remove session from database
|
||||
await prisma.userSession.deleteMany({
|
||||
where: {
|
||||
userId: req.user!.id,
|
||||
token
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Logout successful'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Refresh token endpoint
|
||||
router.post('/refresh', async (req, res, next) => {
|
||||
try {
|
||||
const { refreshToken } = req.body;
|
||||
|
||||
if (!refreshToken) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Refresh token is required',
|
||||
code: 'VALIDATION_ERROR'
|
||||
});
|
||||
}
|
||||
|
||||
// Generate new token pair using refresh token
|
||||
const newTokenPair = AuthService.refreshAccessToken(refreshToken);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: newTokenPair,
|
||||
message: 'Token refreshed successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid or expired refresh token',
|
||||
code: 'INVALID_REFRESH_TOKEN'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Logout from all devices
|
||||
router.post('/logout-all', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
// Revoke all tokens for the user
|
||||
AuthService.revokeAllUserTokens(req.user!.id);
|
||||
|
||||
// Remove all sessions from database
|
||||
await prisma.userSession.deleteMany({
|
||||
where: { userId: req.user!.id }
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Logged out from all devices successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Get current user profile
|
||||
router.get('/me', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: req.user!.id },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
avatar: true,
|
||||
subscription: true,
|
||||
emailVerified: true,
|
||||
createdAt: true,
|
||||
usageMetrics: {
|
||||
select: {
|
||||
pagesCloned: true,
|
||||
storageUsed: true,
|
||||
apiCalls: true,
|
||||
lastReset: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'User not found',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: user
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Update user profile
|
||||
router.patch('/me', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const { firstName, lastName, avatar } = req.body;
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id: req.user!.id },
|
||||
data: {
|
||||
...(firstName && { firstName }),
|
||||
...(lastName && { lastName }),
|
||||
...(avatar && { avatar })
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
avatar: true,
|
||||
subscription: true,
|
||||
updatedAt: true
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: updatedUser,
|
||||
message: 'Profile updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Change password
|
||||
router.post('/change-password', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Current password and new password are required',
|
||||
code: 'VALIDATION_ERROR'
|
||||
});
|
||||
}
|
||||
|
||||
if (newPassword.length < 8) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'New password must be at least 8 characters long',
|
||||
code: 'VALIDATION_ERROR'
|
||||
});
|
||||
}
|
||||
|
||||
// Get current user with password
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: req.user!.id },
|
||||
select: { passwordHash: true }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'User not found',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValidPassword = await AuthService.verifyPassword(currentPassword, user.passwordHash);
|
||||
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Current password is incorrect',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
const newPasswordHash = await AuthService.hashPassword(newPassword);
|
||||
|
||||
// Update password
|
||||
await prisma.user.update({
|
||||
where: { id: req.user!.id },
|
||||
data: { passwordHash: newPasswordHash }
|
||||
});
|
||||
|
||||
// Invalidate all existing sessions
|
||||
AuthService.revokeAllUserTokens(req.user!.id);
|
||||
await prisma.userSession.deleteMany({
|
||||
where: { userId: req.user!.id }
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Password changed successfully. Please log in again.'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
// Get active sessions
|
||||
router.get('/sessions', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const sessions = AuthService.getUserActiveSessions(req.user!.id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
sessions: sessions.map(session => ({
|
||||
sessionId: session.sessionId,
|
||||
expiresAt: session.expiresAt,
|
||||
isCurrentSession: false // TODO: Identify current session
|
||||
}))
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Revoke specific session
|
||||
router.delete('/sessions/:sessionId', authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
|
||||
// Revoke specific session
|
||||
AuthService.revokeUserSession(req.user!.id, sessionId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Session revoked successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Validate token endpoint (for debugging/testing)
|
||||
router.post('/validate', async (req, res, next) => {
|
||||
try {
|
||||
const { token } = req.body;
|
||||
|
||||
if (!token) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Token is required',
|
||||
code: 'VALIDATION_ERROR'
|
||||
});
|
||||
}
|
||||
|
||||
const payload = AuthService.verifyToken(token);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
valid: true,
|
||||
payload: {
|
||||
userId: payload.userId,
|
||||
email: payload.email,
|
||||
subscription: payload.subscription,
|
||||
type: payload.type,
|
||||
sessionId: payload.sessionId,
|
||||
expiresAt: new Date(payload.exp! * 1000)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
valid: false,
|
||||
error: error instanceof Error ? error.message : 'Invalid token'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user