Files
UNITRONIX bbf839754e Harden bd-mgmt, API key, and WS security
Add multiple security hardenings across the server and web console: enforce proof-of-possession for /ws/bd-mgmt using Ed25519-signed headers with timestamp/nonce and replay protection (public key binding, canonicalization, storage, verification, and tests); remove legacy API key query param and config-table fallback in favor of scoped api_keys (migrate bootstrap key into api_keys); tighten WebSocket origin handling for relay and signal servers to allow only localhost origins by default unless an explicit allowlist is set; update auth middleware public paths and test helpers to use X-API-Key header; add ensureScopedAPIKey migration and related helpers; add a GitHub Secret Scan workflow and an audit report. Misc: propagate audit logging on bd-mgmt connect/disconnect and validate enrollment public keys during device register.
2026-03-29 01:48:14 +01:00

238 lines
12 KiB
HTML

<%- include('layouts/main', {
title: _('tokens.title'),
pageStyles: ['tokens'],
pageScripts: ['tokens'],
currentPage: 'tokens',
breadcrumb: [{ label: _('tokens.title') }],
body: `
<div class="tokens-page">
<!-- Header -->
<div class="page-header">
<div class="page-title">
<h1>${_('tokens.title')}</h1>
</div>
<p class="page-subtitle">${_('tokens.subtitle')}</p>
</div>
<!-- Enrollment Mode -->
<div class="enrollment-mode-card" id="enrollment-mode-card">
<div class="enrollment-mode-header">
<span class="material-icons">security</span>
<h3>${_('tokens.enrollment_mode')}</h3>
</div>
<div class="enrollment-mode-options">
<label class="enrollment-option">
<input type="radio" name="enrollment_mode" value="open">
<span class="option-label">${_('tokens.mode_open')}</span>
<span class="option-desc">${_('tokens.mode_open_desc')}</span>
</label>
<label class="enrollment-option">
<input type="radio" name="enrollment_mode" value="managed">
<span class="option-label">${_('tokens.mode_managed')}</span>
<span class="option-desc">${_('tokens.mode_managed_desc')}</span>
</label>
<label class="enrollment-option">
<input type="radio" name="enrollment_mode" value="locked">
<span class="option-label">${_('tokens.mode_locked')}</span>
<span class="option-desc">${_('tokens.mode_locked_desc')}</span>
</label>
</div>
</div>
<!-- Stats Cards -->
<div class="tokens-stats" id="tokens-stats">
<div class="stat-card">
<span class="material-icons stat-icon">vpn_key</span>
<div class="stat-info">
<div class="stat-value" id="stat-total">0</div>
<div class="stat-label">${_('tokens.total')}</div>
</div>
</div>
<div class="stat-card">
<span class="material-icons stat-icon primary">check_circle</span>
<div class="stat-info">
<div class="stat-value" id="stat-active">0</div>
<div class="stat-label">${_('tokens.active')}</div>
</div>
</div>
<div class="stat-card">
<span class="material-icons stat-icon warning">link</span>
<div class="stat-info">
<div class="stat-value" id="stat-used">0</div>
<div class="stat-label">${_('tokens.used')}</div>
</div>
</div>
<div class="stat-card">
<span class="material-icons stat-icon offline">block</span>
<div class="stat-info">
<div class="stat-value" id="stat-revoked">0</div>
<div class="stat-label">${_('tokens.revoked')}</div>
</div>
</div>
</div>
<!-- Toolbar -->
<div class="tokens-toolbar">
<div class="toolbar-left">
<div class="search-wrapper">
<span class="material-icons">search</span>
<input type="text" id="token-search" placeholder="${_('tokens.search_placeholder')}">
</div>
<div class="filter-pills">
<button class="filter-pill active" data-filter="all">${_('common.all')}</button>
<button class="filter-pill" data-filter="pending">${_('tokens.status_pending')}</button>
<button class="filter-pill" data-filter="used">${_('tokens.status_used')}</button>
<button class="filter-pill" data-filter="revoked">${_('tokens.status_revoked')}</button>
</div>
</div>
<div class="toolbar-right">
<button class="btn btn-secondary" data-token-action="show-bulk-modal">
<span class="material-icons">playlist_add</span>
${_('tokens.bulk_generate')}
</button>
<button class="btn btn-primary" data-token-action="show-create-modal">
<span class="material-icons">add</span>
${_('tokens.create')}
</button>
</div>
</div>
<!-- Tokens Table -->
<div class="tokens-table-wrapper">
<table class="data-table" id="tokens-table">
<thead>
<tr>
<th>${_('tokens.token')}</th>
<th>${_('tokens.name')}</th>
<th>${_('tokens.status')}</th>
<th>${_('tokens.uses')}</th>
<th>${_('tokens.bound_peer')}</th>
<th>${_('tokens.expires')}</th>
<th>${_('tokens.created')}</th>
<th>${_('common.actions')}</th>
</tr>
</thead>
<tbody id="tokens-body">
<tr><td colspan="8" class="empty-row">${_('common.loading')}</td></tr>
</tbody>
</table>
</div>
<!-- Create/Edit Token Modal -->
<div class="modal-overlay" id="token-modal" style="display:none">
<div class="modal">
<div class="modal-header">
<h3 id="token-modal-title">${_('tokens.create')}</h3>
<button class="modal-close" data-token-action="close-modal">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal-body">
<input type="hidden" id="token-id">
<div class="form-row">
<div class="form-group">
<label>${_('tokens.name')}</label>
<input type="text" id="token-name" placeholder="${_('tokens.name_placeholder')}">
</div>
<div class="form-group">
<label>${_('tokens.max_uses')}</label>
<input type="number" id="token-max-uses" value="1" min="0">
<small>${_('tokens.max_uses_hint')}</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>${_('tokens.expires_in')}</label>
<select id="token-expires">
<option value="0">${_('tokens.no_expiration')}</option>
<option value="3600">${_('tokens.expires_1h')}</option>
<option value="86400">${_('tokens.expires_24h')}</option>
<option value="604800">${_('tokens.expires_7d')}</option>
<option value="2592000">${_('tokens.expires_30d')}</option>
</select>
</div>
<div class="form-group">
<label>${_('tokens.note')}</label>
<input type="text" id="token-note" placeholder="${_('tokens.note_placeholder')}">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-token-action="close-modal">${_('common.cancel')}</button>
<button class="btn btn-primary" data-token-action="save-token">${_('common.save')}</button>
</div>
</div>
</div>
<!-- Bulk Generate Modal -->
<div class="modal-overlay" id="bulk-modal" style="display:none">
<div class="modal">
<div class="modal-header">
<h3>${_('tokens.bulk_generate')}</h3>
<button class="modal-close" data-token-action="close-bulk-modal">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal-body">
<div class="form-row">
<div class="form-group">
<label>${_('tokens.count')}</label>
<input type="number" id="bulk-count" value="5" min="1" max="100">
</div>
<div class="form-group">
<label>${_('tokens.name_prefix')}</label>
<input type="text" id="bulk-prefix" value="Device" placeholder="Device">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>${_('tokens.max_uses')}</label>
<input type="number" id="bulk-max-uses" value="1" min="0">
</div>
<div class="form-group">
<label>${_('tokens.expires_in')}</label>
<select id="bulk-expires">
<option value="0">${_('tokens.no_expiration')}</option>
<option value="3600">${_('tokens.expires_1h')}</option>
<option value="86400">${_('tokens.expires_24h')}</option>
<option value="604800" selected>${_('tokens.expires_7d')}</option>
<option value="2592000">${_('tokens.expires_30d')}</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-token-action="close-bulk-modal">${_('common.cancel')}</button>
<button class="btn btn-primary" data-token-action="generate-bulk">
<span class="material-icons">playlist_add</span>
${_('tokens.generate')}
</button>
</div>
</div>
</div>
<!-- Token Created Success Modal -->
<div class="modal-overlay" id="token-created-modal" style="display:none">
<div class="modal">
<div class="modal-header">
<h3>${_('tokens.token_created')}</h3>
<button class="modal-close" data-token-action="close-created-modal">
<span class="material-icons">close</span>
</button>
</div>
<div class="modal-body">
<div class="token-created-warning">
<span class="material-icons">warning</span>
<p>${_('tokens.copy_warning')}</p>
</div>
<div class="token-display" id="created-tokens-list"></div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" data-token-action="close-created-modal">${_('common.close')}</button>
</div>
</div>
</div>
</div>
`
}) %>