mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: add contributing guidelines and pull request template; update schema comments and documentation
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
## Summary
|
||||||
|
|
||||||
|
<!-- What changed and why? -->
|
||||||
|
|
||||||
|
## Change Type
|
||||||
|
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] Feature
|
||||||
|
- [ ] Compatibility update
|
||||||
|
- [ ] Documentation
|
||||||
|
- [ ] Refactor
|
||||||
|
|
||||||
|
## Cross-File Checklist
|
||||||
|
|
||||||
|
- [ ] I read `CONTRIBUTING.md`.
|
||||||
|
- [ ] Schema changes, if any, updated both runtime schema and `migrations/0001_init.sql`.
|
||||||
|
- [ ] Persistent data changes, if any, updated backup export/import or documented why backup is not needed.
|
||||||
|
- [ ] User-facing text changes, if any, updated all locale files.
|
||||||
|
- [ ] Bitwarden client compatibility was considered for sync/API shape changes.
|
||||||
|
- [ ] No secrets, tokens, private deployment values, or real vault data are included.
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
- [ ] `npx tsc -p tsconfig.json --noEmit`
|
||||||
|
- [ ] `npx tsc -p webapp/tsconfig.json --noEmit`
|
||||||
|
- [ ] `npm run i18n:validate`
|
||||||
|
- [ ] `npm run build`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
<!-- Anything reviewers should pay special attention to? -->
|
||||||
@@ -67,7 +67,7 @@ class SecurityReport {
|
|||||||
guideStep1: '1. **开发人员**:使用上方表格中的 **位置** 列找到确切的文件和行号。',
|
guideStep1: '1. **开发人员**:使用上方表格中的 **位置** 列找到确切的文件和行号。',
|
||||||
guideStep2: '2. **纠正**:遵循为每个规则提供的文档链接以提交修复。',
|
guideStep2: '2. **纠正**:遵循为每个规则提供的文档链接以提交修复。',
|
||||||
guideStep3: '3. **可追溯性**:完整的原始 `.sarif` 数据已附加到此分支。下载并将其导入您的 IDE(例如 VS Code SARIF 查看器)进行本地分析。',
|
guideStep3: '3. **可追溯性**:完整的原始 `.sarif` 数据已附加到此分支。下载并将其导入您的 IDE(例如 VS Code SARIF 查看器)进行本地分析。',
|
||||||
footer: '💡 *由 Antigravity AI 安全引擎生成。透明度是我们的承诺。*',
|
footer: '💡 *由 NodeWarden 安全工作流生成。透明度是我们的承诺。*',
|
||||||
auditedIcon: '✅ **已审计**',
|
auditedIcon: '✅ **已审计**',
|
||||||
noFiles: '未检索到文件。',
|
noFiles: '未检索到文件。',
|
||||||
trivyTitle: '🛡️ 容器配置安全 (Trivy)',
|
trivyTitle: '🛡️ 容器配置安全 (Trivy)',
|
||||||
@@ -119,7 +119,7 @@ class SecurityReport {
|
|||||||
guideStep1: '1. **Developers**: Use the **Location** column in the tables above to find the exact file and line number.',
|
guideStep1: '1. **Developers**: Use the **Location** column in the tables above to find the exact file and line number.',
|
||||||
guideStep2: '2. **Remediate**: Follow the documentation links provided for each rule to submit a fix.',
|
guideStep2: '2. **Remediate**: Follow the documentation links provided for each rule to submit a fix.',
|
||||||
guideStep3: '3. **Traceability**: Full raw `.sarif` data is attached to this branch. Download and import it into your IDE (e.g., VS Code SARIF Viewer) for local analysis.',
|
guideStep3: '3. **Traceability**: Full raw `.sarif` data is attached to this branch. Download and import it into your IDE (e.g., VS Code SARIF Viewer) for local analysis.',
|
||||||
footer: '💡 *Generated by Antigravity AI Security Engine. Transparency is our commitment.*',
|
footer: '💡 *Generated by the NodeWarden security workflow. Transparency is our commitment.*',
|
||||||
auditedIcon: '✅ **Audited**',
|
auditedIcon: '✅ **Audited**',
|
||||||
noFiles: 'No files found.',
|
noFiles: 'No files found.',
|
||||||
trivyTitle: '🛡️ Container Config Security (Trivy)',
|
trivyTitle: '🛡️ Container Config Security (Trivy)',
|
||||||
|
|||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
# Contributing to NodeWarden
|
||||||
|
|
||||||
|
Thanks for taking the time to improve NodeWarden.
|
||||||
|
|
||||||
|
NodeWarden is a Bitwarden-compatible server with a custom web vault, Cloudflare
|
||||||
|
Workers/D1 storage, attachment storage, imports/exports, and scheduled backups.
|
||||||
|
Small changes can affect official clients, backups, migrations, or locale files,
|
||||||
|
so please keep changes focused and check the related parts of the project.
|
||||||
|
|
||||||
|
## Before Opening an Issue
|
||||||
|
|
||||||
|
For bug reports, include enough detail for someone else to reproduce the problem:
|
||||||
|
|
||||||
|
- The client or browser you used.
|
||||||
|
- The page, API route, or action that failed.
|
||||||
|
- Screenshots, logs, or the exact error message.
|
||||||
|
- Whether the problem happened after sync, import, export, restore, upgrade, or
|
||||||
|
a fresh deployment.
|
||||||
|
|
||||||
|
Please do not report NodeWarden-specific problems to the official Bitwarden
|
||||||
|
team. This project is independent from Bitwarden.
|
||||||
|
|
||||||
|
## Pull Request Guidelines
|
||||||
|
|
||||||
|
Keep pull requests small enough to review. A good PR should explain:
|
||||||
|
|
||||||
|
- What changed and why.
|
||||||
|
- What user-facing behavior changed.
|
||||||
|
- Which related areas were checked.
|
||||||
|
- Which commands were run before submitting.
|
||||||
|
|
||||||
|
Avoid mixing unrelated refactors with feature or bug-fix work. If a cleanup is
|
||||||
|
needed before the real fix, mention that clearly in the PR.
|
||||||
|
|
||||||
|
## Areas That Need Extra Care
|
||||||
|
|
||||||
|
Some parts of the codebase are deliberately connected. When changing one of
|
||||||
|
these areas, check the related files before calling the work complete.
|
||||||
|
|
||||||
|
### Database Changes
|
||||||
|
|
||||||
|
Runtime schema lives in `src/services/storage-schema.ts`. The initial D1 schema
|
||||||
|
lives in `migrations/0001_init.sql`.
|
||||||
|
|
||||||
|
If you add or change a table, column, or index:
|
||||||
|
|
||||||
|
- Update both schema files.
|
||||||
|
- Bump `STORAGE_SCHEMA_VERSION` in `src/services/storage.ts`.
|
||||||
|
- Decide whether the data should be included in instance backup.
|
||||||
|
|
||||||
|
### Backup And Restore
|
||||||
|
|
||||||
|
Backup export and restore are whitelist-based. This protects old backups from
|
||||||
|
breaking when fields are removed and prevents transient or secret runtime data
|
||||||
|
from being exported by accident.
|
||||||
|
|
||||||
|
When adding persistent data, check:
|
||||||
|
|
||||||
|
- `src/services/backup-archive.ts`
|
||||||
|
- `src/services/backup-import.ts`
|
||||||
|
- `webapp/src/lib/api/backup.ts`
|
||||||
|
|
||||||
|
Do not export runtime lock rows such as `backup.runner.lock.v1`. Do not import
|
||||||
|
retired sensitive fields such as `users.api_key`.
|
||||||
|
|
||||||
|
### Secrets And Provider Settings
|
||||||
|
|
||||||
|
Provider credentials must not be stored or exported as plain config JSON. Follow
|
||||||
|
the encrypted settings pattern in `src/services/backup-settings-crypto.ts`, or
|
||||||
|
document a replacement design before changing it.
|
||||||
|
|
||||||
|
### Bitwarden Client Compatibility
|
||||||
|
|
||||||
|
Official Bitwarden clients may send or expect fields that are not used directly
|
||||||
|
by the web vault. Cipher and sync changes should preserve unknown client fields
|
||||||
|
unless they are known-invalid or server-owned.
|
||||||
|
|
||||||
|
Check these files when changing vault item shape or sync behavior:
|
||||||
|
|
||||||
|
- `src/handlers/ciphers.ts`
|
||||||
|
- `src/handlers/sync.ts`
|
||||||
|
- `src/services/storage-cipher-repo.ts`
|
||||||
|
|
||||||
|
### Domain Rules
|
||||||
|
|
||||||
|
Equivalent-domain settings store both client/UI rule state and derived active
|
||||||
|
groups. Do not remove `equivalent_domains`, `custom_equivalent_domains`, or
|
||||||
|
`excluded_global_equivalent_domains` as duplicates without a migration and
|
||||||
|
compatibility plan.
|
||||||
|
|
||||||
|
### Accounts And Passwords
|
||||||
|
|
||||||
|
`users.master_password_hash` is for server-side login verification. It is not the
|
||||||
|
vault decryption key. Password changes, key material, `securityStamp`, and
|
||||||
|
refresh-token revocation must stay aligned.
|
||||||
|
|
||||||
|
Password hints are reminders, not recovery secrets. They must never contain the
|
||||||
|
master password, recovery codes, API keys, or anything that directly unlocks the
|
||||||
|
vault.
|
||||||
|
|
||||||
|
### i18n
|
||||||
|
|
||||||
|
Locale files are complete standalone bundles. When adding or changing user-facing
|
||||||
|
text, keep every locale in sync and run the validation script.
|
||||||
|
|
||||||
|
For new locales, update:
|
||||||
|
|
||||||
|
- `webapp/src/lib/i18n.ts`
|
||||||
|
- `webapp/src/lib/i18n/locales/*`
|
||||||
|
- `scripts/i18n-utils.cjs`
|
||||||
|
|
||||||
|
## Recommended Checks
|
||||||
|
|
||||||
|
For most backend or shared changes:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx tsc -p tsconfig.json --noEmit
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
For webapp text or locale changes:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run i18n:validate
|
||||||
|
npx tsc -p webapp/tsconfig.json --noEmit
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
For documentation-only changes:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
@@ -25,6 +25,9 @@
|
|||||||
|
|
||||||
English: <a href="./README_EN.md"><code>README_EN.md</code></a>
|
English: <a href="./README_EN.md"><code>README_EN.md</code></a>
|
||||||
|
|
||||||
|
贡献指南:<a href="./CONTRIBUTING.md"><code>CONTRIBUTING.md</code></a> |
|
||||||
|
贡献与开发说明:<a href="./CONTRIBUTING.md"><code>CONTRIBUTING.md</code></a>
|
||||||
|
|
||||||
> **免责声明**
|
> **免责声明**
|
||||||
> 本项目仅供学习与交流使用,请定期备份你的密码库。
|
> 本项目仅供学习与交流使用,请定期备份你的密码库。
|
||||||
> 本项目与 Bitwarden 官方无关,请不要向 Bitwarden 官方反馈 NodeWarden 的问题。
|
> 本项目与 Bitwarden 官方无关,请不要向 Bitwarden 官方反馈 NodeWarden 的问题。
|
||||||
|
|||||||
@@ -25,6 +25,9 @@
|
|||||||
|
|
||||||
中文说明:<a href="./README.md"><code>README.md</code></a>
|
中文说明:<a href="./README.md"><code>README.md</code></a>
|
||||||
|
|
||||||
|
Contributing guide: <a href="./CONTRIBUTING.md"><code>CONTRIBUTING.md</code></a> |
|
||||||
|
Contributing and development notes: <a href="./CONTRIBUTING.md"><code>CONTRIBUTING.md</code></a>
|
||||||
|
|
||||||
> **Disclaimer**
|
> **Disclaimer**
|
||||||
>
|
>
|
||||||
> This project is for learning and discussion purposes only. Please back up your vault regularly.
|
> This project is for learning and discussion purposes only. Please back up your vault regularly.
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
PRAGMA foreign_keys = ON;
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
||||||
-- IMPORTANT:
|
-- IMPORTANT:
|
||||||
-- Keep this file in sync with src/services/storage-schema.ts (SCHEMA_STATEMENTS).
|
-- This is the initial D1 schema. Keep it in sync with
|
||||||
|
-- src/services/storage-schema.ts (SCHEMA_STATEMENTS).
|
||||||
-- Any new table/column/index must be added to both places together.
|
-- Any new table/column/index must be added to both places together.
|
||||||
|
--
|
||||||
|
-- WHEN CHANGING THIS:
|
||||||
|
-- - Also bump STORAGE_SCHEMA_VERSION in src/services/storage.ts.
|
||||||
|
-- - If the new table stores persistent data, update backup export/import.
|
||||||
|
-- - Keep src/services/storage-schema.ts idempotent for existing installs.
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS config (
|
CREATE TABLE IF NOT EXISTS config (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const vm = require('vm');
|
const vm = require('vm');
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// This list is the script-side locale source of truth. Keep it in sync with
|
||||||
|
// webapp/src/lib/i18n.ts whenever adding/removing a locale.
|
||||||
const localeDir = path.join(__dirname, '..', 'webapp', 'src', 'lib', 'i18n', 'locales');
|
const localeDir = path.join(__dirname, '..', 'webapp', 'src', 'lib', 'i18n', 'locales');
|
||||||
|
|
||||||
const localeFiles = [
|
const localeFiles = [
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
const { localeFiles, readLocale } = require('./i18n-utils.cjs');
|
const { localeFiles, readLocale } = require('./i18n-utils.cjs');
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// This is the authoritative locale consistency gate. It checks key parity,
|
||||||
|
// placeholder parity, and accidental mostly-English locale files. Run after any
|
||||||
|
// user-facing text or locale-file change.
|
||||||
const locales = Object.fromEntries(
|
const locales = Object.fromEntries(
|
||||||
localeFiles.map(([locale, fileName, variableName]) => [locale, readLocale(fileName, variableName)])
|
localeFiles.map(([locale, fileName, variableName]) => [locale, readLocale(fileName, variableName)])
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
// Shared backup settings types used by both Worker and webapp code.
|
||||||
|
//
|
||||||
|
// CONTRACT:
|
||||||
|
// Keep this file serializable and provider-neutral. Runtime state is operational
|
||||||
|
// metadata; destination fields can contain provider credentials and must be
|
||||||
|
// encrypted by src/services/backup-settings-crypto.ts before storage/export.
|
||||||
|
// User-facing provider names should use canonical values here. Legacy aliases
|
||||||
|
// belong in backend normalization, not in this shared type.
|
||||||
export const BACKUP_DEFAULT_TIMEZONE = 'UTC';
|
export const BACKUP_DEFAULT_TIMEZONE = 'UTC';
|
||||||
export const BACKUP_DEFAULT_RETENTION_COUNT = 30;
|
export const BACKUP_DEFAULT_RETENTION_COUNT = 30;
|
||||||
export const BACKUP_DEFAULT_S3_REGION = 'auto';
|
export const BACKUP_DEFAULT_S3_REGION = 'auto';
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import { isTotpEnabled, verifyTotpToken } from '../utils/totp';
|
|||||||
import { createRecoveryCode, recoveryCodeEquals } from '../utils/recovery-code';
|
import { createRecoveryCode, recoveryCodeEquals } from '../utils/recovery-code';
|
||||||
import { buildAccountKeys } from '../utils/user-decryption';
|
import { buildAccountKeys } from '../utils/user-decryption';
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// users.master_password_hash is server-side login verification only. It does
|
||||||
|
// not decrypt vault data. Password changes must keep encrypted user key material,
|
||||||
|
// securityStamp, refresh-token invalidation, and client compatibility together.
|
||||||
|
// Password hints are non-secret reminders; never treat them as recovery secrets.
|
||||||
function looksLikeEncString(value: string): boolean {
|
function looksLikeEncString(value: string): boolean {
|
||||||
if (!value) return false;
|
if (!value) return false;
|
||||||
const firstDot = value.indexOf('.');
|
const firstDot = value.indexOf('.');
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ const BACKUP_RUNNER_LOCK_KEY = 'backup.runner.lock.v1';
|
|||||||
const BACKUP_RUNNER_LEASE_MS = 10 * 60 * 1000;
|
const BACKUP_RUNNER_LEASE_MS = 10 * 60 * 1000;
|
||||||
const BACKUP_RUNNER_HEARTBEAT_MS = 30 * 1000;
|
const BACKUP_RUNNER_HEARTBEAT_MS = 30 * 1000;
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// The runner lock is a config-row lease, not a queue. It only prevents two
|
||||||
|
// backup/restore jobs from overlapping. Manual runs return conflict when the
|
||||||
|
// lease is held; scheduled runs skip quietly. Never export this row in backups.
|
||||||
interface BackupRunnerLease {
|
interface BackupRunnerLease {
|
||||||
token: string;
|
token: string;
|
||||||
touch: () => Promise<void>;
|
touch: () => Promise<void>;
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ import { deleteAllAttachmentsForCipher, deleteAllAttachmentsForCiphers } from '.
|
|||||||
import { parsePagination, encodeContinuationToken } from '../utils/pagination';
|
import { parsePagination, encodeContinuationToken } from '../utils/pagination';
|
||||||
import { readActingDeviceIdentifier } from '../utils/device';
|
import { readActingDeviceIdentifier } from '../utils/device';
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// Cipher JSON is the highest-risk Bitwarden compatibility surface. Preserve
|
||||||
|
// unknown/future client fields by default, then override only server-owned
|
||||||
|
// fields. Any change to cipher response shape must be checked against /api/sync,
|
||||||
|
// attachments, import/export, and current official clients.
|
||||||
function normalizeOptionalId(value: unknown): string | null {
|
function normalizeOptionalId(value: unknown): string | null {
|
||||||
if (value == null) return null;
|
if (value == null) return null;
|
||||||
const normalized = String(value).trim();
|
const normalized = String(value).trim();
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import {
|
|||||||
} from '../services/domain-rules';
|
} from '../services/domain-rules';
|
||||||
import { errorResponse, jsonResponse } from '../utils/response';
|
import { errorResponse, jsonResponse } from '../utils/response';
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// This route accepts both camelCase and PascalCase Bitwarden-compatible payloads.
|
||||||
|
// It stores custom rules, then derives equivalentDomains from the non-excluded
|
||||||
|
// custom rules. Keep this behavior aligned with backup import/export and
|
||||||
|
// src/services/storage-domain-rules-repo.ts.
|
||||||
function firstPresent(payload: Record<string, unknown>, keys: string[]): unknown {
|
function firstPresent(payload: Record<string, unknown>, keys: string[]): unknown {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
if (Object.prototype.hasOwnProperty.call(payload, key)) return payload[key];
|
if (Object.prototype.hasOwnProperty.call(payload, key)) return payload[key];
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import {
|
|||||||
} from '../utils/user-decryption';
|
} from '../utils/user-decryption';
|
||||||
import { buildDomainsResponse } from '../services/domain-rules';
|
import { buildDomainsResponse } from '../services/domain-rules';
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// /api/sync reuses cipherToResponse() as the single cipher response shaper.
|
||||||
|
// Filtering invalid cipher responses here protects clients from stored rows that
|
||||||
|
// would otherwise make official apps fail after an HTTP 200 sync.
|
||||||
|
// Keep this aligned with src/handlers/ciphers.ts when adding new vault fields.
|
||||||
function buildSyncCacheRequest(request: Request, userId: string, revisionDate: string, excludeDomains: boolean, excludeSends: boolean): Request {
|
function buildSyncCacheRequest(request: Request, userId: string, revisionDate: string, excludeDomains: boolean, excludeSends: boolean): Request {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const cacheUrl = new URL(
|
const cacheUrl = new URL(
|
||||||
|
|||||||
@@ -8,6 +8,17 @@ import {
|
|||||||
getBlobStorageKind,
|
getBlobStorageKind,
|
||||||
} from './blob-store';
|
} from './blob-store';
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// This file defines the exported instance-backup archive shape. Keep it in lock
|
||||||
|
// step with src/services/backup-import.ts and webapp/src/lib/api/backup.ts.
|
||||||
|
//
|
||||||
|
// WHEN CHANGING THIS:
|
||||||
|
// - Add persistent tables to BackupPayload, export SQL, manifest tableCounts,
|
||||||
|
// and validateBackupPayloadContents().
|
||||||
|
// - Keep secrets and transient runtime rows sanitized before writing db.json.
|
||||||
|
// - users.api_key is intentionally not exported.
|
||||||
|
// - backup.settings.v1 is exported as portable-only; the current server runtime
|
||||||
|
// envelope must not leave the instance.
|
||||||
type SqlRow = Record<string, string | number | null>;
|
type SqlRow = Record<string, string | number | null>;
|
||||||
|
|
||||||
const BACKUP_FORMAT_VERSION = 1;
|
const BACKUP_FORMAT_VERSION = 1;
|
||||||
|
|||||||
@@ -8,6 +8,16 @@ import {
|
|||||||
validateBackupPayloadContents,
|
validateBackupPayloadContents,
|
||||||
} from './backup-archive';
|
} from './backup-archive';
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// Restore is intentionally whitelist-based. Old backups may contain retired
|
||||||
|
// fields, but only the columns listed here are imported. Keep this file in sync
|
||||||
|
// with src/services/backup-archive.ts whenever backup contents change.
|
||||||
|
//
|
||||||
|
// WHEN CHANGING THIS:
|
||||||
|
// - Update BackupTableName, BACKUP_TABLES, reset statements, prepared payloads,
|
||||||
|
// shadow-table count validation, insert column lists, and frontend import
|
||||||
|
// count types together.
|
||||||
|
// - Do not import users.api_key, even if an older backup contains it.
|
||||||
type SqlRow = Record<string, string | number | null>;
|
type SqlRow = Record<string, string | number | null>;
|
||||||
type BackupTableName =
|
type BackupTableName =
|
||||||
| 'config'
|
| 'config'
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import type { Env, User } from '../types';
|
import type { Env, User } from '../types';
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// Backup settings contain provider credentials. They are stored as a v2 envelope:
|
||||||
|
// - runtime: AES-GCM encrypted with a key derived from JWT_SECRET for the current
|
||||||
|
// server's scheduled backup runner.
|
||||||
|
// - portable: AES-GCM encrypted with a random DEK; that DEK is RSA-wrapped for
|
||||||
|
// active admin public keys so settings can be repaired after restore/migration.
|
||||||
|
//
|
||||||
|
// New admin-entered provider secrets, such as mail API keys, should use this
|
||||||
|
// pattern or a deliberately documented replacement. Do not store provider
|
||||||
|
// secrets as plain config JSON.
|
||||||
const RUNTIME_SALT = 'nodewarden.backup-settings.runtime.v2';
|
const RUNTIME_SALT = 'nodewarden.backup-settings.runtime.v2';
|
||||||
const RUNTIME_INFO = 'runtime';
|
const RUNTIME_INFO = 'runtime';
|
||||||
const PORTABLE_ALGORITHM = 'RSA-OAEP';
|
const PORTABLE_ALGORITHM = 'RSA-OAEP';
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ import customGlobalDomainsRaw from '../static/global_domains.custom.json';
|
|||||||
import type { CustomEquivalentDomain, DomainRulesResponse, GlobalEquivalentDomain } from '../types';
|
import type { CustomEquivalentDomain, DomainRulesResponse, GlobalEquivalentDomain } from '../types';
|
||||||
import { normalizeEquivalentDomain } from '../../shared/domain-normalize';
|
import { normalizeEquivalentDomain } from '../../shared/domain-normalize';
|
||||||
|
|
||||||
|
// CONTRACT:
|
||||||
|
// Equivalent domains are a Bitwarden compatibility surface. The DB stores both
|
||||||
|
// the full custom rule list and the derived active equivalent-domain groups:
|
||||||
|
// - custom_equivalent_domains: UI/client rules with id + excluded state.
|
||||||
|
// - equivalent_domains: active groups derived from non-excluded custom rules.
|
||||||
|
// - excluded_global_equivalent_domains: disabled global rule type ids.
|
||||||
|
// Do not treat equivalent_domains and custom_equivalent_domains as accidental
|
||||||
|
// duplicates without a migration and compatibility plan.
|
||||||
type RawGlobalDomain = Partial<GlobalEquivalentDomain> & {
|
type RawGlobalDomain = Partial<GlobalEquivalentDomain> & {
|
||||||
Type?: unknown;
|
Type?: unknown;
|
||||||
Domains?: unknown;
|
Domains?: unknown;
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import type { UserDomainSettings } from '../types';
|
import type { UserDomainSettings } from '../types';
|
||||||
import { normalizeCustomEquivalentDomains, normalizeEquivalentDomains } from './domain-rules';
|
import { normalizeCustomEquivalentDomains, normalizeEquivalentDomains } from './domain-rules';
|
||||||
|
|
||||||
|
// Storage adapter for the domain_settings table.
|
||||||
|
//
|
||||||
|
// CONTRACT:
|
||||||
|
// equivalent_domains is kept as the active derived groups for compatibility and
|
||||||
|
// fallback reads. custom_equivalent_domains is the full rule list that preserves
|
||||||
|
// UI/client state. Save both together through saveUserDomainSettings().
|
||||||
function parseJsonArray<T>(raw: string | null | undefined, fallback: T[]): T[] {
|
function parseJsonArray<T>(raw: string | null | undefined, fallback: T[]): T[] {
|
||||||
if (!raw) return fallback;
|
if (!raw) return fallback;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
// IMPORTANT:
|
// IMPORTANT:
|
||||||
// Keep this schema list in sync with migrations/0001_init.sql.
|
// This is the runtime D1 schema bootstrap. Keep it in sync with
|
||||||
// Any new table/column/index must be added to both places together.
|
// migrations/0001_init.sql. Any new table/column/index must be added to both
|
||||||
|
// places together.
|
||||||
|
//
|
||||||
|
// WHEN CHANGING THIS:
|
||||||
|
// - Bump STORAGE_SCHEMA_VERSION in src/services/storage.ts so existing installs
|
||||||
|
// rerun these idempotent statements.
|
||||||
|
// - If the new table stores persistent data, update the backup export/import
|
||||||
|
// contract in src/services/backup-archive.ts and backup-import.ts.
|
||||||
|
// - Keep statements idempotent; D1 may execute them again on later requests.
|
||||||
const SCHEMA_STATEMENTS: readonly string[] = [
|
const SCHEMA_STATEMENTS: readonly string[] = [
|
||||||
'CREATE TABLE IF NOT EXISTS users (' +
|
'CREATE TABLE IF NOT EXISTS users (' +
|
||||||
'id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, name TEXT, master_password_hint TEXT, master_password_hash TEXT NOT NULL, ' +
|
'id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, name TEXT, master_password_hint TEXT, master_password_hash TEXT NOT NULL, ' +
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ import {
|
|||||||
|
|
||||||
const TWO_FACTOR_REMEMBER_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
const TWO_FACTOR_REMEMBER_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
||||||
const STORAGE_SCHEMA_VERSION_KEY = 'schema.version';
|
const STORAGE_SCHEMA_VERSION_KEY = 'schema.version';
|
||||||
|
// IMPORTANT:
|
||||||
|
// Bump this whenever src/services/storage-schema.ts or migrations/0001_init.sql
|
||||||
|
// changes. Existing D1 installs only rerun ensureStorageSchema() when this value
|
||||||
|
// differs from config.schema.version.
|
||||||
const STORAGE_SCHEMA_VERSION = '2026-05-05-domain-rules-v2';
|
const STORAGE_SCHEMA_VERSION = '2026-05-05-domain-rules-v2';
|
||||||
|
|
||||||
// D1-backed storage.
|
// D1-backed storage.
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
// CONTRACT:
|
||||||
|
// Locale bundles are standalone and loaded on demand. Adding a locale requires
|
||||||
|
// updating Locale, AVAILABLE_LOCALES, browser-language detection, localeLoaders,
|
||||||
|
// scripts/i18n-utils.cjs, and the locale file itself.
|
||||||
|
//
|
||||||
|
// Do not call t() at module scope for exported arrays/constants; async init can
|
||||||
|
// otherwise leave raw txt_* keys in the rendered UI.
|
||||||
export type Locale =
|
export type Locale =
|
||||||
| 'en'
|
| 'en'
|
||||||
| 'zh-CN'
|
| 'zh-CN'
|
||||||
|
|||||||
Reference in New Issue
Block a user