Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/unified-request-context-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@truefoundry/trueforge': minor
---

Unify request-scoped RequestContext across standalone, OIDC, and TrueFoundry auth. `/auth/me` now returns `{ tenant_id, subject, is_admin }` (OpenAPI/SDK regen deferred to CI).
49 changes: 35 additions & 14 deletions .github/fern/openapi/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1342,27 +1342,48 @@
},
"GetMeResponse": {
"properties": {
"email": {
"description": "User email from the ID token when connected; `\"default\"` when anonymous.",
"is_admin": {
"description": "Whether the caller has admin privileges.",
"type": "boolean"
},
"subject": {
"$ref": "#/components/schemas/GetMeSubject"
},
"tenant_id": {
"description": "Tenant scope for the authenticated caller.",
"type": "string"
}
},
"required": [
"tenant_id",
"subject",
"is_admin"
],
"type": "object"
},
"GetMeSubject": {
"properties": {
"display_name": {
"description": "Human-readable name for the caller.",
"type": "string"
},
"role": {
"description": "Caller role.",
"id": {
"description": "Stable subject identifier for the caller.",
"type": "string"
},
"type": {
"description": "Session kind: `default` when no valid OIDC session; `oidc-connected` after a successful browser login.",
"description": "Subject kind: interactive user or virtual account.",
"enum": [
"default",
"oidc-connected"
"user",
"virtualaccount"
],
"type": "string"
}
},
"required": [
"id",
"type",
"email",
"role"
"display_name"
],
"type": "object"
},
Expand Down Expand Up @@ -4823,14 +4844,14 @@
"securitySchemes": {
"BearerAuth": {
"bearerFormat": "JWT",
"description": "ID token (`Authorization: Bearer <id_token>`). Required on protected routes. Browser sessions may use the HttpOnly `id_token` cookie instead.",
"description": "Caller credential (`Authorization: Bearer <token>`). Required on protected routes when auth is enabled. Browser sessions may use the HttpOnly `id_token` or `accessToken` cookie instead.",
"scheme": "bearer",
"type": "http"
}
}
},
"info": {
"description": "HTTP API for the TrueForge agent server (`/api/v1`). Interactive docs are served at `/api/v1/docs` (OpenAPI JSON at `/api/v1/openapi.json`).\n\n**Authentication:** Standalone deployments (no OIDC) accept requests without credentials — middleware stamps a local default user. When OIDC is configured, protected routes require a valid `id_token` cookie or `Authorization: Bearer` ID token. There is no built-in API-key scheme; pass custom headers only if your reverse proxy or IdP layer requires them.\n\nCovers DB-backed sessions, the agent registry, settings catalogs, and model/MCP/skill/sandbox providers.",
"description": "HTTP API for the TrueForge agent server (`/api/v1`). Interactive docs are served at `/api/v1/docs` (OpenAPI JSON at `/api/v1/openapi.json`).\n\n**Authentication:** Standalone auth accepts requests without credentials — middleware stamps a local default user. When OIDC or TrueFoundry auth is configured, protected routes require a valid cookie or `Authorization: Bearer` token. There is no built-in API-key scheme; pass custom headers only if your reverse proxy or IdP layer requires them.\n\nCovers DB-backed sessions, the agent registry, settings catalogs, and model/MCP/skill/sandbox providers.",
"title": "TrueForge API",
"version": "0.2.0-rc.0"
},
Expand Down Expand Up @@ -5529,7 +5550,7 @@
},
"/api/v1/auth/me": {
"get": {
"description": "Returns the authenticated caller identity. When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` ID token (401 otherwise). When auth is disabled, returns the default identity.",
"description": "Returns the authenticated caller identity (`tenant_id`, `subject`, `is_admin`). When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` token (401 otherwise). When auth is disabled, returns the standalone default identity.",
"responses": {
"200": {
"content": {
Expand All @@ -5539,7 +5560,7 @@
}
}
},
"description": "Session type and identity for the current request."
"description": "Caller identity for the current request."
},
"401": {
"content": {
Expand All @@ -5549,7 +5570,7 @@
}
}
},
"description": "Auth is enabled and the request has no valid cookie or Bearer ID token."
"description": "Auth is enabled and the request has no valid cookie or Bearer token."
}
},
"summary": "Current session",
Expand Down
1 change: 1 addition & 0 deletions charts/trueforge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ configs:
key: client-secret
# optional claim overrides (defaults shown):
# userReferenceClaim: sub
# userDisplayNameClaim: name
# userRoleClaim: groups
# adminRoleValue: admin
# scopes: "openid,profile,email,groups"
Expand Down
1 change: 1 addition & 0 deletions charts/trueforge/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ fields, wires bundled Postgres/Redis, optional OIDC, then server.extraEnv.
{{- $env = append $env (dict "name" "OIDC_CLIENT_ID" "value" .Values.configs.oidc.clientId) -}}
{{- $env = append $env (include "trueforge.env.fromStringOrValueFrom" (dict "name" "OIDC_CLIENT_SECRET" "field" "configs.oidc.clientSecret" "value" .Values.configs.oidc.clientSecret) | fromJson) -}}
{{- $env = append $env (dict "name" "OIDC_USER_REFERENCE_CLAIM" "value" .Values.configs.oidc.userReferenceClaim) -}}
{{- $env = append $env (dict "name" "OIDC_USER_DISPLAY_NAME_CLAIM" "value" .Values.configs.oidc.userDisplayNameClaim) -}}
{{- $env = append $env (dict "name" "OIDC_USER_ROLE_CLAIM" "value" .Values.configs.oidc.userRoleClaim) -}}
{{- $env = append $env (dict "name" "OIDC_ADMIN_ROLE_VALUE" "value" .Values.configs.oidc.adminRoleValue) -}}
{{- $env = append $env (dict "name" "OIDC_SCOPES" "value" .Values.configs.oidc.scopes) -}}
Expand Down
1 change: 1 addition & 0 deletions charts/trueforge/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ configs:
# name: trueforge-oidc
# key: client-secret
userReferenceClaim: sub
userDisplayNameClaim: name
userRoleClaim: groups
adminRoleValue: admin
scopes: "openid,profile,email"
Expand Down
5 changes: 5 additions & 0 deletions docs/authentication/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ The default. Do **not** set any `OIDC_*` variables, run TrueForge as usual ([Qui
OIDC_CLIENT_ID=0oa...
OIDC_CLIENT_SECRET=...
OIDC_USER_REFERENCE_CLAIM=email
OIDC_USER_DISPLAY_NAME_CLAIM=name
OIDC_USER_ROLE_CLAIM=groups
OIDC_ADMIN_ROLE_VALUE=harness-admin
OIDC_SCOPES=openid,profile,email,groups
Expand Down Expand Up @@ -104,6 +105,7 @@ The default. Do **not** set any `OIDC_*` variables, run TrueForge as usual ([Qui
OIDC_CLIENT_ID=<application-client-id>
OIDC_CLIENT_SECRET=...
OIDC_USER_REFERENCE_CLAIM=email
OIDC_USER_DISPLAY_NAME_CLAIM=name
OIDC_USER_ROLE_CLAIM=groups
OIDC_ADMIN_ROLE_VALUE=<admin-group-object-id>
OIDC_SCOPES=openid,profile,email
Expand All @@ -124,6 +126,7 @@ The default. Do **not** set any `OIDC_*` variables, run TrueForge as usual ([Qui
| `OIDC_CLIENT_ID` | Yes | OIDC client ID |
| `OIDC_CLIENT_SECRET` | Yes | OIDC client secret |
| `OIDC_USER_REFERENCE_CLAIM` | No (default `sub`) | Claim used as the user id for session ownership |
| `OIDC_USER_DISPLAY_NAME_CLAIM` | No (default `name`) | Claim used as the user's display name |
| `OIDC_USER_ROLE_CLAIM` | No (default `groups`) | Claim inspected for admin membership |
| `OIDC_ADMIN_ROLE_VALUE` | No (default `admin`) | Exact string that grants `admin` (case-sensitive) |
| `OIDC_SCOPES` | No (default `openid,profile,email`) | Scopes for the authorize request |
Expand All @@ -141,6 +144,7 @@ The default. Do **not** set any `OIDC_*` variables, run TrueForge as usual ([Qui
OIDC_CLIENT_ID=0oa...
OIDC_CLIENT_SECRET=...
OIDC_USER_REFERENCE_CLAIM=email
OIDC_USER_DISPLAY_NAME_CLAIM=name
OIDC_USER_ROLE_CLAIM=groups
OIDC_ADMIN_ROLE_VALUE=harness-admin
OIDC_SCOPES=openid,profile,email,groups
Expand All @@ -167,6 +171,7 @@ The default. Do **not** set any `OIDC_*` variables, run TrueForge as usual ([Qui
name: trueforge-oidc
key: client-secret
userReferenceClaim: email
userDisplayNameClaim: name
userRoleClaim: groups
adminRoleValue: harness-admin
scopes: "openid,profile,email,groups"
Expand Down
49 changes: 35 additions & 14 deletions docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1342,27 +1342,48 @@
},
"GetMeResponse": {
"properties": {
"email": {
"description": "User email from the ID token when connected; `\"default\"` when anonymous.",
"is_admin": {
"description": "Whether the caller has admin privileges.",
"type": "boolean"
},
"subject": {
"$ref": "#/components/schemas/GetMeSubject"
},
"tenant_id": {
"description": "Tenant scope for the authenticated caller.",
"type": "string"
}
},
"required": [
"tenant_id",
"subject",
"is_admin"
],
"type": "object"
},
"GetMeSubject": {
"properties": {
"display_name": {
"description": "Human-readable name for the caller.",
"type": "string"
},
"role": {
"description": "Caller role.",
"id": {
"description": "Stable subject identifier for the caller.",
"type": "string"
},
"type": {
"description": "Session kind: `default` when no valid OIDC session; `oidc-connected` after a successful browser login.",
"description": "Subject kind: interactive user or virtual account.",
"enum": [
"default",
"oidc-connected"
"user",
"virtualaccount"
],
"type": "string"
}
},
"required": [
"id",
"type",
"email",
"role"
"display_name"
],
"type": "object"
},
Expand Down Expand Up @@ -4823,14 +4844,14 @@
"securitySchemes": {
"BearerAuth": {
"bearerFormat": "JWT",
"description": "ID token (`Authorization: Bearer <id_token>`). Required on protected routes. Browser sessions may use the HttpOnly `id_token` cookie instead.",
"description": "Caller credential (`Authorization: Bearer <token>`). Required on protected routes when auth is enabled. Browser sessions may use the HttpOnly `id_token` or `accessToken` cookie instead.",
"scheme": "bearer",
"type": "http"
}
}
},
"info": {
"description": "HTTP API for the TrueForge agent server (`/api/v1`). Interactive docs are served at `/api/v1/docs` (OpenAPI JSON at `/api/v1/openapi.json`).\n\n**Authentication:** Standalone deployments (no OIDC) accept requests without credentials — middleware stamps a local default user. When OIDC is configured, protected routes require a valid `id_token` cookie or `Authorization: Bearer` ID token. There is no built-in API-key scheme; pass custom headers only if your reverse proxy or IdP layer requires them.\n\nCovers DB-backed sessions, the agent registry, settings catalogs, and model/MCP/skill/sandbox providers.",
"description": "HTTP API for the TrueForge agent server (`/api/v1`). Interactive docs are served at `/api/v1/docs` (OpenAPI JSON at `/api/v1/openapi.json`).\n\n**Authentication:** Standalone auth accepts requests without credentials — middleware stamps a local default user. When OIDC or TrueFoundry auth is configured, protected routes require a valid cookie or `Authorization: Bearer` token. There is no built-in API-key scheme; pass custom headers only if your reverse proxy or IdP layer requires them.\n\nCovers DB-backed sessions, the agent registry, settings catalogs, and model/MCP/skill/sandbox providers.",
"title": "TrueForge API",
"version": "0.2.0-rc.0"
},
Expand Down Expand Up @@ -5529,7 +5550,7 @@
},
"/api/v1/auth/me": {
"get": {
"description": "Returns the authenticated caller identity. When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` ID token (401 otherwise). When auth is disabled, returns the default identity.",
"description": "Returns the authenticated caller identity (`tenant_id`, `subject`, `is_admin`). When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` token (401 otherwise). When auth is disabled, returns the standalone default identity.",
"responses": {
"200": {
"content": {
Expand All @@ -5539,7 +5560,7 @@
}
}
},
"description": "Session type and identity for the current request."
"description": "Caller identity for the current request."
},
"401": {
"content": {
Expand All @@ -5549,7 +5570,7 @@
}
}
},
"description": "Auth is enabled and the request has no valid cookie or Bearer ID token."
"description": "Auth is enabled and the request has no valid cookie or Bearer token."
}
},
"summary": "Current session",
Expand Down
2 changes: 1 addition & 1 deletion packages/trueforge-sdk/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ await client.agents.delete("agent_id");
<dl>
<dd>

Returns the authenticated caller identity. When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` ID token (401 otherwise). When auth is disabled, returns the default identity.
Returns the authenticated caller identity (`tenant_id`, `subject`, `is_admin`). When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` token (401 otherwise). When auth is disabled, returns the standalone default identity.
</dd>
</dl>
</dd>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class AuthClient {
}

/**
* Returns the authenticated caller identity. When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` ID token (401 otherwise). When auth is disabled, returns the default identity.
* Returns the authenticated caller identity (`tenant_id`, `subject`, `is_admin`). When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` token (401 otherwise). When auth is disabled, returns the standalone default identity.
*
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
*
Expand Down
11 changes: 5 additions & 6 deletions packages/trueforge-sdk/src/api/types/GetMeResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
import type * as TrueForge from "../index.js";

export interface GetMeResponse {
/** User email from the ID token when connected; `"default"` when anonymous. */
email: string;
/** Caller role. */
role: string;
/** Session kind: `default` when no valid OIDC session; `oidc-connected` after a successful browser login. */
type: TrueForge.GetMeResponseType;
/** Whether the caller has admin privileges. */
isAdmin: boolean;
subject: TrueForge.GetMeSubject;
/** Tenant scope for the authenticated caller. */
tenantId: string;
}
8 changes: 0 additions & 8 deletions packages/trueforge-sdk/src/api/types/GetMeResponseType.ts

This file was deleted.

12 changes: 12 additions & 0 deletions packages/trueforge-sdk/src/api/types/GetMeSubject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This file was auto-generated by Fern from our API Definition.

import type * as TrueForge from "../index.js";

export interface GetMeSubject {
/** Human-readable name for the caller. */
displayName: string;
/** Stable subject identifier for the caller. */
id: string;
/** Subject kind: interactive user or virtual account. */
type: TrueForge.GetMeSubjectType;
}
8 changes: 8 additions & 0 deletions packages/trueforge-sdk/src/api/types/GetMeSubjectType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file was auto-generated by Fern from our API Definition.

/** Subject kind: interactive user or virtual account. */
export const GetMeSubjectType = {
User: "user",
Virtualaccount: "virtualaccount",
} as const;
export type GetMeSubjectType = (typeof GetMeSubjectType)[keyof typeof GetMeSubjectType];
3 changes: 2 additions & 1 deletion packages/trueforge-sdk/src/api/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export * from "./GetCapabilitiesResponse.js";
export * from "./GetMcpServerCatalogResponse.js";
export * from "./GetMcpServerResponse.js";
export * from "./GetMeResponse.js";
export * from "./GetMeResponseType.js";
export * from "./GetMeSubject.js";
export * from "./GetMeSubjectType.js";
export * from "./GetModelProviderCatalogResponse.js";
export * from "./GetModelProviderResponse.js";
export * from "./GetSandboxProviderCatalogResponse.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
import type * as TrueForge from "../../api/index.js";
import * as core from "../../core/index.js";
import type * as serializers from "../index.js";
import { GetMeResponseType } from "./GetMeResponseType.js";
import { GetMeSubject } from "./GetMeSubject.js";

export const GetMeResponse: core.serialization.ObjectSchema<serializers.GetMeResponse.Raw, TrueForge.GetMeResponse> =
core.serialization.object({
email: core.serialization.string(),
role: core.serialization.string(),
type: GetMeResponseType,
isAdmin: core.serialization.property("is_admin", core.serialization.boolean()),
subject: GetMeSubject,
tenantId: core.serialization.property("tenant_id", core.serialization.string()),
});

export declare namespace GetMeResponse {
export interface Raw {
email: string;
role: string;
type: GetMeResponseType.Raw;
is_admin: boolean;
subject: GetMeSubject.Raw;
tenant_id: string;
}
}

This file was deleted.

Loading
Loading