People and sessions
Sign-up, sign-in, sessions, password resets and email verification.
packages/api/src/auth/auth.controller.ts by packages/docs/tools/generate-reference.mjs. Do not edit by hand: src/reference.spec.ts regenerates it and fails on a difference.The person plane's front door.
Three endpoints here are unauthenticated by definition — sign up, log in, ask for a reset — and all three answer the same thing whether or not the address has an account. That is not politeness: an API that says "no such user" is a list of a vendor's staff to anyone who can guess addresses, and this product's customers are companies whose staff directory is public.
The API sets no cookie and reads none. A session is a bearer token, so there is no ambient authority attached to a cross-site request and therefore no CSRF surface at all; a browser front end keeps the token in its own server-side session and never hands it to script. That is a deliberate division of labour rather than an omission, and it is why the dashboard is a separate deliverable rather than a folder in this package.
POST /v1/auth/users
202, always, with no body that varies. A new account is created and
queued a verification link; an address that already has one is told so in
its own inbox and the caller is told nothing. Both branches queue exactly
one message, so the queue depth is not an oracle either.
Answers 202 on success.
Authentication.
- No credential.
Body. Validated by this schema, from the control plane's own source:
const SignUp = z.object({
email: Email,
password: Password,
name: Name.optional(),
});const Email = z.email().max(320);const Password = z
.string()
.min(PASSWORD_POLICY.minLength)
.max(PASSWORD_POLICY.maxLength);POST /v1/auth/sessions
Answers 201 on success.
Authentication.
- No credential.
Body. Validated by this schema, from the control plane's own source:
const LogIn = z.object({
email: Email,
password: z.string().min(1).max(PASSWORD_POLICY.maxLength),
workspace_id: z.uuid().optional(),
});const Email = z.email().max(320);GET /v1/auth/me
Answers 200 on success.
Authentication.
- A
krses_dashboard session only.
PATCH /v1/auth/me
Answers 200 on success.
Authentication.
- A
krses_dashboard session only.
Body. Validated by this schema, from the control plane's own source:
const UpdateMe = z.object({ name: Name.nullable() });POST /v1/auth/me/password
Changing a password ends every other session. The reason someone changes one is usually that they think somebody else has it, and leaving that other session alive answers the wrong question.
Answers 200 on success.
Authentication.
- A
krses_dashboard session only.
Body. Validated by this schema, from the control plane's own source:
const ChangePassword = z.object({
current_password: z.string().min(1).max(PASSWORD_POLICY.maxLength),
password: Password,
});const Password = z
.string()
.min(PASSWORD_POLICY.minLength)
.max(PASSWORD_POLICY.maxLength);GET /v1/auth/sessions
Answers 200 on success.
Authentication.
- A
krses_dashboard session only.
DELETE /v1/auth/sessions/current
Answers 200 on success.
Authentication.
- A
krses_dashboard session only.
DELETE /v1/auth/sessions/:id
Answers 200 on success.
Authentication.
- A
krses_dashboard session only.
Path parameters. id.
POST /v1/auth/sessions/current/workspace
Which workspace this session acts in. It rewrites the session row rather than issuing a token: the token identifies the sign-in, and minting one per workspace would mean a person in three workspaces holds three live credentials for one sign-in — three things to steal and three to revoke.
Answers 200 on success.
Authentication.
- A
krses_dashboard session only.
Body. Validated by this schema, from the control plane's own source:
const SwitchWorkspace = z.object({ workspace_id: z.uuid() });POST /v1/auth/email_verifications
A second verification link. Session-authenticated, 202 always: the caller
is already this account, and an address that is already verified queues
nothing.
Answers 202 on success.
Authentication.
- A
krses_dashboard session only.
POST /v1/auth/email_verifications/consume
Unauthenticated on purpose: the link arrives in a mailbox and is clicked from wherever that mailbox is read, which is not necessarily the browser holding the session. The token is the whole credential and it verifies one address on one account.
Answers 200 on success.
Authentication.
- No credential.
Body. Validated by this schema, from the control plane's own source:
const ConsumeVerification = z.object({ token: z.string().min(1).max(500) });POST /v1/auth/password_resets
Answers 202 on success.
Authentication.
- No credential.
Body. Validated by this schema, from the control plane's own source:
const RequestReset = z.object({ email: Email });const Email = z.email().max(320);POST /v1/auth/password_resets/consume
Answers 200 on success.
Authentication.
- No credential.
Body. Validated by this schema, from the control plane's own source:
const ConsumeReset = z.object({
token: z.string().min(1).max(500),
password: Password,
});const Password = z
.string()
.min(PASSWORD_POLICY.minLength)
.max(PASSWORD_POLICY.maxLength);Shared validators
Defined once in packages/api/src/validation.ts and used by the schemas above.
const Name = z.string().min(1).max(200);