fix(server): add registry size/duplicate validation; update database and API docs
This commit is contained in:
@@ -22,6 +22,8 @@ POST /users/@me/verify-password { password } → { valid }
|
||||
POST /users/@me/change-password { currentPassword?, newPassword } → { token }
|
||||
DELETE /users/@me { password, username } → { success }
|
||||
PUT /users/@me/space-layout { items, folders, updatedAt? } → { items, folders, updatedAt }
|
||||
GET /users/@me/federation-registry → { registry: FederationRegistryEntry[], updatedAt: number }
|
||||
PUT /users/@me/federation-registry { registry, updatedAt } → { ok: true, updatedAt } (409 if not newer)
|
||||
GET /users/:id → { user }
|
||||
GET /users/:id/mutuals ?homeUserId= → { mutualFriends[], mutualSpaces[] }
|
||||
```
|
||||
|
||||
@@ -32,6 +32,7 @@ IDs: Snowflake text, permissions: bigint decimal strings
|
||||
| profileUpdatedAt | integer | | Epoch ms |
|
||||
| passwordChangedAt | integer | | Token revocation: tokens before this rejected |
|
||||
| showActivity | integer NOT NULL | 1 | Rich presence visibility |
|
||||
| federationRegistryUpdatedAt | integer | 0 | LWW timestamp for federation registry sync |
|
||||
| createdAt | integer NOT NULL | | Epoch ms |
|
||||
|
||||
### spaces
|
||||
@@ -409,3 +410,21 @@ UNIQUE: (peerId, entityId)
|
||||
| mutatedAt | integer NOT NULL | | Checkpoint for sync |
|
||||
| payload | text | | JSON |
|
||||
Retention: 90 days (cleaned by federation janitor)
|
||||
|
||||
### user_federation_registry
|
||||
Persistent registry of all instances a user has federated with. Tracks full lifecycle.
|
||||
|
||||
| Column | Type | Constraints | Purpose |
|
||||
|--------|------|-------------|---------|
|
||||
| user_id | TEXT | NOT NULL, FK→users(id) CASCADE | Owner |
|
||||
| origin | TEXT | NOT NULL | Instance origin URL (e.g., `https://domain.com`) |
|
||||
| label | TEXT | NOT NULL DEFAULT '' | Instance display name |
|
||||
| username | TEXT | NOT NULL DEFAULT '' | Federated username on remote |
|
||||
| remote_user_id | TEXT | NOT NULL DEFAULT '' | Snowflake ID on remote |
|
||||
| status | TEXT | NOT NULL DEFAULT 'connected' | connected/disconnected/unreachable/auth_expired |
|
||||
| added_at | INTEGER | NOT NULL | Epoch ms — when first federated |
|
||||
| last_connected_at | INTEGER | | Epoch ms — last successful connection |
|
||||
| disconnected_at | INTEGER | | Epoch ms — when user disconnected |
|
||||
| error_message | TEXT | | Last error message |
|
||||
|
||||
**PK:** `(user_id, origin)`
|
||||
|
||||
@@ -510,6 +510,17 @@ export async function userRoutes(app: FastifyInstance): Promise<void> {
|
||||
return reply.code(400).send({ error: 'updatedAt must be a positive number', statusCode: 400 });
|
||||
}
|
||||
|
||||
// Size cap — prevent unbounded registry writes
|
||||
if (registry.length > 100) {
|
||||
return reply.code(400).send({ error: 'Registry cannot exceed 100 entries', statusCode: 400 });
|
||||
}
|
||||
|
||||
// Duplicate origin check
|
||||
const origins = registry.map(e => e.origin);
|
||||
if (new Set(origins).size !== origins.length) {
|
||||
return reply.code(400).send({ error: 'Duplicate origins are not allowed', statusCode: 400 });
|
||||
}
|
||||
|
||||
const validStatuses = ['connected', 'disconnected', 'unreachable', 'auth_expired'];
|
||||
for (const entry of registry) {
|
||||
if (!entry || typeof entry.origin !== 'string' || !entry.origin) {
|
||||
|
||||
Reference in New Issue
Block a user