toga-ai 1.0.257 → 1.0.258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -36,10 +36,13 @@ auto-created.
|
|
|
36
36
|
## How it works
|
|
37
37
|
|
|
38
38
|
### Wiring overview
|
|
39
|
-
- **`/sso/initiate`** (`mvc/sso/initiate/get.php`) — guards `isLoggedIn`, reads `config[saml]
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
- **`/sso/initiate`** (`mvc/sso/initiate/get.php`) — guards `isLoggedIn`, reads `config[saml]`
|
|
40
|
+
for the IdP/ACS urls + `domain_uuid`, sources the **rotating shared secret from `Core.Parameters`
|
|
41
|
+
via `App_Auth::currentApiSecret()`** (NOT from config — see below), calls `App_Sso::initiate([...])`
|
|
42
|
+
passing `'apiSecretAccessToken' => App_Auth::currentApiSecret()`, then `App_MVC::routeTo($url); exit;`.
|
|
43
|
+
Initiation uses the reusable 1.0 [`App_Sso`](../../library/features/app-sso-initiation.md) library
|
|
44
|
+
class (no longer a placeholder). The secret encrypts the RelayState — see the non-empty-token
|
|
45
|
+
gotcha below.
|
|
43
46
|
- **`/sso`** (`mvc/sso/get.php`) — the handoff consumer (below).
|
|
44
47
|
- **Core.Domains** — a fixed-uuid row registers this app's return domain so the gateway resolves
|
|
45
48
|
client/environment/return URL: `uuid 2927bc15-e347-4358-a430-fb28f9446d27`, `clientId 1` (True),
|
|
@@ -59,18 +62,29 @@ auto-created.
|
|
|
59
62
|
and that both decrypted values match a UUID regex.
|
|
60
63
|
4. Load the active Client_True user: `WHERE uuid = ? AND isActive = 1`. No match → fail closed.
|
|
61
64
|
|
|
62
|
-
### Handoff key sourcing (`handoffKeys()` → `Core.Parameters`)
|
|
63
|
-
The shared secret is **rotated regularly**, so
|
|
65
|
+
### Handoff key sourcing (`handoffKeys()` → `Core.Parameters`) — used by BOTH SSO legs
|
|
66
|
+
The shared secret is **rotated regularly**, so any static `config.*.ini [saml]` copy goes stale —
|
|
64
67
|
that staleness was exactly why `decryptHandoffValue()` returned `false` and production login failed
|
|
65
68
|
(the payload decoded to the correct iv+base64 shape; it was a **key** mismatch, not a format bug).
|
|
66
|
-
|
|
69
|
+
**Both SSO legs must source the secret from `Core.Parameters` at runtime** — the initiate leg
|
|
70
|
+
encrypts the RelayState with it, the consumer leg decrypts the handoff with it. A config-only copy
|
|
71
|
+
silently breaks SSO on the next rotation.
|
|
72
|
+
- `App_Auth::handoffKeys()` (private helper) `SELECT \`key\`,\`value\` FROM Parameters WHERE
|
|
67
73
|
\`key\` IN ('API_SECRET_ACCESS_TOKEN','API_SECRET_ACCESS_TOKEN_PREVIOUS')` over the **`db_toga2core`**
|
|
68
74
|
connection (2.0 PROD Core DB; `const CORE_DB = 'db_toga2core'`). Result is **cached per request**.
|
|
69
75
|
`\`key\`` and `\`value\`` are SQL reserved words → **must be backticked**.
|
|
70
|
-
- `decryptHandoffValue()`
|
|
71
|
-
Its **public signature is unchanged
|
|
72
|
-
-
|
|
73
|
-
|
|
76
|
+
- **Consumer leg** (`mvc/sso/get.php`): `decryptHandoffValue()` tries the current key, falling back
|
|
77
|
+
to previous (rotation window). Its **public signature is unchanged**.
|
|
78
|
+
- **Initiate leg** (`mvc/sso/initiate/get.php`): calls **`App_Auth::currentApiSecret()`** (public;
|
|
79
|
+
reuses the cached `handoffKeys()` lookup, returns the current `API_SECRET_ACCESS_TOKEN`) and passes
|
|
80
|
+
it to `App_Sso::initiate(['apiSecretAccessToken' => …])` to encrypt the RelayState.
|
|
81
|
+
- **Do NOT remove `[saml] api_secret_access_token` / `_previous` from config.** ⚠ An earlier capture
|
|
82
|
+
this session wrongly called these "dead/removable" — that guidance **broke SSO**: the initiate leg
|
|
83
|
+
still read the config token, so removing it made `App_Sso::initiate()` throw on an empty token
|
|
84
|
+
(`/login?error=1`). The fix moved the initiate leg to `Core.Parameters` too; the config copies are
|
|
85
|
+
now genuinely unused, but the durable rule is that the **only source of truth is `Core.Parameters`**.
|
|
86
|
+
- **`[saml] true_client_uuid` is still read from config** (`sso/get.php:80-84`) and **must NOT be
|
|
87
|
+
removed**. The IdP/ACS urls and `domain_uuid` also stay in config.
|
|
74
88
|
- See [Core.Parameters key/value store](#coreparameters-key-value-store) for the table shape and
|
|
75
89
|
which DB cluster it lives on.
|
|
76
90
|
|
|
@@ -115,6 +129,19 @@ removed.
|
|
|
115
129
|
preloader and runs `App_Page::flushCapture()` (`ob_end_flush + flush`, `library/app/page.php:213`)
|
|
116
130
|
**before** `body()/App_MVC::loadFile()` runs the mvc page. Fixed with the same
|
|
117
131
|
`if (!headers_sent()) { http_response_code(401); }` guard (matches `App_MVC::routeTo`'s own check).
|
|
132
|
+
- **`App_Sso::initiate()` hard-requires a non-empty `apiSecretAccessToken`** (`library/app/sso.php:64-65`,
|
|
133
|
+
`empty()` guard → `InvalidArgumentException`; the token encrypts the RelayState). An empty/absent token
|
|
134
|
+
surfaces as a redirect to **`/login?error=1` ("Sign in failed. Please try again.")** — the initiate leg
|
|
135
|
+
catches the exception (`sso/initiate/get.php:30-34`) and routes to `/login?error=1`. This fails
|
|
136
|
+
**before** the IdP round-trip, so it never reaches the consumer's **"Authentication failed"**
|
|
137
|
+
(`tools_ssoFail`) page. **The two error UIs tell you which leg failed:** `/login?error=1` = initiate
|
|
138
|
+
(RelayState encryption / token), "Authentication failed" = consumer (decrypt). This was the
|
|
139
|
+
2026-06-30 regression — removing the config token per the (wrong) earlier note made initiate throw.
|
|
140
|
+
- **Both legs depend on `db_toga2core` + a populated `Core.Parameters` token.** If `[database_toga2core]`
|
|
141
|
+
is not configured, `currentApiSecret()`/`handoffKeys()` runs `App_Database::query` against an
|
|
142
|
+
unregistered alias and **throws**. Because initiate only catches `InvalidArgumentException`/`RuntimeException`,
|
|
143
|
+
that surfaces as a **500** — not the login banner. **Diagnostic:** a 500 at initiate = missing
|
|
144
|
+
`db_toga2core`; `/login?error=1` = empty/absent `API_SECRET_ACCESS_TOKEN`.
|
|
118
145
|
- **No app-side replay defense.** The handoff token carries no nonce/timestamp the app verifies.
|
|
119
146
|
Recommend the gateway embed `iat` + `jti`.
|
|
120
147
|
|
|
@@ -140,8 +167,11 @@ from the 1.0 prod-cluster and from the 2.0 client cluster that backs `db_true`/`
|
|
|
140
167
|
`db_toga2core`, dbname `Core`, the prod core cluster host (`production-core-cluster…us-west-2…`),
|
|
141
168
|
needed in **each** `config.*.ini` so the handoff keys can be read at runtime (developer is adding
|
|
142
169
|
creds). `[saml]`: `true_client_uuid` (still used), `client_authentication_uuid`, `domain_uuid`,
|
|
143
|
-
plus the IdP/ACS urls used by initiation; `[internal]` `dev_mode`.
|
|
144
|
-
|
|
170
|
+
plus the IdP/ACS urls used by initiation; `[internal]` `dev_mode`. `[saml] api_secret_access_token` /
|
|
171
|
+
`_previous` are **superseded by `Core.Parameters`** (the only source of truth for BOTH legs) — but do
|
|
172
|
+
**not** treat them as freely removable: removing the config token while the initiate leg still read it
|
|
173
|
+
is what caused the 2026-06-30 `/login?error=1` regression. `[saml] true_client_uuid` is **still read
|
|
174
|
+
from config** and must stay.
|
|
145
175
|
|
|
146
176
|
**Secret location.** Rotating secrets are now sourced from `Core.Parameters` **at runtime** rather
|
|
147
177
|
than static config — an improvement over the old committed `config.production.ini [saml]` tokens.
|
|
@@ -150,6 +180,7 @@ remains (already noted in tools knowledge) — **flag for rotation.** Document *
|
|
|
150
180
|
never the values.
|
|
151
181
|
|
|
152
182
|
## Change history
|
|
183
|
+
- 2026-06-30 — Fixed SSO "Sign in failed" (`/login?error=1`) regression and **corrected the earlier wrong "config tokens are removable" note**. The prior fix moved only the consumer/decrypt leg to `Core.Parameters` and advised the `[saml] api_secret_access_token` was removable — but the **initiate** leg (`mvc/sso/initiate/get.php`) still read it from config, so removing it made `App_Sso::initiate()` throw on an empty token (`library/app/sso.php:64-65`) → `/login?error=1`. Fix: **both** legs now source the rotating secret from `Core.Parameters` — added public `App_Auth::currentApiSecret()` (reuses cached `handoffKeys()`), and `sso/initiate/get.php` passes it instead of the config value. Documented the non-empty-token requirement, the `/login?error=1` (initiate) vs "Authentication failed" (consumer) leg distinction, the `true_client_uuid`-stays-in-config rule, and the `db_toga2core`-missing→500 diagnostic. `php -l` clean on both files. (jcardinal)
|
|
153
184
|
- 2026-06-30 — Fixed prod SSO login failure: handoff decrypt keys now sourced from `Core.Parameters` (rotated `API_SECRET_ACCESS_TOKEN`/`_PREVIOUS`) via new `App_Auth::handoffKeys()` over `db_toga2core` (per-request cached, current→previous fallback), instead of the stale committed `config [saml]` tokens — the stale key was the root cause. Added `[database_toga2core]` connection requirement to every config and documented the AES-256-CBC crypto format. Fixed `tools_ssoFail()` 500→intended 401: `http_response_code(401)` fataled on headers-already-sent (preloader flushes before mvc page runs); guarded with `!headers_sent()`. (jcardinal)
|
|
154
185
|
- 2026-06-26 — Wired up real SSO initiation via the new 1.0 `App_Sso` library class (`/sso/initiate`), replacing the `initiation_url` placeholder; registered the fixed-uuid Core.Domains return row (dbchanges2 `2026-06-26a`); fixed the `session_regenerate_id` headers-already-sent fatal (guarded with `!headers_sent()`) and documented the mid-render failure-path limitation; collapsed home → `/login` to one-step sign-in; noted prod secrets live in `config.production.ini [saml]` (jcardinal)
|
|
155
186
|
- 2026-06-25 — Built App_Auth: SAML `?saml=` handoff consumer with dual-key decrypt, hash_equals client-uuid check, fail-closed 401, persona-cached session (HttpOnly+SameSite=Lax), and a double-gated dev bypass. Initiation + replay defense left as open items (jcardinal)
|
package/package.json
CHANGED