dragonflight/services/mam-api/src/routes/tokens.js

47 lines
1.8 KiB
JavaScript
Raw Normal View History

// Current-user API token CRUD. The raw token is returned exactly once at
// creation time; only the SHA-256 hash and an 8-char display prefix are stored.
import express from 'express';
import pool from '../db/pool.js';
import { generateToken, hashToken, tokenDisplayPrefix } from '../auth/tokens.js';
const router = express.Router();
// GET / — list current user's tokens (prefix only, never the raw token)
router.get('/', async (req, res, next) => {
try {
const { rows } = await pool.query(
`SELECT id, name, token_prefix AS prefix, last_used_at, expires_at, created_at
FROM api_tokens WHERE user_id = $1 ORDER BY created_at DESC`,
[req.user.id]);
res.json(rows);
} catch (err) { next(err); }
});
// POST / — create a new token
router.post('/', async (req, res, next) => {
try {
const { name, expires_at } = req.body || {};
if (!name || typeof name !== 'string') return res.status(400).json({ error: 'name required' });
const raw = generateToken();
const { rows } = await pool.query(
`INSERT INTO api_tokens (user_id, name, token_hash, token_prefix, expires_at)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, name, token_prefix AS prefix, expires_at, created_at`,
[req.user.id, name.trim(), hashToken(raw), tokenDisplayPrefix(raw), expires_at || null]);
res.status(201).json({ ...rows[0], token: raw }); // raw token shown exactly once
} catch (err) { next(err); }
});
// DELETE /:id — revoke; only the owner can revoke
router.delete('/:id', async (req, res, next) => {
try {
const { rowCount } = await pool.query(
`DELETE FROM api_tokens WHERE id = $1 AND user_id = $2`,
[req.params.id, req.user.id]);
if (rowCount === 0) return res.status(404).json({ error: 'token not found' });
res.status(204).end();
} catch (err) { next(err); }
});
export default router;