first commit

This commit is contained in:
2026-01-30 14:02:52 +01:00
commit 0c86217bde
52 changed files with 10219 additions and 0 deletions

163
api/index.js Normal file
View File

@@ -0,0 +1,163 @@
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const { Pool } = require('pg');
const { createClient } = require('redis');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
const port = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(morgan('combined'));
// Database
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
// Redis
const redisClient = createClient({
url: process.env.REDIS_URL
});
redisClient.on('error', (err) => console.log('Redis Client Error', err));
(async () => {
await redisClient.connect();
console.log('Connected to Redis');
})();
// Health Check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date() });
});
const srp = require('secure-remote-password/server');
const jwt = require('jsonwebtoken');
// Route: Registration
app.post('/auth/register', async (req, res) => {
try {
const { email, salt, verifier } = req.body;
if (!email || !salt || !verifier) {
return res.status(400).json({ error: 'Missing fields' });
}
// Check if user exists
const userCheck = await pool.query('SELECT id FROM api.users WHERE email = $1', [email]);
if (userCheck.rows.length > 0) {
return res.status(409).json({ error: 'User already exists' });
}
await pool.query(
'INSERT INTO api.users (email, salt, verifier) VALUES ($1, $2, $3)',
[email, salt, verifier]
);
res.status(201).json({ message: 'User registered' });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Registration failed' });
}
});
// Route: Login Step 1
app.post('/auth/login/step1', async (req, res) => {
try {
const { email, clientPublic } = req.body; // clientPublic is 'A'
if (!email || !clientPublic) return res.status(400).json({ error: 'Missing email or clientPublic' });
const result = await pool.query('SELECT id, salt, verifier FROM api.users WHERE email = $1', [email]);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'User not found' });
}
const user = result.rows[0];
// Generate server ephemeral (B)
const serverEphemeral = srp.generateEphemeral(user.verifier);
// serverEphemeral contains { secret, public } (b, B)
// Store session in Redis
const sessionKey = `srp:${email}`;
await redisClient.set(sessionKey, JSON.stringify({
salt: user.salt,
verifier: user.verifier,
serverSecret: serverEphemeral.secret,
serverPublic: serverEphemeral.public,
clientPublic: clientPublic,
userId: user.id
}), {
EX: 300 // 5 minutes expiration
});
res.json({
salt: user.salt,
serverPublic: serverEphemeral.public
});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Login step 1 failed' });
}
});
// Route: Login Step 2
app.post('/auth/login/step2', async (req, res) => {
try {
const { email, clientProof } = req.body; // clientProof is 'M1'
if (!email || !clientProof) return res.status(400).json({ error: 'Missing email or clientProof' });
const sessionKey = `srp:${email}`;
const sessionDataString = await redisClient.get(sessionKey);
if (!sessionDataString) {
return res.status(401).json({ error: 'Session expired or invalid' });
}
const session = JSON.parse(sessionDataString);
// Calculate server session
const serverSession = srp.deriveSession(
session.serverSecret,
session.clientPublic,
session.salt,
email,
session.verifier,
clientProof
);
if (!serverSession) {
return res.status(401).json({ error: 'Authentication failed' });
}
// Generate JWT
const token = jwt.sign(
{
sub: session.userId,
role: 'authenticated', // For PostgREST
email: email
},
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
// Clear session (optional, prevents replay of step 2 within window, though M1 is unique per session usually)
await redisClient.del(sessionKey);
res.json({
serverProof: serverSession.proof, // M2
token: token
});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Login step 2 failed' });
}
});
app.listen(port, () => {
console.log(`API listening on port ${port}`);
});