Docs: recent sign-in on every page and endpoint it gates, 90-day session cap in limits (#89)
#86 documented Confirm it's you on the sign-in and security pages. This adds it where people meet it: creating tokens and agents, deleting and transferring a repository, deleting an organization, the six API operations it gates (with the 403 and its confirm URL), the REST errors table, and two rows in Limits (session cap and the 2-hour window). Claude-Session: https://claude.ai/code/session_016q5wwBFck8JHq14gHUM2CL Co-authored-by: Claude <noreply@anthropic.com>
Canberk Demirci authored · GitHub committed signed1 parent 8a8ac92commit b5b97a43723dad1a1856806eee0cd76f13f94915Browse files
7 files changed+14−10
Files (7)
- apps/diffinery/docs/accounts/organizations.md+1−1
- apps/diffinery/docs/accounts/tokens.md+2−0
- apps/diffinery/docs/agents/overview.md+1−1
- apps/diffinery/docs/reference/limits.md+2−1
- apps/diffinery/docs/reference/rest-api.md+1−1
- apps/diffinery/docs/repositories/overview.md+1−0
- apps/diffinery/src/docs/api-meta.ts+6−6
modifiedapps/diffinery/docs/accounts/organizations.md+1−1View file
| @@ -351,7 +351,7 @@ | ||
| 351 | 351 | 2. In the **Danger zone**, under **Delete this organization**, type the organization's login (for example `acme`) in the confirmation box. |
| 352 | 352 | 3. Click **Delete this organization**. |
| 353 | 353 | |
| 354 | -You see `Deleted acme.` and land on your home page. | |
| 354 | +You see `Deleted acme.` and land on your home page. If you signed in more than 2 hours ago, Diffinery first asks you to [Confirm it's you](/docs/security/overview#confirming-its-you) with your passkey. | |
| 355 | 355 | |
| 356 | 356 | ### Delete with the API |
| 357 | 357 |
modifiedapps/diffinery/docs/accounts/tokens.md+2−0View file
| @@ -64,6 +64,8 @@ | ||
| 64 | 64 | |
| 65 | 65 | ## Create a token |
| 66 | 66 | |
| 67 | +Creating a token needs a sign-in from the last 2 hours. If yours is older, Diffinery asks you to [Confirm it's you](/docs/security/overview#confirming-its-you) with your passkey first. | |
| 68 | + | |
| 67 | 69 | ### In the web UI |
| 68 | 70 | |
| 69 | 71 | 1. Go to **Settings → Access tokens** (`/settings/tokens`). |
modifiedapps/diffinery/docs/agents/overview.md+1−1View file
| @@ -15,7 +15,7 @@ | ||
| 15 | 15 | |
| 16 | 16 | ## Create an agent |
| 17 | 17 | |
| 18 | -Only people can create agents. To create an agent owned by an organization, you must be an owner or admin of that organization. | |
| 18 | +Only people can create agents. To create an agent owned by an organization, you must be an owner or admin of that organization. Creating an agent or an agent token needs a sign-in from the last 2 hours; if yours is older, Diffinery asks you to [Confirm it's you](/docs/security/overview#confirming-its-you) with your passkey first. | |
| 19 | 19 | |
| 20 | 20 | The agent's login follows the same rules as usernames: letters, digits, `-` and `_`, starting with a letter or digit, up to 100 characters, no dots, and not a reserved name (such as `api`, `mcp`, `settings` or `admin`). |
| 21 | 21 |
modifiedapps/diffinery/docs/reference/limits.md+2−1View file
| @@ -11,7 +11,8 @@ | ||
| 11 | 11 | |
| 12 | 12 | | Limit | Value | Applies to | |
| 13 | 13 | | --- | --- | --- | |
| 14 | -| Browser session lifetime | 30 days, extended to 30 days from now on each day the session is used | The session cookie you get after signing in with a passkey, a sign-in link or email recovery. An unused session ends 30 days after its last extension. | | |
| 14 | +| Browser session lifetime | 30 days, extended to 30 days from now on each day the session is used, never past 90 days after sign-in | The session cookie you get after signing in with a passkey, a sign-in link or email recovery. An unused session ends 30 days after its last extension, and every session ends 90 days after its sign-in. | | |
| 15 | +| Recent sign-in for sensitive actions | 2 hours | Adding or removing a passkey, creating a token, an agent or an agent token, changing your username, issuing a sign-in link, and deleting or transferring a repository or deleting an organization. An older browser sign-in gets **Confirm it's you** (`403` with a `confirm` URL over the API). See [Confirming it's you](/docs/security/overview#confirming-its-you). | | |
| 15 | 16 | | Passkey browser prompt | 2 minutes | Time to complete the browser's passkey dialog when signing in or adding a passkey. | |
| 16 | 17 | | Passkey sign-in attempt | 5 minutes | A sign-in or registration that started more than 5 minutes ago can't be completed; start again. | |
| 17 | 18 | | Sign-in link lifetime | 24 hours, single use | Links a site administrator sends with **Sign-in link** in **Settings → People and invitations**. | |
modifiedapps/diffinery/docs/reference/rest-api.md+1−1View file
| @@ -335,7 +335,7 @@ | ||
| 335 | 335 | | `205 Reset Content` | `PUT /api/v3/notifications`: every notification marked as read. | | |
| 336 | 336 | | `400 Bad Request` | The body is not valid JSON, or the URL has a malformed percent-encoding. | `request body must be JSON`, `Problems parsing JSON`, `malformed percent-encoding in the URL` | |
| 337 | 337 | | `401 Unauthorized` | No credentials where they are needed, or a bad token. | See [Authentication](#authentication). | |
| 338 | -| `403 Forbidden` | You are signed in but your role, token scope or account kind doesn't allow this. | `write access required`, `admin access required`, `this token needs the write scope`, `this token is limited to repositories and can't change account settings`, `repository is archived (read-only)`, `agents cannot change branch protection`, `agents cannot manage secrets`, `agents cannot manage variables`, `agents can't dismiss reviews`, `only the app that created a check run can update it`, `cross-site request refused`, `Resource not accessible by integration (...)` (a workflow's `GITHUB_TOKEN`) | | |
| 338 | +| `403 Forbidden` | You are signed in but your role, token scope or account kind doesn't allow this. | `write access required`, `admin access required`, `this token needs the write scope`, `this token is limited to repositories and can't change account settings`, `repository is archived (read-only)`, `agents cannot change branch protection`, `agents cannot manage secrets`, `agents cannot manage variables`, `agents can't dismiss reviews`, `only the app that created a check run can update it`, `cross-site request refused`, `Resource not accessible by integration (...)` (a workflow's `GITHUB_TOKEN`), `Confirm it's you: sign in with your passkey again to do this.` (a sensitive call from a browser session signed in more than 2 hours ago; the body has a `confirm` URL) | | |
| 339 | 339 | | `404 Not Found` | The resource doesn't exist, or it is private and you can't see it. Diffinery doesn't reveal whether a private repository exists, and draft releases are hidden from readers the same way. | `repository not found`, `Branch not found`, `Not Found` | |
| 340 | 340 | | `405 Method Not Allowed` | A pull request merge was refused by policy. | `pull request is not mergeable: ...` (followed by the blockers), `main requires a merge queue: add the pull request to the queue`, `main requires linear history; use squash or rebase` | |
| 341 | 341 | | `409 Conflict` | Someone else changed the branch, ref or file first, or a merge conflicts. Fetch the latest state and retry. | `branch moved; refresh and retry`, `head branch was modified; review and try again`, `README.md does not match <sha>`, `Merge conflict`, `Git Repository is empty.`, `Already exists - Variable already exists` | |
modifiedapps/diffinery/docs/repositories/overview.md+1−0View file
| @@ -388,6 +388,7 @@ | ||
| 388 | 388 | |
| 389 | 389 | - **Web UI**: **Settings → General → Danger zone → Delete this repository**. Type the full name (for example `acme/website`) and click **Delete this repository**. |
| 390 | 390 | - **API**: `DELETE /api/v3/repos/acme/website` returns `204 No Content`. It needs an admin role. Agents can't delete repositories (`agents cannot delete repositories`, HTTP 403). |
| 391 | +- **Recent sign-in**: from a browser whose sign-in is more than 2 hours old, Diffinery first asks you to [Confirm it's you](/docs/security/overview#confirming-its-you) with your passkey. Transferring a repository asks the same. | |
| 391 | 392 | |
| 392 | 393 | Deletions are recorded in the audit log as `repo.delete`. The repository's owner (for an organization, its owners and admins) gets an email, unless they deleted it themselves. |
| 393 | 394 |
modifiedapps/diffinery/src/docs/api-meta.ts+6−6View file
| @@ -137,7 +137,7 @@ | ||
| 137 | 137 | "DELETE /orgs/{org}": { |
| 138 | 138 | summary: "Delete an organization", |
| 139 | 139 | description: |
| 140 | - "Owners only (or site admins). Refused with 422 while the organization still owns repositories (transfer or delete them first), container images, or an import in progress. Its members, pending invitations, secrets and variables are removed, its agents disabled and their tokens revoked, and its login freed. Repositories transferred out of it keep redirecting from their old URLs. Audited.", | |
| 140 | + "Owners only (or site admins). Refused with 422 while the organization still owns repositories (transfer or delete them first), container images, or an import in progress. Its members, pending invitations, secrets and variables are removed, its agents disabled and their tokens revoked, and its login freed. Repositories transferred out of it keep redirecting from their old URLs. Audited. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).", | |
| 141 | 141 | access: "org-admin", |
| 142 | 142 | status: 202, |
| 143 | 143 | }, |
| @@ -230,11 +230,11 @@ | ||
| 230 | 230 | archived: ["boolean", "Archive (read-only) or unarchive."], |
| 231 | 231 | }, |
| 232 | 232 | }, |
| 233 | - "DELETE /repos/{owner}/{repo}": { summary: "Delete a repository", description: "People only; agents can't delete repositories. Works on an archived repository too. Audited.", access: "admin", status: 204 }, | |
| 233 | + "DELETE /repos/{owner}/{repo}": { summary: "Delete a repository", description: "People only; agents can't delete repositories. Works on an archived repository too. Audited. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).", access: "admin", status: 204 }, | |
| 234 | 234 | "POST /repos/{owner}/{repo}/transfer": { |
| 235 | 235 | summary: "Transfer a repository", |
| 236 | 236 | description: |
| 237 | - "Moves the repository to `new_owner`: the caller's own account, or an organization where the caller is an owner or admin. Needs admin access to the repository (a personal repository: its owner) and a token with the admin scope; agents, job tokens (`GITHUB_TOKEN`) and sandbox tokens are refused. 422 when the new owner already has a repository (or a container image of a moved image's name) with that name. Git data, issues, pull requests, Actions runs, secrets, variables, environments, webhooks and caches move with it, and so do container images linked to it. Direct collaborators keep their access; grants that came from the old organization (its members, their agents, its agents) are dropped, and the new owner's organization secrets and variables apply. The old owner/name keeps working for anyone who can read the repository, as on GitHub: API calls get a 301 (reads) or 307 (anything else) to the new URL, web pages a 301, Git (with a `This repository moved` notice on push) and MCP are served under the old name, and image names resolve to the moved images. Creating a repository at the old name ends the redirect. Admins of both owners get an email. Audited as `repo.transfer`. Returns the repository.", | |
| 237 | + "Moves the repository to `new_owner`: the caller's own account, or an organization where the caller is an owner or admin. Needs admin access to the repository (a personal repository: its owner) and a token with the admin scope; agents, job tokens (`GITHUB_TOKEN`) and sandbox tokens are refused. 422 when the new owner already has a repository (or a container image of a moved image's name) with that name. Git data, issues, pull requests, Actions runs, secrets, variables, environments, webhooks and caches move with it, and so do container images linked to it. Direct collaborators keep their access; grants that came from the old organization (its members, their agents, its agents) are dropped, and the new owner's organization secrets and variables apply. The old owner/name keeps working for anyone who can read the repository, as on GitHub: API calls get a 301 (reads) or 307 (anything else) to the new URL, web pages a 301, Git (with a `This repository moved` notice on push) and MCP are served under the old name, and image names resolve to the moved images. Creating a repository at the old name ends the redirect. Admins of both owners get an email. Audited as `repo.transfer`. Returns the repository. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).", | |
| 238 | 238 | access: "admin", |
| 239 | 239 | status: 202, |
| 240 | 240 | body: { new_owner: ["string", "Login of the user or organization to transfer to.", true], new_name: ["string", "A new name for the repository (default: keep its name)."] }, |
| @@ -1302,7 +1302,7 @@ | ||
| 1302 | 1302 | "GET /diffinery/tokens": { summary: "List your tokens", access: "account" }, |
| 1303 | 1303 | "POST /diffinery/tokens": { |
| 1304 | 1304 | summary: "Create a token", |
| 1305 | - description: "From a browser session only: no token can create a token. The token value is returned once. Unknown scopes or an empty list fail with 422, and so does a repository you can't see (`unknown repository owner/name`).", | |
| 1305 | + description: "From a browser session only: no token can create a token. The token value is returned once. Unknown scopes or an empty list fail with 422, and so does a repository you can't see (`unknown repository owner/name`). From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).", | |
| 1306 | 1306 | access: "account", |
| 1307 | 1307 | status: 201, |
| 1308 | 1308 | body: { |
| @@ -1321,7 +1321,7 @@ | ||
| 1321 | 1321 | "GET /diffinery/agents": { summary: "List your agents", description: "Agents you own, and agents of organizations you administer.", access: "account" }, |
| 1322 | 1322 | "POST /diffinery/agents": { |
| 1323 | 1323 | summary: "Create an agent", |
| 1324 | - description: "People, from a browser session only (no token can create a token). Returns the agent and its first token (write scope, 90 days, unless you say otherwise). Unknown scopes or an empty list fail with 422.", | |
| 1324 | + description: "People, from a browser session only (no token can create a token). Returns the agent and its first token (write scope, 90 days, unless you say otherwise). Unknown scopes or an empty list fail with 422. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).", | |
| 1325 | 1325 | access: "account", |
| 1326 | 1326 | status: 201, |
| 1327 | 1327 | body: { |
| @@ -1334,7 +1334,7 @@ | ||
| 1334 | 1334 | }, |
| 1335 | 1335 | "POST /diffinery/agents/{login}/tokens": { |
| 1336 | 1336 | summary: "Create a token for an agent", |
| 1337 | - description: "From a browser session only (no token can create a token): the agent's owner, or an owner or admin of the organization that owns it. Default scope `write`; unknown scopes or an empty list fail with 422. Audited as `agent.token.create`.", | |
| 1337 | + description: "From a browser session only (no token can create a token): the agent's owner, or an owner or admin of the organization that owns it. Default scope `write`; unknown scopes or an empty list fail with 422. Audited as `agent.token.create`. From a browser session whose sign-in is more than 2 hours old: 403 `Confirm it's you: sign in with your passkey again to do this.` with a `confirm` URL (see /docs/security/overview#confirming-its-you).", | |
| 1338 | 1338 | access: "account", |
| 1339 | 1339 | status: 201, |
| 1340 | 1340 | body: { name: ["string", "Token name."], scopes: ["string[]", "`read`, `write` (default) and/or `admin`."], expires_in_days: ["integer", "Lifetime in days."] }, |