AGM (Admin UAA) is the user realm for the ShopSTAR3 admin plane. It owns all staff principal records, role grants, access restrictions, and external federation source configuration. It is the source of truth for who admin users are and what they are permitted to do — not for how they prove their identity.
The identity-service consumes AGM’s gRPC read API to resolve principals during authentication. Identity-service owns JWT issuance, JWT validation, and all external federation protocol flows (SAML, OAuth, LDAP). AGM does not issue or validate tokens.
flowchart LR
SPA([Admin SPA]) -->|REST management API| AGM[AGM]
IS[identity-service] -->|gRPC — resolve principal\nfetch restrictions\nMFA secret| AGM
AGM -->|store.provisioned\nstore.suspended| KF([Kafka])
KF --> SVC[all services]Boundary#
| Concern | Owner |
|---|---|
| Staff principal records | AGM |
| Role grants | AGM |
| Access restriction rules | AGM |
| Federation source configuration | AGM |
| Store master data | AGM |
| JWT issuance and validation | identity-service |
| SAML / OAuth / LDAP protocol flows | identity-service |
| Session and refresh token lifecycle | identity-service |
Principal Model#
AGM manages staff principals only. Customer identities are held by customer-service (profiles) and identity-service (auth).
CREATE TABLE principal (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
display_name VARCHAR(255),
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE'
CHECK (status IN ('ACTIVE', 'SUSPENDED', 'OFFBOARDED')),
mfa_enabled BOOLEAN NOT NULL DEFAULT TRUE,
mfa_method VARCHAR(8) NOT NULL DEFAULT 'TOTP'
CHECK (mfa_method IN ('TOTP', 'EMAIL')),
mfa_secret TEXT, -- TOTP secret, encrypted at rest; NULL for EMAIL method
source VARCHAR(16) NOT NULL DEFAULT 'LOCAL'
CHECK (source IN ('LOCAL', 'FEDERATED')),
federation_source_id UUID REFERENCES federation_source(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- In-DB credentials (LOCAL principals only)
CREATE TABLE credential (
principal_id UUID PRIMARY KEY REFERENCES principal(id) ON DELETE CASCADE,
password_hash TEXT NOT NULL, -- bcrypt
must_reset BOOLEAN NOT NULL DEFAULT FALSE,
last_changed TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Store-scoped role grants
CREATE TABLE role_grant (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
principal_id UUID NOT NULL REFERENCES principal(id) ON DELETE CASCADE,
store_id UUID NOT NULL,
role VARCHAR(64) NOT NULL,
granted_by UUID REFERENCES principal(id),
granted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (principal_id, store_id, role)
);Role grants are store-scoped. A staff member may hold different roles across different stores simultaneously — ADMIN on Store A and CATALOG_EDITOR on Store B. There are no global roles that implicitly span stores.
Access Restrictions#
Restriction rules are stored per principal or per role, optionally scoped to a specific store. The identity-service reads and enforces them at login time.
CREATE TABLE access_restriction (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
subject_type VARCHAR(16) NOT NULL CHECK (subject_type IN ('PRINCIPAL', 'ROLE')),
subject_id VARCHAR(128) NOT NULL, -- principal UUID or role name
store_id UUID, -- NULL = platform-wide
type VARCHAR(32) NOT NULL,
config JSONB NOT NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);| Restriction type | Config shape |
|---|---|
IP_ALLOWLIST | { "ranges": ["203.0.113.0/24"] } |
TIME_WINDOW | { "timezone": "America/Chicago", "windows": [{ "days": ["MON",...], "start": "08:00", "end": "18:00" }] } |
STORE_SCOPE | { "allowed_store_ids": ["uuid-1", "uuid-2"] } |
MFA_REQUIRED | { "enforce": true } |
Federation Sources#
AGM stores the connection parameters for each external identity provider. The identity-service uses these to execute the actual SSO protocol flows. Secrets (OIDC client secrets, LDAP bind passwords) are stored as Vault path references — AGM never holds them in plaintext.
CREATE TABLE federation_source (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(128) NOT NULL,
type VARCHAR(8) NOT NULL CHECK (type IN ('SAML', 'OIDC', 'LDAP')),
store_id UUID, -- NULL = platform-wide
config JSONB NOT NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);SAML config shape#
{
"idp_metadata_url": "https://idp.example.com/metadata.xml",
"sp_entity_id": "https://agm.ss3.internal/saml/sp",
"attribute_mapping": {
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"display_name": "http://schemas.microsoft.com/identity/claims/displayname"
},
"sign_requests": true,
"want_assertions_signed": true
}OIDC config shape#
{
"discovery_url": "https://accounts.google.com/.well-known/openid-configuration",
"client_id": "...",
"client_secret_ref": "ss3/kv/agm/oidc/{source_id}/client_secret",
"scopes": ["openid", "email", "profile"],
"attribute_mapping": { "email": "email", "display_name": "name" }
}LDAP config shape#
{
"host": "ldap.corp.example.com",
"port": 636,
"use_tls": true,
"bind_dn": "cn=agm-svc,dc=example,dc=com",
"bind_password_ref": "ss3/kv/agm/ldap/{source_id}/bind_password",
"user_search_base": "ou=users,dc=example,dc=com",
"user_search_filter": "(mail={email})",
"attribute_mapping": { "email": "mail", "display_name": "cn" }
}Identity-Service Integration#
The identity-service calls AGM via gRPC to resolve principals and fetch restrictions during authentication. AGM does not call the identity-service.
service AgmReaderService {
rpc ResolvePrincipal(ResolvePrincipalRequest) returns (PrincipalResponse);
rpc ResolveOrProvisionFederated(FederatedIdentityRequest) returns (PrincipalResponse);
rpc GetRestrictions(GetRestrictionsRequest) returns (RestrictionsResponse);
// Returns method (TOTP|EMAIL) and secret for TOTP; triggers email OTP dispatch for EMAIL
rpc GetMfaChallenge(GetMfaChallengeRequest) returns (MfaChallengeResponse);
rpc GetFederationSource(GetFederationSourceRequest) returns (FederationSourceResponse);
}PrincipalResponse carries id, status, mfa_enabled, source, and the full set of store-scoped role grants. The identity-service uses the role grants to populate the roles claim in the platform JWT.
JIT Provisioning#
When a federated login arrives for an unknown email address, the identity-service calls ResolveOrProvisionFederated. AGM creates a principal record (source = FEDERATED) with no role grants and returns it to the identity-service to continue the auth flow. An admin must grant roles before the new user can access any store.
Management API#
AGM exposes a REST management API consumed by the admin SPA. All routes require a platform-level staff principal.
| Method | Route | Description |
|---|---|---|
GET | /admin/principals | List staff principals |
POST | /admin/principals | Create staff principal |
GET | /admin/principals/{id} | Get principal |
PATCH | /admin/principals/{id} | Update profile or status |
DELETE | /admin/principals/{id} | Offboard (soft-delete) |
POST | /admin/principals/{id}/reset-password | Force password reset |
GET | /admin/principals/{id}/roles | List role grants |
POST | /admin/principals/{id}/roles | Grant role on a store |
DELETE | /admin/principals/{id}/roles/{grantId} | Revoke role grant |
GET | /admin/principals/{id}/restrictions | List restrictions |
POST | /admin/principals/{id}/restrictions | Add restriction rule |
DELETE | /admin/principals/{id}/restrictions/{restrictionId} | Remove restriction |
GET | /admin/federation-sources | List federation sources |
POST | /admin/federation-sources | Create federation source |
PATCH | /admin/federation-sources/{id} | Update source config |
DELETE | /admin/federation-sources/{id} | Deactivate source |
Kafka Events#
AGM consumes store.provisioned from store-service to register new store UUIDs as valid targets for role grants and restrictions. AGM does not publish store lifecycle events.
| Topic | Direction | Purpose |
|---|---|---|
store.provisioned | Consumed from store-service | Register store UUID as a valid role grant target |
Vault Configuration#
| Key path | Purpose |
|---|---|
ss3/kv/agm/db.url | PostgreSQL JDBC URL |
ss3/kv/agm/db.username | Database username |
ss3/kv/agm/db.password | Database password |
ss3/kv/agm/mfa.encryption_key | AES key for TOTP secret encryption at rest |
ss3/kv/agm/oidc/{source_id}/client_secret | OIDC client secret per federation source |
ss3/kv/agm/ldap/{source_id}/bind_password | LDAP bind password per federation source |
ss3/kv/shared/ | Shared Kafka bootstrap and OTel config |