key-cape/src/internal/domain/model.go
tegwick ac8ed65203
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 40s
Support opt-in MFA per browser client with authoritative enrollment checks
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 00:27:28 +02:00

95 lines
4.6 KiB
Go

// Package domain contains the canonical identity model for KeyCape.
//
// These types are the runtime authority for all user, group, client and MFA
// data: the server reads them, not spec/canonical-model.yaml. That YAML is the
// reviewed contract, and conformance_test.go holds the two together — adding a
// serialised field here without describing it there fails the build, and so
// does describing a field there that nothing here reads (KEY-WP-0017).
//
// Fields tagged `yaml:"-"` are outside the canonical model by construction:
// they are runtime policy or secret material, not identity data.
package domain
import "time"
// User is the canonical identity entity — source of truth for all user data.
type User struct {
ID string `yaml:"id" json:"id"`
Username string `yaml:"username" json:"username"`
DisplayName string `yaml:"displayName" json:"displayName"`
Email string `yaml:"email" json:"email"`
Enabled bool `yaml:"enabled" json:"enabled"`
Groups []string `yaml:"groups" json:"groups"`
Roles []string `yaml:"roles" json:"roles"`
// Tenant is the NetKingdom IAM Profile tenant claim value
// (e.g. "tenant:friendly:binky"), per net-kingdom/canon/standards/
// iam-profile_v0.3.md. Empty means "not yet assigned" -- token
// issuance falls back to the platform default (KEY-WP-0005-T01) rather
// than emitting an empty tenant claim, since the profile requires
// tenant on every token.
Tenant string `yaml:"tenant,omitempty" json:"tenant,omitempty"`
MFAEnrollment *MFAEnrollment `yaml:"mfaEnrollment,omitempty" json:"mfaEnrollment,omitempty"`
LDAPAttributes map[string]string `yaml:"ldapAttributes,omitempty" json:"ldapAttributes,omitempty"`
}
// Group is a named collection of users.
type Group struct {
ID string `yaml:"id" json:"id"`
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
Members []string `yaml:"members" json:"members"`
}
// Role is a named permission set.
type Role struct {
ID string `yaml:"id" json:"id"`
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
}
// Client is a registered OIDC client (static in v0.1 — no dynamic registration).
type Client struct {
ClientID string `yaml:"clientId" json:"clientId"`
DisplayName string `yaml:"displayName" json:"displayName"`
RedirectURIs []string `yaml:"redirectUris" json:"redirectUris"`
AllowedScopes []string `yaml:"allowedScopes" json:"allowedScopes"`
GrantTypes []string `yaml:"grantTypes" json:"grantTypes"`
ClientType string `yaml:"clientType" json:"clientType"` // "confidential" | "public"
SecretRef string `yaml:"secretRef,omitempty" json:"secretRef,omitempty"`
ClientSecret string `yaml:"-" json:"-"`
Audience string `yaml:"audience,omitempty" json:"audience,omitempty"`
ServiceSubject string `yaml:"serviceSubject,omitempty" json:"serviceSubject,omitempty"`
Tenant string `yaml:"tenant,omitempty" json:"tenant,omitempty"`
Roles []string `yaml:"roles,omitempty" json:"roles,omitempty"`
// TokenLifetime overrides the server default for this confidential client.
// It is internal runtime policy, not identity data serialized into tokens.
TokenLifetime time.Duration `yaml:"-" json:"-"`
// MFAOptional requires MFA for enrolled users, independently of RequireForAll.
MFAOptional bool `yaml:"mfaOptional,omitempty" json:"mfaOptional,omitempty"`
MFARequired *bool `yaml:"mfaRequired,omitempty" json:"mfaRequired,omitempty"`
RegistrationURL string `yaml:"registrationUrl,omitempty" json:"registrationUrl,omitempty"`
EnrollmentURL string `yaml:"enrollmentUrl,omitempty" json:"enrollmentUrl,omitempty"`
}
// Membership links a user to a group.
type Membership struct {
UserID string `yaml:"userId" json:"userId"`
GroupID string `yaml:"groupId" json:"groupId"`
}
// MFAEnrollment records that a user has enrolled MFA via privacyIDEA.
type MFAEnrollment struct {
UserID string `yaml:"userId" json:"userId"`
Provider string `yaml:"provider" json:"provider"` // "privacyidea"
State string `yaml:"state" json:"state"` // "enabled" | "disabled" | "pending"
EnrolledAt time.Time `yaml:"enrolledAt,omitempty" json:"enrolledAt,omitempty"`
}
// Directory is the full canonical identity directory snapshot.
// Used for provisioning, validation, and migration operations.
type Directory struct {
Users []User `yaml:"users" json:"users"`
Groups []Group `yaml:"groups" json:"groups"`
Roles []Role `yaml:"roles" json:"roles"`
Clients []Client `yaml:"clients" json:"clients"`
}