Authentication
Speechwave never asks anyone for a password. There are two ways to sign in, and a third path just for the Chrome extension, which isn’t a web page and can’t use either one.
Enter your email, get a one-time link. Clicking it logs you in. The token is single-use and gets deleted the moment it's consumed.
Sign in with Google, Microsoft, or GitHub, handled through the Assent library. Works the same way whether it's your first visit or your fiftieth.
Routing
GET /users/log-in UserLive.Login, :new GET /users/magic_link/:token UserSessionController, :magic_link DELETE /users/log-out UserSessionController, :delete GET /auth/:provider UserSessionController, :oauth_authorize GET /auth/:provider/callback UserSessionController, :oauth_callback
The magic link flow
OAuth
OAuth covers two different situations with the same provider code:
- Login: find or create an account by provider and provider account id, or match an existing account by email and link the new provider to it.
- Connect: an already signed-in user adds another provider from Settings, so they can log in either way later.
OAuth login requires the provider to claim the email is verified. If that claim is missing or false, the login is rejected. Otherwise a provider could vouch for an email address nobody actually controls.
The Scope struct
Every context function that touches user-owned data takes a %Scope{user: user} as its first argument, so filtering by ownership is explicit instead of something you have to remember. This is a standard Phoenix 1.8 convention, not anything unique to Speechwave, but it’s worth knowing because you’ll see it everywhere in the codebase.
defmodule Speechwave.Accounts.Scope do
defstruct user: nil
end
def list_talks(%Scope{user: user}) do
Talk
|> where(user_id: ^user.id)
|> Repo.all()
end
Users and linked identities
| Field | Type | Notes |
|---|---|---|
| string | The login identity, no password stored alongside it | |
| plan | atom | free, pro, or org, see Plans and limits |
| api_key | string | A long random key, auto-generated, used by the Chrome extension |
| Field | Type | Notes |
|---|---|---|
| provider | string | google, microsoft, or github |
| uid | string | The provider's account id |
| user_id | references | The linked account |
Chrome extension auth
The extension can’t do magic links or OAuth, since it isn’t a web page. Instead, every user gets an API key, auto-generated and visible in Account Settings, and the extension sends it as a join parameter when it connects to the Channel socket. See WebSockets for the exact check chain that key goes through.