PRIMEIRO ENVIO

This commit is contained in:
2025-12-25 12:02:07 -03:00
parent ca49575224
commit c4d3bd9a86
92 changed files with 26976 additions and 1 deletions
+507
View File
@@ -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'
}
});
}
});