realtimecursor
Version:
Real-time collaboration system with cursor tracking and approval workflow
309 lines (266 loc) • 6.91 kB
JavaScript
/**
* Authentication Service for RealtimeCursor
* Handles user authentication, registration, and authorization
*/
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
// Secret key for JWT signing
const JWT_SECRET = process.env.JWT_SECRET || 'realtimecursor-secret-key-for-development';
class AuthService {
constructor() {
this.users = new Map();
this.projects = new Map();
this.invitations = new Map();
this.comments = new Map();
this.stagedChanges = new Map();
// Create default admin user
this.createDefaultAdmin();
}
/**
* Create a default admin user
*/
createDefaultAdmin() {
const adminId = 'admin-' + crypto.randomBytes(8).toString('hex');
this.users.set(adminId, {
id: adminId,
email: 'admin@example.com',
password: this.hashPassword('Admin123!'),
firstName: 'Admin',
lastName: 'User',
role: 'admin',
email_confirmed: true,
created_at: new Date().toISOString(),
last_sign_in_at: null,
user_metadata: {
firstName: 'Admin',
lastName: 'User',
fullName: 'Admin User'
}
});
}
/**
* Hash a password
*/
hashPassword(password) {
return crypto.createHash('sha256').update(password).digest('hex');
}
/**
* Generate a JWT token
*/
generateToken(user) {
return jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role || 'user'
},
JWT_SECRET,
{ expiresIn: '7d' }
);
}
/**
* Register a new user
*/
async register(userData) {
const { email, password, firstName, lastName } = userData;
// Check if email already exists
for (const [id, user] of this.users.entries()) {
if (user.email === email) {
throw new Error('Email already registered');
}
}
const userId = 'user-' + crypto.randomBytes(8).toString('hex');
const now = new Date().toISOString();
const newUser = {
id: userId,
email,
password: this.hashPassword(password),
firstName,
lastName,
role: 'user',
email_confirmed: true,
created_at: now,
last_sign_in_at: now,
user_metadata: {
firstName,
lastName,
fullName: `${firstName} ${lastName}`
}
};
this.users.set(userId, newUser);
const token = this.generateToken(newUser);
return {
user: {
id: userId,
email,
firstName,
lastName,
role: 'user'
},
token
};
}
/**
* Login a user
*/
async login(email, password) {
let foundUser = null;
// Find user by email
for (const [id, user] of this.users.entries()) {
if (user.email === email) {
foundUser = user;
break;
}
}
if (!foundUser) {
throw new Error('Invalid email or password');
}
// Check password
if (foundUser.password !== this.hashPassword(password)) {
throw new Error('Invalid email or password');
}
// Update last login
foundUser.last_sign_in_at = new Date().toISOString();
const token = this.generateToken(foundUser);
return {
user: {
id: foundUser.id,
email: foundUser.email,
firstName: foundUser.user_metadata?.firstName,
lastName: foundUser.user_metadata?.lastName,
role: foundUser.role || 'user'
},
token
};
}
/**
* Get user by ID
*/
async getUserById(userId) {
return this.users.get(userId);
}
/**
* Update user
*/
async updateUser(userId, userData) {
const user = this.users.get(userId);
if (!user) {
throw new Error('User not found');
}
const { firstName, lastName, email } = userData;
if (email && email !== user.email) {
// Check if email already exists
for (const [id, u] of this.users.entries()) {
if (u.email === email && id !== userId) {
throw new Error('Email already in use');
}
}
user.email = email;
}
if (firstName) {
user.user_metadata.firstName = firstName;
}
if (lastName) {
user.user_metadata.lastName = lastName;
}
if (firstName || lastName) {
user.user_metadata.fullName = `${user.user_metadata.firstName} ${user.user_metadata.lastName}`;
}
return {
user: {
id: user.id,
email: user.email,
firstName: user.user_metadata.firstName,
lastName: user.user_metadata.lastName,
fullName: user.user_metadata.fullName,
role: user.role
}
};
}
/**
* Authenticate token middleware
*/
authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
success: false,
message: 'Authentication token required'
});
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(403).json({
success: false,
message: 'Invalid or expired token'
});
}
}
/**
* Require admin middleware
*/
requireAdmin(req, res, next) {
this.authenticateToken(req, res, () => {
if (req.user.role !== 'admin' && req.user.role !== 'superadmin') {
return res.status(403).json({
success: false,
message: 'Admin access required'
});
}
next();
});
}
/**
* Get all users
*/
async getAllUsers(filters = {}) {
let users = Array.from(this.users.values()).map(user => ({
id: user.id,
email: user.email,
firstName: user.user_metadata?.firstName,
lastName: user.user_metadata?.lastName,
fullName: user.user_metadata?.fullName,
role: user.role || 'user',
emailConfirmed: user.email_confirmed,
createdAt: user.created_at,
lastLoginAt: user.last_sign_in_at
}));
// Apply filters
if (filters.role) {
users = users.filter(user => user.role === filters.role);
}
if (filters.search) {
const search = filters.search.toLowerCase();
users = users.filter(user =>
user.email.toLowerCase().includes(search) ||
user.fullName.toLowerCase().includes(search)
);
}
return {
users,
total: users.length
};
}
/**
* Check if a token is valid
*/
isAuthenticated(token) {
try {
const decoded = jwt.verify(token, JWT_SECRET);
return {
authenticated: true,
user: decoded
};
} catch (error) {
return {
authenticated: false,
error: error.message
};
}
}
}
module.exports = AuthService;