dorfteich/packages/shared/i18n/en/errors.json
Claude Opus 4.8 4d48d72c40
All checks were successful
CD / Build and push images (push) Successful in 3m3s
CI / Lint, typecheck, test (push) Successful in 2m21s
CI / Auth e2e pack (push) Successful in 2m58s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
Add grant model and shared permission-resolution algorithm (#51)
The heart of the security model: one algorithm, implemented once, for API,
collab, and UI (permissions.md — authoritative).

- shared `permissions/`: pure resolution (`resolvePageCapability`) exactly per
  permissions.md — specificity page > label (incl. ancestor labels) > pond,
  deny wins within a level, default-closed, Site Admin bypass — plus the trash
  rule (`canAccessPage` / `canAccessTrashedPage`, ADR 0013). `grantValidationError`
  enforces the structural constraints. Documented, I/O-free signatures for
  API/collab reuse.
- prisma: `RoleGrant` (+ grant enums) per data-model.md, unique on
  (pond, subject, role, scope); migration adds a CHECK backstop that a
  POND_ADMIN grant is pond-scope + user-subject.
- api `grants/`: `GrantsService.createGrant` validates before insert (structural
  + no extra admin on a personal pond), rejects duplicates; `grantsForPond`
  returns the shared resolver model (what #52/#53 consume); enum mappers between
  the DB and the shared model. Interim "who may manage grants" stays until #52.
- tests: exhaustive table-driven resolver suite — every worked example from
  permissions.md §Resolution, edge cases (multi-label deny-wins, ancestor
  inheritance, anonymous/public, most-specific-allow-beats-less-specific-deny,
  trash) and a property test (a less-specific grant never overrides a
  more-specific decision); validation unit tests; grants db test proving
  write-time rejection of invalid grants.
- i18n: grant error codes (de + en).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-09 14:02:07 +02:00

62 lines
3.2 KiB
JSON

{
"bad_request": "The request is invalid.",
"unauthorized": "Please sign in to continue.",
"forbidden": "You do not have permission for this action.",
"not_found": "The requested resource does not exist.",
"conflict": "The request conflicts with the current state.",
"gone": "This resource is no longer available.",
"payload_too_large": "The submitted data is too large.",
"rate_limited": "Too many requests — please try again later.",
"internal_error": "Internal server error.",
"registration_closed": "Registration is currently closed on this instance.",
"token_invalid": "This link is invalid or has expired.",
"login_failed": "Username/e-mail or password is incorrect.",
"login_backoff": "Too many failed attempts — please wait a few minutes.",
"email_unverified": "Please confirm your e-mail address first.",
"account_disabled": "This account has been disabled.",
"password_incorrect": "The current password is incorrect.",
"csrf_origin_mismatch": "The request came from an unexpected origin.",
"cannot_revoke_current_session": "Use sign-out to end your current session.",
"personal_pond_undeletable": "The personal pond cannot be deleted.",
"quota_exceeded": "The quota has been reached (limit: {{limit}}).",
"slug_taken": "This slug is already taken in this pond.",
"page_document_too_large": "The page is too large (limit: {{limitBytes}} bytes).",
"invalid_page_state": "The submitted page content is invalid.",
"page_trashed": "This page has been moved to the trash.",
"label_name_taken": "A label with this name already exists at this level.",
"label_cycle": "A label cannot be moved into its own subtree.",
"label_depth_exceeded": "Labels can be nested at most {{max}} levels deep.",
"label_has_pages": "This label still has pages assigned; confirm to detach them.",
"label_wrong_pond": "This label belongs to a different pond.",
"unsupported_file_type": "This file type is not supported.",
"file_too_large": "The file is too large (limit: {{limitBytes}} bytes).",
"network": "The server could not be reached.",
"grant_exists": "This grant already exists.",
"grant_pond_admin_scope": "A Pond Admin grant must apply to the whole pond and a specific user.",
"grant_pond_admin_personal_pond": "A personal pond's only administrator is its owner.",
"grant_subject_id_mismatch": "The grant's subject is inconsistent.",
"grant_scope_id_mismatch": "The grant's scope is inconsistent.",
"validation": {
"required": "This field is required.",
"taken": "This value is already taken.",
"username": {
"tooShort": "The username needs at least 3 characters.",
"tooLong": "The username can have at most 32 characters.",
"charset": "Only letters, digits, and hyphens are allowed."
},
"password": {
"tooShort": "The password needs at least 10 characters.",
"tooLong": "The password can have at most 128 characters.",
"tooCommon": "This password is too common."
},
"email": {
"invalid": "Please enter a valid e-mail address."
},
"displayName": {
"required": "Please enter a display name."
},
"labelColor": "Please enter a colour like #a1b2c3.",
"tooLong": "The input is too long."
}
}