Skip to content

[Mobile] Registration and invite acceptance - #22

Merged
wak327 merged 1 commit into
mobile-teamsfrom
mobile-register-invites
Sep 11, 2026
Merged

wak327 merged 1 commit into
mobile-teamsfrom
mobile-register-invites

Conversation

@wak327

@wak327 wak327 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Closes #3.

Stacked on #21 (teams), which is stacked on #20 (login). Review in that order; this PR's diff is only the registration and invitation work.

Ordered after teams on purpose: accepting an invitation has to set the active team, so it needs the team context to exist.

What changed

The invitation screen sits outside both route groups, guarded by neither. Most people opening one of these links have no account yet, and bouncing them to a login screen without saying what they were invited to is how an invitation gets ignored. Same reasoning as the web keeping /invite/:token outside RequireAuth.

Registration derives its email from the invitation when there is one, because typing a different address would only earn a 403 on submit. The backend accepts an invite best-effort after creating the account and swallows failures (lib_identity/identity.py:144-150), so the team list is re-read rather than assumed.

The in-app banner is the only thing that tells an existing account an invitation exists, since nothing sends email.

A limit worth stating plainly

On the web, an invite link's own origin is the instance. A softtrack:// link carries no host, and true universal links would require declaring each self-hosted domain in the build — which is impossible for arbitrary hosts.

So: softtrack://invite/<token> works, and an invitation arriving with no known instance asks you to sign in first and then returns to itself, rather than guessing. The issue's "(or store fallback)" seems to anticipate this, but it is a real constraint rather than something worked around. Documented in the README.

Verification

73 unit tests plus 5 integration tests against a live instance: registration, the 422 whose detail is an array rather than a string (the case the web's extractor drops), public invite preview while signed out, registration-by-invite, and the pending-invite banner path.

The suite registers real accounts. Registration is throttled per IP and charged even on success, so running it repeatedly will 429 — the suite now uses two registrations per run instead of four, and reports the throttle with the fix (docker compose restart backend) rather than a bare error.

Not run on a device.

Closes #3.

The invitation screen sits outside both route groups, guarded by neither: most
people opening one of these have no account yet, and bouncing them to a login
screen without saying what they were invited to is how an invitation gets
ignored. It mirrors the web keeping /invite/:token outside RequireAuth.

One thing the web gets for free that this cannot: on the web the page's own
origin IS the instance, while a softtrack:// link carries no host. Arriving with
no known instance asks for sign-in first and comes back, rather than guessing.
True universal links would need each self-hosted domain declared in the build,
which is not possible for arbitrary hosts -- noted in the README rather than
papered over.

Registration derives its email from the invitation when there is one, because
typing a different address would only earn a 403 on submit. The backend accepts
an invite best-effort after creating the account and swallows failures, so the
team list is re-read rather than assumed.

The in-app banner is the only thing that tells an existing account an
invitation exists, since nothing sends email.

Verified against the reference stack, including the 422 whose `detail` is an
array rather than a string -- the case the web's extractor drops.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wak327
wak327 merged commit 3f21a9e into mobile-teams Sep 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant