mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: remove setup disabling functionality and related UI elements
This commit is contained in:
+5
-26
@@ -1,6 +1,6 @@
|
|||||||
import { Env, DEFAULT_DEV_SECRET } from '../types';
|
import { Env, DEFAULT_DEV_SECRET } from '../types';
|
||||||
import { StorageService } from '../services/storage';
|
import { StorageService } from '../services/storage';
|
||||||
import { jsonResponse, errorResponse, htmlResponse } from '../utils/response';
|
import { jsonResponse, htmlResponse } from '../utils/response';
|
||||||
import { renderRegisterPageHTML } from '../setup/pageTemplate';
|
import { renderRegisterPageHTML } from '../setup/pageTemplate';
|
||||||
import { LIMITS } from '../config/limits';
|
import { LIMITS } from '../config/limits';
|
||||||
|
|
||||||
@@ -16,42 +16,21 @@ function getJwtSecretState(env: Env): JwtSecretState | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleRegisterPage(request: Request, env: Env, jwtState: JwtSecretState | null): Promise<Response> {
|
async function handleRegisterPage(request: Request, env: Env, jwtState: JwtSecretState | null): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
void request;
|
||||||
const disabled = await storage.isSetupDisabled();
|
void env;
|
||||||
if (disabled) {
|
|
||||||
return new Response(null, { status: 404 });
|
|
||||||
}
|
|
||||||
return htmlResponse(renderRegisterPageHTML(jwtState));
|
return htmlResponse(renderRegisterPageHTML(jwtState));
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET / - Setup page
|
// GET / - Setup page
|
||||||
export async function handleSetupPage(request: Request, env: Env): Promise<Response> {
|
export async function handleSetupPage(request: Request, env: Env): Promise<Response> {
|
||||||
const storage = new StorageService(env.DB);
|
|
||||||
const disabled = await storage.isSetupDisabled();
|
|
||||||
if (disabled) {
|
|
||||||
return new Response(null, { status: 404 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 引导页内会处理 JWT_SECRET 检测与分流(坏密钥停留在修复步骤)。
|
|
||||||
const jwtState = getJwtSecretState(env);
|
const jwtState = getJwtSecretState(env);
|
||||||
return handleRegisterPage(request, env, jwtState);
|
return handleRegisterPage(request, env, jwtState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /setup/status
|
// GET /setup/status
|
||||||
export async function handleSetupStatus(request: Request, env: Env): Promise<Response> {
|
export async function handleSetupStatus(request: Request, env: Env): Promise<Response> {
|
||||||
|
void request;
|
||||||
const storage = new StorageService(env.DB);
|
const storage = new StorageService(env.DB);
|
||||||
const registered = await storage.isRegistered();
|
const registered = await storage.isRegistered();
|
||||||
const disabled = await storage.isSetupDisabled();
|
return jsonResponse({ registered });
|
||||||
return jsonResponse({ registered, disabled });
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /setup/disable
|
|
||||||
export async function handleDisableSetup(request: Request, env: Env): Promise<Response> {
|
|
||||||
const storage = new StorageService(env.DB);
|
|
||||||
const registered = await storage.isRegistered();
|
|
||||||
if (!registered) {
|
|
||||||
return errorResponse('Registration required', 403);
|
|
||||||
}
|
|
||||||
await storage.setSetupDisabled();
|
|
||||||
return jsonResponse({ success: true });
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-9
@@ -37,7 +37,7 @@ import {
|
|||||||
import { handleSync } from './handlers/sync';
|
import { handleSync } from './handlers/sync';
|
||||||
|
|
||||||
// Setup handlers
|
// Setup handlers
|
||||||
import { handleSetupPage, handleSetupStatus, handleDisableSetup } from './handlers/setup';
|
import { handleSetupPage, handleSetupStatus } from './handlers/setup';
|
||||||
import { handleKnownDevice, handleGetDevices, handleUpdateDeviceToken } from './handlers/devices';
|
import { handleKnownDevice, handleGetDevices, handleUpdateDeviceToken } from './handlers/devices';
|
||||||
|
|
||||||
// Import handler
|
// Import handler
|
||||||
@@ -176,14 +176,6 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
|
|||||||
return handleSetupStatus(request, env);
|
return handleSetupStatus(request, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable setup page (one-way)
|
|
||||||
if (path === '/setup/disable' && method === 'POST') {
|
|
||||||
if (!isSameOriginWriteRequest(request)) {
|
|
||||||
return errorResponse('Forbidden origin', 403);
|
|
||||||
}
|
|
||||||
return handleDisableSetup(request, env);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Browser/devtools probe endpoint
|
// Browser/devtools probe endpoint
|
||||||
if (path === '/.well-known/appspecific/com.chrome.devtools.json' && method === 'GET') {
|
if (path === '/.well-known/appspecific/com.chrome.devtools.json' && method === 'GET') {
|
||||||
return new Response('{}', {
|
return new Response('{}', {
|
||||||
|
|||||||
@@ -162,17 +162,6 @@ export class StorageService {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
async isSetupDisabled(): Promise<boolean> {
|
|
||||||
const row = await this.db.prepare('SELECT value FROM config WHERE key = ?').bind('setup_disabled').first<{ value: string }>();
|
|
||||||
return row?.value === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
async setSetupDisabled(): Promise<void> {
|
|
||||||
await this.db.prepare('INSERT INTO config(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value')
|
|
||||||
.bind('setup_disabled', 'true')
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Users ---
|
// --- Users ---
|
||||||
|
|
||||||
async getUser(email: string): Promise<User | null> {
|
async getUser(email: string): Promise<User | null> {
|
||||||
|
|||||||
+2
-143
@@ -452,52 +452,6 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
color: var(--muted2);
|
color: var(--muted2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-mask {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(16, 24, 40, 0.45);
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 9999;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.modal-mask.show { display: flex; }
|
|
||||||
.modal {
|
|
||||||
width: min(520px, 100%);
|
|
||||||
border-radius: 16px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: #ffffff;
|
|
||||||
box-shadow: 0 24px 56px rgba(16, 24, 40, 0.18);
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.modal h3 {
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #101828;
|
|
||||||
}
|
|
||||||
.modal p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.7;
|
|
||||||
color: #475467;
|
|
||||||
}
|
|
||||||
.modal-warn {
|
|
||||||
margin-top: 10px;
|
|
||||||
border: 1px solid #fecdca;
|
|
||||||
background: #fff6f5;
|
|
||||||
color: #b42318;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.modal-actions {
|
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -696,13 +650,6 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
<p id="t_done_desc">Your server is ready. Configure your Bitwarden client with this server URL:</p>
|
<p id="t_done_desc">Your server is ready. Configure your Bitwarden client with this server URL:</p>
|
||||||
<div class="server" id="serverUrl"></div>
|
<div class="server" id="serverUrl"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="kv">
|
|
||||||
<h3 id="t_hide_title">Hide setup page</h3>
|
|
||||||
<p id="t_hide_desc"></p>
|
|
||||||
<div style="margin-top:10px;">
|
|
||||||
<button type="button" id="hideBtn" class="btn primary" onclick="openHideConfirmModal()">Hide setup page</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -731,18 +678,6 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="hideModal" class="modal-mask" role="dialog" aria-modal="true" aria-labelledby="hideModalTitle" aria-describedby="hideModalDesc">
|
|
||||||
<div class="modal">
|
|
||||||
<h3 id="hideModalTitle"></h3>
|
|
||||||
<p id="hideModalDesc"></p>
|
|
||||||
<div class="modal-warn" id="hideModalWarn"></div>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button id="hideModalCancel" class="btn" type="button" onclick="closeHideConfirmModal()"></button>
|
|
||||||
<button id="hideModalConfirm" class="btn primary" type="button" onclick="disableSetupPage()"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const JWT_STATE = ${jwtStateJson};
|
const JWT_STATE = ${jwtStateJson};
|
||||||
|
|
||||||
@@ -823,7 +758,7 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
copyCode: '复制验证码',
|
copyCode: '复制验证码',
|
||||||
totpExpire: '秒后过期',
|
totpExpire: '秒后过期',
|
||||||
s6Title: '最终页面',
|
s6Title: '最终页面',
|
||||||
s6Desc: '最后一步:查看客户端使用地址,并可选择隐藏初始化页面。',
|
s6Desc: '最后一步:查看客户端使用地址。',
|
||||||
nameLabel: '昵称',
|
nameLabel: '昵称',
|
||||||
emailLabel: '邮箱',
|
emailLabel: '邮箱',
|
||||||
pwLabel: '主密码',
|
pwLabel: '主密码',
|
||||||
@@ -833,18 +768,7 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
creating: '正在创建…',
|
creating: '正在创建…',
|
||||||
doneTitle: '初始化完成',
|
doneTitle: '初始化完成',
|
||||||
doneDesc: '服务已就绪。在 Bitwarden 客户端中填入以下服务器地址:',
|
doneDesc: '服务已就绪。在 Bitwarden 客户端中填入以下服务器地址:',
|
||||||
hideTitle: '隐藏初始化页',
|
|
||||||
hideDesc: '隐藏后,初始化页对任何人都会返回 404。你的密码库仍可正常使用。',
|
|
||||||
hideBtn: '隐藏初始化页',
|
|
||||||
hideWorking: '正在隐藏…',
|
|
||||||
hideDone: '已隐藏,此页面将返回 404。',
|
|
||||||
hideFailed: '隐藏失败',
|
|
||||||
hideConfirm: '确认隐藏初始化页?隐藏后页面将不可访问,但你的密码库不会受影响。',
|
|
||||||
hideModalTitle: '确认隐藏初始化页',
|
|
||||||
hideModalDesc: '隐藏后,初始化页将被永久关闭(返回 404)。你的密码库可继续使用。',
|
|
||||||
hideModalWarn: '此操作不可恢复。若要重新进入初始化流程,只能重新部署。',
|
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
confirmHide: '确认隐藏',
|
|
||||||
|
|
||||||
prev: '上一步',
|
prev: '上一步',
|
||||||
next: '下一步',
|
next: '下一步',
|
||||||
@@ -920,7 +844,7 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
copyCode: 'Copy code',
|
copyCode: 'Copy code',
|
||||||
totpExpire: 's left',
|
totpExpire: 's left',
|
||||||
s6Title: 'Final step',
|
s6Title: 'Final step',
|
||||||
s6Desc: 'Last step: check your server URL, then optionally hide this setup page.',
|
s6Desc: 'Last step: check your server URL.',
|
||||||
nameLabel: 'Name',
|
nameLabel: 'Name',
|
||||||
emailLabel: 'Email',
|
emailLabel: 'Email',
|
||||||
pwLabel: 'Master password',
|
pwLabel: 'Master password',
|
||||||
@@ -930,18 +854,7 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
creating: 'Creating…',
|
creating: 'Creating…',
|
||||||
doneTitle: 'Setup complete',
|
doneTitle: 'Setup complete',
|
||||||
doneDesc: 'Your server is ready. Use this URL in Bitwarden clients:',
|
doneDesc: 'Your server is ready. Use this URL in Bitwarden clients:',
|
||||||
hideTitle: 'Hide setup page',
|
|
||||||
hideDesc: 'After hiding, this page returns 404 for everyone. Vault still works.',
|
|
||||||
hideBtn: 'Hide setup page',
|
|
||||||
hideWorking: 'Hiding…',
|
|
||||||
hideDone: 'Hidden. This page will now return 404.',
|
|
||||||
hideFailed: 'Failed to hide setup page',
|
|
||||||
hideConfirm: 'Hide setup page? It will no longer be accessible, but vault keeps working.',
|
|
||||||
hideModalTitle: 'Confirm hide setup page',
|
|
||||||
hideModalDesc: 'After hiding, this setup page is permanently closed (returns 404). Your vault keeps working.',
|
|
||||||
hideModalWarn: 'This action cannot be undone. Re-entering setup requires redeploy.',
|
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
confirmHide: 'Confirm hide',
|
|
||||||
|
|
||||||
prev: 'Previous',
|
prev: 'Previous',
|
||||||
next: 'Next',
|
next: 'Next',
|
||||||
@@ -1033,9 +946,6 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
setText('submitBtn', t('create'));
|
setText('submitBtn', t('create'));
|
||||||
setText('t_done_title', t('doneTitle'));
|
setText('t_done_title', t('doneTitle'));
|
||||||
setText('t_done_desc', t('doneDesc'));
|
setText('t_done_desc', t('doneDesc'));
|
||||||
setText('t_hide_title', t('hideTitle'));
|
|
||||||
setText('t_hide_desc', t('hideDesc'));
|
|
||||||
setText('hideBtn', t('hideBtn'));
|
|
||||||
setText('t_s5_title', t('s5Title'));
|
setText('t_s5_title', t('s5Title'));
|
||||||
setText('t_s5_enable_title', t('s5EnableTitle'));
|
setText('t_s5_enable_title', t('s5EnableTitle'));
|
||||||
setText('t_s5_enable_1', t('s5Enable1'));
|
setText('t_s5_enable_1', t('s5Enable1'));
|
||||||
@@ -1046,11 +956,6 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
setText('copyTotpCodeBtnText', t('copyCode'));
|
setText('copyTotpCodeBtnText', t('copyCode'));
|
||||||
setText('t_s6_title', t('s6Title'));
|
setText('t_s6_title', t('s6Title'));
|
||||||
setText('t_s6_desc', t('s6Desc'));
|
setText('t_s6_desc', t('s6Desc'));
|
||||||
setText('hideModalTitle', t('hideModalTitle'));
|
|
||||||
setText('hideModalDesc', t('hideModalDesc'));
|
|
||||||
setText('hideModalWarn', t('hideModalWarn'));
|
|
||||||
setText('hideModalCancel', t('cancel'));
|
|
||||||
setText('hideModalConfirm', t('confirmHide'));
|
|
||||||
|
|
||||||
setText('prevBtn', t('prev'));
|
setText('prevBtn', t('prev'));
|
||||||
setText('nextBtn', t('next'));
|
setText('nextBtn', t('next'));
|
||||||
@@ -1336,42 +1241,6 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
showMessage(t('doneTitle'), 'success');
|
showMessage(t('doneTitle'), 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
function openHideConfirmModal() {
|
|
||||||
const modal = document.getElementById('hideModal');
|
|
||||||
if (modal) modal.classList.add('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeHideConfirmModal() {
|
|
||||||
const modal = document.getElementById('hideModal');
|
|
||||||
if (modal) modal.classList.remove('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function disableSetupPage() {
|
|
||||||
if (!isRegistered) return;
|
|
||||||
closeHideConfirmModal();
|
|
||||||
const btn = document.getElementById('hideBtn');
|
|
||||||
if (btn) {
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.textContent = t('hideWorking');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await fetch('/setup/disable', { method: 'POST' });
|
|
||||||
const data = await res.json();
|
|
||||||
if (res.ok && data.success) {
|
|
||||||
showMessage(t('hideDone'), 'success');
|
|
||||||
setTimeout(() => window.location.reload(), 650);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showMessage(data.error || t('hideFailed'), 'error');
|
|
||||||
} catch {
|
|
||||||
showMessage(t('hideFailed'), 'error');
|
|
||||||
}
|
|
||||||
if (btn) {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = t('hideBtn');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pbkdf2(password, salt, iterations, keyLen) {
|
async function pbkdf2(password, salt, iterations, keyLen) {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const passwordBytes = (password instanceof Uint8Array) ? password : encoder.encode(password);
|
const passwordBytes = (password instanceof Uint8Array) ? password : encoder.encode(password);
|
||||||
@@ -1566,13 +1435,6 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = document.getElementById('hideModal');
|
|
||||||
if (hideModal) {
|
|
||||||
hideModal.addEventListener('click', (e) => {
|
|
||||||
if (e.target === hideModal) closeHideConfirmModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailInput = document.getElementById('email');
|
const emailInput = document.getElementById('email');
|
||||||
if (emailInput) {
|
if (emailInput) {
|
||||||
emailInput.addEventListener('change', () => {
|
emailInput.addEventListener('change', () => {
|
||||||
@@ -1582,9 +1444,6 @@ export function renderRegisterPageHTML(jwtState: JwtSecretState | null): string
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Escape') closeHideConfirmModal();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
Reference in New Issue
Block a user