Skip to content

AI Workspace - Configuration Convention & the /me Endpoint#2642

Merged
Thushani-Jayasekera merged 4 commits into
wso2:mainfrom
Thushani-Jayasekera:aiws-configs-new
Jul 14, 2026
Merged

AI Workspace - Configuration Convention & the /me Endpoint#2642
Thushani-Jayasekera merged 4 commits into
wso2:mainfrom
Thushani-Jayasekera:aiws-configs-new

Conversation

@Thushani-Jayasekera

@Thushani-Jayasekera Thushani-Jayasekera commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

AI Workspace BFF — Configuration Convention & the /me Endpoint

This note captures two related pieces of the AI Workspace BFF: the restructured
configuration model, and the GET /me permissions hop the BFF makes to the
Platform API. They are documented together because the /me call exists to
resolve a gap that the configuration model deliberately refuses to paper over.


1. Configuration restructuring

1.1 One source of truth: config.toml

The BFF loads all configuration from a single config.toml
(internal/config/settings.go, config.go). There is no implicit environment
overlay on top of the file — a key takes its value from the environment or a
mounted secret only when its own interpolation token says so:

log_level    = '{{ env "APIP_AIW_LOG_LEVEL" "info" }}'
client_secret = '{{ file "/secrets/ai-workspace/oidc_client_secret" }}'

Consequences of this design:

  • Every source a value can come from is visible in the file itself, rather
    than implied by a naming rule or a precedence ladder. If a key has no token, it
    simply cannot be set from the environment.
  • One mechanism covers both ordinary settings and secrets. The OIDC client
    secret is resolved the same way as log_level — via a {{ env }} / {{ file }}
    token — so there is no separate secret-loading path.
  • Interpolation fails closed. An unset {{ env }} variable with no default,
    or an unreadable/disallowed {{ file }} path, aborts startup rather than
    silently yielding an empty credential.

Tokens are resolved by the shared common/configinterpolate library. {{ file }}
reads are constrained to an allowlist (/etc/ai-workspace, /secrets/ai-workspace
by default, overridable via APIP_CONFIG_FILE_SOURCE_ALLOWLIST).

1.2 Naming convention: APIP_AIW_

The AI Workspace namespaces its environment variables with the APIP_AIW_
prefix — mirroring the Platform API's APIP_CP_ and the Developer Portal's
APIP_DP_. The convention is: a key's variable is its dotted config path,
uppercased, dots→underscores, prefixed
.

[oidc] client_id   →   APIP_AIW_OIDC_CLIENT_ID
oidc.scope         →   APIP_AIW_OIDC_SCOPE

Crucially this is a convention, not a binding — the prefix documents the
expected variable name, but the environment only reaches config through the
explicit {{ env "NAME" }} token. A token may name any variable.

1.3 Flattened, typed access

The decoded TOML tree is flattened to dotted keys ([platform_api] url
platform_api.url). Values are read through small typed accessors that decide
what a bad value should do:

  • get / getbool / getdur — a malformed boolean or duration fails
    startup
    rather than silently reverting to the default (a typo shouldn't
    quietly change behaviour).
  • getint64 — used for defensive size ceilings (e.g. MaxMeResponseBytes); an
    unset, malformed, or non-positive value degrades to the safe default
    instead of blocking startup, because a size bound is a guard rail, not a
    correctness knob.

auth_mode is validated explicitly: anything other than basic or oidc fails
startup, so a typo can't silently degrade to basic auth.

1.4 Browser-safe runtime config (allowlist, not filter)

The SPA reads a subset of config from window.__RUNTIME_CONFIG__. The set of
keys that reach the browser is an allowlist (browserSafeKeys in
runtime_config.go), not a filter:

  • Only keys on the list ever reach the page. A new server-side key — a secret,
    an upstream URL, a cookie setting — cannot leak into the browser merely by
    being added to config.toml.
  • [oidc] client_secret / client_id / authority are deliberately absent: the
    BFF performs the entire OIDC handshake, so the SPA needs no client identity.
  • The SPA's API base URLs are forced onto the same-origin proxy prefix, so
    the browser only ever talks to the BFF, never the Platform API directly.

The runtime key spelling (runtimeKey) matches the {{ env }} token spelling, so
a value has one name across config.toml, the environment, Vite's
import.meta.env, and window.__RUNTIME_CONFIG__.

1.5 Claim-mapping mirror

[oidc.claim_mappings] deliberately mirrors the Platform API's
[auth.idp.claim_mappings] key for key. Both sides describe the same IDP
token, so they must agree — naming them identically makes drift obvious. The same
config entry drives both the BFF's session mapping and the SPA's identity display,
keeping the two layers in sync from one source.


2. The GET /me endpoint

2.1 What it is

internal/server/permissions.go calls the Platform API endpoint:

GET /api/portal/v0.9/me

It returns the signed-in user's effective identity and permissions — the
roles they hold and the scopes those roles grant:

type meResponse struct {
    Roles  []string `json:"roles"`
    Scopes []string `json:"scopes"`
}

The BFF issues this call on the user's behalf (forwarding their JWT) and folds the
result into the server-side session.

2.2 Why it matters — the problem it solves

The SPA gates every UI control on hasPermission(scope). So the session must
carry a scope list. But there is a mode where the token does not provide one:

Role validation mode. The IDP issues a token with roles and no scope
claim.
Decoding the scope claim yields nothing — so a fully privileged user
would be handed an empty app, every privileged control hidden, because the
SPA has no scopes to check against.

The Platform API already knows how to expand roles into scopes — it does this via
roles.yaml for its own authorization decisions. GET /me returns that same
expansion
. This is the important part:

  • The UI is driven by the exact scope list the API actually enforces, not by a
    second copy of the role→scope mapping shipped to the browser.
  • There is no duplicated authorization logic. If the mapping changes on the
    Platform API, the workspace UI follows automatically — nothing to re-ship.

2.3 How it behaves (enrichPermissions)

if the token already carries scopes  → no upstream hop, use them as-is
else                                 → GET /me, adopt returned scopes
  • A scope-bearing token needs no /me call. The enrichment only runs to fill
    a gap the token left.
  • Failure degrades safely. If the /me lookup fails, the user is left with
    no scopes — the app hides privileged controls rather than offering actions
    the API would then reject. It logs a warning and continues; it does not block
    login.
  • Role label fallback. In role mode there's no platform_role claim, so the
    displayed role label falls back to the first IDP role from /me.

2.4 Defensive bound: MaxMeResponseBytes

The /me payload is a small identity record, so the response read is capped
(platform_api.max_me_response_bytes, default 1 MiB) via io.LimitReader.
Anything larger is treated as an upstream fault, not something to buffer into
memory. Per §1.3, a non-positive or malformed configured limit falls back to the
safe default rather than failing startup.


3. How the two connect

Concern Config model says /me does
Where does authoritative permission data live? Not duplicated to the browser (allowlist keeps role→scope mapping server-side) Fetches the API's own expansion at request time
What if the token lacks scopes? The BFF won't fabricate them from a shipped mapping Resolves them from the enforcing service
What if resolution fails? Fail-closed philosophy Degrades to no scopes (hide, don't offer)
Defensive limits getint64 degrades to safe default Caps the response read at MaxMeResponseBytes

The through-line: a single authoritative source, resolved at the layer that
enforces it, never re-implemented in the browser.
The configuration model keeps
authorization data off the client; /me supplies it on demand from the service
that owns it.

- Updated the configuration reference to clarify the use of environment variables and interpolation tokens for secrets.
- Introduced a section on managing sensitive information, emphasizing the use of mounted secret files and environment variables for the OIDC client secret.
- Adjusted the examples in the authentication setup to reflect the new configuration practices.
- Improved consistency in naming conventions for environment variables across documentation and code.
- Added tests to validate the loading of configuration values from environment variables and files, ensuring proper error handling for missing or invalid configurations.
- Restructured the configuration in `config.toml` to group keys into TOML tables for better organization and clarity.
- Updated the documentation to reflect the new configuration format, emphasizing the use of interpolation tokens for environment variables and secrets.
- Clarified the handling of OIDC settings, including client ID and secret, ensuring they are referenced correctly in the configuration.
- Improved consistency in naming conventions for environment variables across documentation and code.
- Added tests to validate the loading of configuration values from environment variables and files, ensuring proper error handling for missing or invalid configurations.
- Updated the secrets management documentation to clarify the required environment variable for the Platform API encryption.
- Modified the Dockerfile to remove unnecessary GOFLAGS, streamlining the build process.
- Improved error handling in the configuration loading logic to ensure invalid authentication modes are properly reported.
- Added tests for the shipped configuration to validate loading behavior with and without environment variables, ensuring correct defaults and overrides.
- Adjusted the OIDC claim mappings in the configuration to align with the BFF defaults, enhancing consistency across components.
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Validation Results

Dependency name: github.com/pelletier/go-toml/v2
Version: v2.4.3
Approved: ❌ No - Module not found in dependency registry


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

The PR migrates AI Workspace to tokenized TOML configuration, adds structured OIDC and Platform API settings, updates BFF loading and browser-safe runtime configuration, and aligns deployment, frontend, build, test, and authentication documentation.

Changes

AI Workspace configuration migration

Layer / File(s) Summary
TOML contracts and deployment guidance
docs/ai-workspace/..., portals/ai-workspace/configs/*, portals/ai-workspace/docker-compose.yaml, portals/ai-workspace/production/README.md
Defines structured TOML tables, interpolation tokens, OIDC claim mappings, secret handling, and APIP_AIW_ environment naming.
Settings loading and startup validation
portals/ai-workspace/bff/internal/config/*, portals/ai-workspace/bff/main.go
Loads and flattens TOML settings, resolves env/file tokens, parses typed values, validates configuration, and adds the -config option.
Runtime configuration and tests
portals/ai-workspace/bff/internal/config/runtime_config.go, portals/ai-workspace/bff/internal/config/*_test.go
Restricts browser runtime settings to an allowlist and tests interpolation, parsing, validation, claim mappings, and secret exclusion.
Build and client integration
portals/ai-workspace/Dockerfile, Makefile, bff/go.mod, src/*, vite.config.ts
Updates container module contexts, local BFF execution, frontend environment prefixes, session-cookie references, and related tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: krishanx92, malinthaprasan, pubudu538, rakhitharr, virajsalaka

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description explains the changes, but it does not follow the required template and omits most required sections. Reformat the PR description into the required sections: Purpose, Goals, Approach, User stories, Documentation, tests, security, samples, related PRs, and test environment.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately reflects the two main changes: configuration convention and the /me endpoint.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
portals/ai-workspace/bff/internal/config/runtime_config.go (2)

67-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: auth_mode in browserSafeKeys is always overwritten by the forced value below.

Since out[runtimeKey("auth_mode")] = cfg.AuthMode (Line 78) unconditionally overwrites whatever the allowlist loop set for "auth_mode" (Line 31), keeping "auth_mode" in browserSafeKeys is dead weight — the raw s["auth_mode"] value it would surface is discarded either way. Not incorrect, just redundant.

♻️ Optional cleanup
 var browserSafeKeys = []string{
 	// Identity of the deployment
 	"domain",
-	"auth_mode",
 	"default_org_region",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@portals/ai-workspace/bff/internal/config/runtime_config.go` around lines 67 -
79, Remove "auth_mode" from browserSafeKeys, since buildRuntimeConfig always
sets the corresponding runtime key from cfg.AuthMode after processing the
allowlist. Keep the existing forced auth_mode assignment and all other
allowlisted keys unchanged.

28-53: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Confirm moesif_app_api_key is Moesif's publishable/collector key, not a secret.

Moesif does have a "Publishable Application Id" explicitly meant for browser JS use, distinct from its private "Secret Application Id" — so moesif_app_api_key is plausibly safe here. Given this file's entire purpose is to prevent secrets from reaching the browser, and the name is easy to confuse with a private API key, please double-check that the config value intended for this key is always the publishable Moesif Application Id and never a secret/management key, and consider a more explicit name (e.g. moesif_publishable_app_id) to reduce future misconfiguration risk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@portals/ai-workspace/bff/internal/config/runtime_config.go` around lines 28 -
53, Verify the configuration source for browserSafeKeys and confirm
moesif_app_api_key is always the publishable Moesif Application Id, never a
secret or management key; if the contract is ambiguous, rename the configuration
key and all consumers to an explicit publishable-app-id name and update the
allowlist accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/ai-workspace/authentication/oidc-auth.md`:
- Around line 33-60: Merge the split [oidc] TOML sections so each example
contains one [oidc] table, placing redirect_url, post_logout_redirect_url, and
client_secret before [oidc.claim_mappings]. Apply this to
docs/ai-workspace/authentication/oidc-auth.md lines 33-60 and
docs/ai-workspace/authentication/asgardeo-setup.md lines 156-179; do not leave
duplicate [oidc] headers or require readers to manually combine snippets.

In `@docs/ai-workspace/configuration.md`:
- Line 167: Revise the configuration documentation sentence near the
environment-variable override guidance to state that only keys containing an {{
env }} token can be overridden via APIP_AIW_ plus the uppercased key path. Do
not claim that arbitrary literal or absent config keys accept environment
overrides.

In `@portals/ai-workspace/bff/internal/config/settings.go`:
- Around line 160-171: Update settings.getdur to reject durations that are zero
or negative after successful time.ParseDuration parsing, returning a startup
error with the setting key and invalid value; continue returning the default for
missing or empty values and preserve valid positive durations.

In `@portals/ai-workspace/bff/main.go`:
- Around line 178-183: Update the TLS-related log and error messages in the
startup configuration flow to reference the actual TOML keys using `[tls]
cert_file`, `[tls] key_file`, and `[tls] enabled` (or equivalent dotted `tls.*`
notation), replacing bare underscore-joined names such as `tls_cert_file` and
`tls_enabled`. Apply this consistently to the affected messages near the TLS
checks while preserving their existing behavior.

In `@portals/ai-workspace/configs/config-template.toml`:
- Around line 231-233: Update the scope configuration default near the scope
setting so it never emits the literal “...” as an OAuth scope. Use the BFF’s
built-in full-scope fallback by making the template default empty, or replace it
with the complete valid scope list; preserve the documented behavior that
leaving APIP_AIW_OIDC_SCOPE unset requests the full ap:* set.

In `@portals/ai-workspace/vite.config.ts`:
- Around line 71-76: Update the Vite configuration around envPrefix so
APIP_AIW_OIDC_CLIENT_SECRET and any other secrets are not exposed through
import.meta.env. Keep only browser-safe AIW variables in the client-exposed
prefix, using a server-only namespace or explicit allowlist for secret
configuration while preserving the existing runtime naming contract for safe
values.

---

Nitpick comments:
In `@portals/ai-workspace/bff/internal/config/runtime_config.go`:
- Around line 67-79: Remove "auth_mode" from browserSafeKeys, since
buildRuntimeConfig always sets the corresponding runtime key from cfg.AuthMode
after processing the allowlist. Keep the existing forced auth_mode assignment
and all other allowlisted keys unchanged.
- Around line 28-53: Verify the configuration source for browserSafeKeys and
confirm moesif_app_api_key is always the publishable Moesif Application Id,
never a secret or management key; if the contract is ambiguous, rename the
configuration key and all consumers to an explicit publishable-app-id name and
update the allowlist accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 96946a97-3526-42d6-b36d-94287cf23f04

📥 Commits

Reviewing files that changed from the base of the PR and between 51fc39a and 23cd0e0.

⛔ Files ignored due to path filters (1)
  • portals/ai-workspace/bff/go.sum is excluded by !**/*.sum
📒 Files selected for processing (30)
  • docs/ai-workspace/authentication/asgardeo-setup.md
  • docs/ai-workspace/authentication/oidc-auth.md
  • docs/ai-workspace/configuration.md
  • docs/ai-workspace/features/secrets-management.md
  • portals/ai-workspace/.env.example
  • portals/ai-workspace/Dockerfile
  • portals/ai-workspace/Makefile
  • portals/ai-workspace/README.md
  • portals/ai-workspace/bff/go.mod
  • portals/ai-workspace/bff/internal/config/config.go
  • portals/ai-workspace/bff/internal/config/config_test.go
  • portals/ai-workspace/bff/internal/config/runtime_config.go
  • portals/ai-workspace/bff/internal/config/settings.go
  • portals/ai-workspace/bff/internal/config/shipped_config_test.go
  • portals/ai-workspace/bff/internal/config/toml_mapping.go
  • portals/ai-workspace/bff/internal/proxy/reverse_proxy_test.go
  • portals/ai-workspace/bff/internal/proxy/transport.go
  • portals/ai-workspace/bff/internal/server/composite_handlers_test.go
  • portals/ai-workspace/bff/main.go
  • portals/ai-workspace/configs/config-template.toml
  • portals/ai-workspace/configs/config.toml
  • portals/ai-workspace/docker-compose.yaml
  • portals/ai-workspace/production/README.md
  • portals/ai-workspace/src/apis/platformApis.ts
  • portals/ai-workspace/src/auth/logout.ts
  • portals/ai-workspace/src/clients/choreoApiClient.ts
  • portals/ai-workspace/src/config.env.ts
  • portals/ai-workspace/src/contexts/ChoreoUserContext.tsx
  • portals/ai-workspace/src/utils/logger.ts
  • portals/ai-workspace/vite.config.ts
💤 Files with no reviewable changes (1)
  • portals/ai-workspace/bff/internal/config/toml_mapping.go

Comment thread docs/ai-workspace/authentication/oidc-auth.md Outdated
Comment thread docs/ai-workspace/configuration.md Outdated
Comment thread portals/ai-workspace/bff/internal/config/settings.go Outdated
Comment thread portals/ai-workspace/bff/main.go Outdated
Comment thread portals/ai-workspace/configs/config-template.toml Outdated
Comment thread portals/ai-workspace/vite.config.ts Outdated
- Refined the environment variable override mechanism in the configuration documentation to clarify the use of interpolation tokens for sensitive values.
- Added BFF-side redirect URLs and client secret handling in the authentication setup documentation, ensuring clarity on their usage and security.
- Improved the error messages in the configuration loading logic for better clarity on required fields and conditions.
- Introduced a new TOML parser for the AI Workspace configuration, ensuring strict adherence to the expected format and enhancing error handling.
- Added tests for the new TOML parser to validate its functionality and robustness against various input scenarios.
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Validation Results

Dependency name: github.com/gorilla/websocket
Version: v1.5.4-0.20250319132907-e064f32e3674 (was v1.5.3)
Allowed range: >=v1.5.3
Approved: ✅ Yes

Dependency name: golang.org/x/crypto
Version: v0.54.0 (was v0.50.0)
Allowed range: >=v0.31.0
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

Comment on lines +134 to +138
# certificate is self-signed.
- APIP_AIW_PLATFORM_API_URL=https://platform-api:9243
- APIP_AIW_PLATFORM_API_TLS_SKIP_VERIFY=true
- APIP_DEMO_MODE=${APIP_DEMO_MODE:-true}
# ── OIDC — to delegate login to any OIDC provider, set auth_mode = "oidc" and

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all these configs from the dockerfile will be removed from the next effort and .env file mount will be used

@Thushani-Jayasekera
Thushani-Jayasekera merged commit 8fec15c into wso2:main Jul 14, 2026
11 checks passed
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.

3 participants