Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 5 additions & 22 deletions portals/ai-workspace/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,7 @@ dist: clean-dist ## Build standalone AI Workspace + Platform API distribution zi
@echo "Building distribution $(DIST_NAME)..."
@echo " optional developer-portal image pinned to $(DOCKER_REGISTRY)/developer-portal:$(DEVPORTAL_VERSION)"
@mkdir -p $(DIST_DIR)/configs $(DIST_DIR)/scripts $(DIST_DIR)/resources/certificates $(DIST_DIR)/resources/platform-api/db-scripts
# Stage the Platform API source configs (active + template) into temp files so
# the merge below can fold them into the unified config.toml/config-template.toml.
# Both the tag and working-tree branches produce the same two temp files.
# Stage platform-api configs + db-scripts (from tag or working tree) for the merge below.
ifeq ($(PLATFORM_API_FROM_TAG),true)
@echo "Fetching platform-api db-scripts from tag $(PLATFORM_API_TAG)..."
@tag="$(PLATFORM_API_TAG)"; dest="$(DIST_DIR)/resources/platform-api/db-scripts"; \
Expand All @@ -327,27 +325,16 @@ else
@cp ../../platform-api/config/config.toml $(DIST_DIR)/configs/.pa-config.toml
@cp ../../platform-api/resources/roles.yaml $(DIST_DIR)/resources/roles.yaml
endif
# The merge below assumes each component's config is namespaced under its own
# root table. Platform API versions predating the unified config ship
# un-namespaced tables ([database] rather than [platform_api.database]), which
# would merge into a silently broken file — fail loudly on that instead.
# Require a [platform_api] root table — pre-unified configs would merge into a broken file.
@if ! grep -q '^\[platform_api' $(DIST_DIR)/configs/.pa-config.toml; then \
echo "Error: the platform-api config for $(PLATFORM_API_VERSION) has no [platform_api] root table." >&2; \
echo "That version predates the unified multi-component config and cannot be bundled — pick a newer PLATFORM_API_VERSION." >&2; \
exit 1; \
fi
# Unified active config.toml: two sections ([ai_workspace] + [platform_api]),
# the same file mounted into both services (each reads only its own table).
# The optional Developer Portal is opt-in (see README), so its section is NOT
# added here — a user enabling that profile appends [developer_portal] itself,
# copying it from the template below.
# Merge ai-workspace + platform-api into one config.toml — each service reads only its own root table.
$(call merge_config,configs/config.toml,$(DIST_DIR)/configs/.pa-config.toml,$(DIST_DIR)/configs/config.toml)
# Unified config-template.toml (the full reference): the same two sections PLUS
# the optional Developer Portal template appended at the bottom, so a user
# enabling that profile has its [developer_portal] settings documented to copy.
$(call merge_config,configs/config-template.toml,$(DIST_DIR)/configs/.pa-config-template.toml,$(DIST_DIR)/configs/config-template.toml)
# Sourced from the developer-portal release tag when a version was supplied, so
# the documented settings match the pinned image.
# Developer Portal is opt-in: append its template to config-template.toml only, not the active config.
ifeq ($(DEVPORTAL_FROM_TAG),true)
$(call stage_optional_template,$(DEVPORTAL_TAG),portals/developer-portal/configs/config-template.toml,../developer-portal/configs/config-template.toml,$(DIST_DIR)/configs/.dp-config-template.toml)
else
Expand All @@ -362,11 +349,7 @@ endif
> $(DIST_DIR)/.gitignore
@cp setup.sh $(DIST_DIR)/scripts/setup.sh
@chmod +x $(DIST_DIR)/scripts/setup.sh
# Repoint the platform-api mount at the single merged file (the source compose
# mounts the monorepo's ../../platform-api/config/config.toml); the ai-workspace
# mount already points at ./configs/config.toml, so both now share one file.
# The trailing ':' anchors the match to the volume mapping so comments that
# mention the source path are left untouched.
# Point the platform-api mount at the merged config so both containers share one file.
@sed 's#\.\./\.\./platform-api/config/config\.toml:#./configs/config.toml:#' \
docker-compose.yaml > $(DIST_DIR)/docker-compose.yaml
@cp distribution/README.md $(DIST_DIR)/README.md
Expand Down
27 changes: 5 additions & 22 deletions portals/developer-portal/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,7 @@ dist: clean-dist ## Build standalone developer portal distribution zip
@mkdir -p $(DIST_DIR)/resources/samples
@cp -R samples/apis $(DIST_DIR)/resources/samples/
@cp -R samples/mcps $(DIST_DIR)/resources/samples/
# Stage the Platform API source configs (active + template) and db-scripts into
# the staging dir so the merge below can fold them into the unified config files.
# Both the tag and working-tree branches produce the same two temp configs.
# Stage platform-api configs + db-scripts (from tag or working tree) for the merge below.
ifeq ($(PLATFORM_API_FROM_TAG),true)
@echo "Fetching platform-api configs and db-scripts from tag $(PLATFORM_API_TAG)..."
@tag="$(PLATFORM_API_TAG)"; dest="$(DIST_DIR)/resources/platform-api/db-scripts"; \
Expand All @@ -291,39 +289,24 @@ else
@cp ../../platform-api/config/config.toml $(DIST_DIR)/configs/.pa-config.toml
@cp ../../platform-api/config/config-template.toml $(DIST_DIR)/configs/.pa-config-template.toml
endif
# The merge below assumes each component's config is namespaced under its own
# root table. Platform API versions predating the unified config ship
# un-namespaced tables ([database] rather than [platform_api.database]), which
# would merge into a silently broken file — fail loudly on that instead.
# Require a [platform_api] root table — pre-unified configs would merge into a broken file.
@if ! grep -q '^\[platform_api' $(DIST_DIR)/configs/.pa-config.toml; then \
echo "Error: the platform-api config for $(PLATFORM_API_VERSION) has no [platform_api] root table." >&2; \
echo "That version predates the unified multi-component config and cannot be bundled — pick a newer PLATFORM_API_VERSION." >&2; \
exit 1; \
fi
# Unified config: the standalone zip ships ONE config.toml carrying both the
# [developer_portal] and [platform_api] sections (portal on top, platform-api
# below). Each service reads only its own root table and ignores the other's,
# so the same file is mounted into both containers. Merged from the monorepo's
# two per-component source configs — the zip has no platform-api/ sibling.
# Merge both components into one config.toml — each service reads only its own root table.
$(call merge_config,configs/config.toml,$(DIST_DIR)/configs/.pa-config.toml,$(DIST_DIR)/configs/config.toml)
$(call merge_config,configs/config-template.toml,$(DIST_DIR)/configs/.pa-config-template.toml,$(DIST_DIR)/configs/config-template.toml)
# AI Workspace is an opt-in Compose profile (see README) — its section is NOT
# added to the active config.toml, but its template is appended at the bottom
# of config-template.toml so a user enabling the profile has the [ai_workspace]
# settings documented to copy. Sourced from the ai-workspace release tag when a
# version was supplied, so the documented settings match the pinned image.
# AI Workspace is opt-in: append its template to config-template.toml only, not the active config.
ifeq ($(AIW_FROM_TAG),true)
$(call stage_optional_template,$(AIW_TAG),portals/ai-workspace/configs/config-template.toml,../ai-workspace/configs/config-template.toml,$(DIST_DIR)/configs/.aiw-config-template.toml)
else
@cp ../ai-workspace/configs/config-template.toml $(DIST_DIR)/configs/.aiw-config-template.toml
endif
$(call append_section,AI WORKSPACE (optional),$(DIST_DIR)/configs/.aiw-config-template.toml,$(DIST_DIR)/configs/config-template.toml)
@rm -f $(DIST_DIR)/configs/.pa-config.toml $(DIST_DIR)/configs/.pa-config-template.toml $(DIST_DIR)/configs/.aiw-config-template.toml
# Repoint the platform-api mount at the single merged file (the source compose
# mounts the monorepo's ../../platform-api/config/config.toml); the devportal
# mount already points at ./configs/config.toml, so both now share one file.
# The trailing ':' anchors the match to the volume mapping so comments that
# mention the source path are left untouched.
# Point the platform-api mount at the merged config so both containers share one file.
@sed 's#\.\./\.\./platform-api/config/config\.toml:#./configs/config.toml:#' \
docker-compose.yaml > $(DIST_DIR)/docker-compose.yaml
@cp distribution/README.md $(DIST_DIR)/README.md
Expand Down
39 changes: 24 additions & 15 deletions portals/developer-portal/src/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const { verifyPlatformJwtClaims, decodePlatformJwtClaims } = require('../utils/p
const { accessTokenPresent, refreshAccessToken, verifyWithCertificate, resolveOrgIdp } = require('../utils/tokenUtil');
const orgDao = require('../dao/organizationDao');
const userIdpReferenceDao = require('../dao/userIdpReferenceDao');
const { getNestedClaim } = require('./passportConfig');
const { NotFoundError } = require('../utils/errors/customErrors');
const userOrganizationMappingDao = require('../dao/userOrganizationMappingDao');

Expand Down Expand Up @@ -256,17 +257,21 @@ async function authResolver(req, res, next) {
// dp:* scopes in the OIDC scope config. Set preauthorized to bypass the per-operation
// scope check for session users (same as API key and mTLS paths).
if (req.isAuthenticated && req.isAuthenticated() && req.user?.grantedScopes !== undefined && config.auth.mode === 'idp') {
const orgIDClaim = config.auth.idp?.claims?.orgId;
if (orgIDClaim) {
const sessionOrgClaim = req.user[constants.ROLES.ORGANIZATION_CLAIM];
if (!sessionOrgClaim) {
const err = new Error('Missing organization claim in session');
err.status = 403;
return next(err);
}
const orgErr = await resolveOrgFromClaim(req, sessionOrgClaim);
if (orgErr) return next(orgErr);
// The session's org claim is populated at login from
// config.auth.claimMappings.organization (see passportConfig) and stored
// under ORGANIZATION_CLAIM. Resolve req.orgId from it directly — do NOT
// gate on config.auth.idp.claims.orgId, which has no default and is unset
// in typical IDP configs, which would leave req.orgId empty and break every
// tenant-scoped operation (reads return the wrong scope; writes fail the
// org_uuid foreign key). Fail closed when no org claim is present.
const sessionOrgClaim = req.user[constants.ROLES.ORGANIZATION_CLAIM];
if (!sessionOrgClaim) {
const err = new Error('Missing organization claim in session');
err.status = 403;
return next(err);
}
const orgErr = await resolveOrgFromClaim(req, sessionOrgClaim);
if (orgErr) return next(orgErr);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const rawSub = req.user[constants.USER_ID];
const userUuid = await resolveUserUuid(req, rawSub);
req[constants.USER_ID] = userUuid;
Expand All @@ -290,11 +295,15 @@ async function authResolver(req, res, next) {
return next(err);
}
const decoded = safeDecodeJwt(req.user?.[constants.ACCESS_TOKEN] || token) || {};
// Resolve org UUID from the token's org claim (IDP_REF_ID).
// Only in IDP mode — local-auth and platform-JWT tokens carry no org claim.
const orgIDClaim = config.auth.idp?.claims?.orgId;
if (config.auth.mode === 'idp' && orgIDClaim) {
const tokenOrgClaim = decoded[orgIDClaim];
// Resolve org UUID from the token's org claim. Use the same claim
// mapping login uses (config.auth.claimMappings.organization) rather
// than config.auth.idp.claims.orgId, which has no default and is
// typically unset — gating on it left req.orgId empty and broke every
// tenant-scoped operation. Only in IDP mode — local-auth and
// platform-JWT tokens carry no org claim.
if (config.auth.mode === 'idp') {
const orgClaimKey = config.auth.claimMappings?.organization;
const tokenOrgClaim = (orgClaimKey ? getNestedClaim(decoded, orgClaimKey) : undefined) || decoded.org_handle;
if (!tokenOrgClaim) {
const err = new Error('Missing organization claim in token');
err.status = 403;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,5 +179,5 @@ function configurePassport(SERVER_ID) {
});
}

module.exports = { configurePassport };
module.exports = { configurePassport, getNestedClaim };

Loading