Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ba11b1a
feat(dashboard-api): add OIDC user bootstrap endpoint
ben-fornefeld May 23, 2026
f1194de
fix(dashboard-api): generate internal ids for OIDC bootstrap
ben-fornefeld May 23, 2026
145d6bc
fix(dashboard-api): keep profile name separate from team naming
ben-fornefeld May 24, 2026
6be72e7
fix(dashboard-api): serialize OIDC bootstrap identity claim
ben-fornefeld May 25, 2026
c0511c8
fix(dashboard-api): satisfy perfsprint lint in bootstrap handler
ben-fornefeld May 25, 2026
993a842
docs(dashboard-api): clarify Supabase vs OIDC bootstrap handlers
ben-fornefeld May 25, 2026
04c74aa
fix(dashboard-api): make oidc_user_name optional in bootstrap schema
ben-fornefeld May 25, 2026
f21a49f
feat(dashboard-api): add ory user profile provider
ben-fornefeld May 26, 2026
2a4264e
feat(dashboard-api): require oidc_issuer in bootstrap and allow-list it
ben-fornefeld May 26, 2026
cf55a5a
refactor(dashboard-api): decouple ory userprofile issuer from auth pr…
ben-fornefeld May 26, 2026
f16a246
feat(dashboard-api): default ORY_ISSUER_URL to ORY_SDK_URL when unset
ben-fornefeld May 26, 2026
bcd9b52
fix(dashboard-api): align ORY_ISSUER_URL with auth-provider issuer
ben-fornefeld May 26, 2026
21f1bf5
Merge remote-tracking branch 'origin/main' into full-stack
ben-fornefeld May 26, 2026
7a4f6ea
fix(dashboard-api): close ory response bodies and align lint nits
ben-fornefeld May 26, 2026
912eb1a
feat(iac): wire ORY/USER_PROFILE_PROVIDER env into dashboard-api job
ben-fornefeld May 26, 2026
54b7797
fix(dashboard-api): align bootstrap issuer check with Ory resolver
ben-fornefeld May 26, 2026
9a38969
fix(dashboard-api): resolve Ory identities on the primary
ben-fornefeld May 26, 2026
cd142a9
feat(dashboard-api): resolve profile_picture_url from ory traits
ben-fornefeld May 26, 2026
0c0ff6a
docs(dashboard-api): add Ory Kratos identity schema fixture
ben-fornefeld May 26, 2026
9fb896e
refactor(dashboard-api): rename ory profile picture trait to picture
ben-fornefeld May 26, 2026
de7e682
feat(dashboard-api): expose name, picture, providers on team members
ben-fornefeld May 26, 2026
ef04269
Merge remote-tracking branch 'origin/main' into full-stack
ben-fornefeld May 27, 2026
5304e8c
fix(auth): silence per-scheme misses during OR-of-AND negotiation
ben-fornefeld May 28, 2026
1b4afae
test(dashboard-api): guard sibling static + param admin/users routes
ben-fornefeld May 28, 2026
ad40cb0
test(dashboard-api): satisfy golangci-lint in route conflict test
ben-fornefeld May 28, 2026
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
13 changes: 13 additions & 0 deletions .env.gcp.template
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ PERSISTENT_VOLUME_TYPES=
# Client proxy OIDC issuer for edge gRPC autoresume auth.
CLIENT_PROXY_OIDC_ISSUER_URL=

# Dashboard API user profile resolver (default: supabase)
# One of: supabase, ory, supabase-ory-fallback
USER_PROFILE_PROVIDER=
# Ory Network admin SDK URL (required when USER_PROFILE_PROVIDER uses ory)
# e.g. https://<project-slug>.projects.oryapis.com
ORY_SDK_URL=
# Ory OIDC issuer URL (must match an AUTH_PROVIDER_CONFIG.jwt[*].issuer.url;
# defaults to the configured single JWT issuer if unset). Not the SDK URL.
ORY_ISSUER_URL=
# Ory Project API token: set the secret value out-of-band via Google Secret
# Manager on `<prefix>ory-project-api-token` (the secret resource is managed
# here with a placeholder so terraform doesn't overwrite operator updates).

# Sandbox firewall: comma-separated CIDRs to allow through the private-range deny list
# ALLOW_SANDBOX_INTERNAL_CIDRS=

Expand Down
11 changes: 9 additions & 2 deletions iac/modules/job-dashboard-api/main.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
locals {
base_env = {
ory_env = merge(
var.user_profile_provider != "" ? { USER_PROFILE_PROVIDER = var.user_profile_provider } : {},
var.ory_sdk_url != "" ? { ORY_SDK_URL = var.ory_sdk_url } : {},
var.ory_issuer_url != "" ? { ORY_ISSUER_URL = var.ory_issuer_url } : {},
var.ory_project_api_token != "" ? { ORY_PROJECT_API_TOKEN = var.ory_project_api_token } : {},
)

base_env = merge({
GIN_MODE = "release"
ENVIRONMENT = var.environment
ADMIN_TOKEN = var.admin_token
Expand All @@ -19,7 +26,7 @@ locals {
BILLING_SERVER_API_TOKEN = var.billing_server_api_token
OTEL_COLLECTOR_GRPC_ENDPOINT = "localhost:${var.otel_collector_grpc_port}"
LOGS_COLLECTOR_ADDRESS = "http://localhost:${var.logs_proxy_port.port}"
}
}, local.ory_env)
}

resource "nomad_job" "dashboard_api" {
Expand Down
21 changes: 21 additions & 0 deletions iac/modules/job-dashboard-api/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,27 @@ variable "billing_server_api_token" {
default = ""
}

variable "user_profile_provider" {
type = string
default = ""
}

variable "ory_sdk_url" {
type = string
default = ""
}

variable "ory_issuer_url" {
type = string
default = ""
}

variable "ory_project_api_token" {
type = string
sensitive = true
default = ""
}

variable "logs_proxy_port" {
type = object({
name = string
Expand Down
3 changes: 3 additions & 0 deletions iac/provider-gcp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ tf_vars := \
$(call tfvar, AUTH_DB_MAX_OPEN_CONNECTIONS) \
$(call tfvar, AUTH_DB_MIN_IDLE_CONNECTIONS) \
$(call tfvar, CLIENT_PROXY_OIDC_ISSUER_URL) \
$(call tfvar, USER_PROFILE_PROVIDER) \
$(call tfvar, ORY_SDK_URL) \
$(call tfvar, ORY_ISSUER_URL) \
$(call tfvar, GCS_GRPC_CONNECTION_POOL_SIZE) \
$(call tfvar, ADDITIONAL_API_PATHS_HANDLED_BY_INGRESS) \
$(call tfvar, ANYWHERE_CACHE_ENABLED)
Expand Down
4 changes: 4 additions & 0 deletions iac/provider-gcp/init/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ output "posthog_api_key_secret_name" {
value = google_secret_manager_secret_version.posthog_api_key.secret
}

output "ory_project_api_token_secret_name" {
value = google_secret_manager_secret_version.ory_project_api_token.secret
}

output "supabase_jwt_secret_name" {
value = google_secret_manager_secret_version.supabase_jwt_secrets.secret
}
Expand Down
20 changes: 20 additions & 0 deletions iac/provider-gcp/init/secrets.tf
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,26 @@ resource "google_secret_manager_secret_version" "posthog_api_key" {
}


resource "google_secret_manager_secret" "ory_project_api_token" {
secret_id = "${var.prefix}ory-project-api-token"

replication {
auto {}
}

depends_on = [time_sleep.secrets_api_wait_60_seconds]
}

resource "google_secret_manager_secret_version" "ory_project_api_token" {
secret = google_secret_manager_secret.ory_project_api_token.name
secret_data = " "

lifecycle {
ignore_changes = [secret_data]
}
}


resource "google_secret_manager_secret" "redis_cluster_url" {
secret_id = "${var.prefix}redis-cluster-url"

Expand Down
4 changes: 4 additions & 0 deletions iac/provider-gcp/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,10 @@ module "nomad" {
dashboard_api_count = var.dashboard_api_count
dashboard_api_admin_token_secret_name = module.init.dashboard_api_admin_token_secret_name
supabase_db_connection_string_secret_version = module.init.supabase_db_connection_string_secret_version
user_profile_provider = var.user_profile_provider
ory_sdk_url = var.ory_sdk_url
ory_issuer_url = var.ory_issuer_url
ory_project_api_token_secret_name = module.init.ory_project_api_token_secret_name

# Docker reverse proxy
docker_reverse_proxy_port = var.docker_reverse_proxy_port
Expand Down
8 changes: 8 additions & 0 deletions iac/provider-gcp/nomad/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ data "google_secret_manager_secret_version" "dashboard_api_admin_token" {
secret = var.dashboard_api_admin_token_secret_name
}

data "google_secret_manager_secret_version" "ory_project_api_token" {
secret = var.ory_project_api_token_secret_name
}

data "google_secret_manager_secret_version" "supabase_db_connection_string" {
secret = var.supabase_db_connection_string_secret_version.secret
}
Expand Down Expand Up @@ -181,6 +185,10 @@ module "dashboard_api" {
redis_tls_ca_base64 = trimspace(data.google_secret_manager_secret_version.redis_tls_ca_base64.secret_data)
billing_server_url = local.dashboard_api_billing_server_url
billing_server_api_token = local.dashboard_api_billing_server_api_token
user_profile_provider = var.user_profile_provider
ory_sdk_url = var.ory_sdk_url
ory_issuer_url = var.ory_issuer_url
ory_project_api_token = trimspace(data.google_secret_manager_secret_version.ory_project_api_token.secret_data)

otel_collector_grpc_port = var.otel_collector_grpc_port
logs_proxy_port = var.logs_proxy_port
Expand Down
19 changes: 19 additions & 0 deletions iac/provider-gcp/nomad/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,25 @@ variable "dashboard_api_admin_token_secret_name" {
type = string
}

variable "user_profile_provider" {
type = string
default = ""
}

variable "ory_sdk_url" {
type = string
default = ""
}

variable "ory_issuer_url" {
type = string
default = ""
}

variable "ory_project_api_token_secret_name" {
type = string
}

variable "sandbox_access_token_hash_seed" {
type = string
}
Expand Down
18 changes: 18 additions & 0 deletions iac/provider-gcp/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,24 @@ variable "auth_provider_config" {
default = null
}

variable "user_profile_provider" {
type = string
default = ""
description = "Source for dashboard-api user profile lookups. One of: supabase, ory, supabase-ory-fallback. Empty leaves the binary default (supabase)."
}

variable "ory_sdk_url" {
type = string
default = ""
description = "Ory Network admin SDK URL (e.g. https://<slug>.projects.oryapis.com). Required when user_profile_provider uses ory."
}

variable "ory_issuer_url" {
type = string
default = ""
description = "Ory OIDC issuer URL used to namespace public.user_identities. Must match one of auth_provider_config.jwt[*].issuer.url; defaults to the single configured JWT issuer if unset."
}

variable "ingress_port" {
type = object({
name = string
Expand Down
9 changes: 5 additions & 4 deletions packages/auth/pkg/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,13 @@ func (a *commonAuthenticator[T]) getHeaderKeysFromRequest(req *http.Request) (st
func (a *commonAuthenticator[T]) Authenticate(ctx context.Context, ginCtx *gin.Context, input *openapi3filter.AuthenticationInput) error {
key, err := a.getHeaderKeysFromRequest(input.RequestValidationInput.Request)
if err != nil {
telemetry.ReportError(ctx,
"authorization header is missing",
err,
attribute.String("error.message", a.errorMessage),
telemetry.ReportEvent(ctx, "auth scheme skipped",
attribute.String("auth.scheme", a.schemeName),
attribute.String("auth.reason", err.Error()),
)

// stamp 401 so the ErrorHandler's max(writer, 400) resolves to 401
// when every security group fails. without this, auth failures become 400s.
ginCtx.Status(http.StatusUnauthorized)

return err
Expand Down
63 changes: 63 additions & 0 deletions packages/dashboard-api/fixtures/ory/identity.v1.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"$id": "https://schemas.e2b.dev/presets/kratos/identity.v1.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"title": "E-Mail",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
},
"webauthn": {
"identifier": true
},
"totp": {
"account_name": true
},
"code": {
"identifier": true,
"via": "email"
},
"passkey": {
"display_name": true
}
},
"recovery": {
"via": "email"
},
"verification": {
"via": "email"
},
"organizations": {
"matcher": "email_domain"
}
},
"maxLength": 320
},
"name": {
"type": "string",
"title": "Name",
"maxLength": 320
},
"picture": {
"type": "string",
"format": "uri",
"title": "Profile picture URL",
"maxLength": 2048
}
},
"required": [
"email"
],
"additionalProperties": false
}
}
}
2 changes: 2 additions & 0 deletions packages/dashboard-api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/jackc/pgx/v5 v5.9.2
github.com/oapi-codegen/gin-middleware v1.0.2
github.com/oapi-codegen/runtime v1.4.0
github.com/ory/client-go v1.22.42
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
go.opentelemetry.io/otel v1.43.0
Expand Down Expand Up @@ -160,6 +161,7 @@ require (
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions packages/dashboard-api/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading