| M1-41/1 |
2026-10-14 |
Base int/m1-wave13 at 82693e50, branch feat/M1-41-identity-oidc in its own worktree; not merged locally and not pushed, the PR lane ports it (the launcher's task overrides the brief's local merge). The migration takes the next free number of the base, 00018; tickets of the same wave that also take 00018 are renumbered at integration. |
Instructions of the launcher; migration numbers are per base (INT-W13/2). |
| M1-41/2 |
2026-10-14 |
The callback reads the flow without a lock and talks to the provider outside any transaction; its only writes are conditional on the status (IssueLoginCode from started, FailLoginFlow from started or code_issued). Of two callbacks with one state at most one issues a code; the other gets 400 like a used state. |
02 section 8.5 forbids outgoing calls inside a transaction; a row lock would have to be held across the token request. |
| M1-41/3 |
2026-10-14 |
flow_expired is the answer of a found flow whose expires_at passes while the callback runs (after the token request); a flow already expired when the callback arrives is 400 without redirect, as 08 section 2.2 step 6 says for an expired state. |
08 names both; this keeps both meaningful. |
| M1-41/4 |
2026-10-14 |
The discovery document of an issuer with {tenantid} is read from the URL with organizations in place of the placeholder; its issuer must equal the configured template exactly. The iss response parameter of a template issuer must name one of allowed_tenant_ids, and the ID token's iss must equal it. |
Entra publishes the multi-tenant document under the alias whose issuer is the template; M1-36/5 refuses the alias as issuer, so the alias appears only in this URL. Written into 08 section 2.6. |
| M1-41/5 |
2026-10-14 |
external_identity.v1 gains schema and preferred_username (the handle of a provisioned person starts from its local part, which display_name alone cannot give); auth_time is stored as Unix seconds. |
Brief rule "Provisioned handle" needs the claim at complete, and the callback stores only this document. Written into 08 section 2.2 step 6. |
| M1-41/6 |
2026-10-14 |
Provider secret references are confined at resolution (wire.ProviderSecrets): only files below <secrets.dir>/idp/ and variables BOTHY_IDP_*, never the value of any secret_ref key of the server schema; another reference makes the token request fail, so the callback answers login_failed. The write-time check of M1-36 stays syntax only. |
Closes M1-36/14 with the minimum: the server's own secrets cannot leave as client secret; a write-time refusal would need the real secrets directory in the domain rules. |
| M1-41/7 |
2026-10-14 |
CompleteLogin gets AllowPending (OIDC only): a provisioned person in status pending logs in again with a pending device and gets only the self information; disabled and deleted never log in. The allow entry of a provisioning login carries provisioned: true; no user.updated event is written for the provisioned person. |
08 R-ID-14 and 3.1 step 3; the other login paths keep refusing pending people. The event belongs to the owner's enableUser, which already emits it. |
| M1-41/8 |
2026-10-14 |
A subject that is linked at the provider under another issuer (another tenant of a multi-tenant provider) is neither mapped nor linked again: complete answers the neutral 401 and fails the flow. An enrollment code that became unusable between begin and complete is the same 401. |
(provider_id, subject) is unique among active identities (03); mapping needs the issuer too (R-ID-02). |
| M1-41/9 |
2026-10-14 |
Link mode: an unknown, expired or other person's login_code is 404 not_found, a wrong verifier 401 unauthenticated (flow failed), a subject linked to anyone 409 conflict (flow failed); identity.link (audit always) writes an allow entry at begin (step: begin) and at complete (step: complete). |
The brief fixes 404 for another actor and 409; the same 404 for an unknown code hides whether a code exists. |
| M1-41/10 |
2026-10-14 |
The re-login rule counts from created_at of the session, for a child session from its parent; the refusal commits the revocation (expired, families revoked, session.revoked) like a reuse. A zero auth.idp_recheck_interval switches the rule off; the schema refuses values below 1m, so only tests can set it. |
The child inherits the parent's login; without the parent's time an extension session would outlive the interval. |
| M1-41/11 |
2026-10-14 |
kernel/jose: RSA keys only through the new ParseVerificationJWK (2048 to 8192 bits, odd exponent below 2^31); ParseJWK, used for device keys and DPoP, still refuses RSA. PS256 verifies with a salt of the hash length. The header member x5t#S256 is written when signing a client assertion and ignored when parsing. ADR 0067 notes both. |
Keeps the device profile unchanged and bounds the cost of a provider key. |
| M1-41/12 |
2026-10-14 |
The adapter tests of client authentication, JWKS reload, cache TTL and private targets run in adapters/oidc against the fake provider with the real egress client; the HTTP tests repeat private targets through the chain. The ID token claims are verified by the use case over the parsed claims the adapter returns. |
Brief "Scope" splits JWS (adapter) and claim rules (domain); the adapter tests need no database. |
| M1-41/13 |
2026-10-14 |
The HTTP tests of M1-41 run as subtests named after the brief in four groups with one world each (TestOIDC_Login, TestOIDC_IssParameter, TestOIDC_Entra, TestIdentityLink), plus TestOIDCEgress_PrivateTargetsRejected with two worlds; cases that move the clock run last in their group, and the denial aggregator is flushed before each case. |
One world per test made identity/adapters/httpapi exceed the default go test timeout of 10 minutes under -race (each world builds the request validator of the contract); the pattern is the one of M1-44 (runTokenCases). |
| M1-41/14 |
2026-10-14 |
httpservertest.New takes the request validator from a process-wide cache per contract and operation list (SharedValidator, like SharedResponseValidator; on main it came first with PL/3). cmd/bothy pins the five new configuration keys in TestConfigPrintShowsSourcePerKey. |
identity/adapters/httpapi exceeded the 10-minute default timeout of go test already on the base under load (the same failure stands in the reports of M1-37, M1-45, M1-46 and M2-24); building the validator per world was almost all of its run time. With the cache the package runs in a fraction of that time under -race, without a data race; the validator is read only and serves concurrent requests in production. A test helper outside the scope list of the brief, changed because the brief requires make check green and the same failure recurred (fix the class once). |
| M1-41/15 |
2026-10-14 |
Test code only: TestOwnerRace (M1-31) also accepts [204, 403 forbidden] for the two crossing revocations, the outcome when the second request authorizes after the first has committed; the invariant "exactly one owner left" is checked unchanged. |
The test failed with [204 403] under load in the suites of M1-44, M1-50 and this ticket; the serialised order is correct behaviour, so the flake class is fixed in the test, not in the code. |
| M1-41/16 |
2026-10-15 |
Port onto main: the migration is 00020_identity_login_flows.sql as in the integration branch (INT-W14/2; M1-41/1 was written with the number 18 of its base), its comments, the query comments, the code comments and these rulings name the English chapter files (new checksum recorded with make migrations-lock). auth.login_flow_ttl, EnrollmentCodes.MatchID and the query LockEnrollmentCode came with M1-40 (M1-40/13); this ticket reuses them and only widens their comments (INT-W14/3). The split of the REST package (INT-W14/4) is already on main and is regenerated, not merged. The constant fieldCodeTaken of INT-W14/10 comes with this ticket, because on main its handle conflict is the third use of the literal taken and goconst counts from three. LoginCompletion.AllowPending is added once here; M1-43 reuses it (INT-W14/5). The fix of INT-W14/6 to TestOIDC_Login/TestIDPRecheckForcesRelogin is part of this commit. |
main holds M1-38 with 00018 and M1-40 with 00019, so 00020 follows without a gap; the specification is English on main. No behaviour changes beyond the integration content. |
| M1-41/17 |
2026-10-17 |
The two RS256 calls of internal/kernel/jose (rsa.VerifyPKCS1v15 in verifyRSA, rsa.SignPKCS1v15 in RS256Signer.Sign) carry NOSONAR: the SonarQube Cloud rule for a secure mode and padding scheme is about RSA encryption, and these lines make and check PKCS #1 v1.5 signatures. RS256 stays, because OpenID Connect providers sign ID tokens with it by default and the client assertion offers it next to PS256. |
The rule reports a vulnerability and fails the security rating of the quality gate; a signature with PKCS #1 v1.5 padding is no encryption, and RFC 7518 section 3.3 defines RS256 with exactly this padding. |