'use strict'; // Self-service API token (PAT) management. Every endpoint is owner-scoped: a // user only sees / mutates tokens where created_by === req.user.username. // Mirrors proxy's routes/api_token.js. Mounted under middleware.auth only // (no requireAdmin) — any authenticated user may mint one, but the token // itself carries no group claims (see models/api_token.js), so it can only // reach non-admin routes (e.g. GET /api/user/hosts), never the admin-gated // ones under routes/jump.js. const router = require('express').Router(); const {ApiToken} = require('../models'); function forbidden(){ let error = new Error('Forbidden'); error.name = 'Forbidden'; error.message = 'You do not own this API token.'; error.status = 403; return error; } // Resolve a token the caller owns. Missing or not-yours both raise 403 (no // existence leak; ids are unguessable random hex anyway). async function getOwned(req, id){ let token; try{ token = await ApiToken.get(id); }catch(e){ throw forbidden(); } if(!token || token.created_by !== req.user.username) throw forbidden(); return token; } router.get('/', async function(req, res, next){ try{ return res.json({results: await ApiToken.listDetail({created_by: req.user.username})}); }catch(error){ next(error); } }); router.post('/', async function(req, res, next){ try{ const days = req.body.expires_in_days !== '' && req.body.expires_in_days !== undefined ? Number(req.body.expires_in_days) : 0; const token = await ApiToken.add({ name: req.body.name, description: req.body.description || '', created_by: req.user.username, expires_at: days > 0 ? (new Date).getTime() + days * 86400000 : 0, }); return res.json({ results: token, token: token._raw_token, message: `API token '${token.name}' created. Save it now — it will not be shown again.`, }); }catch(error){ next(error); } }); router.get('/:id', async function(req, res, next){ try{ return res.json({results: await getOwned(req, req.params.id)}); }catch(error){ next(error); } }); router.put('/:id', async function(req, res, next){ try{ const token = await getOwned(req, req.params.id); const update = {}; for(const k of ['name', 'description']){ if(req.body[k] !== undefined) update[k] = req.body[k]; } if(req.body.expires_in_days !== undefined && req.body.expires_in_days !== ''){ const days = Number(req.body.expires_in_days); update.expires_at = days > 0 ? (new Date).getTime() + days * 86400000 : 0; }else if(req.body.expires_at !== undefined){ update.expires_at = Number(req.body.expires_at) || 0; } return res.json({ results: await token.update(update), message: `API token '${token.name}' updated.`, }); }catch(error){ next(error); } }); router.delete('/:id', async function(req, res, next){ try{ const token = await getOwned(req, req.params.id); await token.remove(); return res.json({id: req.params.id, message: `API token '${token.name}' revoked.`}); }catch(error){ next(error); } }); router.post('/:id/rotate', async function(req, res, next){ try{ const token = await getOwned(req, req.params.id); const raw = await token.rotate(); return res.json({ token: raw, message: `API token '${token.name}' rotated. Save it — it will not be shown again.`, }); }catch(error){ next(error); } }); module.exports = router;