/* Secret-Swap – minimal Express server with flat-file storage */ import express from 'express'; import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const USERS_FILE = path.join(__dirname, 'users.json'); const EXCH_FILE = path.join(__dirname, 'exchanges.json'); const PORT = process.env.PORT || 3000; /* ---------- helpers ---------- */ const readJSON = (f, d = {}) => (fs.existsSync(f) ? JSON.parse(fs.readFileSync(f)) : d); const writeJSON = (f, o) => fs.writeFileSync(f, JSON.stringify(o, null, 2)); const sessions = new Map(); // token → username const genToken = () => crypto.randomUUID(); function hashPass(pw, salt = crypto.randomBytes(16).toString('hex')) { const hash = crypto.scryptSync(pw, salt, 64).toString('hex'); return `${salt}:${hash}`; } function checkPass(pw, stored) { const [salt, ref] = stored.split(':'); const hash = crypto.scryptSync(pw, salt, 64).toString('hex'); return crypto.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(ref, 'hex')); } /* ---------- tiny templating ---------- */ const page = (title, body) => `${title}

${title}

${body}`; const input = (n,l,t='text') => `
`; /* ---------- app ---------- */ const app = express(); app.use(express.urlencoded({extended:true})); app.use(express.static(path.join(__dirname,'public'))); app.use((req, _res, next) => { const token = (req.headers.cookie||'').split(';').map(c=>c.trim().split('='))[0]?.[1]; req.user = sessions.get(token); req.token = token; next(); }); const needAuth = (req,res,next)=>req.user?next():res.redirect('/login'); /* ---------- auth ---------- */ app.get('/register', (req,res)=>res.send(page('Register',`
${input('username','Username')} ${input('password','Password','password')}

Have an account? Login

`))); app.post('/register',(req,res)=>{ const {username,password}=req.body; const users=readJSON(USERS_FILE,{}); if(users[username])return res.send(page('Error','

User exists.

')); users[username]=hashPass(password); writeJSON(USERS_FILE,users); res.redirect('/login'); }); app.get('/login',(req,res)=>res.send(page('Login',`
${input('username','Username')} ${input('password','Password','password')}

No account? Register

`))); app.post('/login',(req,res)=>{ const {username,password}=req.body; const users=readJSON(USERS_FILE,{}); if(!users[username]||!checkPass(password,users[username])) return res.send(page('Error','

Bad credentials.

')); const token=genToken(); sessions.set(token,username); res.setHeader('Set-Cookie',`token=${token}; HttpOnly; Path=/`); res.redirect('/dashboard'); }); app.get('/logout',(req,res)=>{ if(req.token) sessions.delete(req.token); res.setHeader('Set-Cookie','token=; Max-Age=0; Path=/'); res.redirect('/login'); }); /* ---------- dashboard ---------- */ app.get(['/','/dashboard'],needAuth,(req,res)=>{ const exchanges=readJSON(EXCH_FILE,{}); const list=Object.entries(exchanges) .filter(([id,x])=>x.owner===req.user||x.partner===req.user) .map(([id,x])=>{ const role = x.owner===req.user? 'owner':'partner'; return `
  • [${role}] “${x.secret}” → link${x.responses.length?` (${x.responses.length} reply)`:' '}
  • `; }).join(''); res.send(page('Dashboard',`

    Logged in as ${req.user} | Logout

    Create new secret swap

    ${input('partner','Partner username')} ${input('secret','Your secret')}

    Your swaps

    `)); }); /* ---------- create ---------- */ app.post('/create',needAuth,(req,res)=>{ const {secret,partner}=req.body; const users=readJSON(USERS_FILE,{}); if(!users[partner]) return res.send(page('Error','

    Partner username not found.

    ')); const exchanges=readJSON(EXCH_FILE,{}); const id=crypto.randomUUID(); exchanges[id]={id,owner:req.user,partner,secret,responses:[]}; writeJSON(EXCH_FILE,exchanges); const host=req.headers.host; res.send(page('Swap created',`

    Send this link to ${partner}:

    http://${host}/respond/${id}

    Return to dashboard

    `)); }); /* ---------- respond ---------- */ app.get('/respond/:id',needAuth,(req,res)=>{ const ex=readJSON(EXCH_FILE,{})[req.params.id]; if(!ex) return res.send(page('Error','

    Swap not found.

    ')); if(req.user!==ex.partner && req.user!==ex.owner) return res.send(page('Forbidden','

    You are not part of this swap.

    ')); if(req.user===ex.owner) return res.redirect(`/view/${req.params.id}`); const done=ex.responses.some(r=>r.from===req.user); if(done) return res.redirect(`/view/${req.params.id}`); res.send(page('Respond to secret', `

    Original secret will be revealed after you submit yours.

    ${input('response','Your secret')}
    `)); }); app.post('/respond/:id',needAuth,(req,res)=>{ const data=readJSON(EXCH_FILE,{}); const ex=data[req.params.id]; if(!ex||req.user!==ex.partner) return res.send(page('Error','

    Not allowed.

    ')); ex.responses.push({from:req.user,secret:req.body.response}); writeJSON(EXCH_FILE,data); res.send(page('Secret revealed',`

    Original secret from ${ex.owner}: ${ex.secret}

    Back to dashboard

    `)); }); /* ---------- view (owner) ---------- */ app.get('/view/:id',needAuth,(req,res)=>{ const ex=readJSON(EXCH_FILE,{})[req.params.id]; if(!ex||req.user!==ex.owner) return res.send(page('Error','

    Not allowed.

    ')); const list=ex.responses.map(r=>`
  • ${r.from}: ${r.secret}
  • `).join('')||'
  • (no response yet)
  • '; res.send(page('Swap responses',`

    Your secret: ${ex.secret}

    Back to dashboard

    `)); }); /* ---------- start ---------- */ app.listen(PORT,()=>console.log(`Secret-Swap listening on ${PORT}`));