Passwordless sign-in: email magic link as primary auth (password optional) #42

Closed
opened 2026-06-24 14:53:06 +00:00 by casper-stevens · 1 comment
Member

Change the way people sign up / sign in. The email magic link becomes the primary method; passwords become optional/secondary.

Flow

  • Primary — magic link: the user clicks the link in their email and is automatically logged in. No password required to get in.
  • Optional password: once logged in, the user can set a password if they want one.
  • Fallback: if someone visits the site without an active session / the necessary cookies, they can log in with their password — provided they set one.

Notes

  • Ties into invite emails (#34) and invite links / pre-population (#30) — the same emailed link can both onboard and authenticate the user.
  • Keep existing email+password auth working as the secondary path; the magic link is the new default.
  • Related: #27 (account set-up currently gets stuck on Saving…).

Filed from the conference-organiser action items (2026-06-24). P0.

Change the way people sign up / sign in. **The email magic link becomes the primary method**; passwords become optional/secondary. ### Flow - **Primary — magic link:** the user clicks the **link in their email** and is **automatically logged in**. No password required to get in. - **Optional password:** once logged in, the user can **set a password** if they want one. - **Fallback:** if someone visits the site **without an active session / the necessary cookies**, they can **log in with their password** — provided they set one. ### Notes - Ties into **invite emails (#34)** and **invite links / pre-population (#30)** — the same emailed link can both onboard and authenticate the user. - Keep existing email+password auth working as the secondary path; the magic link is the new default. - Related: **#27** (account set-up currently gets stuck on *Saving…*). --- _Filed from the conference-organiser action items (2026-06-24). **P0**._
Author
Member

Implemented + live-tested. Commits on development: b90afee (feature), 0dc7671 (in-process test + lib/bin split).

What changed — primary sign-in is now an admin-distributed email magic link; password is an optional fallback.

  • New "login" token kind: 5-day TTL, MULTI-USE (the same emailed link signs in on multiple devices/sessions throughout the event); invite/reset stay single-use.
  • auth_send_login_link (admin-gated, no account enumeration, 30s per-email throttle) emails {app_url}?login=<token>.
  • auth_consume_magic_link mints a new session per consume, never deletes the token until expiry.
  • auth_set_my_password lets a logged-in user set/change a password.
  • Constant-time (subtle::ct_eq) compares for token + password-hash.
  • Frontend: ?login= auto-login page (reuses the verified invite-success pattern), admin "Send sign-in link" control + login email template, profile change-password section.

Live test (stack up under the new cm50_app/ socket, browser-driven):

Test Result
Router routes cm50_app; UI served + loads PASS
Admin login PASS
Admin → Send sign-in link (seeded email) PASS (success, no RPC error)
Change password while logged in PASS
Password fallback login with new password PASS
Consume semantics (multi-use, kind-gated, expiry) — hermetic test PASS
?login=<invalid> error path (live RPC) PASS

Multi-use/kind-gated/expiry consume behavior is pinned by cm50_app_server/tests/magic_link.rs.

One remaining caveat (environment, not code): real email delivery can only be confirmed against a live Resend config (API key + verified meet.tf from-domain in Admin → Email). auth_send_login_link mints the token and returns success before the send; the success banner proves the RPC works, not that an email arrived. Set up Resend and send yourself a link to confirm end-to-end delivery.

Also flagged: CARGO_TARGET_DIR isn't exported by the current init.sh shell, so make install can deploy a stale binary from the repo-local target/ instead of the shared build dir — export it (or lab build --install) when deploying.

Implemented + live-tested. Commits on `development`: `b90afee` (feature), `0dc7671` (in-process test + lib/bin split). **What changed** — primary sign-in is now an **admin-distributed email magic link**; password is an optional fallback. - New `"login"` token kind: **5-day TTL, MULTI-USE** (the same emailed link signs in on multiple devices/sessions throughout the event); invite/reset stay single-use. - `auth_send_login_link` (admin-gated, no account enumeration, 30s per-email throttle) emails `{app_url}?login=<token>`. - `auth_consume_magic_link` mints a **new session per consume**, never deletes the token until expiry. - `auth_set_my_password` lets a logged-in user set/change a password. - Constant-time (`subtle::ct_eq`) compares for token + password-hash. - Frontend: `?login=` auto-login page (reuses the verified invite-success pattern), admin "Send sign-in link" control + login email template, profile change-password section. **Live test (stack up under the new `cm50_app/` socket, browser-driven):** | Test | Result | |---|---| | Router routes `cm50_app`; UI served + loads | PASS | | Admin login | PASS | | Admin → Send sign-in link (seeded email) | PASS (success, no RPC error) | | Change password while logged in | PASS | | Password fallback login with new password | PASS | | Consume semantics (multi-use, kind-gated, expiry) — hermetic test | PASS | | `?login=<invalid>` error path (live RPC) | PASS | Multi-use/kind-gated/expiry consume behavior is pinned by `cm50_app_server/tests/magic_link.rs`. **One remaining caveat (environment, not code):** real email *delivery* can only be confirmed against a live Resend config (API key + verified `meet.tf` from-domain in Admin → Email). `auth_send_login_link` mints the token and returns success before the send; the success banner proves the RPC works, not that an email arrived. Set up Resend and send yourself a link to confirm end-to-end delivery. Also flagged: `CARGO_TARGET_DIR` isn't exported by the current `init.sh` shell, so `make install` can deploy a stale binary from the repo-local `target/` instead of the shared build dir — export it (or `lab build --install`) when deploying.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coopcloud_code/cm50_app#42
No description provided.