Skip to main content

Org Policy Config

This document describes the OrgPolicyConfigService: per-org structured policy configuration with five sections, Get/Update API, storage in org_policy_config, and sync of Auth & MFA and Device Trust sections to org_mfa_settings. The canonical proto is orgpolicyconfig/orgpolicyconfig.proto; the handler is internal/orgpolicyconfig/handler/grpc.go.

Audience: Developers building the policy UI, integrating with the policy API, or extending enforcement (session TTL, domain/action rules).

Overview

OrgPolicyConfigService provides GetOrgPolicyConfig and UpdateOrgPolicyConfig. The config is stored as JSON in the org_policy_config table (one row per org). The Auth & MFA and Device Trust sections are synced to org_mfa_settings on update so existing auth_service and OPA evaluator behavior stay aligned without code changes. All RPCs require org admin or owner (RequireOrgAdmin); request org_id must match the caller's context org.

Five sections

Defaults below are from internal/orgpolicyconfig/domain/config.go (DefaultAuthMfa, DefaultDeviceTrust, etc.).

1. Auth & MFA

Org-level when to require MFA and which methods are allowed.

FieldTypeDefaultDescription
mfa_requirementenum/stringnew_deviceWhen to require MFA: always, new_device, untrusted. Synced to org_mfa_settings.
allowed_mfa_methodsrepeated string["sms_otp"]Allowed methods (e.g. sms_otp). Stored; future use for step-up.
step_up_sensitive_actionsboolfalseRequire step-up MFA for sensitive actions. Stored for future.
step_up_policy_violationboolfalseRequire step-up on policy violation. Stored for future.

2. Device Trust

Device registration, auto-trust after MFA, and trust TTL.

FieldTypeDefaultDescription
device_registration_allowedbooltrueWhether devices can be registered. Stored for future.
auto_trust_after_mfabooltrueAfter MFA, mark device trusted. Synced to RegisterTrustAfterMFA.
max_trusted_devices_per_userint3200 = unlimited. Stored for future.
reverify_interval_daysint3230Trust TTL in days. Synced to TrustTTLDays.
admin_revoke_allowedbooltrueAdmins may revoke devices. Stored for future.

3. Session Management

Session lifetime, idle timeout, and concurrent-session limits. Stored for future enforcement.

FieldTypeDefaultDescription
session_max_ttlstring"24h"Max session lifetime (duration). Stored for future.
idle_timeoutstring"30m"Idle timeout (duration). Stored for future.
concurrent_session_limitint3200 = unlimited. Stored for future.
admin_forced_logoutbooltrueAdmins may force logout. Stored for future.
reauth_on_policy_changeboolfalseRequire reauth when policy changes. Stored for future.

4. Access Control

Allowed/blocked domains and default action for the browser. Enforced by the user browser via GetBrowserPolicy and CheckUrlAccess; see User Browser.

FieldTypeDefaultDescription
allowed_domainsrepeated string[]Allowed domains (browser).
blocked_domainsrepeated string[]Blocked domains.
wildcard_supportedboolfalseWhether wildcards are supported.
default_actionenum/stringallowallow or deny when no rule matches.

5. Action Restrictions

Allowed actions and read-only mode. Enforced by the user browser; see User Browser.

FieldTypeDefaultDescription
allowed_actionsrepeated stringnavigate, download, upload, copy_pasteAllowed actions.
read_only_modeboolfalseRestrict to read-only.

API

Request and response

  • GetOrgPolicyConfigRequest: org_id (optional; defaults to context org).
  • GetOrgPolicyConfigResponse: config (OrgPolicyConfig with five sections; nil sections are merged with defaults when returned).
  • UpdateOrgPolicyConfigRequest: org_id, config (full or partial; merged with defaults before save).
  • UpdateOrgPolicyConfigResponse: config (merged result).
  • RBAC: Caller must be org admin or owner (RequireOrgAdmin). If request org_id is empty, context org is used; if non-empty, it must equal context org.

GetOrgPolicyConfig behavior

If no row exists for the org, GetByOrgID returns nil. The handler then calls MergeWithDefaults(nil) and returns that merged config (all five sections filled with defaults). So the client always receives a full config.

UpdateOrgPolicyConfig behavior

The request may contain a full or partial config (any section may be omitted). The handler uses protoToDomain (partial OK), then Upsert with that domain config. Before returning and before sync, the handler uses MergeWithDefaults(config) so stored JSON and response are consistent with defaults for missing sections. Sync to org_mfa_settings runs only when config.AuthMfa != nil or config.DeviceTrust != nil, using the merged config.

Storage

  • Table: org_policy_configorg_id (VARCHAR PK, REFERENCES organizations), config_json (TEXT NOT NULL, default '{}'), updated_at (TIMESTAMPTZ NOT NULL). One row per org.
  • Domain: Structs and defaults in internal/orgpolicyconfig/domain/config.go; MergeWithDefaults fills nil sections.
  • Repository: GetByOrgID (returns nil when no row), Upsert (JSON marshal); see internal/orgpolicyconfig/repository.

Sync to org_mfa_settings

On UpdateOrgPolicyConfig, if auth_mfa or device_trust is present, the handler maps to OrgMFASettings and calls the org_mfa_settings repository Upsert via domainToOrgMFASettings. Only Auth & MFA and Device Trust are synced; Session Management, Access Control, and Action Restrictions are not written to org_mfa_settings.

Policy config fieldorg_mfa_settings columnNotes
auth_mfa.mfa_requirement = alwaysMFARequiredAlways = true, MFARequiredForNewDevice = false, MFARequiredForUntrusted = false
auth_mfa.mfa_requirement = new_deviceMFARequiredForNewDevice = true, MFARequiredForUntrusted = true, MFARequiredAlways = false
auth_mfa.mfa_requirement = untrustedMFARequiredForUntrusted = true, MFARequiredForNewDevice = false, MFARequiredAlways = false
device_trust.auto_trust_after_mfaRegisterTrustAfterMFA
device_trust.reverify_interval_daysTrustTTLDaysOnly applied when > 0

Other OrgMFASettings columns (e.g. when a section is absent) get default values in the handler (MFARequiredForNewDevice true, MFARequiredForUntrusted true, RegisterTrustAfterMFA true, TrustTTLDays 30). Consumers (auth_service, OPA evaluator) read from org_mfa_settings only; updating policy config keeps that table in sync so no code changes are needed there.

Default values reference

When a section is absent (nil), the handler and domain use these defaults (from domain/config.go):

SectionDefaults
Auth & MFAmfa_requirement = new_device, allowed_mfa_methods = ["sms_otp"], step_up_sensitive_actions = false, step_up_policy_violation = false
Device Trustdevice_registration_allowed = true, auto_trust_after_mfa = true, max_trusted_devices_per_user = 0, reverify_interval_days = 30, admin_revoke_allowed = true
Session Managementsession_max_ttl = "24h", idle_timeout = "30m", concurrent_session_limit = 0, admin_forced_logout = true, reauth_on_policy_change = false
Access Controlallowed_domains = [], blocked_domains = [], wildcard_supported = false, default_action = allow
Action Restrictionsallowed_actions = ["navigate", "download", "upload", "copy_paste"], read_only_mode = false

Dashboard and enforcement

Dashboard: The org admin dashboard Policy page loads config via GET and saves via PUT. Clients may need to normalize proto enums (e.g. mfa_requirement, default_action) when they are returned as strings.

Enforcement: Auth & MFA and Device Trust are effectively enforced today because they are synced to org_mfa_settings and used by auth_service and the policy engine. Session Management is still for future enforcement. Access Control and Action Restrictions are enforced by the user browser (see User Browser) via GetBrowserPolicy and CheckUrlAccess; org admins configure these in the Policy page.

Wiring

OrgPolicyConfigService is registered in internal/server/grpc.go. The handler is constructed in cmd/server/main.go with the org policy config repo, membershipRepo (for RequireOrgAdmin), and orgMfaSettingsRepo (for sync).