feat(server): add project sharing access control

This commit is contained in:
2026-07-10 14:46:18 +08:00
parent 60130b3d9b
commit 9d47cb9bbd
41 changed files with 3069 additions and 17 deletions
+53
View File
@@ -204,6 +204,59 @@ Renames the infinite canvas project and updates `updatedAt`.
Deletes the project record. PostgreSQL cascades canvas nodes, chat messages, history, and connections; after the database delete succeeds, image assets referenced by the canvas and generated-file history are queued for asynchronous OSS deletion.
## Sharing and access control
Sharing uses a separate authorization policy from project ownership. Effective access is resolved in this order: owner, explicit email/phone member, then link permission.
- `canvas`: canvas document only; messages and agent endpoints are unavailable.
- `viewer`: canvas and chat history are readable, but every mutation is denied.
- `editor`: viewer access plus canvas/agent mutations; authentication is mandatory.
- `owner`: editor access plus share administration and project deletion.
Share IDs are 27-character, case-sensitive base62 values generated from cryptographic randomness. The database stores a SHA-256 lookup hash and AES-GCM ciphertext rather than a plaintext link credential. Supply `SHARING_ENCRYPTION_SECRET` through the deployment secret manager; when omitted, local development falls back to `Auth.TokenSecret`.
All management endpoints require `Authorization: Bearer <accessToken>` and verify project ownership:
- `GET /api/projects/:id/sharing` returns link permission, authorized people, owner information, and visitor count.
- `PUT /api/projects/:id/sharing/link` accepts `private`, `canvas`, `viewer`, or `editor`.
- `POST /api/projects/:id/sharing/members` accepts an email/phone `identifier` and a `canvas`, `viewer`, or `editor` permission. Identifiers are normalized and unique per project; inviting the owner returns `share.self_invite`, while inviting an existing member returns `share.member_exists` (both HTTP `409`).
- `PATCH /api/projects/:id/sharing/members/:memberId` changes a member permission.
- `DELETE /api/projects/:id/sharing/members/:memberId` removes one member.
- `GET /api/projects/:id/sharing/visitors` lists deduplicated visitors and visit counts.
- `DELETE /api/projects/:id/sharing/access` removes the policy, members, and visitors; the old link immediately fails on the next request.
Example invite:
```json
{
"identifier": "editor@example.com",
"permission": "editor"
}
```
`GET /api/shares/:shareId` is the public bootstrap. `Authorization` is optional for `canvas`/`viewer`, mandatory for `editor`, and used to resolve explicit member access. Clients send a random installation-scoped `X-Share-Visitor` value for visitor deduplication.
```json
{
"shareId": "XhRewKnS4it7X9kEMFLcdVJentg",
"project": { "id": "project-id", "nodes": [], "messages": [] },
"access": {
"permission": "canvas",
"source": "link",
"authenticated": false,
"isOwner": false,
"capabilities": {
"canViewCanvas": true,
"canViewChat": false,
"canEdit": false,
"canManage": false
}
}
}
```
Subsequent shared project requests send `X-Share-Id: <shareId>`. Middleware re-resolves access, binds it to the exact project ID, and only then creates an internal owner-scoped repository context. Client-supplied `X-User-Id` is ignored.
## Agent Chat
`POST /api/projects/:id/agent/chat`