// MFA ticket binding tests — the second login step's tickets are bound to the
// issuing request's IP + User-Agent (hashed) so a stolen ticket replayed from
// a different origin can't complete the second factor.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { issueTicket, redeemTicket } from '../../src/auth/mfa-tickets.js';

test('ticket round-trips when ip + userAgent match', () => {
  const id = issueTicket('user-1', { ip: '1.2.3.4', userAgent: 'curl/8' });
  assert.equal(redeemTicket(id, { ip: '1.2.3.4', userAgent: 'curl/8' }), 'user-1');
});

test('ticket rejects redemption from a different IP', () => {
  const id = issueTicket('user-1', { ip: '1.2.3.4', userAgent: 'curl/8' });
  assert.equal(redeemTicket(id, { ip: '9.9.9.9', userAgent: 'curl/8' }), null);
});

test('ticket rejects redemption with a different User-Agent', () => {
  const id = issueTicket('user-1', { ip: '1.2.3.4', userAgent: 'curl/8' });
  assert.equal(redeemTicket(id, { ip: '1.2.3.4', userAgent: 'Mozilla/5.0' }), null);
});

test('ticket is single-use even on binding mismatch', () => {
  // A wrong-binding probe must still burn the ticket — otherwise an attacker
  // could try multiple IPs/UAs against the same ticket.
  const id = issueTicket('user-1', { ip: '1.2.3.4', userAgent: 'curl/8' });
  assert.equal(redeemTicket(id, { ip: '9.9.9.9', userAgent: 'curl/8' }), null);
  // Same ticket with correct bindings now also fails — it was consumed.
  assert.equal(redeemTicket(id, { ip: '1.2.3.4', userAgent: 'curl/8' }), null);
});

test('redeemTicket returns null for missing or unknown id', () => {
  assert.equal(redeemTicket(null), null);
  assert.equal(redeemTicket(undefined), null);
  assert.equal(redeemTicket(''), null);
  assert.equal(redeemTicket('not-a-real-id', { ip: 'x', userAgent: 'y' }), null);
});

test('ticket is single-use on success', () => {
  const id = issueTicket('user-1', { ip: '1.2.3.4', userAgent: 'curl/8' });
  assert.equal(redeemTicket(id, { ip: '1.2.3.4', userAgent: 'curl/8' }), 'user-1');
  assert.equal(redeemTicket(id, { ip: '1.2.3.4', userAgent: 'curl/8' }), null);
});

test('issueTicket without bindings still works (back-compat / tests)', () => {
  const id = issueTicket('user-1');
  // No bindings on redeem either — both sides skip the check.
  assert.equal(redeemTicket(id), 'user-1');
});
