Skip to content

feat: choose and switch organisations for one person (1.5.0) - #13

Merged
BackStacked merged 1 commit into
mainfrom
claude/project-thread-ovi2jd
Nov 10, 2026
Merged

BackStacked merged 1 commit into
mainfrom
claude/project-thread-ovi2jd

Conversation

@BackStacked

Copy link
Copy Markdown
Contributor

One person can have an account (same email) in several labs. Until now sign-in answered ORG_REQUIRED and the client had to know the lab code. The dashboard now wants two things: a pick list at sign-in, and a lab switcher once signed in. The owner chose to allow switching without asking for the password again.

Changes

  • ORG_REQUIRED names the choices. details.organizations is an OrgChoice[] of { orgId, code, name }, sorted by name.
    • It lists only the accounts whose password matched and that are active. A wrong password still says nothing beyond INVALID_CREDENTIALS.
    • The names come from the new optional OrgStore.get(id). db-kit's repos.orgs already has it.
  • organizationsOf(userId) returns every active account with the same email, the current one included.
  • switchOrganization({ token, orgId, ip?, userAgent?, device?, tlsFingerprint? }) returns what login returns, for the account in orgId.
    • The current session must be valid, and it ends on switch.
    • Device binding works as on refresh: a different deviceId is refused and ends the session.
    • The switch is audited in both organisations, as auth.org_switch_out and auth.org_switch.
    • Switching to the same organisation, an unknown one, or one without an active account fails with the new code ORG_UNAVAILABLE. No existing code is renamed.
  • Login refactor. The session-issuing tail of login is now issue(), shared with the switch. Login behaviour is unchanged.
  • Release. The version is 1.5.0. README, AGENTS and llms-full.txt are updated.

Tests

npm run typecheck && npm test && npm run build passes with 155 checks; there were 131.

  • The new checks run on both memory and Redis stores, in opaque and JWT modes.
  • They cover the pick list (only accounts whose password matched), switch and old-session end, audit in both organisations, every ORG_UNAVAILABLE case, and a disabled account.
  • They also check JWT switch from another device (refused), the old refresh token being dead, and the new session refreshing on the same device.

Follow-up

The api needs this release to expose myOrganizations, switchOrganization and the pick list. That PR comes next.


Generated by Claude Code

- ORG_REQUIRED now carries details.organizations: the organisations whose
  account matched the email and password (named via the new optional
  OrgStore.get), so clients can show a pick list.
- organizationsOf(userId): every active account with the same email.
- switchOrganization({ token, orgId, … }): moves a valid session to the same
  person's account in another organisation without the password. The old
  session ends, device binding applies as on refresh, and the switch is
  audited in both organisations. New error code ORG_UNAVAILABLE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jBohk4otGVRjt3Y1cF1N2
@BackStacked
BackStacked merged commit 4c333ec into main Nov 10, 2026
@BackStacked
BackStacked deleted the claude/project-thread-ovi2jd branch November 10, 2026 07:17
BackStacked added a commit to mgpl-diagnostics/client that referenced this pull request Nov 10, 2026
This mirrors the api's new fields for one person with accounts in
several labs.

## Changes
- **`auth.organizations()`** calls query `myOrganizations` and returns
`{ code, name, current }[]`.
- **`auth.switchOrganization(orgCode)`** calls mutation
`switchOrganization` with this device's signals and keeps the new tokens
in the session manager. Apps should reset their cached data afterwards.
- **`organizationChoices(error)`** returns the labs to pick from after
`ORG_REQUIRED` (`extensions.organizations`). It is exported from the
main and `/graphql` entries.
- **Types and docs.** Adds the `OrganizationChoice` type.
docs/graphql.md lists the new calls and `ORG_UNAVAILABLE`. llms-full.txt
is regenerated.
- **Version** is 1.5.0.

## Depends on
The api PR that adds `myOrganizations` / `switchOrganization`. That PR
relies on auth-kit 1.5.0 (mgpl-diagnostics/auth-kit#13).

## Checks
- `tsc --noEmit` and `npm run build` pass.
- The api's suite, run against this build, passes "every query and
mutation has a client operation" (myOrganizations and switchOrganization
are covered).
- The dashboard's lab picker and switcher were tested end to end against
a mock of the new schema.

---
_Generated by [Claude
Code](https://claude.ai/code/session_018jBohk4otGVRjt3Y1cF1N2)_

Co-authored-by: Claude <noreply@anthropic.com>
BackStacked added a commit to mgpl-diagnostics/api that referenced this pull request Nov 10, 2026
One person (one email) can have an account in several labs. This lets
the dashboard show a pick list at sign-in and switch labs once signed
in. The owner chose switching without asking for the password again.

## Changes
- **Pick list at sign-in.** `ORG_REQUIRED` now carries
`extensions.organizations`, a list of `[{ code, name }]`.
- It lists only the labs whose account matched the password, with no
ids.
  - A wrong password still lists nothing.
- **Query `myOrganizations: [OrganizationChoice!]!`** returns `{ code,
name, current }` for every lab the person can work in.
- **Mutation `switchOrganization(orgCode: String!, device: DeviceInput):
LoginResult!`**, built on auth-kit `switchOrganization`.
- It takes the lab **code**, not an id, so rule 3 (no `orgId` arguments)
holds.
- An unknown code and a lab without an account both answer
`ORG_UNAVAILABLE`, so codes can't be probed.
  - The old session ends.
  - The same device rule as `refreshSession` applies.
  - The switch is audited in both labs.
- **New error code `ORG_UNAVAILABLE`.** It is in `ERROR_CODES` and
docs/05-errors.md.
- **Docs.** docs/06 has a "One person, several labs" section.
`schema.graphql`, docs/04 and llms-full.txt are regenerated.

## Why this is a draft
It needs **auth-kit 1.5.0** (mgpl-diagnostics/auth-kit#13).
- `kits.json` points auth-kit at that branch's commit for `npm run
kits`.
- `package.json` and `package-lock.json` still pin the published
auth-kit, so CI's `npm ci` + typecheck will fail until 1.5.0 is
published.
- After the release, run `npm install @mgpl-diagnostics/auth-kit@1.5.0`,
set `kits.json` to `v1.5.0`, and mark this ready.
- The client side is mgpl-diagnostics/client (1.5.0); bump its ref here
after it's released.

## Tests
I ran `KITS_SRC=.. npm run kits`, then `npm run typecheck`, then
`TEST_BACKENDS=pg npm test`. The result was 127 passed and 1 failed.
- **New checks on PostgreSQL:**
- the pick list (codes and names, no ids) and nothing on a wrong
password;
  - `myOrganizations` for a two-lab and a one-lab person;
  - a switch works in the other lab and ends the old session;
- `ORG_UNAVAILABLE` for the current lab, an unknown code, or a lab
without an account;
  - no session gives `UNAUTHENTICATED`;
  - a different device gives `INVALID_TOKEN` and ends the session.
- **The one failure is not from this change.** It is "client: all
GraphQL operations are valid": client `main` already selects partner-lab
fields (`provenance`, `externalReports`, …) that api `main` doesn't
serve yet. With client 1.5.0 the "every field has a client operation"
check passes.
- **MongoDB didn't run here.** The binary download is blocked by this
environment's proxy (403); CI runs both backends.

---
_Generated by [Claude
Code](https://claude.ai/code/session_018jBohk4otGVRjt3Y1cF1N2)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants