ChiragPatankar's picture
Upload 33 files
1cf0854 verified
import express from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid';
import { OAuth2Client } from 'google-auth-library';
import { database } from '../db/database';
import { authenticateToken } from '../middleware/auth';
const router = express.Router();
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
// Sign up with email/password
router.post('/signup', async (req, res) => {
try {
const { email, password, name } = req.body;
// Validation
if (!email || !password || !name) {
return res.status(400).json({ error: 'Email, password, and name are required' });
}
if (password.length < 6) {
return res.status(400).json({ error: 'Password must be at least 6 characters' });
}
// Check if user already exists
const existingUser = await database.get('SELECT id FROM users WHERE email = ?', [email]);
if (existingUser) {
return res.status(400).json({ error: 'User already exists' });
}
// Hash password
const saltRounds = 10;
const passwordHash = await bcrypt.hash(password, saltRounds);
// Generate verification token
const verificationToken = uuidv4();
// Create user
const result = await database.run(
'INSERT INTO users (email, name, password_hash, verification_token) VALUES (?, ?, ?, ?)',
[email, name, passwordHash, verificationToken]
);
// Create default tenant for user
const tenantResult = await database.run(
'INSERT INTO tenants (name, subdomain) VALUES (?, ?)',
[`${name}'s Workspace`, `tenant-${result.lastID}`]
);
// Link user to tenant
await database.run(
'INSERT INTO user_tenants (user_id, tenant_id, role) VALUES (?, ?, ?)',
[result.lastID, tenantResult.lastID, 'owner']
);
// Generate JWT token
const token = jwt.sign(
{ userId: result.lastID, tenantId: tenantResult.lastID },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
// Get user data
const user = await database.get(
'SELECT id, email, name, avatar, created_at FROM users WHERE id = ?',
[result.lastID]
);
res.status(201).json({
message: 'User created successfully',
token,
user,
tenant: { id: tenantResult.lastID, name: `${name}'s Workspace` }
});
} catch (error) {
console.error('Signup error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Sign in with email/password
router.post('/signin', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
// Get user
const user = await database.get(
'SELECT * FROM users WHERE email = ?',
[email]
);
if (!user || !user.password_hash) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check password
const isValidPassword = await bcrypt.compare(password, user.password_hash);
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Get user's tenant
const userTenant = await database.get(
`SELECT t.id, t.name, t.subdomain, t.plan
FROM tenants t
JOIN user_tenants ut ON t.id = ut.tenant_id
WHERE ut.user_id = ? AND ut.role = 'owner'`,
[user.id]
);
// Generate JWT token
const token = jwt.sign(
{ userId: user.id, tenantId: userTenant?.id },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
// Remove sensitive data
const { password_hash, verification_token, ...safeUser } = user;
res.json({
message: 'Signed in successfully',
token,
user: safeUser,
tenant: userTenant
});
} catch (error) {
console.error('Signin error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Google OAuth
router.post('/google', async (req, res) => {
try {
const { credential } = req.body;
if (!credential) {
return res.status(400).json({ error: 'Google credential is required' });
}
// Verify Google token
const ticket = await client.verifyIdToken({
idToken: credential,
audience: process.env.GOOGLE_CLIENT_ID,
});
const payload = ticket.getPayload();
if (!payload) {
return res.status(400).json({ error: 'Invalid Google token' });
}
const { sub: googleId, email, name, picture } = payload;
// Check if user exists
let user = await database.get('SELECT * FROM users WHERE google_id = ? OR email = ?', [googleId, email]);
if (user) {
// Update Google ID if needed
if (!user.google_id) {
await database.run('UPDATE users SET google_id = ?, avatar = ? WHERE id = ?', [googleId, picture, user.id]);
user.google_id = googleId;
user.avatar = picture;
}
} else {
// Create new user
const result = await database.run(
'INSERT INTO users (email, name, google_id, avatar, email_verified) VALUES (?, ?, ?, ?, ?)',
[email, name, googleId, picture, true]
);
// Create default tenant
const tenantResult = await database.run(
'INSERT INTO tenants (name, subdomain) VALUES (?, ?)',
[`${name}'s Workspace`, `tenant-${result.lastID}`]
);
// Link user to tenant
await database.run(
'INSERT INTO user_tenants (user_id, tenant_id, role) VALUES (?, ?, ?)',
[result.lastID, tenantResult.lastID, 'owner']
);
user = {
id: result.lastID,
email,
name,
google_id: googleId,
avatar: picture,
email_verified: true
};
}
// Get user's tenant
const userTenant = await database.get(
`SELECT t.id, t.name, t.subdomain, t.plan
FROM tenants t
JOIN user_tenants ut ON t.id = ut.tenant_id
WHERE ut.user_id = ? AND ut.role = 'owner'`,
[user.id]
);
// Generate JWT token
const token = jwt.sign(
{ userId: user.id, tenantId: userTenant?.id },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
);
// Remove sensitive data
const { password_hash, verification_token, ...safeUser } = user;
res.json({
message: 'Google authentication successful',
token,
user: safeUser,
tenant: userTenant
});
} catch (error) {
console.error('Google auth error:', error);
res.status(500).json({ error: 'Google authentication failed' });
}
});
// Get current user
router.get('/me', authenticateToken, async (req, res) => {
try {
const { userId, tenantId } = (req as any).user;
// Get user data
const user = await database.get(
'SELECT id, email, name, avatar, email_verified, created_at FROM users WHERE id = ?',
[userId]
);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Get tenant data
const tenant = await database.get(
'SELECT id, name, subdomain, plan, settings FROM tenants WHERE id = ?',
[tenantId]
);
res.json({
user,
tenant
});
} catch (error) {
console.error('Get user error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Update user profile
router.put('/profile', authenticateToken, async (req, res) => {
try {
const { userId } = (req as any).user;
const { name, avatar } = req.body;
const updates = [];
const params = [];
if (name) {
updates.push('name = ?');
params.push(name);
}
if (avatar) {
updates.push('avatar = ?');
params.push(avatar);
}
if (updates.length === 0) {
return res.status(400).json({ error: 'No valid fields to update' });
}
updates.push('updated_at = CURRENT_TIMESTAMP');
params.push(userId);
await database.run(
`UPDATE users SET ${updates.join(', ')} WHERE id = ?`,
params
);
// Get updated user
const user = await database.get(
'SELECT id, email, name, avatar, email_verified, created_at FROM users WHERE id = ?',
[userId]
);
res.json({
message: 'Profile updated successfully',
user
});
} catch (error) {
console.error('Update profile error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Logout (client-side token removal, but we can blacklist if needed)
router.post('/logout', authenticateToken, (req, res) => {
res.json({ message: 'Logged out successfully' });
});
export default router;