feat: 添加密码管理功能,包括 API、数据库支持和前端界面
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# Design: Persistent ordering + touch-friendly DnD
|
||||
|
||||
## Database
|
||||
- Add `sort_order integer not null default 0` to `bookmarks`.
|
||||
- Add indexes to support ordered listing:
|
||||
- `(user_id, folder_id, sort_order)`
|
||||
|
||||
## API
|
||||
- Extend `Bookmark` DTO/schema with `sortOrder`.
|
||||
- Add `POST /bookmarks/reorder` similar to existing `/folders/reorder`:
|
||||
- Input: `{ folderId: uuid|null, orderedIds: uuid[] }`
|
||||
- Validates `orderedIds` is a permutation of all bookmarks for that user+folder (excluding deleted).
|
||||
- Transactionally updates `sort_order` for each id.
|
||||
|
||||
## Web UI
|
||||
- Replace native HTML5 drag/drop with a touch-capable approach.
|
||||
- Implementation choice: `sortablejs` (small, proven, touch-friendly).
|
||||
- Bind Sortable to:
|
||||
- Folder header list (per parent group) for folder ordering.
|
||||
- Each open folder’s bookmark list for bookmark ordering.
|
||||
- Root group is rendered as a first-class group and can also be reordered.
|
||||
|
||||
## Compatibility
|
||||
- If the DB schema lacks ordering columns (fresh/old DB), endpoints should return a clear 409 prompting `db:migrate`.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Change: Add persistent drag-and-drop sorting (folders + bookmarks)
|
||||
|
||||
## Why
|
||||
Users need to reorder folders and bookmarks via drag-and-drop (including mobile/touch) and have that order persist across reloads. Current HTML5 drag/drop is unreliable on mobile and ordering is not stored for bookmarks.
|
||||
|
||||
## What Changes
|
||||
- Add persistent ordering for bookmarks (new DB column and API endpoint to reorder within a folder).
|
||||
- Use a touch-friendly drag-and-drop implementation in the web UI for:
|
||||
- Reordering folders within the same parent.
|
||||
- Reordering bookmarks within the same folder.
|
||||
- Keep the root group (no folder) as a first-class group in the UI.
|
||||
|
||||
## Impact
|
||||
- Affected specs: API (OpenAPI-backed)
|
||||
- Affected code:
|
||||
- Server: migrations, bookmarks routes, admin routes, row DTO mapping
|
||||
- Web: MyPage and AdminPage UI ordering and drag/drop
|
||||
- OpenAPI: Bookmark schema and reorder endpoint
|
||||
@@ -0,0 +1,35 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Folder ordering persistence
|
||||
The system SHALL persist folder ordering per user per parent folder.
|
||||
|
||||
#### Scenario: List folders returns stable ordered result
|
||||
- **GIVEN** an authenticated user
|
||||
- **WHEN** the user calls `GET /folders`
|
||||
- **THEN** the server returns folders ordered by `(parentId, sortOrder, name)`
|
||||
|
||||
#### Scenario: Reorder folders within the same parent
|
||||
- **GIVEN** an authenticated user
|
||||
- **WHEN** the user calls `POST /folders/reorder` with `parentId` and `orderedIds`
|
||||
- **THEN** the server persists the new order and returns `{ ok: true }`
|
||||
|
||||
### Requirement: Bookmark ordering persistence
|
||||
The system SHALL persist bookmark ordering per user per folder.
|
||||
|
||||
#### Scenario: List my bookmarks returns stable ordered result
|
||||
- **GIVEN** an authenticated user
|
||||
- **WHEN** the user calls `GET /bookmarks`
|
||||
- **THEN** the server returns bookmarks ordered by `(folderId, sortOrder, updatedAt desc)`
|
||||
|
||||
#### Scenario: Reorder bookmarks within the same folder
|
||||
- **GIVEN** an authenticated user
|
||||
- **WHEN** the user calls `POST /bookmarks/reorder` with `folderId` and `orderedIds`
|
||||
- **THEN** the server persists the new order and returns `{ ok: true }`
|
||||
|
||||
### Requirement: Root group treated consistently
|
||||
The system SHALL treat `folderId=null` bookmarks as belonging to the root group.
|
||||
|
||||
#### Scenario: Reorder root-group bookmarks
|
||||
- **GIVEN** an authenticated user
|
||||
- **WHEN** the user calls `POST /bookmarks/reorder` with `folderId=null`
|
||||
- **THEN** the server reorders root-group bookmarks and returns `{ ok: true }`
|
||||
12
openspec/changes/archive/2026-01-22-add-dnd-sorting/tasks.md
Normal file
12
openspec/changes/archive/2026-01-22-add-dnd-sorting/tasks.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## 1. Implementation
|
||||
- [ ] Add DB support for bookmark ordering (migration + init schema)
|
||||
- [ ] Expose bookmark ordering in DTOs and OpenAPI schema
|
||||
- [ ] Add API endpoint to reorder bookmarks within the same folder
|
||||
- [ ] Ensure list endpoints return folders/bookmarks in stable order (parent+sortOrder, etc.)
|
||||
- [ ] Implement touch-friendly drag sorting in Web UI for folders and bookmarks
|
||||
- [ ] Treat root group (folderId null) as a first-class group for display and bookmark reorder
|
||||
- [ ] Add basic verification steps (build + manual smoke checklist)
|
||||
|
||||
## 2. Spec Updates
|
||||
- [ ] Update OpenAPI contract for bookmark sortOrder and reorder endpoint
|
||||
- [ ] Update OpenSpec API capability delta requirements
|
||||
@@ -0,0 +1,33 @@
|
||||
## Context
|
||||
We need a password manager across extension and web, with admin visibility and per-user isolation. Non-admin users must re-verify their login password to view plaintext.
|
||||
|
||||
## Goals / Non-Goals
|
||||
- Goals:
|
||||
- Save credentials with explicit confirmation.
|
||||
- Autofill selector for saved accounts per site.
|
||||
- Admin can view all users’ credentials.
|
||||
- Non-admin must re-verify password before plaintext reveal.
|
||||
- Encrypt credentials at rest.
|
||||
- Non-Goals:
|
||||
- Browser-level credential integration outside the extension.
|
||||
- Password sharing between users.
|
||||
|
||||
## Decisions
|
||||
- Site key = URL origin (scheme + host + port).
|
||||
- Storage model: one row per (user_id, site_origin, username), allowing multiple accounts per site.
|
||||
- Encrypt password using AES-256-GCM with server-side master key (env), store iv + tag + ciphertext.
|
||||
- Use a session-only toggle to reveal plaintext in the web UI (sessionStorage; reset on browser close).
|
||||
- Extension content script detects login forms; popup asks to save; only on confirm does it call API.
|
||||
|
||||
## Risks / Trade-offs
|
||||
- Storing decryptable passwords increases risk. Mitigation: encryption at rest, strict auth, session-only plaintext reveal, audit logging (future).
|
||||
|
||||
## Migration Plan
|
||||
- Add DB migration for credential tables and indexes.
|
||||
- Add API endpoints and update OpenAPI.
|
||||
- Implement extension flows and web UI.
|
||||
- Add tests for CRUD, reauth, admin access.
|
||||
|
||||
## Open Questions
|
||||
- Confirm site matching scope (origin vs eTLD+1).
|
||||
- Save prompt triggers on form submit (username + password present).
|
||||
@@ -0,0 +1,20 @@
|
||||
# Change: Add password manager (Web + Extension)
|
||||
|
||||
## Why
|
||||
Provide built-in credential saving and autofill for users, with centralized management and admin oversight.
|
||||
|
||||
## What Changes
|
||||
- Add credential save + autofill flows in the extension (explicit user confirmation required).
|
||||
- Add a Web password management page (desktop only) with view/edit/delete.
|
||||
- Add APIs for credential CRUD and admin access; plaintext view available during the current browser session.
|
||||
- Add database schema for credential storage (per-user, per-site, multiple accounts).
|
||||
- Add tests for API and DB flows.
|
||||
|
||||
## Impact
|
||||
- Affected specs: api, password-manager
|
||||
- Affected code: apps/server, apps/web, apps/extension, migrations, spec/openapi.yaml
|
||||
|
||||
## Assumptions (confirm)
|
||||
- “同一网站” is defined as the URL origin (scheme + host + port).
|
||||
- The extension prompts on form submit after username + password are provided.
|
||||
- Credentials are stored encrypted at rest and decrypted server-side for plaintext display.
|
||||
@@ -0,0 +1,41 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Credential storage API
|
||||
The system SHALL provide authenticated CRUD APIs for credentials scoped to the current user.
|
||||
|
||||
#### Scenario: Create credential
|
||||
- **WHEN** an authenticated user calls `POST /credentials` with `siteOrigin`, `username`, and `password`
|
||||
- **THEN** the server stores the credential and returns the created record
|
||||
|
||||
#### Scenario: List credentials
|
||||
- **WHEN** an authenticated user calls `GET /credentials?siteOrigin=...`
|
||||
- **THEN** the server returns the matching credentials for that user
|
||||
|
||||
#### Scenario: Update credential
|
||||
- **WHEN** an authenticated user calls `PATCH /credentials/{id}`
|
||||
- **THEN** the server updates the credential and returns the updated record
|
||||
|
||||
#### Scenario: Delete credential
|
||||
- **WHEN** an authenticated user calls `DELETE /credentials/{id}`
|
||||
- **THEN** the server deletes the credential
|
||||
|
||||
### Requirement: Credential plaintext reveal
|
||||
The system SHALL allow authenticated users to request plaintext passwords for their own credentials.
|
||||
|
||||
#### Scenario: User requests plaintext
|
||||
- **GIVEN** an authenticated user
|
||||
- **WHEN** the user requests plaintext credential data
|
||||
- **THEN** the server returns plaintext passwords for that user
|
||||
|
||||
#### Scenario: Admin requests plaintext
|
||||
- **GIVEN** an authenticated admin user
|
||||
- **WHEN** the admin requests plaintext credential data
|
||||
- **THEN** the server returns plaintext passwords for the target user
|
||||
|
||||
### Requirement: Admin credential access
|
||||
The system SHALL allow an admin to list and manage any user’s credentials.
|
||||
|
||||
#### Scenario: Admin lists user credentials
|
||||
- **GIVEN** an authenticated admin user
|
||||
- **WHEN** the admin calls `GET /admin/users/{id}/credentials`
|
||||
- **THEN** the server returns that user’s credentials
|
||||
@@ -0,0 +1,44 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Extension save prompt
|
||||
The extension SHALL prompt the user to save credentials when a login form is detected and filled.
|
||||
|
||||
#### Scenario: Save confirmed
|
||||
- **WHEN** the user confirms “保存/记住密码” in the prompt
|
||||
- **THEN** the extension sends the credential to the server for storage
|
||||
|
||||
#### Scenario: Save canceled
|
||||
- **WHEN** the user cancels or dismisses the prompt
|
||||
- **THEN** the extension MUST NOT store the credential
|
||||
|
||||
### Requirement: Extension autofill selector
|
||||
The extension SHALL show a credential selector near login fields for sites with saved accounts.
|
||||
|
||||
#### Scenario: Select credential
|
||||
- **GIVEN** a site with multiple saved credentials
|
||||
- **WHEN** the user opens the selector and chooses one
|
||||
- **THEN** the username and password fields are filled with that credential
|
||||
|
||||
### Requirement: Web password manager (desktop only)
|
||||
The web app SHALL provide a desktop-only password manager view.
|
||||
|
||||
#### Scenario: Desktop view
|
||||
- **WHEN** the user visits the password manager page on desktop
|
||||
- **THEN** the page is visible and provides list/edit/delete
|
||||
|
||||
#### Scenario: Mobile view hidden
|
||||
- **WHEN** the user visits the password manager page on mobile
|
||||
- **THEN** the page is hidden or redirects to a notice page
|
||||
|
||||
### Requirement: Plaintext visibility control
|
||||
The system SHALL allow a user to reveal plaintext passwords for their own credentials during the current browser session.
|
||||
|
||||
#### Scenario: User reveals plaintext
|
||||
- **GIVEN** a non-admin user
|
||||
- **WHEN** the user chooses to reveal plaintext
|
||||
- **THEN** the UI shows plaintext passwords during the current browser session
|
||||
|
||||
#### Scenario: Admin view
|
||||
- **GIVEN** an admin user
|
||||
- **WHEN** the admin views credentials
|
||||
- **THEN** plaintext is visible
|
||||
@@ -0,0 +1,31 @@
|
||||
## 1. Spec
|
||||
- [x] 1.1 Update OpenSpec deltas for api/password-manager
|
||||
- [x] 1.2 Update OpenAPI 3.1 contract (spec/openapi.yaml)
|
||||
|
||||
## 2. Database
|
||||
- [x] 2.1 Add migrations for credential storage tables + indexes
|
||||
|
||||
## 3. Server
|
||||
- [x] 3.1 Implement credential CRUD APIs
|
||||
- [x] 3.2 Enable plaintext credential access
|
||||
- [x] 3.3 Implement admin credential access APIs
|
||||
|
||||
## 4. Extension
|
||||
- [x] 4.1 Add content script for detecting login forms
|
||||
- [x] 4.2 Add save-credential prompt + confirm flow
|
||||
- [x] 4.3 Add autofill selector UI on login fields
|
||||
|
||||
## 5. Web
|
||||
- [x] 5.1 Add desktop-only password manager page
|
||||
- [x] 5.2 Add session-based plaintext toggle
|
||||
- [x] 5.3 Add admin view for all users
|
||||
|
||||
## 6. Tests
|
||||
- [x] 6.1 API tests for CRUD + plaintext + admin access
|
||||
- [x] 6.2 DB migration verification
|
||||
|
||||
## 7. Verification
|
||||
- [x] 7.1 Specs updated in openspec/specs
|
||||
- [x] 7.2 OpenAPI updated and validated
|
||||
- [x] 7.3 DB migration applied
|
||||
- [x] 7.4 Server tests executed
|
||||
@@ -63,3 +63,43 @@ The system SHALL treat exactly one configured email as an administrator and allo
|
||||
- **GIVEN** an authenticated admin user
|
||||
- **WHEN** the admin calls `GET /admin/users/{id}/bookmarks`
|
||||
- **THEN** the server returns `200` and that user's bookmarks
|
||||
|
||||
### Requirement: Credential storage API
|
||||
The system SHALL provide authenticated CRUD APIs for credentials scoped to the current user.
|
||||
|
||||
#### Scenario: Create credential
|
||||
- **WHEN** an authenticated user calls `POST /credentials` with `siteOrigin`, `username`, and `password`
|
||||
- **THEN** the server stores the credential and returns the created record
|
||||
|
||||
#### Scenario: List credentials
|
||||
- **WHEN** an authenticated user calls `GET /credentials?siteOrigin=...`
|
||||
- **THEN** the server returns the matching credentials for that user
|
||||
|
||||
#### Scenario: Update credential
|
||||
- **WHEN** an authenticated user calls `PATCH /credentials/{id}`
|
||||
- **THEN** the server updates the credential and returns the updated record
|
||||
|
||||
#### Scenario: Delete credential
|
||||
- **WHEN** an authenticated user calls `DELETE /credentials/{id}`
|
||||
- **THEN** the server deletes the credential
|
||||
|
||||
### Requirement: Credential plaintext access
|
||||
The system SHALL allow authenticated users to request plaintext passwords for their own credentials.
|
||||
|
||||
#### Scenario: User requests plaintext
|
||||
- **GIVEN** an authenticated user
|
||||
- **WHEN** the user calls `GET /credentials?includePassword=true`
|
||||
- **THEN** the server returns plaintext passwords for that user
|
||||
|
||||
#### Scenario: Admin requests plaintext for a user
|
||||
- **GIVEN** an authenticated admin user
|
||||
- **WHEN** the admin calls `GET /admin/users/{id}/credentials?includePassword=true`
|
||||
- **THEN** the server returns plaintext passwords for that user
|
||||
|
||||
### Requirement: Admin credential management
|
||||
The system SHALL allow an admin to list and manage any user’s credentials.
|
||||
|
||||
#### Scenario: Admin lists user credentials
|
||||
- **GIVEN** an authenticated admin user
|
||||
- **WHEN** the admin calls `GET /admin/users/{id}/credentials`
|
||||
- **THEN** the server returns that user’s credentials
|
||||
|
||||
64
openspec/specs/password-manager/spec.md
Normal file
64
openspec/specs/password-manager/spec.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Capability: Password Manager
|
||||
|
||||
## Purpose
|
||||
Define password-manager behavior across the extension and web UI.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Extension save prompt
|
||||
The extension SHALL prompt the user to save credentials when a login form is detected and submitted.
|
||||
|
||||
#### Scenario: Save confirmed
|
||||
- **WHEN** the user confirms “保存/记住密码” in the prompt
|
||||
- **THEN** the extension sends the credential to the server for storage
|
||||
|
||||
#### Scenario: Save canceled
|
||||
- **WHEN** the user cancels or dismisses the prompt
|
||||
- **THEN** the extension MUST NOT store the credential
|
||||
|
||||
#### Scenario: Save prompt suppressed for matching credential
|
||||
- **GIVEN** a previously saved credential for the same `siteOrigin` and `username`
|
||||
- **WHEN** the user submits the same password
|
||||
- **THEN** the save prompt is not shown
|
||||
|
||||
#### Scenario: Save prompt update for password change
|
||||
- **GIVEN** a previously saved credential for the same `siteOrigin` and `username`
|
||||
- **WHEN** the user submits a different password
|
||||
- **THEN** the prompt message indicates a password update
|
||||
|
||||
#### Scenario: Save prompt for new username
|
||||
- **GIVEN** a site with saved credentials
|
||||
- **WHEN** the user submits a username that does not exist
|
||||
- **THEN** the prompt message indicates a new account
|
||||
|
||||
### Requirement: Extension autofill selector
|
||||
The extension SHALL show a credential selector near login fields for sites with saved accounts.
|
||||
|
||||
#### Scenario: Select credential
|
||||
- **GIVEN** a site with multiple saved credentials
|
||||
- **WHEN** the user opens the selector and chooses one
|
||||
- **THEN** the username and password fields are filled with that credential
|
||||
|
||||
### Requirement: Web password manager (desktop only)
|
||||
The web app SHALL provide a desktop-only password manager view.
|
||||
|
||||
#### Scenario: Desktop view
|
||||
- **WHEN** the user visits the password manager page on desktop
|
||||
- **THEN** the page is visible and provides list/edit/delete
|
||||
|
||||
#### Scenario: Mobile view hidden
|
||||
- **WHEN** the user visits the password manager page on mobile
|
||||
- **THEN** the page is hidden or redirects to a notice page
|
||||
|
||||
### Requirement: Plaintext visibility control
|
||||
The system SHALL allow a user to reveal plaintext passwords for their own credentials during the current browser session.
|
||||
|
||||
#### Scenario: User reveals plaintext
|
||||
- **GIVEN** a non-admin user
|
||||
- **WHEN** the user chooses to reveal plaintext
|
||||
- **THEN** the UI shows plaintext passwords during the current browser session
|
||||
|
||||
#### Scenario: Admin view
|
||||
- **GIVEN** an admin user
|
||||
- **WHEN** the admin views credentials
|
||||
- **THEN** plaintext is visible
|
||||
Reference in New Issue
Block a user