Skip to content

feat(sdks): SDK-CLIENTS-1 — typed MFA-required error + purgeAt in Go/Python/Node/Java - #63

Open
aibeshkov wants to merge 3 commits into
mainfrom
sdk-clients-1/mfa-error-purge-at
Open

aibeshkov wants to merge 3 commits into
mainfrom
sdk-clients-1/mfa-error-purge-at

Conversation

@aibeshkov

Copy link
Copy Markdown
Contributor

One PR, one commit per item. All four SDKs (Go, Python, Node, Java). No server change, no .github change. The repo has no changelog.d, so entries are in each SDK's own CHANGELOG.md.

Item 1 — SDK-MFA-ERR-1: typed MFA error + second step

Server contract (read from server/http/handlers/auth.go and openapi.yaml): POST /auth/login for an MFA account answers 200 {data:{mfa_required:true, mfa_challenge, totp_available, webauthn_available}} (no token), and POST /auth/mfa/verify {mfa_challenge, code} (TOTP or recovery code) returns the normal login payload. The SDKs treated the 200 as a missing token.

SDK Error Second step
Go *MFARequiredError VerifyMFA(ctx, url, challenge, code)
Python MFARequiredError verify_mfa(...), login(..., mfa_code=)
Node MFARequiredError verifyMfa(...), login(..., { mfaCode })
Java MFARequiredException Keyorix.verifyMfa(...), Keyorix.login(url, user, pass, mfaCode)

Each carries the challenge plus totp_available / webauthn_available. Passkey ceremony is not implemented.

Contract tests (real server, fresh SQLite): create a user, enrol TOTP through the API, assert the typed error + challenge, a wrong code gives the auth error, the right code gives a working token.

Red on main (main's SDK source, new contract test):

  • Node: ❌ login() for an MFA-enrolled account rejects with MFARequiredError — got AuthError: No token in login response
  • Python: AttributeError: module 'keyorix' has no attribute 'MFARequiredError'
  • Go: undefined: MFARequiredError, undefined: VerifyMFA (does not compile)
  • Java: cannot find symbol class MFARequiredException, String cannot be converted to Duration (does not compile)

Green with the change: TestContract_MFALogin PASS; Python TestContractMFALogin OK; Node 3 MFA assertions ✅; Java ContractTest 8/9 (see below).

Item 2 — purgeAt (RETENTION-1)

The server only reports purge_at for soft-deleted secrets: the recycle-bin list GET /projects/{id}/secrets/deleted (and a Keyorix-Purge-At header on DELETE). None of the SDKs could list deleted secrets, so each gets the field on its secret type (PurgeAt *time.Time / purge_at / purgeAt / getPurgeAt()) plus ListDeletedSecrets / list_deleted_secrets / listDeletedSecrets to fill it. Live secrets have it empty/nil.

Contract tests soft-delete a secret and assert purgeAt is ~30 days out (retention_days: 30) and empty beforehand. Red on main: field/method missing (s.PurgeAt undefined, AttributeError: 'Secret' object has no attribute 'purge_at', client.listDeletedSecrets is not a function). Green after.

Item 3 — COMPATIBILITY.md

MFA/purge rows, minimum server versions (MFA flow v0.4.0, purgeAt v0.96.0, from git tag --contains), and an "Open items" section.

Verification and caveats

Run natively on this Mac against a keyorix server built from main (c753f5444), SQLite, security.require_mfa: false:

  • Go go test ./... ok, go vet clean, gofmt clean; Python unit suite 44 ok; Node test.js 47/47.
  • Java: Maven cannot resolve its plugins in the sandbox (Maven Central is not reachable), so Java was compiled with javac and run through the JUnit launcher with the jars from ~/.m2. CI's mvn run is the real check.
  • Pre-existing, not from this PR: .github/contract-server/keyorix.yaml lacks require_mfa: false; with the ADR-112 default (on) the bootstrap admin is confined to enrolment (403) and most contract cases fail on current server main. Separately, the machine-token "must be forbidden" case fails in all four SDKs even with it off (a fresh machine identity is no longer forbidden). Both are in COMPATIBILITY.md "Open items"; the first needs a .github change for the coordinator.
  • Login rate limit: each MFA login holds a per-IP slot, and the four contract tests share one server in CI. The MFA tests use two password logins each to keep within the budget; a 429 on repeated local runs confirmed it is finite.

Session: SDK-CLIENTS-1

…ava (SDK-MFA-ERR-1)

Login against an MFA-enabled account returns 200 {mfa_required, mfa_challenge, totp_available, webauthn_available}; the SDKs read that as a missing token. Now: Go *MFARequiredError + VerifyMFA, Python MFARequiredError + verify_mfa/login(mfa_code=), Node MFARequiredError + verifyMfa/login(..,{mfaCode}), Java MFARequiredException + verifyMfa/login(..,mfaCode). Contract tests enrol a TOTP user on a real server.

Session: SDK-CLIENTS-1
Signed-off-by: Andrei Beshkov <andrey.beshkov@gmail.com>
…, Node, Java

The server reports purge_at only for soft-deleted secrets (recycle-bin listing GET /projects/{id}/secrets/deleted; DELETE /secrets/{id} also sends a Keyorix-Purge-At header). Each SDK gets the field on its secret type plus a list-deleted-secrets method that fills it. Contract tests soft-delete a secret on a real server and assert purgeAt is ~30 days out.

Session: SDK-CLIENTS-1
Signed-off-by: Andrei Beshkov <andrey.beshkov@gmail.com>
…ions

Session: SDK-CLIENTS-1
Signed-off-by: Andrei Beshkov <andrey.beshkov@gmail.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.

1 participant