Skip to content

0.4.0 (beta)

Choose a tag to compare

@BeneODev BeneODev released this 10 Oct 14:51
· 123 commits to main since this release
ea6d91f

Added

  • A second factor for the sign-in to the UI, a choice of each user. Its
    first method is an authenticator app (TOTP): a code after the
    password. A user holds it on up to ten named devices, each with codes
    of its own, added, renamed and removed on the page Second factor
    with a QR code. The first device brings ten recovery codes shown
    once, also as a text file to download. A further device, new
    recovery codes and the recovery key need a code. A password set by
    someone else is changed only after the code. docs/AUTHENTICATION.md
    has the details. Recovery codes made before schema 19 are void: make
    new ones on the page Second factor after the update.
  • Gmail and Google Workspace accounts over the Gmail API, with the
    kind of account gmail. They sign in with Google in the browser,
    through a Google client of the deployment's own, set up as
    docs/GOOGLE.md says and named in MAILBOX_SERVICE_OAUTH_GOOGLE_CLIENT_ID
    with its secret. Without one, Gmail connects over IMAP with an app
    password as before.
  • In a Gmail account labels are folders, and a message can be in
    several. "All Mail" is the folder ALL_MAIL with the role all.
    Setting keywords answers 501 not_supported. The change feed follows
    Gmail's history, asked every MAILBOX_SERVICE_SYNC_INTERVAL seconds.
  • Looking up a Gmail address offers Sign in with Google first,
    where the deployment has a Google client. The way over IMAP links to
    Google's page for app passwords.
  • Each user in /v1/users says whether it has a second factor for the
    UI sign-in, in second_factor. GET /v1/users/{user_id}/second-factor
    (get_second_factor, in users.read) lists the devices of each
    method, totp so far, never a secret. DELETE /v1/users/{user_id}/second-factor (remove_second_factor) removes the
    whole second factor, DELETE /v1/users/{user_id}/second-factor/totp/{device_id}
    (remove_totp_device) one TOTP device, both in users.manage, for a
    user whose rights the caller holds, never for the caller itself. The
    database moves to schema 19.
  • PATCH /v1/webhooks/{webhook_id} (update_webhook) changes a
    webhook's url, events or accounts. A field left out stays,
    accounts set to null is every account the owner may read. Its
    deliveries, where its posts stand and its secret stay. POST /v1/webhooks/{webhook_id}/secret (renew_webhook_secret) answers a
    new signing secret, shown once. The one before stops at once. Both are
    in webhooks.manage, for the webhook's owner alone, and audited as
    webhooks.changed and webhooks.secret_renewed. The UI's webhook
    page has a Change card and New secret. mailbox-client has both
    as update_webhook and renew_webhook_secret, with the records
    Webhook and WebhookSecret. The secret is a Secret, kept out of
    repr and str, read with get_secret_value().
  • users reset-second-factor <name> on the host removes a user's
    second factor, for the last administrator who lost every device and
    the recovery codes.
  • mailbox-client covers the whole REST API: every operation is a
    method of both clients, named like its operationId. New are the
    administration side (accounts, discovery and the sign-in with a
    code, users and passwords, tokens, roles, second factors, webhooks,
    the audit of administration and of sends, the state of the service,
    the catalogue of rights, health), folders renamed, moved and
    deleted, a message's source (get_message_raw), one message changed
    (update_message), a batch (batch_messages) and the lists across
    accounts (list_all_messages, list_all_changes, list_all_sends).
    They answer records such as Account, User, Grant, Role,
    Token, Webhook and Activity, a list that pages as Paged. A
    secret shown once, a new token, a one-time password or a signing
    secret, comes as a Secret, kept out of repr and str.
  • Python 3.15. The packages support 3.11 to 3.15. The images stay on
    3.14 until a Python 3.15 base image exists.

Changed

  • The MCP server reads its settings as the service does, through
    benethos-mailbox-common[settings], and no longer needs
    python-dotenv. A wrong value in the environment or the settings
    file stops the start and names the setting. The settings file no
    longer changes the environment of the process.
  • The service and the MCP server install a fourth package,
    benethos-mailbox-common, of the same version: what both of them
    need. The MCP server's log is written as the service's: in colour on
    a terminal unless NO_COLOR is set, and its tokens masked as ***.
  • A wrong password or code asked once more before a step, such as the
    recovery key or new recovery codes, counts against the client address
    as a failed sign-in does, not only against the name. Ten from one
    address lock it out, whichever users they were for.
  • A session of the UI ends after 24 hours however it is used
    (MAILBOX_SERVICE_SESSION_MAX_HOURS), and a user holds 10 at most: a
    new sign-in ends the oldest (MAILBOX_SERVICE_SESSIONS_PER_USER). A
    secret shown once, such as a new token, recovery codes or the
    recovery key, waits 5 minutes for the page that shows it
    (MAILBOX_SERVICE_SHOWN_ONCE_MINUTES).
  • Breaking: mailbox-client: list_messages and list_changes take one
    account, account_id is required. list_all_messages and
    list_all_changes read every account. A change of the feed is the
    record Change and a failed id of a batch the record Failed,
    where both were a dict.
  • Breaking: mailbox-client: a message is the record Message,
    a summary in a page, a changed message and a written draft the record
    MessageSummary, where each was a dict. The API's from is
    sender, a time a datetime, an address an Address, an attachment
    of a message an AttachedFile. Change.at is a datetime too.
  • Breaking: mailbox-client: me() is now get_me(), named like
    its operation, as every method of the client is.
  • The UI names the company a person signs in at: "Sign in with
    Google" for Gmail. It offers a sign-in with a code only where the
    provider has one.
  • The UI shows icons, a set of Bootstrap Icons kept in the service, and
    asks before a form that changes much in a dialog of its own. Every
    secret shown once has a copy button. Lists show recent times as
    "3 minutes ago". / puts the cursor in the search field. Nothing is
    loaded from elsewhere.
  • The UI's sidebar ends in an account menu with your page, Password,
    Second factor and Sign out. It folds to icons, and a dot marks
    Accounts or Webhooks when something there needs a look. Mail is now
    Mail search. The Status page is gone: the Accounts list shows
    each account's last sync and error, the overview the sync worker.
  • Lists in the UI create at their head and act on their rows: a plus in
    the card's header, a pencil and a bin on each device, token and the
    folder shown. Several devices or tokens are ticked and removed or
    revoked together, all of them or none, devices after one password
    and one code. A folder is renamed and moved in one form, and a move
    asks first.
  • A user's page in the UI has the tabs Rights, Access and Activity. The
    grant editor shows each grant as a line to change or remove. The
    Users list disables, enables, gives or takes a role of the ticked
    users, all of them or none, and names those it could not change.
  • Each row of the UI's Accounts list links to the account's mail and
    sends. A message has its actions in its header and its keywords as
    chips, and Move asks for the folder in a dialog. The batch bar of a
    mail list asks for a folder only to move, and its head ticks all.

Fixed

  • The MCP server writes a sender's name, a subject and a file name on
    one line each. A line break in them could start a line inside the
    mail's marker that looked like a header of its own.
  • The mail form of the UI shows a name with a double quote or a
    backslash in it so that a reply or a forward reads it back as it was.
  • A Gmail or Microsoft account whose OAuth app is missing from the
    settings no longer breaks every page of the UI. The Accounts list
    says it cannot be used, it counts as needing attention, and it can be
    removed. In GET /v1/status it is not synced and needs
    attention.
  • Changing your password checks the current one as any confirmation
    does: a wrong one counts against your name and is audited as
    auth.confirm_failed.
  • A webhook's accounts cannot be an empty list, which would hear
    nothing. Send null for every account.
  • Changing a webhook needs list_changes only on the accounts the
    change adds. An owner who lost the right on one of its accounts can
    still change the URL or the events.
  • The UI's Users list counts only the users a batch changed, not those
    that were as asked already.
  • mailbox-client: Me has the caller's user_id and name.
  • The text of a mail written as HTML only, on the mail page and as the
    text part of a send, leaves out what its inline style hides as the
    MCP server does: too small, too faint, off the page, cut to nothing,
    or in the colour of its background. An end tag without its start tag
    no longer ends hidden text early.
  • The MCP server's text of an HTML mail keeps a link's address after
    its text and puts a dash before each list item, as the mail page
    does.
  • The text of an HTML mail keeps text whose colour and background are
    alike only by a keyword such as inherit or currentcolor. Only
    colours of their own count, such as #fff, rgb(...) or white.
    left and top far off the page hide text only with position
    absolute, fixed or relative.

Security

  • A secret shown once in the UI goes when its 5 minutes run out, at
    the next request of its session or the next sign-in of anyone, not
    only at the next page that could show it.
  • The audit keeps auth.session_evicted when a sign-in ends the
    user's oldest session of the UI, and users.change_refused for each
    user a batch of the Users list could not change.