Commit Graph
80 Commits
Author SHA1 Message Date
Devon HudsonandClaude Opus 5.5 e1ca3a128c Email invite links for existing registration tokens
`POST /api/admin/v1/room-invites` minted a token for each address and
emailed it in one call. It becomes
`POST /api/admin/v1/user-registration-tokens/invite-emails`, which
emails the links of tokens minted through the token API: to the token's
own address, or to one given for a token without an address. A token
without a room can be sent too.

The `room_invites.enabled` and `room_invites.invite_lifetime` settings
are gone: minting is the token API's, which takes an expiry, and every
admin API client may already read invite links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R27QDzJTWSBxkrfbxBxcKF
2026-10-16 18:45:32 +00:00
Devon HudsonandClaude Opus 5.5 65a0c5ee74 Rename guest invites to room invites
Nothing in the endpoint, its email or its config is specific to who is
invited, so they take neutral names: `POST /api/admin/v1/room-invites`
with the `room-invite` OpenAPI tag, `SendRoomInviteEmailJob` on the
`send-room-invite-email` queue, the `emails/room_invite.*` templates and
their `mas.emails.room_invite.*` strings, and the `room_invites_enabled`
and `room_invite_lifetime` site config fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkxQWj8RAKd7iZ1JQtXdxm
2026-10-16 18:45:31 +00:00
Devon HudsonandClaude Opus 5.5 1a5002e894 Add a POST /api/admin/v1/invite-guests admin endpoint
`SendGuestInviteEmailJob` sends one guest invite email for a
registration token, so that a bad address or a transient SMTP failure
retries that one email rather than a whole batch. It takes both the
recipient and the invite link from the token, so the link can't be sent
to anyone but the address the token is pinned to. A token revoked,
expired or used by the time the job runs gets no email.

The link registers with the token, then opens the room through the
`OpenRoom` post-auth action. A browser already signed in to MAS goes
straight to the room.

The email names the room and who sent the invite, when the job has
them, and tells the recipient they are asking to join, since someone in
the room still has to approve them. Each combination has a message of
its own, rather than one message passed into another, which would
escape the names twice in HTML. Its subject names neither, as both are
chosen by the sender.

Mints a single-use, passwordless registration token for each guest,
pinned to their address and username and expiring after
`guest_invites.invite_lifetime`, and schedules their invite emails. The
links are only ever mailed to the guests, so the response carries a
count rather than the tokens.

Everything is validated before anything is minted, and the batch is
committed as a whole, so a rejected request sends no email at all. Each
invite goes through the registration policy as `POST /register` will
check it, so an invite the guest form would refuse is a 400 naming the
invite and every violation. The policy sees an empty requester, as the
request comes from the inviter's homeserver rather than the guest. A
username that is already taken is refused up front, rather than emailed
out as a link nobody can register with. Addresses are deduplicated
ignoring case before the usernames are checked, keeping the first entry,
so a repeated address never counts as a username given twice.

The caller sends the room name and the inviter on the inviter's behalf,
so the endpoint doesn't trust them. The room name and the inviter's
display name are stripped of control characters, including the
bidirectional controls `char::is_control` misses, and truncated before
they are stored in the job payload. The inviter's Matrix ID is never
truncated, so it can't show a server it isn't from: it is kept whole if
it is valid under the historical grammar, and left out otherwise.

The room ID is checked as `/open-room` checks it, and is only recorded
on the span once it is valid.

The endpoint answers 404 while guest invites are disabled. The check is
in the handler, as the admin router doesn't see the configuration.
`guest_invites.enabled` turns it on, and requires `client_room_url`.
`/open-room` keeps using `client_room_url` while the endpoint is
disabled, so that links already sent keep working.

`invite_lifetime` is a positive number of seconds that fits in a `u32`,
so MAS refuses a zero or negative lifetime at startup, and adding it to
the current time can't overflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R27QDzJTWSBxkrfbxBxcKF
2026-10-16 18:45:31 +00:00
Devon HudsonandClaude Opus 5.5 46b97d9f24 Rename the guest_invites config section to room_invites
The section only says where to send someone invited to a room, so it is
named after room invites. Nothing is released yet, so no alias for the
old key is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RkxQWj8RAKd7iZ1JQtXdxm
2026-10-16 14:58:23 +02:00
Devon HudsonandClaude Opus 5.5 50b818bd76 Add a guest_invites config section
Holds the settings for inviting guests to rooms by email, starting with
the client URL invitees are sent to once they are signed in. The value
goes on `SiteConfig`, which the admin handlers and the views already
take.

`client_room_url` is a string rather than a URL, because parsing it as
one would percent-encode the braces of a `{room_id}` placeholder in its
path, which would then never be replaced. Whenever it is set, it must
contain `{room_id}` and still parse as an absolute `http` or `https` URL
with a host once the placeholder is filled in, so that `/open-room`
never redirects relative to MAS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:58:23 +02:00
Quentin Gliech 24683fb938 Add a usernameAvailable GraphQL query for anonymous lookups
Exposes a new query telling whether a given username is available for
registration, and if not, why: already taken, or rejected by the
registration policy. A username reserved on the homeserver side is
deliberately reported as taken, mirroring the ambiguity of the
registration form, which doesn't tell the two apart either.

Because the query reveals whether a username exists, it is rate-limited
per requester, configurable through the new
`rate_limiting.username_availability` option.
2026-10-16 11:34:22 +02:00
Hugh Nimmo-Smith 357f83c96a Rate limit device code link attempts
Add a dedicated `device_code_link` rate limiter for device authorization user code verification.

This is to help mitigate the User Code Brute Forcing attack as described by [RFC 8628 section 5.1](https://datatracker.ietf.org/doc/html/rfc8628#section-5.1)

Proposed default is 1 per minute and burst of 10. This can be overridden by the server admin.
2026-09-11 13:17:56 +01:00
Quentin Gliech c3b5fa3b0c Remove the GraphQL playground
The interactive playground page and its route/config option are gone; the GraphQL API itself (POST /graphql) is unaffected. External GraphQL usage is deprecated anyway in favour of the REST admin API.
2026-08-07 12:26:42 +02:00
Quentin Gliech 7abe147ba0 Expose a logged_out variable to upstream authorization parameter templates (#5859) 2026-07-29 17:57:55 +02:00
Quentin Gliech cd78f82f9c Replace Unix socket on startup & allow setting permissions
Fixes #5572
Fixes #2424

This makes it so that MAS binds on a temporary path on startup, sets
permissions and then moves it to the configured path. This means that we don't
error out anymore if there is a stale socket file, and that it is safe to
rollout a new process without killing the old one first, without breaking
connections.
2026-07-13 13:46:47 +02:00
Quentin Gliech e7c57e013a Expose a logged_out variable to upstream authorization parameter templates
When a browser signs out (or discovers its session was ended), the
session cookie now remembers when that happened; the flag is cleared by
the next successful login. The `additional_authorization_parameters`
templates can read it as `logged_out`, so operators can force a fresh
prompt at the upstream provider after sign-out, e.g.:

    additional_authorization_parameters:
      prompt: "{% if logged_out %}login{% endif %}"

This addresses the "sign out signs me back in" loop with upstream SSO
providers that still hold a live session.

Fixes #1569
2026-07-10 15:55:14 +02:00
Quentin Gliech f948dec457 config: remove the unused branding.logo_uri option
branding.logo_uri was accepted and validated by the config layer but never
plumbed through to SiteConfig or any template, so it had no effect. Remove the
field and regenerate the committed config schema to remove the confusion.
2026-06-25 19:04:19 +02:00
Quentin Gliech a22b71d7d9 docs: document missing configuration options
This was done by auditing what's in the `mas-config` crate (and resulting
config JSON schema) and comparing it to the current configuration options
documentation.
2026-06-25 19:04:18 +02:00
Quentin Gliech eb0ef6380c Merge branch 'main' into quenting/upstream-pass-parameters 2026-06-17 14:54:37 +02:00
Quentin Gliech db58127117 Merge branch 'main' into patch-1 2026-06-10 10:11:00 +02:00
Quentin Gliech 3025708520 Add a configuration option to disable device code auto-fill 2026-05-28 13:30:25 +02:00
Quentin Gliech 11d68cc8c9 Merge branch 'main' into quenting/upstream-pass-parameters 2026-05-20 10:14:00 +02:00
Quentin Gliech 6b8decf609 Document MiniJinja templating for additional_authorization_parameters
Updates the field doc-comment to describe the new templating semantics
and adds a deprecation note on `forward_login_hint` pointing to the
recommended replacement. Regenerates the JSON schema.

The runtime behavior (template rendering, forward_login_hint
soft-deprecation) is wired up in follow-up commits.
2026-05-13 17:44:15 +02:00
c-bg 47aa94c465 fix typo: client_secret_jwk-> client_secret_jwt 2026-05-05 11:40:53 +02:00
Hugh Nimmo-Smith 53e6d05f40 Add oauth.device_code_grant_enabled configuration option 2026-04-07 11:13:56 +01:00
Olivier 'reivilibre 20c68d4e76 Explain the purpose of signing keys in the config documentation. (#5286) 2025-12-03 13:02:04 +00:00
Olivier 'reivilibre 262e235c75 Convert use case list to bullet points and note the niche private_key_jwt method 2025-12-02 12:22:33 +00:00
Quentin Gliech df14076dd0 Merge branch 'quenting/upstream-oauth/better-conflict-options' into quenting/upstream-oauth/skip-interactive 2025-11-28 18:08:09 +01:00
Quentin Gliech 47d411f641 Option to skip confirmation when registering through an upstream OAuth provider 2025-11-28 15:51:43 +01:00
Quentin Gliech f0d84a4b47 Document the new conflict options 2025-11-28 11:57:46 +01:00
copilot-swe-agent[bot]andsandhose af15767135 Remove deprecated set_email_verification option from docs
Co-authored-by: sandhose <1549952+sandhose@users.noreply.github.com>
2025-11-27 15:47:37 +00:00
Olivier 'reivilibre f662b0b132 drive-by: Singing -> Signing 2025-11-25 17:06:38 +00:00
Olivier 'reivilibre 039cb09815 Add a little bit of explanation to the documentation about keys 2025-11-25 17:06:38 +00:00
Kai A. Hiller a93fa72477 Merge branch 'main' into keys_dir 2025-11-18 18:12:14 +01:00
networkException b5a0834faa Add upstream_oauth2.providers.[].client_secret_file config option
This patch factors out the previously introduced config
wrapper for client secrets to also use it for upstream oauth
providers.

See a7e7c3caa1
2025-11-08 16:10:19 +01:00
Quentin Gliech e4844968d3 Add a configuration option to make email optional for password registration 2025-10-07 17:28:01 +02:00
Kai A. Hiller 6a28950d8e Add secrets.keys_dir config option 2025-09-08 16:02:38 +02:00
Quentin Gliech 6eac7ed2d7 Automatically derive the kid from the key fingerprint if missing (#4876) 2025-09-02 17:04:35 +02:00
Patrick Maier cf997048e4 Add missing branding config to docs (#4577) 2025-08-15 12:22:07 +02:00
Kai A. Hiller 1b7b43b559 Auto-generate kid if not given 2025-08-08 11:38:45 +02:00
Quentin Gliech 884c66891a Merge branch 'main' into secret_file 2025-08-05 11:17:39 +02:00
Kai A. Hiller a7e7c3caa1 Add clients.[].client_secret_file config option 2025-08-04 19:32:39 +02:00
Kai A. Hiller d4a318a8ae Add matrix.secret_file config option 2025-07-29 19:46:07 +02:00
Quentin Gliech 97cd5d86ba docs: Remove requirement for arbitrary KIDs (#4807) 2025-07-23 12:38:47 +02:00
Kai A. Hiller 858b388eec KIDs must be stable across restarts 2025-07-23 12:31:38 +02:00
Kai A. Hiller da94650706 Fix wording 2025-07-23 09:43:43 +02:00
Kai A. Hiller 2ecc502b05 Adapt markdown formatting 2025-07-23 09:39:52 +02:00
Kai A. Hiller 5587dd37e3 docs: Remove requirement for arbitrary KIDs 2025-07-21 18:37:03 +02:00
mcalinghee 98912f4ada allow importing existing users when the localpart matches in upstream OAuth 2.0 logins 2025-07-21 09:52:24 +02:00
Quentin Gliech 3bc3db1527 Add documentation for backchannel logout 2025-07-04 16:27:10 +02:00
Quentin Gliech 50b41a6613 Add secrets.encryption_file config option (#4617) 2025-06-05 15:14:55 +02:00
Kai A. Hiller 187838802d Update encryption secret warning in docs 2025-06-04 14:50:54 +02:00
Kai A. Hiller fbee4bfe8c Document secrets.encryption_file
Signed-off-by: Kai A. Hiller <git@kaialexhiller.de>
2025-06-04 11:42:51 +02:00
Quentin Gliech 685f4761cd Add config flag to require registration tokens for password registrations 2025-06-03 17:42:53 +02:00
Jason Robinson f3ef263e11 Add missing branding config to docs
Mostly copied from the code, with some additions from https://github.com/matrix-org/matrix-authentication-service/pull/2325
2025-05-20 15:21:13 +03:00