mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 17:45:42 +00:00
bbf839754e
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.
238 lines
12 KiB
HTML
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>
|
|
`
|
|
}) %>
|