- Introduce `LoginWithOptions` method to allow for customizable login flows, enabling overrides for scopes, token expiration, and refresh token issuance. - Add `LoginOptions` struct to encapsulate optional parameters for login, improving flexibility in user authentication. - Update token handling in `issueTokens` to accommodate new options, ensuring proper management of access and refresh tokens based on user preferences. - Integrate OTP service initialization into the OpenAPI server setup for enhanced authentication capabilities. |
||
|---|---|---|
| .. | ||
| DESIGN.md | ||
| generate.go | ||
| handler.go | ||
| login.go | ||
| otp.go | ||
| process.go | ||
| README.md | ||
| revoke.go | ||
| verify.go | ||
OTP — Passwordless Authentication
One-time password (OTP) module for passwordless login. An authorized caller generates a short-lived code bound to a user/member and a redirect URL. The recipient opens /v/<code> in a browser to authenticate without credentials.
Process
| Process | Args | Returns | Description |
|---|---|---|---|
otp.Create |
params (map) |
code (string) | Generate an OTP code |
otp.Verify |
code |
payload (map) | Look up a code without consuming it |
otp.Login |
code, locale? |
LoginResult | Verify code, issue access token, optionally consume |
otp.Revoke |
code |
nil | Delete a code immediately |
otp.Create
yao run otp.Create '::{"team_id":"T1","member_id":"M1","redirect":"/dashboard"}'
Parameters:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
team_id |
string | When member_id set |
— | Team context |
member_id |
string | Either this or user_id |
— | Target member (resolved to user_id at login) |
user_id |
string | Either this or member_id |
— | Target user |
redirect |
string | Yes | — | Post-login redirect URL |
expires_in |
int | No | 86400 | Code TTL in seconds |
token_expires_in |
int | No | system default | Access token lifetime override (seconds) |
scope |
string | No | — | Space-separated scopes for the issued token |
consume |
bool | No | true | Revoke code after first login |
otp.Verify
yao run otp.Verify abc123def456
Returns the stored payload without consuming the code.
otp.Login
yao run otp.Login abc123def456 en-US
Verifies the code, resolves identity (member_id → user_id if needed), issues an access token (no refresh token), and returns LoginResult. Consumes the code if consume is true.
otp.Revoke
yao run otp.Revoke abc123def456
Deletes the code. Silent if the code does not exist.
HTTP API
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/otp/create |
Bearer token (team context) | Create an OTP code |
POST |
/otp/login |
Public | Verify code, set session cookies |
POST /otp/create
Requires a valid access token with team context. The team_id is forced from the caller's identity. Validates that the target user/member belongs to the same team.
Request:
{"member_id": "M1", "redirect": "/agents/keeper/entry/xxx"}
Response:
{"code": "abc123def456"}
POST /otp/login
Public endpoint. Checks for an existing valid session first — if found, returns already_logged_in without issuing new tokens. Otherwise performs login and sets access_token cookie (no refresh_token).
Request:
{"code": "abc123def456", "locale": "en-US"}
Response:
{"status": "success", "redirect": "/agents/keeper/entry/xxx"}
Status is either success (new session) or already_logged_in (existing session).
Security
- Codes are 12-char NanoID (
[2-9a-hjkmnp-z]), ~62 bits of entropy - Default TTL: 24 hours
- Codes are single-use by default (
consume: true) - No refresh token issued — access token only
POST /otp/createenforces team membership validationteam_idis always derived from the caller's token, not the request body