develop #1
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
ai::retry
ai::retry_once
ai::retry_until_green
ai::validate_plan
status::blocked
status::done
status::functional_analysis
status::in_ci
status::in_development
status::in_review
status::technical_analysis
status::waiting_info
status::waiting_validation
type::analysis
type::bug
type::question
type::task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Alchemic.Studio/Trapbox!1
Loading…
Reference in a new issue
No description provided.
Delete branch "develop"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Every /api error now uses the { error: { code, message, details } } envelope: route handlers, a catch-all answering 404 not_found or 405 method_not_allowed (with Allow) for addresses no route serves, and a Nitro error handler placed before Nuxt's. Zod validation errors become 400 validation_failed with [{ path, message }] details. Lists use ?page/?per_page (1-200) and answer { items, page, per_page, total }. Authorization goes through guards: requireUser, requireAdmin, requireScope and requireEnvironmentCapability, which answers 404 for environments the caller cannot see and 403 only when a visible one lacks the capability. Nitro generates the OpenAPI document at /api/v1/openapi.json (limited to /api/v1 endpoints) with the Scalar reference at /api/v1/docs; every route declares its metadata. First endpoints: GET /api/v1/environments, /api/v1/environments/{id} (usage, settings, banner flags) and /api/v1/me. The app gains useApi() with typed ApiError, shared DTO types and an API conventions document; end-to-end tests seed data through the core services. Refs: tasks/18-api-foundation-and-openapi.mdRecord a personal read status per user (and, later, per environment token) in message_reads. GET /api/v1/environments/{id}/messages lists the messages newest first with the caller's read status joined in the same query; opening a message (summary endpoint, placeholder page) marks it read. GET /api/v1/events streams, per browser session, the message and environment events of every environment the user can read. Access and session validity are re-checked for every event; a ready event follows the subscription, a heartbeat keeps the connection alive, and open streams are closed on SIGTERM so that shutdown does not wait for them. The webmail keeps one EventSource per tab with exponential reconnection and a discreet "Reconnecting..." indicator, and the message list (unread in bold, attachment, pin and parsing indicators, relative dates, keyboard navigation, load more) applies new, updated and deleted messages live: a new message shows within 2 seconds of its acceptance. Icons are now bundled locally, so the browser never contacts the Iconify API, which the content security policy refuses. Refs: tasks/20-message-list-and-real-time-updates.mdShow a message's HTML in a sandboxed, script-free frame served on the content origin through short-lived signed links, with its own content security policy and remote content blocked until the user loads it. The email HTML is transformed as a parsed tree (no scripts, frames, objects, forms, event handlers, dangerous URLs, meta refresh or base; cid: images rewritten to signed part links; links in a new tab without referrer). Inline parts are extracted on demand by the parse worker and kept in a small LRU cache. Add GET /api/v1/messages/{id} (marks the message read) with its text, highlighted HTML source (shiki), frame URL and raw text endpoints, and the message page with its HTML, Text, HTML source, Raw message, Headers and Envelope and session tabs, the altered-copy and malformed banners and the 404 state. The placeholder summary endpoint is replaced. Cover the transform, signatures and part cache with unit tests, the endpoints, content routes and page with end-to-end tests, and 32 known XSS payloads sent over SMTP and opened in the browser. Refs: tasks/21-message-view-and-isolated-rendering.mdAdd the pins service: pinning and unpinning are idempotent, announce the change, and refuse with 409 pinned_limit_reached once the environment holds the instance's pinned_limit, the count and the update sharing one immediate transaction. Pinned messages stay in the quota; rotation, retention and delete all keep them, as proven by new integration tests over SMTP and the jobs. Expose PUT and DELETE /api/v1/messages/{id}/pin, the ?pinned= list filter (also used by previous/next and live updates) and pinned_limit in the environment usage. The web app gains pin toggles in the list rows and the message view, a "Pinned" filter and a toast at the limit; Viewers see the indicator only. Refs: tasks/25-pinned-messages.mdAdd GET /api/v1/environments/{id}/usage (storage and message count against their limits, pinned messages against the instance limit, retention, expiry, quota reached, expiry within 7 days and the when-full mode) and GET /api/v1/environments/{id}/rejections (the messages refused after authentication during the last 7 days, at most 500, newest first), visible to every role of the environment. The environment view gains a usage gauge that turns to warning above 80 % and to error at 100 %, banners for a full quota (explaining what happens to new messages) and an approaching expiry, with a slot for the failure-injection banner, and a rejections page with reason badges and the injected marker. Usage and banners follow the live events. Also let the e2e seeding helper wait while another test worker holds the migration lock, and make the previous/next e2e test wait for each message's own frame before the next key press. Refs: tasks/27-usage-gauge-rejections-and-banners.mdManagers and administrators change the expiry date of an environment with PATCH /api/v1/environments/{id}, which accepts only expires_at: a date strictly in the future by the core clock (400 validation_failed with the detail code expires_at_in_past) or null to clear it. Any other field is refused, so quotas and retention stay with the administrators. The response is the environment details with the new expiring_soon flag, and each change is audited as environment.expiry_set with the previous and new dates. The expiry job deletes the environment at a Manager-set date like any other. GET /api/v1/environments/{id}/settings gives Managers every setting of the environment with expiring_soon; other roles get 403. The environment's Settings entry now opens an overview for Managers: quotas and retention, read-only with a note that only administrators change them ("Keep until deleted" flagged), the expiry date with set, extend and a confirmed clear, and links to tagging, credentials and tokens, in English, French and Dutch. Refs: tasks/33-manager-environment-settings-expiry.mdGET /api/v1/environments/{id}/messages/wait answers with the first message matching every given criterion: a recipient (envelope, To, Cc or Bcc address), a sender (envelope or From address), a subject substring and a tag, all without case, received after a date that defaults to the request start. A message already processed and matching is returned at once, the newest first; otherwise the wait subscribes to message.received and answers as soon as a matching message is processed, within a second of its SMTP acceptance, or answers { status: "timeout", message: null } after 1 to 120 seconds (30 by default). Waiting never marks the message read. The wait is event-driven: one listener and one timer, both removed on a match, a timeout or a client disconnect. Every role may wait, with the read scope; environment tokens wait on their own environment only. docs/api/wait-for-message.md documents the parameters, both answers and a Playwright example that replaces a polling loop. Refs: tasks/34-wait-for-message.mdA personal token with the provision scope, owned by a Manager of the project or an administrator, creates an environment in the project with POST /api/v1/projects/{id}/environments. It gets the instance defaults, an SMTP credential labelled provisioned and a read_write environment token named provisioned. All three are created in one transaction, and the secret, the token and the SMTP connection settings are returned once. Members and Viewers of the project get 403, projects the owner cannot see 404, and a name already used in the project 409. POST /api/v1/environments/{id}/credentials/rotate creates a new credential and lets the other active ones expire after grace_minutes (60 by default, 0 at once, at most 1440), listing them with their new expiry. DELETE /api/v1/environments/{id} deletes an environment and all its data for the project's Managers and the administrators; standalone environments are for administrators only. GET /api/v1/projects lists the caller's projects with their role. Every step is audited with the token's owner as actor, never with a secret. docs/api/provisioning.md walks through the merge-request flow. Refs: tasks/35-provisioning-api.mdThe message view gets an Analysis tab with a link check. The check covers every link and linked image of the HTML and text versions: a, area and img in the HTML, http(s) URLs in the text, each once, at most 200. Each one is reported as working, redirected, broken, timed out or blocked by policy, with its HTTP status, final URL and redirects. Links are requested only through the outbound client, so private and internal addresses are never contacted unless allowlisted. Each link gets 10 seconds, five are checked at a time, and GET is tried when HEAD is refused. Analyses get their own framework: results are stored for one hour, refreshed on demand, deduplicated while running, and purged by a job every 15 minutes. Background analyzers answer 202 at once. POST /api/v1/messages/{id}/analysis/links starts a check (force=true refreshes it); GET returns the result, 202 while running, or 404 not_computed. When administrators turn the link checker off, both answer 404 feature_disabled and /api/v1/instance reports features.link_check false. Every role may run it, with the read scope. Refs: tasks/37-link-check.mdWhen the operator sets TRAPBOX_SPAMASSASSIN, the Analysis tab of the message view gets a spam score. The stored message is submitted to the SpamAssassin daemon with the SPAMC/1.5 REPORT command, and the section shows the verdict, the score against the threshold and every rule triggered with its points and explanation. Messages above 512 KiB are not submitted. A copy whose attachments were removed is flagged, since its score may differ from the original. The client speaks SPAMC over TCP with a 30-second timeout. It reads the report tolerantly (wrapped descriptions, negative points, \n line endings) and answers 503 spam_service_unavailable when the daemon cannot be reached, is too slow or fails. The message and the report never appear in logs or error responses. Without TRAPBOX_SPAMASSASSIN, /api/v1/instance reports features.spam_score false, the section is absent, and GET/POST /api/v1/messages/{id}/analysis/spam answer 404 feature_disabled. The end-to-end server runs against a fake spamd started by the Playwright global setup. Refs: tasks/39-spam-score.mdAdd environment administration (spec §9.3, §7): administrators list environments with their project, usage and expiry, create them in a project or standalone with the instance defaults and optional overrides, rename and move them (names unique in their scope), change their quotas and retention ("Keep until deleted" only with an explicit acknowledgment, the maximum message size within the instance maximum, attachments within messages), replace their members, purge all their messages including pinned ones, and delete them with all their data. Every change is audited. The detail page shows the settings form with the instance ceiling, the environment and inherited project members, links to the pages its Managers use, and purge and deletion confirmed by typing the name. The public instance information now includes the maximum message size. Refs: tasks/47-environment-administration.mdSign in through any OpenID Connect provider (spec §4.5): openid-client discovery cached after the first success and retried with a growing delay (503 sso_unavailable meanwhile), PKCE S256, state and nonce kept in a sealed 10-minute trapbox_oidc cookie, and a UserInfo fallback for the email claims. - /auth/oidc/start and /auth/oidc/callback, with /login?error=... redirects - accounts matched on the verified email address, case-insensitively; disabled accounts refused; pending accounts activated - optional automatic account creation without memberships - TRAPBOX_LOCAL_LOGIN=admins refuses other accounts' passwords with 403 local_login_disabled after the password check, still throttled - GET /api/v1/instance exposes sso { enabled, label, local_login } - sign-in page: SSO button, administrator sign-in link, localized errors - sign-in audit entries carry the method and the failure reason - mock OpenID Connect issuer, integration and e2e tests Refs: tasks/50-oidc-single-sign-on.mdClose spec §10.2: every route under server/api/v1 is documented, errors share one envelope and lists one pagination shape, and the reference explains how to authenticate (§10.1). - data-driven Playwright suite reading the route files from disk: each one must appear in /api/v1/openapi.json with a summary, tags, its path parameters and responses; probes of the envelope for 400, 401, 403, 404 and 405; pagination shape and per_page bounds of every list - GET /api/v1/messages/{id}/headers: the ordered headers alone - path parameters documented on the ten routes whose query parameters hid them - "Getting started" description (session cookie, tbp_ and tbe_ tokens, scopes, 600 requests per minute, errors, pagination, wait, provisioning, Mailpit base URL), the public URL as server, session and bearer security schemes; readable without signing in - docs/api/README.md leads to the reference and the guides - "API reference" item in the user menu, in a new tab - fix a timing flake of the content frame expiry test Refs: tasks/52-rest-api-completeness-and-reference.md