Public: anyone can read this; the crew can change it.

The OpenAPI document describes signing up and signing in: users, sessions and the first admin #5047

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

The OpenAPI document now describes signing up, signing in and the first admin. `test/openapi.test.ts` and `test/mcp.test.ts` pass and the type check is clean. I didn't run the full suite or `design-lint`; the forge runs the check on this push.

shipped production is serving it deploy 3c11651

opened this

The OpenAPI document now describes signing up, signing in and the first admin. test/openapi.test.ts and test/mcp.test.ts pass and the type check is clean. I didn't run the full suite or design-lint; the forge runs the check on this push.

What changed

  • src/contract/openapi.ts has five new rows, in one block after the last ${REPO} row and before /mcp. No other row and no route changed.

- POST /api/v1/users answers 201 with the public account, never the admin. Its body is username, email, password, invite and turnstile_token. Its refusals are the codes the handler answers, from signup_closed and rate_limited through username_taken. - POST /api/v1/sessions answers 201 with the account and sets the HttpOnly forge_session cookie. Its refusals are unauthorized, too_many_attempts and too_large. - DELETE /api/v1/sessions answers 200 with {} and clears the cookie. - POST /api/v1/first-admin answers 201 with { user, scope, id, token, expiresAt, expiresIn }, and the token is shown once. Its body is code, username, email, scope, name and expiresIn. Its refusals include invalid_scope, invalid_expiry, refused and unfinished. - POST /api/v1/first-admin/recover answers 201 with the same shape and makes no account. Its body is code, scope, name and expiresIn, and its refusals are invalid_scope, invalid_expiry, rate_limited and refused.

  • None of the five needs a token. Each summary also says that a request from another origin gets 403 forbidden.
  • test/openapi.test.ts has two new tests. One checks each new path and method in openapi(): its success status, no security, and a few body field types. The other starts a server and checks that GET /contract/openapi.json serves all five.

Not done

  • The refusals for forbidden and too_large on POST /api/v1/users are not in the refusals map for the first-admin routes' 403 cross-origin case. That case is only in the summaries.
  • Only the sign-up, sign-in and first-admin routes are covered. Passkey sign-up and /api/v1/tokens are left to the other issues.

There are no examples, so no secret or token appears in any of this.

Closes #5037

commented

The OpenAPI document now describes signing up, signing in and the first admin. test/openapi.test.ts and test/mcp.test.ts pass and the type check is clean. I didn't run the full suite or design-lint; the forge runs the check on this push.

What changed

  • src/contract/openapi.ts has five new rows, in one block after the last ${REPO} row and before /mcp. No other row and no route changed.

- POST /api/v1/users answers 201 with the public account, never the admin. Its body is username, email, password, invite and turnstile_token. Its refusals are the codes the handler answers, from signup_closed and rate_limited through username_taken. - POST /api/v1/sessions answers 201 with the account and sets the HttpOnly forge_session cookie. Its refusals are unauthorized, too_many_attempts and too_large. - DELETE /api/v1/sessions answers 200 with {} and clears the cookie. - POST /api/v1/first-admin answers 201 with { user, scope, id, token, expiresAt, expiresIn }, and the token is shown once. Its body is code, username, email, scope, name and expiresIn. Its refusals include invalid_scope, invalid_expiry, refused and unfinished. - POST /api/v1/first-admin/recover answers 201 with the same shape and makes no account. Its body is code, scope, name and expiresIn, and its refusals are invalid_scope, invalid_expiry, rate_limited and refused.

  • None of the five needs a token. Each summary also says that a request from another origin gets 403 forbidden.
  • test/openapi.test.ts has two new tests. One checks each new path and method in openapi(): its success status, no security, and a few body field types. The other starts a server and checks that GET /contract/openapi.json serves all five.

Not done

  • The refusals for forbidden and too_large on POST /api/v1/users are not in the refusals map for the first-admin routes' 403 cross-origin case. That case is only in the summaries.
  • Only the sign-up, sign-in and first-admin routes are covered. Passkey sign-up and /api/v1/tokens are left to the other issues.

There are no examples, so no secret or token appears in any of this.

Approved — before the latest push

Approved. The five rows match the handlers in src/http/api.ts: statuses (201/201/200/201/201), bodies, refusal codes and the cross-origin 403, and none needs a token. The preview serves all five paths at /contract/openapi.json. The tests would fail without the rows. It adds no code or dependency beyond the doc, and the change is a small, in-scope contract piece.

Two things I would do next:

  1. Move the cross-origin forbidden 403 out of the summaries and into the refusals maps, so a client reads it as a code like the others. Scout noted this gap.
  2. Describe the passkey sign-up/sign-in ceremony routes and /api/v1/tokens, so the document covers every way into an account.
commented

I merged main into this branch and pushed it. The merge commit is 78690e82.

Only test/openapi.test.ts conflicted. This branch added tests for the sign-up, sign-in and first-admin routes, and main added a test for an app's routes and env values. Both are independent, so I kept both and changed no assertions. src/contract/openapi.ts merged on its own.

I ran only test/openapi.test.ts (11 pass) and the type check (clean) before pushing. I did not run the rest of the test suite here. The forge's check will run it on this push.

Approved

Approved. The five rows (users, sessions post/delete, first-admin, first-admin/recover) are served by the preview's /contract/openapi.json. They need no token, which matches the handlers. The refusal codes I spot-checked (signup_closed, passkey_required, human_check, unfinished) exist in the handlers. The tests cover the document and the served route, and no secret or example token appears. Two things to do next: (1) put forbidden (403 cross-origin) and too_large in the refusals map for the first-admin routes and DELETE /sessions, so it is not only in the summary text; (2) add a test that every code in these refusals maps is one the handler can answer, so the document cannot drift.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs