Privacy and personal data¶
Every module that stores data about a user contributes it through IPersonalDataContributor: one named JSON section for the export, and an erase step for account deletion.
internal sealed class TaskPersonalData(CoworkeeDbContext db) : IPersonalDataContributor
{
public string Section => "tasks";
public async Task<object?> ExportAsync(PersonalDataSubject subject, CancellationToken ct) =>
await db.Set<TaskItem>().Where(t => t.AssigneeId == subject.UserId).Select(t => new { t.Title, t.DueAt }).ToListAsync(ct);
public Task EraseAsync(PersonalDataSubject subject, CancellationToken ct) =>
db.Set<TaskItem>().Where(t => t.AssigneeId == subject.UserId).ExecuteUpdateAsync(s => s.SetProperty(t => t.AssigneeId, (Guid?)null), ct);
}
services.AddScoped<IPersonalDataContributor, TaskPersonalData>();
PersonalDataSubject carries user id, tenant and e-mail address, read before any contributor runs, so the order does not matter. A section that returns null is left out. Erasing runs in one transaction; direct ExecuteDelete/ExecuteUpdate calls write no audit entries with the old values.
Built-in sections¶
| Section | Module | Erase |
|---|---|---|
profile |
Identity: name, e-mail, phone, address, picture, roles, groups, external logins | user deleted (roles, claims, logins, memberships by cascade), own permission grants removed, values in the user's own audit trail replaced by *** |
settings |
Settings: values with user scope (secrets as ***) |
deleted |
notifications |
Notifications | deleted, with the digest state |
chat |
Chat: sent and received messages | both sides of the conversations deleted |
aiToolCalls |
AI: tool call log (the assistant keeps no chat history) | deleted |
mails |
Mailing: mails queued for the address, without bodies | deleted |
activity |
Auditing: what the user changed | kept; the trail names the actor only by id, which points to nobody afterwards |
social |
Comments, tags and ratings: own comments and ratings | deleted, replies to the comments too |
files |
Files: uploaded files (name, type, size) | kept, they belong to the organisation's folders |
signIns |
Auth server: granted clients | grants and tokens deleted, every session ends |
Database backups keep earlier data until they are rotated.
Endpoints¶
| Endpoint | Who | Effect |
|---|---|---|
GET /api/v1/identity/me/personal-data |
the user | JSON object with one property per section |
POST /api/v1/identity/me/delete with { "email": "…" } |
the user | erases everything; the address must match the account (400 otherwise) |
DELETE /api/v1/identity/users/{id} |
Identity.Users.Manage |
the same for another user of the tenant; only administrators delete administrators |
The last active administrator cannot be deleted (409 identity.last_admin). None of these requests is offered to the AI assistant.
The export is built synchronously: per-user data is small, and nothing is left behind in storage that would need its own expiry and clean-up. Move it into a background job once single users reach many megabytes.
In the client¶
The account page has a Privacy tab: download the data as personal-data.json, or delete the account after typing the own e-mail address; the client signs out afterwards. Administrators find Delete user on the user detail page.