Files
DiscordClone/Backend/routes/auth.js

159 lines
5.5 KiB
JavaScript

const express = require('express');
const router = express.Router();
const db = require('../db');
const crypto = require('crypto');
// Helper to generate fake salt for user privacy
function generateFakeSalt(username) {
return crypto.createHmac('sha256', 'SERVER_SECRET_KEY') // In prod, use env var
.update(username)
.digest('hex');
}
router.post('/register', async (req, res) => {
const { username, salt, encryptedMK, hak, publicKey, signingKey, encryptedPrivateKeys, inviteCode } = req.body;
try {
// Step 1: Enforce Invite (unless first user)
const userCountRes = await db.query('SELECT count(*) FROM users');
const userCount = parseInt(userCountRes.rows[0].count);
if (userCount > 0) {
if (!inviteCode) {
return res.status(403).json({ error: 'Invite code required' });
}
// Check Invite validity
const inviteRes = await db.query('SELECT * FROM invites WHERE code = $1', [inviteCode]);
if (inviteRes.rows.length === 0) {
return res.status(403).json({ error: 'Invalid invite code' });
}
var invite = inviteRes.rows[0];
// Check Expiration
if (invite.expires_at && new Date() > new Date(invite.expires_at)) {
return res.status(410).json({ error: 'Invite expired' });
}
// Check Usage Limits
if (invite.max_uses !== null && invite.uses >= invite.max_uses) {
return res.status(410).json({ error: 'Invite max uses reached' });
}
}
// START TRANSACTION - To ensure invite usage and user creation are atomic
await db.query('BEGIN');
try {
// Update Invite Usage (only if enforced)
if (userCount > 0) {
await db.query('UPDATE invites SET uses = uses + 1 WHERE code = $1', [inviteCode]);
}
// Create User
// Create User
const result = await db.query(
`INSERT INTO users (username, client_salt, encrypted_master_key, hashed_auth_key, public_identity_key, public_signing_key, encrypted_private_keys)
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
[username, salt, encryptedMK, hak, publicKey, signingKey, encryptedPrivateKeys]
);
const newUserId = result.rows[0].id;
// Assign Roles
// 1. @everyone (Always)
await db.query(`
INSERT INTO user_roles (user_id, role_id)
SELECT $1, id FROM roles WHERE name = '@everyone'
`, [newUserId]);
// 2. Owner (If first user or if admin logic allows)
if (userCount === 0) {
await db.query(`
INSERT INTO user_roles (user_id, role_id)
SELECT $1, id FROM roles WHERE name = 'Owner'
`, [newUserId]);
// Also set is_admin = true for legacy support
await db.query('UPDATE users SET is_admin = TRUE WHERE id = $1', [newUserId]);
}
await db.query('COMMIT');
res.json({ success: true, userId: result.rows[0].id });
} catch (txErr) {
await db.query('ROLLBACK');
throw txErr;
}
} catch (err) {
console.error(err);
if (err.code === '23505') { // Unique violation
res.status(400).json({ error: 'Username taken' });
} else {
res.status(500).json({ error: 'Server error' });
}
}
});
router.post('/login/salt', async (req, res) => {
const { username } = req.body;
try {
const result = await db.query('SELECT client_salt FROM users WHERE username = $1', [username]);
if (result.rows.length > 0) {
res.json({ salt: result.rows[0].client_salt });
} else {
// Return fake salt to prevent enumeration
res.json({ salt: generateFakeSalt(username) });
}
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Server error' });
}
});
router.post('/login/verify', async (req, res) => {
const { username, dak } = req.body;
try {
const result = await db.query(
'SELECT id, hashed_auth_key, encrypted_master_key, encrypted_private_keys, public_identity_key FROM users WHERE username = $1',
[username]
);
if (result.rows.length === 0) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const user = result.rows[0];
const hashedDAK = crypto.createHash('sha256').update(dak).digest('hex');
if (hashedDAK === user.hashed_auth_key) {
res.json({
success: true,
userId: user.id,
encryptedMK: user.encrypted_master_key,
encryptedPrivateKeys: user.encrypted_private_keys,
publicKey: user.public_identity_key // Return Public Key
});
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Server error' });
}
});
router.get('/users/public-keys', async (req, res) => {
try {
const result = await db.query('SELECT id, username, public_identity_key FROM users');
res.json(result.rows);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Server error' });
}
});
module.exports = router;