Files
upstream-ory-kratos/driver/config/config.go
upstream-mirror c60cc7ca75
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

1733 lines
71 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package config
import (
"bytes"
"cmp"
"context"
"crypto/fips140"
"crypto/sha512"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"testing"
"time"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/gofrs/uuid"
"github.com/inhies/go-bytesize"
"github.com/pkg/errors"
"github.com/rs/cors"
"github.com/stretchr/testify/require"
"github.com/ory/herodot"
"github.com/ory/jsonschema/v3"
"github.com/ory/jsonschema/v3/httploader"
"github.com/ory/kratos/embedx"
"github.com/ory/kratos/request"
"github.com/ory/x/configx"
"github.com/ory/x/contextx"
"github.com/ory/x/corsx"
"github.com/ory/x/crdbx"
"github.com/ory/x/httpx"
"github.com/ory/x/jsonschemax"
"github.com/ory/x/logrusx"
"github.com/ory/x/otelx"
"github.com/ory/x/region"
"github.com/ory/x/watcherx"
)
const (
DefaultIdentityTraitsSchemaID = "default"
DefaultBrowserReturnURL = "default_browser_return_url"
DefaultSQLiteMemoryDSN = "sqlite://file::memory:?_fk=true"
DefaultPasswordHashingAlgorithm = "argon2"
DefaultCipherAlgorithm = "noop"
UnknownVersion = "unknown version"
ViperKeyDSN = "dsn"
ViperKeyCourierSMTPURL = "courier.smtp.connection_uri"
ViperKeyCourierSMTPClientCertPath = "courier.smtp.client_cert_path"
ViperKeyCourierSMTPClientKeyPath = "courier.smtp.client_key_path"
ViperKeyCourierTemplatesPath = "courier.template_override_path"
ViperKeyCourierTemplatesRecoveryInvalidEmail = "courier.templates.recovery.invalid.email"
ViperKeyCourierTemplatesRecoveryValidEmail = "courier.templates.recovery.valid.email"
ViperKeyCourierTemplatesRecoveryCodeInvalidEmail = "courier.templates.recovery_code.invalid.email"
ViperKeyCourierTemplatesRecoveryCodeValidEmail = "courier.templates.recovery_code.valid.email"
ViperKeyCourierTemplatesVerificationInvalidEmail = "courier.templates.verification.invalid.email"
ViperKeyCourierTemplatesVerificationValidEmail = "courier.templates.verification.valid.email"
ViperKeyCourierTemplatesVerificationCodeInvalidEmail = "courier.templates.verification_code.invalid.email"
ViperKeyCourierTemplatesVerificationCodeValidEmail = "courier.templates.verification_code.valid.email"
ViperKeyCourierTemplatesVerificationCodeValidSMS = "courier.templates.verification_code.valid.sms"
ViperKeyCourierTemplatesRecoveryCodeValidSMS = "courier.templates.recovery_code.valid.sms"
ViperKeyCourierTemplatesLoginCodeValidSMS = "courier.templates.login_code.valid.sms"
ViperKeyCourierTemplatesRegistrationCodeValidSMS = "courier.templates.registration_code.valid.sms"
ViperKeyCourierTemplatesVerifiableAddressChangedEmail = "courier.templates.verifiable_address_changed.email"
ViperKeyCourierTemplatesVerifiableAddressChangedSMS = "courier.templates.verifiable_address_changed.sms"
ViperKeyCourierTemplatesAuthenticatorKeyAddedEmail = "courier.templates.authenticator_key_added.email"
ViperKeyCourierTemplatesAuthenticatorKeyAddedSMS = "courier.templates.authenticator_key_added.sms"
ViperKeyCourierDeliveryStrategy = "courier.delivery_strategy"
ViperKeyCourierHTTPRequestConfig = "courier.http.request_config"
ViperKeyCourierTemplatesLoginCodeValidEmail = "courier.templates.login_code.valid.email"
ViperKeyCourierTemplatesRegistrationCodeValidEmail = "courier.templates.registration_code.valid.email"
ViperKeyCourierSMTP = "courier.smtp"
ViperKeyCourierSMTPFrom = "courier.smtp.from_address"
ViperKeyCourierSMTPFromName = "courier.smtp.from_name"
ViperKeyCourierSMTPHeaders = "courier.smtp.headers"
ViperKeyCourierSMTPLocalName = "courier.smtp.local_name"
ViperKeyCourierMessageRetries = "courier.message_retries"
ViperKeyCourierWorkerPullCount = "courier.worker.pull_count"
ViperKeyCourierWorkerPullWait = "courier.worker.pull_wait"
ViperKeyCourierChannels = "courier.channels"
ViperKeySecretsDefault = "secrets.default"
ViperKeySecretsCookie = "secrets.cookie"
ViperKeySecretsCipher = "secrets.cipher"
ViperKeySecretsPagination = "secrets.pagination"
ViperKeyPublicBaseURL = "serve.public.base_url"
ViperKeyAdminBaseURL = "serve.admin.base_url"
ViperKeySessionLifespan = "session.lifespan"
ViperKeySessionSameSite = "session.cookie.same_site"
ViperKeySessionSecure = "session.cookie.secure"
ViperKeySessionDomain = "session.cookie.domain"
ViperKeySessionName = "session.cookie.name"
ViperKeySessionPath = "session.cookie.path"
ViperKeySessionPersistentCookie = "session.cookie.persistent"
ViperKeySessionTokenizerTemplates = "session.whoami.tokenizer.templates"
ViperKeySessionWhoAmIAAL = "session.whoami.required_aal"
ViperKeySessionWhoAmICaching = "feature_flags.cacheable_sessions"
ViperKeyFeatureFlagFasterSessionExtend = "feature_flags.faster_session_extend"
ViperKeySessionWhoAmICachingMaxAge = "feature_flags.cacheable_sessions_max_age"
ViperKeyUseContinueWithTransitions = "feature_flags.use_continue_with_transitions"
ViperKeyChooseRecoveryAddress = "feature_flags.choose_recovery_address"
ViperKeyUseLegacyShowVerificationUI = "feature_flags.legacy_continue_with_verification_ui"
ViperKeyLegacyOIDCRegistrationGroup = "feature_flags.legacy_oidc_registration_node_group"
ViperKeyUseLegacyRequireVerifiedLoginError = "feature_flags.legacy_require_verified_login_error"
ViperKeyLegacyAllowInsecureOrigins = "feature_flags.legacy_allow_insecure_origins"
ViperKeyRefreshLoginChooseAddress = "feature_flags.refresh_login_choose_address"
ViperKeySessionRefreshMinTimeLeft = "session.earliest_possible_extend"
ViperKeyCookieSameSite = "cookies.same_site"
ViperKeyCookieDomain = "cookies.domain"
ViperKeyCookiePath = "cookies.path"
ViperKeyCookieSecure = "cookies.secure"
ViperKeySelfServiceStrategyConfig = "selfservice.methods"
ViperKeySelfServiceBrowserDefaultReturnTo = "selfservice." + DefaultBrowserReturnURL
ViperKeyURLsAllowedReturnToDomains = "selfservice.allowed_return_urls"
ViperKeySelfServiceRegistrationEnabled = "selfservice.flows.registration.enabled"
ViperKeySelfServiceRegistrationLoginHints = "selfservice.flows.registration.login_hints"
ViperKeySelfServiceRegistrationEnableLegacyOneStep = "selfservice.flows.registration.enable_legacy_one_step"
ViperKeySelfServiceRegistrationFlowStyle = "selfservice.flows.registration.style"
ViperKeySelfServiceRegistrationUI = "selfservice.flows.registration.ui_url"
ViperKeySelfServiceRegistrationRequestLifespan = "selfservice.flows.registration.lifespan"
ViperKeySelfServiceRegistrationAfter = "selfservice.flows.registration.after"
ViperKeySelfServiceRegistrationBeforeHooks = "selfservice.flows.registration.before.hooks"
ViperKeySelfServiceLoginUI = "selfservice.flows.login.ui_url"
ViperKeySelfServiceLoginFlowStyle = "selfservice.flows.login.style"
ViperKeySecurityAccountEnumerationMitigate = "security.account_enumeration.mitigate"
ViperKeySecurityDisallowRefInIdentitySchemas = "security.disallow_ref_in_identity_schemas"
ViperKeySelfServiceLoginRequestLifespan = "selfservice.flows.login.lifespan"
ViperKeySelfServiceLoginAfter = "selfservice.flows.login.after"
ViperKeySelfServiceLoginBeforeHooks = "selfservice.flows.login.before.hooks"
ViperKeySelfServiceErrorUI = "selfservice.flows.error.ui_url"
ViperKeySelfServiceLogoutBrowserDefaultReturnTo = "selfservice.flows.logout.after." + DefaultBrowserReturnURL
ViperKeySelfServiceLogoutClearBrowserData = "selfservice.flows.logout.clear_browser_data"
ViperKeySelfServiceSettingsURL = "selfservice.flows.settings.ui_url"
ViperKeySelfServiceSettingsAfter = "selfservice.flows.settings.after"
ViperKeySelfServiceSettingsBeforeHooks = "selfservice.flows.settings.before.hooks"
ViperKeySelfServiceSettingsRequestLifespan = "selfservice.flows.settings.lifespan"
ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter = "selfservice.flows.settings.privileged_session_max_age"
ViperKeySelfServiceSettingsRequiredAAL = "selfservice.flows.settings.required_aal"
ViperKeySelfServiceRecoveryAfter = "selfservice.flows.recovery.after"
ViperKeySelfServiceRecoveryBeforeHooks = "selfservice.flows.recovery.before.hooks"
ViperKeySelfServiceRecoveryEnabled = "selfservice.flows.recovery.enabled"
ViperKeySelfServiceRecoveryUse = "selfservice.flows.recovery.use"
ViperKeySelfServiceRecoveryUI = "selfservice.flows.recovery.ui_url"
ViperKeySelfServiceRecoveryRequestLifespan = "selfservice.flows.recovery.lifespan"
ViperKeySelfServiceRecoveryBrowserDefaultReturnTo = "selfservice.flows.recovery.after." + DefaultBrowserReturnURL
ViperKeySelfServiceRecoveryNotifyUnknownRecipients = "selfservice.flows.recovery.notify_unknown_recipients"
ViperKeySelfServiceVerificationEnabled = "selfservice.flows.verification.enabled"
ViperKeySelfServiceVerificationUI = "selfservice.flows.verification.ui_url"
ViperKeySelfServiceVerificationRequestLifespan = "selfservice.flows.verification.lifespan"
ViperKeySelfServiceVerificationBrowserDefaultReturnTo = "selfservice.flows.verification.after." + DefaultBrowserReturnURL
ViperKeySelfServiceVerificationAfter = "selfservice.flows.verification.after"
ViperKeySelfServiceVerificationBeforeHooks = "selfservice.flows.verification.before.hooks"
ViperKeySelfServiceVerificationUse = "selfservice.flows.verification.use"
ViperKeySelfServiceVerificationNotifyUnknownRecipients = "selfservice.flows.verification.notify_unknown_recipients"
ViperKeyDefaultIdentitySchemaID = "identity.default_schema_id"
ViperKeyIdentitySchemas = "identity.schemas"
ViperKeyHasherAlgorithm = "hashers.algorithm"
ViperKeyHasherArgon2ConfigMemory = "hashers.argon2.memory"
ViperKeyHasherArgon2ConfigIterations = "hashers.argon2.iterations"
ViperKeyHasherArgon2ConfigParallelism = "hashers.argon2.parallelism"
ViperKeyHasherArgon2ConfigSaltLength = "hashers.argon2.salt_length"
ViperKeyHasherArgon2ConfigKeyLength = "hashers.argon2.key_length"
ViperKeyHasherArgon2ConfigExpectedDuration = "hashers.argon2.expected_duration"
ViperKeyHasherArgon2ConfigExpectedDeviation = "hashers.argon2.expected_deviation"
ViperKeyHasherArgon2ConfigDedicatedMemory = "hashers.argon2.dedicated_memory"
ViperKeyHasherBcryptCost = "hashers.bcrypt.cost"
ViperKeyCipherAlgorithm = "ciphers.algorithm"
ViperKeyDatabaseCleanupSleepTables = "database.cleanup.sleep.tables"
ViperKeyDatabaseCleanupBatchSize = "database.cleanup.batch_size"
ViperKeyLinkLifespan = "selfservice.methods.link.config.lifespan"
ViperKeyCodeLifespan = "selfservice.methods.code.config.lifespan"
ViperKeyCodeMaxSubmissions = "selfservice.methods.code.config.max_submissions"
ViperKeyCodeConfigMissingCredentialFallbackEnabled = "selfservice.methods.code.config.missing_credential_fallback_enabled"
ViperKeyPasswordHaveIBeenPwnedHost = "selfservice.methods.password.config.haveibeenpwned_host"
ViperKeyPasswordHaveIBeenPwnedEnabled = "selfservice.methods.password.config.haveibeenpwned_enabled"
ViperKeyPasswordMaxBreaches = "selfservice.methods.password.config.max_breaches"
ViperKeyPasswordMinLength = "selfservice.methods.password.config.min_password_length"
ViperKeyPasswordIdentifierSimilarityCheckEnabled = "selfservice.methods.password.config.identifier_similarity_check_enabled"
ViperKeyIgnoreNetworkErrors = "selfservice.methods.password.config.ignore_network_errors"
ViperKeyPasswordRegistrationProfileGroup = "selfservice.methods.password.config.password_profile_registration_node_group"
ViperKeyTOTPIssuer = "selfservice.methods.totp.config.issuer"
ViperKeyOIDCBaseRedirectURL = "selfservice.methods.oidc.config.base_redirect_uri"
ViperKeySAMLBaseRedirectURL = "selfservice.methods.saml.config.base_redirect_uri"
ViperKeyWebAuthnRPDisplayName = "selfservice.methods.webauthn.config.rp.display_name"
ViperKeyWebAuthnRPID = "selfservice.methods.webauthn.config.rp.id"
ViperKeyWebAuthnRPOrigin = "selfservice.methods.webauthn.config.rp.origin"
ViperKeyWebAuthnRPOrigins = "selfservice.methods.webauthn.config.rp.origins"
ViperKeyWebAuthnPasswordless = "selfservice.methods.webauthn.config.passwordless"
ViperKeyPasskeyEnabled = "selfservice.methods.passkey.enabled"
ViperKeyPasskeyRPDisplayName = "selfservice.methods.passkey.config.rp.display_name"
ViperKeyPasskeyRPID = "selfservice.methods.passkey.config.rp.id"
ViperKeyPasskeyRPOrigins = "selfservice.methods.passkey.config.rp.origins"
ViperKeyPasskeyAuthenticatorAttachment = "selfservice.methods.passkey.config.authenticator_selection.attachment"
ViperKeyPasskeyResidentKey = "selfservice.methods.passkey.config.authenticator_selection.resident_key"
ViperKeyPasskeyUserVerification = "selfservice.methods.passkey.config.authenticator_selection.user_verification"
ViperKeyPasskeyAttestationPreference = "selfservice.methods.passkey.config.attestation.preference"
ViperKeyPasskeyRegistrationTimeout = "selfservice.methods.passkey.config.timeouts.registration"
ViperKeyPasskeyLoginTimeout = "selfservice.methods.passkey.config.timeouts.login"
ViperKeyOrganizations = "selfservice.methods.b2b.config.organizations"
ViperKeyOAuth2ProviderURL = "oauth2_provider.url"
ViperKeyOAuth2ProviderHeader = "oauth2_provider.headers"
ViperKeyOAuth2ProviderOverrideReturnTo = "oauth2_provider.override_return_to"
ViperKeyClientHTTPNoPrivateIPRanges = "clients.http.disallow_private_ip_ranges"
ViperKeyClientHTTPPrivateIPExceptionURLs = "clients.http.private_ip_exception_urls"
ViperKeyClientSMTPNoPrivateIPRanges = "clients.smtp.disallow_private_ip_ranges"
ViperKeyWebhookHeaderAllowlist = "clients.web_hook.header_allowlist"
ViperKeyPreviewDefaultReadConsistencyLevel = "preview.default_read_consistency_level"
ViperKeyVersion = "version"
ViperKeyPasswordMigrationHook = "selfservice.methods.password.config.migrate_hook"
)
const (
HighestAvailableAAL = "highest_available"
Argon2DefaultMemory = 128 * bytesize.MB
Argon2DefaultIterations uint32 = 1
Argon2DefaultSaltLength uint32 = 16
Argon2DefaultKeyLength uint32 = 32
Argon2DefaultDuration = 500 * time.Millisecond
Argon2DefaultDeviation = 500 * time.Millisecond
Argon2DefaultDedicatedMemory = 1 * bytesize.GB
BcryptDefaultCost uint32 = 12
)
// DefaultSessionCookieName returns the default cookie name for the kratos session.
const DefaultSessionCookieName = "ory_kratos_session"
type (
Argon2 struct {
Memory bytesize.ByteSize `json:"memory"`
Iterations uint32 `json:"iterations"`
Parallelism uint8 `json:"parallelism"`
SaltLength uint32 `json:"salt_length"`
KeyLength uint32 `json:"key_length"`
ExpectedDuration time.Duration `json:"expected_duration"`
ExpectedDeviation time.Duration `json:"expected_deviation"`
DedicatedMemory bytesize.ByteSize `json:"dedicated_memory"`
}
Bcrypt struct {
Cost uint32 `json:"cost"`
}
SelfServiceHook struct {
Name string `json:"hook"`
Config json.RawMessage `json:"config"`
}
SelfServiceStrategy struct {
Enabled bool `json:"enabled"`
Config json.RawMessage `json:"config"`
}
SelfServiceStrategyCode struct {
*SelfServiceStrategy
PasswordlessEnabled bool `json:"passwordless_enabled"`
MFAEnabled bool `json:"mfa_enabled"`
}
Schema struct {
ID string `json:"id" koanf:"id"`
URL string `json:"url" koanf:"url"`
SelfserviceSelectable bool `json:"selfservice_selectable" koanf:"selfservice_selectable"`
}
PasswordPolicy struct {
HaveIBeenPwnedHost string `json:"haveibeenpwned_host"`
HaveIBeenPwnedEnabled bool `json:"haveibeenpwned_enabled"`
MaxBreaches uint `json:"max_breaches"`
IgnoreNetworkErrors bool `json:"ignore_network_errors"`
MinPasswordLength uint `json:"min_password_length"`
IdentifierSimilarityCheckEnabled bool `json:"identifier_similarity_check_enabled"`
}
Schemas []Schema
CourierEmailBodyTemplate struct {
PlainText string `json:"plaintext"`
HTML string `json:"html"`
}
CourierEmailTemplate struct {
Body *CourierEmailBodyTemplate `json:"body"`
Subject string `json:"subject"`
}
CourierSMSTemplate struct {
Body *CourierSMSTemplateBody `json:"body"`
}
CourierSMSTemplateBody struct {
PlainText string `json:"plaintext"`
}
CourierChannel struct {
ID string `json:"id" koanf:"id"`
Type string `json:"type" koanf:"type"`
SMTPConfig *SMTPConfig `json:"smtp_config" koanf:"smtp_config"`
RequestConfig request.Config `json:"request_config" koanf:"request_config"`
}
SMTPConfig struct {
ConnectionURI string `json:"connection_uri" koanf:"connection_uri"`
ClientCertPath string `json:"client_cert_path" koanf:"client_cert_path"`
ClientKeyPath string `json:"client_key_path" koanf:"client_key_path"`
FromAddress string `json:"from_address" koanf:"from_address"`
FromName string `json:"from_name" koanf:"from_name"`
Headers map[string]string `json:"headers" koanf:"headers"`
LocalName string `json:"local_name" koanf:"local_name"`
}
PasswordMigrationHook struct {
Enabled bool `json:"enabled" koanf:"enabled"`
Config request.Config `json:"config" koanf:"config"`
}
Config struct {
l *logrusx.Logger
p *configx.Provider
c contextx.Contextualizer
identityMetaSchema *jsonschema.Schema
stdOutOrErr io.Writer
}
Provider interface {
Config() *Config
}
CourierConfigs interface {
CourierTemplatesRoot(ctx context.Context) string
CourierTemplatesVerificationInvalid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesVerificationValid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesRecoveryInvalid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesRecoveryValid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesRecoveryCodeInvalid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesRecoveryCodeValid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesVerificationCodeInvalid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesVerificationCodeValid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesLoginCodeValid(ctx context.Context) *CourierEmailTemplate
CourierTemplatesRegistrationCodeValid(ctx context.Context) *CourierEmailTemplate
CourierSMSTemplatesVerificationCodeValid(ctx context.Context) *CourierSMSTemplate
CourierSMSTemplatesRecoveryCodeValid(ctx context.Context) *CourierSMSTemplate
CourierSMSTemplatesLoginCodeValid(ctx context.Context) *CourierSMSTemplate
CourierSMSTemplatesRegistrationCodeValid(ctx context.Context) *CourierSMSTemplate
CourierTemplatesVerifiableAddressChanged(ctx context.Context) *CourierEmailTemplate
CourierSMSTemplatesVerifiableAddressChanged(ctx context.Context) *CourierSMSTemplate
CourierTemplatesAuthenticatorKeyAdded(ctx context.Context) *CourierEmailTemplate
CourierSMSTemplatesAuthenticatorKeyAdded(ctx context.Context) *CourierSMSTemplate
CourierMessageRetries(ctx context.Context) int
CourierWorkerPullCount(ctx context.Context) int
CourierWorkerPullWait(ctx context.Context) time.Duration
CourierChannels(context.Context) ([]*CourierChannel, error)
ClientSMTPNoPrivateIPRanges(ctx context.Context) bool
}
)
func (c *Argon2) MarshalJSON() ([]byte, error) {
type encoded struct {
Memory string `json:"memory"`
Iterations uint32 `json:"iterations"`
Parallelism uint8 `json:"parallelism"`
SaltLength uint32 `json:"salt_length"`
KeyLength uint32 `json:"key_length"`
ExpectedDuration string `json:"minimal_duration"`
ExpectedDeviation string `json:"expected_deviation"`
DedicatedMemory string `json:"dedicated_memory"`
}
return json.Marshal(&encoded{
Memory: c.Memory.String(),
Iterations: c.Iterations,
Parallelism: c.Parallelism,
SaltLength: c.SaltLength,
KeyLength: c.KeyLength,
ExpectedDuration: c.ExpectedDuration.String(),
ExpectedDeviation: c.ExpectedDeviation.String(),
DedicatedMemory: c.DedicatedMemory.String(),
})
}
var Argon2DefaultParallelism = uint8(runtime.NumCPU() * 2)
const HookGlobal = "global"
func HookStrategyKey(key, strategy string) string {
if strategy == HookGlobal {
return fmt.Sprintf("%s.hooks", key)
} else {
return fmt.Sprintf("%s.%s.hooks", key, strategy)
}
}
func (s Schemas) FindSchemaByID(id string) (*Schema, error) {
for _, sc := range s {
if sc.ID == id {
return &sc, nil
}
}
return nil, errors.Errorf("unable to find identity schema with id: %s", id)
}
func MustNew(t testing.TB, l *logrusx.Logger, ctxer contextx.Contextualizer, opts ...configx.OptionModifier) *Config {
p, err := New(t.Context(), l, os.Stderr, ctxer, opts...)
require.NoError(t, err)
return p
}
func New(ctx context.Context, l *logrusx.Logger, stdOutOrErr io.Writer, ctxer contextx.Contextualizer, opts ...configx.OptionModifier) (*Config, error) {
var c *Config
opts = append([]configx.OptionModifier{
configx.WithStderrValidationReporter(),
configx.OmitKeysFromTracing("dsn", "courier.smtp.connection_uri", "secrets.default", "secrets.cookie", "secrets.cipher", "client_secret"),
configx.WithImmutables("serve", "profiling", "log"),
configx.WithExceptImmutables("serve.public.cors.allowed_origins"),
configx.WithLogrusWatcher(l),
configx.WithLogger(l),
configx.WithContext(ctx),
configx.AttachWatcher(func(event watcherx.Event, err error) {
if c == nil {
panic(errors.New("the config provider did not initialise correctly in time"))
}
if err := c.validateIdentitySchemas(ctx); err != nil {
l.WithError(err).
Errorf("The changed identity schema configuration is invalid and could not be loaded. Rolling back to the last working configuration revision. Please address the validation errors before restarting the process.")
}
}),
}, opts...)
p, err := configx.New(ctx, embedx.ConfigSchema, opts...)
if err != nil {
return nil, err
}
l.UseConfig(p)
c = NewCustom(l, p, stdOutOrErr, ctxer)
if !p.SkipValidation() {
if err := c.validateIdentitySchemas(ctx); err != nil {
return nil, err
}
if fips140.Enabled() {
if len(p.Strings(ViperKeySecretsPagination)) == 0 {
return nil, errors.New("you must provide `secrets.pagination` for FIPS compliance")
}
if len(p.Strings(ViperKeySecretsCipher)) == 0 {
return nil, errors.New("you must provide `secrets.cipher` for FIPS compliance")
}
if len(p.Strings(ViperKeySecretsCookie)) == 0 {
return nil, errors.New("you must provide `secrets.cookie` for FIPS compliance")
}
if len(p.Strings(ViperKeySecretsDefault)) == 0 {
return nil, errors.New("you must provide `secrets.default` for FIPS compliance")
}
}
}
return c, nil
}
func NewCustom(l *logrusx.Logger, p *configx.Provider, stdOutOrErr io.Writer, ctxt contextx.Contextualizer) *Config {
l.UseConfig(p)
return &Config{l: l, p: p, c: ctxt, stdOutOrErr: stdOutOrErr}
}
func (p *Config) getIdentitySchemaValidator(ctx context.Context) (*jsonschema.Schema, error) {
if p.identityMetaSchema == nil {
c := jsonschema.NewCompiler()
err := embedx.AddSchemaResources(c, embedx.IdentityMeta)
if err != nil {
return nil, err
}
p.identityMetaSchema, err = c.Compile(ctx, embedx.IdentityMeta.GetSchemaID())
if err != nil {
return nil, errors.WithStack(err)
}
}
return p.identityMetaSchema, nil
}
type validateIdentitySchemasContextKey int
const validateIdentitySchemasClientKey validateIdentitySchemasContextKey = 1
func SetValidateIdentitySchemaResilientClientOptions(ctx context.Context, options []httpx.ResilientOptions) context.Context {
return context.WithValue(ctx, validateIdentitySchemasClientKey, options)
}
func (p *Config) validateIdentitySchemas(ctx context.Context) error {
opts := []httpx.ResilientOptions{
httpx.ResilientClientWithLogger(p.l),
httpx.ResilientClientWithMaxRetry(2),
httpx.ResilientClientWithConnectionTimeout(30 * time.Second),
}
if o, ok := ctx.Value(validateIdentitySchemasClientKey).([]httpx.ResilientOptions); ok {
opts = o
}
if p.ClientHTTPNoPrivateIPRanges(ctx) {
opts = append(opts, httpx.ResilientClientDisallowInternalIPs())
}
ctx = context.WithValue(ctx, httploader.ContextKey, httpx.NewResilientClient(opts...))
j, err := p.getIdentitySchemaValidator(ctx)
if err != nil {
return err
}
ss, err := p.IdentityTraitsSchemas(ctx)
if err != nil {
return err
}
for _, s := range ss {
resource, err := jsonschema.LoadURL(ctx, s.URL)
if err != nil {
return errors.WithStack(err)
}
defer func() { _ = resource.Close() }()
schema, err := io.ReadAll(io.LimitReader(resource, 1024*1024))
if err != nil {
return errors.WithStack(err)
}
if err = j.Validate(bytes.NewBuffer(schema)); err != nil {
p.formatJsonErrors(schema, err)
return errors.WithStack(err)
}
}
return nil
}
func (p *Config) formatJsonErrors(schema []byte, err error) {
_, _ = fmt.Fprintln(p.stdOutOrErr, "")
jsonschemax.FormatValidationErrorForCLI(p.stdOutOrErr, schema, err)
}
func (p *Config) ServePublic(ctx context.Context) *configx.Serve {
return p.GetProvider(ctx).Serve("serve.public", p.IsInsecureDevMode(ctx), configx.Serve{
Port: 4433,
})
}
func (p *Config) ServeAdmin(ctx context.Context) *configx.Serve {
return p.GetProvider(ctx).Serve("serve.admin", p.IsInsecureDevMode(ctx), configx.Serve{
Port: 4434,
})
}
func (p *Config) CORSPublic(ctx context.Context) (cors.Options, bool) {
return p.GetProvider(ctx).CORS("serve.public", cors.Options{
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowedHeaders: []string{"Authorization", "Content-Type", "Cookie"},
ExposedHeaders: []string{"Content-Type", "Set-Cookie"},
AllowCredentials: true,
})
}
// Deprecated: use context-based [contextx.WithConfigValue] instead.
func (p *Config) Set(_ context.Context, key string, value interface{}) error {
return p.p.Set(key, value)
}
// Deprecated: use context-based [contextx.WithConfigValue] instead.
func (p *Config) MustSet(_ context.Context, key string, value interface{}) {
if err := p.p.Set(key, value); err != nil {
p.l.WithError(err).Fatalf("Unable to set %q to %q.", key, value)
}
}
func (p *Config) SessionName(ctx context.Context) string {
return cmp.Or(p.GetProvider(ctx).String(ViperKeySessionName), DefaultSessionCookieName)
}
func (p *Config) HasherArgon2(ctx context.Context) *Argon2 {
// warn about usage of default values and point to the docs
// warning will require https://github.com/ory/viper/issues/19
return &Argon2{
Memory: p.GetProvider(ctx).ByteSizeF(ViperKeyHasherArgon2ConfigMemory, Argon2DefaultMemory),
//nolint:gosec // disable G115
Iterations: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigIterations, int(Argon2DefaultIterations))),
//nolint:gosec // disable G115
Parallelism: uint8(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigParallelism, int(Argon2DefaultParallelism))),
//nolint:gosec // disable G115
SaltLength: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigSaltLength, int(Argon2DefaultSaltLength))),
//nolint:gosec // disable G115
KeyLength: uint32(p.GetProvider(ctx).IntF(ViperKeyHasherArgon2ConfigKeyLength, int(Argon2DefaultKeyLength))),
ExpectedDuration: p.GetProvider(ctx).DurationF(ViperKeyHasherArgon2ConfigExpectedDuration, Argon2DefaultDuration),
ExpectedDeviation: p.GetProvider(ctx).DurationF(ViperKeyHasherArgon2ConfigExpectedDeviation, Argon2DefaultDeviation),
DedicatedMemory: p.GetProvider(ctx).ByteSizeF(ViperKeyHasherArgon2ConfigDedicatedMemory, Argon2DefaultDedicatedMemory),
}
}
func (p *Config) HasherBcrypt(ctx context.Context) *Bcrypt {
cost := uint32(p.GetProvider(ctx).IntF(ViperKeyHasherBcryptCost, int(BcryptDefaultCost))) // #nosec G115 -- if the user configures a cost > MaxUint32, go falls back to MaxUint32
if !p.IsInsecureDevMode(ctx) && cost < BcryptDefaultCost {
cost = BcryptDefaultCost
}
return &Bcrypt{Cost: cost}
}
func (p *Config) DefaultIdentityTraitsSchemaURL(ctx context.Context) (*url.URL, error) {
ss, err := p.IdentityTraitsSchemas(ctx)
if err != nil {
return nil, err
}
search := p.GetProvider(ctx).String(ViperKeyDefaultIdentitySchemaID)
found, err := ss.FindSchemaByID(search)
if err != nil {
return nil, err
}
return p.ParseURI(found.URL)
}
func (p *Config) DefaultIdentityTraitsSchemaID(ctx context.Context) string {
return p.GetProvider(ctx).String(ViperKeyDefaultIdentitySchemaID)
}
func (p *Config) IdentityTraitsSchemaURL(ctx context.Context, schemaID string) (*url.URL, error) {
ss, err := p.IdentityTraitsSchemas(ctx)
if err != nil {
return nil, err
}
found, err := ss.FindSchemaByID(schemaID)
if err != nil {
// default to default schema
search := p.GetProvider(ctx).String(ViperKeyDefaultIdentitySchemaID)
found, err = ss.FindSchemaByID(search)
if err != nil {
return nil, err
}
}
return p.ParseURI(found.URL)
}
func (p *Config) TOTPIssuer(ctx context.Context) string {
return p.GetProvider(ctx).StringF(ViperKeyTOTPIssuer, p.SelfPublicURL(ctx).Hostname())
}
func (p *Config) OIDCRedirectURIBase(ctx context.Context) *url.URL {
return p.GetProvider(ctx).URIF(ViperKeyOIDCBaseRedirectURL, p.SelfPublicURL(ctx))
}
func (p *Config) SAMLRedirectURIBase(ctx context.Context) *url.URL {
return p.GetProvider(ctx).URIF(ViperKeySAMLBaseRedirectURL, p.SelfPublicURL(ctx))
}
func (p *Config) IdentityTraitsSchemas(ctx context.Context) (ss Schemas, err error) {
if err = p.GetProvider(ctx).Unmarshal(ViperKeyIdentitySchemas, &ss); err != nil {
return ss, nil
}
return ss, nil
}
func (p *Config) DSN(ctx context.Context) string {
pp := p.GetProvider(ctx)
dsn := pp.String(ViperKeyDSN)
if dsn == "memory" {
return DefaultSQLiteMemoryDSN
}
if len(dsn) > 0 {
return dsn
}
// Print a stack trace to aid debugging.
p.l.Fatalf("%+v", errors.Errorf("dsn must be set"))
return ""
}
func (p *Config) DisableAPIFlowEnforcement(ctx context.Context) bool {
if p.IsInsecureDevMode(ctx) && os.Getenv("DEV_DISABLE_API_FLOW_ENFORCEMENT") == "true" {
p.l.Warn("Because \"DEV_DISABLE_API_FLOW_ENFORCEMENT=true\" and the \"--dev\" flag are set, self-service API flows will no longer check if the interaction is actually a browser flow. This is very dangerous as it allows bypassing of anti-CSRF measures, leaving the deployment highly vulnerable. This option should only be used for automated testing and never come close to real user data anywhere.")
return true
}
return false
}
func (p *Config) ClientHTTPNoPrivateIPRanges(ctx context.Context) bool {
return p.GetProvider(ctx).BoolF(ViperKeyClientHTTPNoPrivateIPRanges, false)
}
func (p *Config) ClientHTTPPrivateIPExceptionURLs(ctx context.Context) []string {
return p.GetProvider(ctx).Strings(ViperKeyClientHTTPPrivateIPExceptionURLs)
}
func (p *Config) ClientSMTPNoPrivateIPRanges(ctx context.Context) bool {
return p.GetProvider(ctx).BoolF(ViperKeyClientSMTPNoPrivateIPRanges, false)
}
func (p *Config) SelfServiceFlowRegistrationEnabled(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeySelfServiceRegistrationEnabled)
}
func (p *Config) SelfServiceFlowRegistrationLoginHints(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeySelfServiceRegistrationLoginHints)
}
func (p *Config) SelfServiceFlowRegistrationPasswordMethodProfileGroup(ctx context.Context) string {
switch g := p.GetProvider(ctx).String(ViperKeyPasswordRegistrationProfileGroup); g {
case "password":
return "password"
default:
return "default"
}
}
func (p *Config) SelfServiceLegacyOIDCRegistrationGroup(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyLegacyOIDCRegistrationGroup)
}
func (p *Config) SelfServiceFlowRegistrationTwoSteps(ctx context.Context) bool {
// The default in previous versions that legacy one-step would be disabled. If legacy is enabled, it means the
// user has explicitly set the key to true, in which case we respect it.
if useOneStep := p.GetProvider(ctx).Bool(ViperKeySelfServiceRegistrationEnableLegacyOneStep); useOneStep {
p.l.Warnf("Found use of deprecated configuration key %q. Please use key %q instead and delete key %[1]q. Will use value from %[1]q to configure registration style.", ViperKeySelfServiceRegistrationEnableLegacyOneStep, ViperKeySelfServiceRegistrationFlowStyle)
return false
}
// In all other cases, we use the new key which (like the old key) defaults to `profile_first` / two-step registration.
switch style := p.GetProvider(ctx).String(ViperKeySelfServiceRegistrationFlowStyle); style {
case "profile_first":
return true
default:
return false
}
}
func (p *Config) SelfServiceFlowIdentitySchema(ctx context.Context, requestedSchema string) (string, error) {
if requestedSchema == p.GetProvider(ctx).String(ViperKeyDefaultIdentitySchemaID) {
return requestedSchema, nil
}
schemas, err := p.IdentityTraitsSchemas(ctx)
if err != nil {
return "", errors.WithStack(err)
}
for _, schema := range schemas {
if schema.ID == requestedSchema {
if !schema.SelfserviceSelectable {
return "", errors.WithStack(herodot.ErrBadRequest().WithReasonf("Requested identity schema %q is not enabled for self-service flows.", requestedSchema))
}
return requestedSchema, nil
}
}
return "", errors.WithStack(herodot.ErrBadRequest().WithReasonf("Requested identity schema %q does not exist.", requestedSchema))
}
func (p *Config) SelfServiceFlowVerificationEnabled(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeySelfServiceVerificationEnabled)
}
func (p *Config) UseLegacyShowVerificationUI(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyUseLegacyShowVerificationUI)
}
func (p *Config) UseLegacyRequireVerifiedLoginError(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyUseLegacyRequireVerifiedLoginError)
}
func (p *Config) SelfServiceFlowRecoveryEnabled(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeySelfServiceRecoveryEnabled)
}
func (p *Config) SelfServiceFlowRecoveryUse(ctx context.Context) string {
return p.GetProvider(ctx).String(ViperKeySelfServiceRecoveryUse)
}
func (p *Config) SelfServiceFlowLoginBeforeHooks(ctx context.Context) []SelfServiceHook {
return p.selfServiceHooks(ctx, ViperKeySelfServiceLoginBeforeHooks)
}
func (p *Config) SelfServiceFlowRecoveryBeforeHooks(ctx context.Context) []SelfServiceHook {
return p.selfServiceHooks(ctx, ViperKeySelfServiceRecoveryBeforeHooks)
}
func (p *Config) SelfServiceFlowVerificationBeforeHooks(ctx context.Context) []SelfServiceHook {
return p.selfServiceHooks(ctx, ViperKeySelfServiceVerificationBeforeHooks)
}
func (p *Config) SelfServiceFlowVerificationUse(ctx context.Context) string {
return p.GetProvider(ctx).String(ViperKeySelfServiceVerificationUse)
}
func (p *Config) SelfServiceFlowVerificationNotifyUnknownRecipients(ctx context.Context) bool {
return p.GetProvider(ctx).BoolF(ViperKeySelfServiceVerificationNotifyUnknownRecipients, false)
}
func (p *Config) SelfServiceFlowSettingsBeforeHooks(ctx context.Context) []SelfServiceHook {
return p.selfServiceHooks(ctx, ViperKeySelfServiceSettingsBeforeHooks)
}
func (p *Config) SelfServiceFlowRegistrationBeforeHooks(ctx context.Context) []SelfServiceHook {
hooks := p.selfServiceHooks(ctx, ViperKeySelfServiceRegistrationBeforeHooks)
if p.SelfServiceFlowRegistrationTwoSteps(ctx) {
hooks = append(hooks, SelfServiceHook{"two_step_registration", json.RawMessage("{}")})
}
return hooks
}
func (p *Config) selfServiceHooks(ctx context.Context, key string) []SelfServiceHook {
pp := p.GetProvider(ctx)
val := pp.Get(key)
if val == nil {
return []SelfServiceHook{}
}
config, err := json.Marshal(val)
if err != nil {
p.l.WithError(err).Fatalf("Unable to decode values from configuration key: %s", key)
}
var hooks []SelfServiceHook
if err := json.Unmarshal(config, &hooks); err != nil {
p.l.WithError(err).Fatalf("Unable to encode value \"%s\" from configuration key: %s", config, key)
}
for k := range hooks {
if len(hooks[k].Config) == 0 {
hooks[k].Config = json.RawMessage("{}")
}
}
return hooks
}
func (p *Config) SelfServiceFlowLoginAfterHooks(ctx context.Context, strategy string) []SelfServiceHook {
return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceLoginAfter, strategy))
}
func (p *Config) SelfServiceFlowSettingsAfterHooks(ctx context.Context, strategy string) []SelfServiceHook {
return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceSettingsAfter, strategy))
}
func (p *Config) SelfServiceFlowRegistrationAfterHooks(ctx context.Context, strategy string) []SelfServiceHook {
return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceRegistrationAfter, strategy))
}
func (p *Config) SelfServiceStrategy(ctx context.Context, strategy string) *SelfServiceStrategy {
pp := p.GetProvider(ctx)
config := json.RawMessage("{}")
basePath := fmt.Sprintf("%s.%s", ViperKeySelfServiceStrategyConfig, strategy)
var err error
config, err = json.Marshal(pp.GetF(basePath+".config", config))
if err != nil {
p.l.WithError(err).Warn("Unable to marshal self-service strategy configuration.")
config = json.RawMessage("{}")
}
// The default value can easily be overwritten by setting e.g. `{"selfservice": "null"}` which means that
// we need to forcibly set these values here:
defaultEnabled := false
switch strategy {
case "identifier_first":
defaultEnabled = p.SelfServiceLoginFlowIdentifierFirstEnabled(ctx)
case "code", "password", "profile":
defaultEnabled = true
}
// Backwards compatibility for the old "passwordless_enabled" key
// This force-enables the code strategy, if passwordless is enabled, because in earlier versions it was possible to
// disable the code strategy, but enable passwordless
enabled := pp.BoolF(basePath+".enabled", defaultEnabled)
if strategy == "code" {
enabled = enabled || pp.Bool(basePath+".passwordless_enabled")
}
return &SelfServiceStrategy{
Enabled: enabled,
Config: config,
}
}
func (p *Config) SelfServiceCodeStrategy(ctx context.Context) *SelfServiceStrategyCode {
pp := p.GetProvider(ctx)
config := json.RawMessage("{}")
basePath := ViperKeySelfServiceStrategyConfig + ".code"
var err error
config, err = json.Marshal(pp.GetF(basePath+".config", config))
if err != nil {
p.l.WithError(err).Warn("Unable to marshal self service strategy configuration.")
config = json.RawMessage("{}")
}
return &SelfServiceStrategyCode{
SelfServiceStrategy: &SelfServiceStrategy{
Enabled: pp.BoolF(basePath+".enabled", true),
Config: config,
},
PasswordlessEnabled: pp.BoolF(basePath+".passwordless_enabled", false),
MFAEnabled: pp.BoolF(basePath+".mfa_enabled", false),
}
}
func (p *Config) SecretsDefault(ctx context.Context) [][]byte {
pp := p.GetProvider(ctx)
secrets := pp.Strings(ViperKeySecretsDefault)
if len(secrets) == 0 {
secrets = []string{uuid.Must(uuid.NewV4()).String()}
p.MustSet(ctx, ViperKeySecretsDefault, secrets)
}
result := make([][]byte, len(secrets))
for k, v := range secrets {
result[k] = []byte(v)
}
return result
}
func (p *Config) SecretsSession(ctx context.Context) [][]byte {
secrets := p.GetProvider(ctx).Strings(ViperKeySecretsCookie)
if len(secrets) == 0 {
return p.SecretsDefault(ctx)
}
result := make([][]byte, len(secrets))
for k, v := range secrets {
result[k] = []byte(v)
}
return result
}
func (p *Config) SecretsCipher(ctx context.Context) [][32]byte {
secrets := p.GetProvider(ctx).Strings(ViperKeySecretsCipher)
return ToCipherSecrets(secrets)
}
func ToCipherSecrets(secrets []string) [][32]byte {
var cleanSecrets []string
for k := range secrets {
if len(secrets[k]) == 32 {
cleanSecrets = append(cleanSecrets, secrets[k])
}
}
if len(cleanSecrets) == 0 {
return [][32]byte{}
}
result := make([][32]byte, len(cleanSecrets))
for n, s := range secrets {
for k, v := range []byte(s) {
result[n][k] = v
}
}
return result
}
func (p *Config) SecretsPagination(ctx context.Context) [][32]byte {
secrets := p.GetProvider(ctx).Strings(ViperKeySecretsPagination)
encryptionKeys := make([][32]byte, len(secrets))
for i, key := range secrets {
encryptionKeys[i] = sha512.Sum512_256([]byte(key))
}
return encryptionKeys
}
func (p *Config) SelfServiceBrowserDefaultReturnTo(ctx context.Context) *url.URL {
return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceBrowserDefaultReturnTo)
}
func (p *Config) SelfPublicURL(ctx context.Context) *url.URL {
serve := p.ServePublic(ctx)
return serve.BaseURL
}
func (p *Config) SelfAdminURL(ctx context.Context) *url.URL {
serve := p.ServeAdmin(ctx)
return serve.BaseURL
}
func (p *Config) WebhookHeaderAllowlist(ctx context.Context) []string {
return p.GetProvider(ctx).Strings(ViperKeyWebhookHeaderAllowlist)
}
func (p *Config) OAuth2ProviderHeader(ctx context.Context) http.Header {
hh := map[string]string{}
if err := p.GetProvider(ctx).Unmarshal(ViperKeyOAuth2ProviderHeader, &hh); err != nil {
p.l.WithError(errors.WithStack(err)).
Errorf("Configuration value from key %s could not be decoded.", ViperKeyOAuth2ProviderHeader)
return nil
}
h := make(http.Header)
for k, v := range hh {
h.Set(k, v)
}
return h
}
func (p *Config) OAuth2ProviderOverrideReturnTo(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyOAuth2ProviderOverrideReturnTo)
}
func (p *Config) OAuth2ProviderURL(ctx context.Context) *url.URL {
k := ViperKeyOAuth2ProviderURL
v := p.GetProvider(ctx).String(k)
if v == "" {
return nil
}
parsed, err := p.ParseAbsoluteOrRelativeURI(v)
if err != nil {
p.l.WithError(errors.WithStack(err)).
Errorf("Configuration value from key %s is not a valid URL: %s", k, v)
return nil
}
return parsed
}
func (p *Config) SelfServiceFlowLoginUI(ctx context.Context) *url.URL {
return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceLoginUI)
}
func (p *Config) SelfServiceFlowSettingsUI(ctx context.Context) *url.URL {
return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceSettingsURL)
}
func (p *Config) SelfServiceFlowErrorURL(ctx context.Context) *url.URL {
return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceErrorUI)
}
func (p *Config) SelfServiceFlowRegistrationUI(ctx context.Context) *url.URL {
return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceRegistrationUI)
}
func (p *Config) SelfServiceFlowRecoveryUI(ctx context.Context) *url.URL {
return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceRecoveryUI)
}
// SessionLifespan returns time.Hour*24 when the value is not set.
func (p *Config) SessionLifespan(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySessionLifespan, time.Hour*24)
}
// OrganizationSessionLifespan returns the effective session lifespan for a
// session issued to the given organization. If orgID is uuid.Nil, or no
// matching organization is configured, or the organization has no
// session_lifespan override, the project-level session.lifespan is returned.
func (p *Config) OrganizationSessionLifespan(ctx context.Context, orgID uuid.UUID) time.Duration {
if orgID != uuid.Nil {
for _, org := range p.Organizations(ctx) {
if org.ID == orgID && org.SessionLifespan > 0 {
return org.SessionLifespan
}
}
}
return p.SessionLifespan(ctx)
}
func (p *Config) SessionPersistentCookie(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeySessionPersistentCookie)
}
func (p *Config) SelfServiceBrowserAllowedReturnToDomains(ctx context.Context) (us []url.URL) {
src := p.GetProvider(ctx).Strings(ViperKeyURLsAllowedReturnToDomains)
for k, u := range src {
if len(u) == 0 {
continue
}
parsed, err := url.ParseRequestURI(u)
if err != nil {
p.l.WithError(err).Warnf("Ignoring URL \"%s\" from configuration key \"%s.%d\".", u, ViperKeyURLsAllowedReturnToDomains, k)
continue
}
// A wildcard host must use the labelled form "*.example.com" bounded at a
// registrable domain. Reject bare "*", dot-less "*foo.com", public-suffix
// "*.com", and mid-label "foo.*.com" wildcards — each would match an
// attacker-registrable host. See corsx.ClassifyOrigin.
if corsx.ClassifyOrigin(parsed.Host).IsUnsafeWildcard() && !p.LegacyAllowInsecureOrigins(ctx) {
p.l.Warnf("Ignoring wildcard \"%s\" from configuration key \"%s.%d\".", u, ViperKeyURLsAllowedReturnToDomains, k)
continue
}
us = append(us, *parsed)
}
return us
}
func (p *Config) SelfServiceFlowLoginRequestLifespan(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySelfServiceLoginRequestLifespan, time.Hour)
}
func (p *Config) SelfServiceFlowSettingsFlowLifespan(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySelfServiceSettingsRequestLifespan, time.Hour)
}
func (p *Config) SelfServiceFlowRegistrationRequestLifespan(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySelfServiceRegistrationRequestLifespan, time.Hour)
}
func (p *Config) SelfServiceFlowLogoutRedirectURL(ctx context.Context) *url.URL {
return p.GetProvider(ctx).RequestURIF(ViperKeySelfServiceLogoutBrowserDefaultReturnTo, p.SelfServiceBrowserDefaultReturnTo(ctx))
}
func (p *Config) CourierEmailStrategy(ctx context.Context) string {
return p.GetProvider(ctx).StringF(ViperKeyCourierDeliveryStrategy, "smtp")
}
func (p *Config) CourierEmailRequestConfig(ctx context.Context) json.RawMessage {
if p.CourierEmailStrategy(ctx) != "http" {
return nil
}
config, err := json.Marshal(p.GetProvider(ctx).Get(ViperKeyCourierHTTPRequestConfig))
if err != nil {
p.l.WithError(err).Warn("Unable to marshal mailer request configuration.")
return nil
}
return config
}
func (p *Config) CourierTemplatesRoot(ctx context.Context) string {
return p.GetProvider(ctx).StringF(ViperKeyCourierTemplatesPath, "courier/builtin/templates")
}
func (p *Config) CourierEmailTemplatesHelper(ctx context.Context, key string) *CourierEmailTemplate {
courierTemplate := &CourierEmailTemplate{
Body: &CourierEmailBodyTemplate{
PlainText: "",
HTML: "",
},
Subject: "",
}
if !p.GetProvider(ctx).Exists(key) {
return courierTemplate
}
config, err := json.Marshal(p.GetProvider(ctx).Get(key))
if err != nil {
p.l.WithError(err).Fatalf("Unable to decode values from %s.", key)
return courierTemplate
}
if err := json.Unmarshal(config, courierTemplate); err != nil {
p.l.WithError(err).Fatalf("Unable to encode values from %s.", key)
return courierTemplate
}
return courierTemplate
}
func (p *Config) CourierSMSTemplatesHelper(ctx context.Context, key string) *CourierSMSTemplate {
courierTemplate := &CourierSMSTemplate{
Body: &CourierSMSTemplateBody{
PlainText: "",
},
}
if !p.GetProvider(ctx).Exists(key) {
return courierTemplate
}
config, err := json.Marshal(p.GetProvider(ctx).Get(key))
if err != nil {
p.l.WithError(err).Fatalf("Unable to decode values from %s.", key)
return courierTemplate
}
if err := json.Unmarshal(config, courierTemplate); err != nil {
p.l.WithError(err).Fatalf("Unable to encode values from %s.", key)
return courierTemplate
}
return courierTemplate
}
func (p *Config) CourierTemplatesVerificationInvalid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationInvalidEmail)
}
func (p *Config) CourierTemplatesVerificationValid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationValidEmail)
}
func (p *Config) CourierTemplatesRecoveryInvalid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryInvalidEmail)
}
func (p *Config) CourierTemplatesRecoveryValid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryValidEmail)
}
func (p *Config) CourierTemplatesRecoveryCodeInvalid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryCodeInvalidEmail)
}
func (p *Config) CourierTemplatesRecoveryCodeValid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryCodeValidEmail)
}
func (p *Config) CourierTemplatesVerificationCodeInvalid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationCodeInvalidEmail)
}
func (p *Config) CourierTemplatesVerificationCodeValid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationCodeValidEmail)
}
func (p *Config) CourierSMSTemplatesVerificationCodeValid(ctx context.Context) *CourierSMSTemplate {
return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesVerificationCodeValidSMS)
}
func (p *Config) CourierSMSTemplatesRecoveryCodeValid(ctx context.Context) *CourierSMSTemplate {
return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesRecoveryCodeValidSMS)
}
func (p *Config) CourierSMSTemplatesLoginCodeValid(ctx context.Context) *CourierSMSTemplate {
return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesLoginCodeValidSMS)
}
func (p *Config) CourierSMSTemplatesRegistrationCodeValid(ctx context.Context) *CourierSMSTemplate {
return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesRegistrationCodeValidSMS)
}
func (p *Config) CourierTemplatesVerifiableAddressChanged(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesVerifiableAddressChangedEmail)
}
func (p *Config) CourierSMSTemplatesVerifiableAddressChanged(ctx context.Context) *CourierSMSTemplate {
return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesVerifiableAddressChangedSMS)
}
func (p *Config) CourierTemplatesAuthenticatorKeyAdded(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesAuthenticatorKeyAddedEmail)
}
func (p *Config) CourierSMSTemplatesAuthenticatorKeyAdded(ctx context.Context) *CourierSMSTemplate {
return p.CourierSMSTemplatesHelper(ctx, ViperKeyCourierTemplatesAuthenticatorKeyAddedSMS)
}
func (p *Config) CourierTemplatesLoginCodeValid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesLoginCodeValidEmail)
}
func (p *Config) CourierTemplatesRegistrationCodeValid(ctx context.Context) *CourierEmailTemplate {
return p.CourierEmailTemplatesHelper(ctx, ViperKeyCourierTemplatesRegistrationCodeValidEmail)
}
func (p *Config) CourierMessageRetries(ctx context.Context) int {
return p.GetProvider(ctx).IntF(ViperKeyCourierMessageRetries, 5)
}
func (p *Config) CourierWorkerPullCount(ctx context.Context) int {
return p.GetProvider(ctx).Int(ViperKeyCourierWorkerPullCount)
}
func (p *Config) CourierWorkerPullWait(ctx context.Context) time.Duration {
return p.GetProvider(ctx).Duration(ViperKeyCourierWorkerPullWait)
}
func (p *Config) CourierSMTPHeaders(ctx context.Context) map[string]string {
return p.GetProvider(ctx).StringMap(ViperKeyCourierSMTPHeaders)
}
func (p *Config) CourierChannels(ctx context.Context) (ccs []*CourierChannel, _ error) {
if err := p.GetProvider(ctx).Unmarshal(ViperKeyCourierChannels, &ccs); err != nil {
return nil, errors.WithStack(err)
}
// load legacy configs
channel := CourierChannel{
ID: "email",
Type: p.CourierEmailStrategy(ctx),
}
if channel.Type == "smtp" {
if err := p.GetProvider(ctx).Unmarshal(ViperKeyCourierSMTP, &channel.SMTPConfig); err != nil {
return nil, errors.WithStack(err)
}
} else {
if err := p.GetProvider(ctx).Unmarshal(ViperKeyCourierHTTPRequestConfig, &channel.RequestConfig); err != nil {
return nil, errors.WithStack(err)
}
}
ccs = append(ccs, &channel)
return ccs, nil
}
func splitUrlAndFragment(s string) (string, string) {
i := strings.IndexByte(s, '#')
if i < 0 {
return s, ""
}
return s[:i], s[i+1:]
}
func (p *Config) ParseAbsoluteOrRelativeURIOrFail(ctx context.Context, key string) *url.URL {
parsed, err := p.ParseAbsoluteOrRelativeURI(p.GetProvider(ctx).String(key))
if err != nil {
p.l.WithError(errors.WithStack(err)).
Fatalf("Configuration value from key %s is not a valid URL: %s", key, p.GetProvider(ctx).String(key))
}
return parsed
}
func (p *Config) ParseURIOrFail(ctx context.Context, key string) *url.URL {
parsed, err := p.ParseURI(p.GetProvider(ctx).String(key))
if err != nil {
p.l.WithField("reason", "expected scheme to be set").
Fatalf("Configuration value from key %s is not a valid URL: %s", key, p.GetProvider(ctx).String(key))
}
return parsed
}
func (p *Config) ParseAbsoluteOrRelativeURI(rawUrl string) (*url.URL, error) {
u, frag := splitUrlAndFragment(rawUrl)
parsed, err := url.ParseRequestURI(u)
if err != nil {
return nil, errors.Wrapf(err, "configuration value not a valid URL: %s", rawUrl)
}
if frag != "" {
parsed.Fragment = frag
}
return parsed, nil
}
func (p *Config) ParseURI(rawUrl string) (*url.URL, error) {
parsed, err := p.ParseAbsoluteOrRelativeURI(rawUrl)
if err != nil {
return nil, err
}
if parsed.Scheme == "" {
return nil, errors.Errorf("configuration value is not a valid URL: %s", rawUrl)
}
return parsed, nil
}
func (p *Config) Tracing(ctx context.Context) *otelx.Config {
return p.GetProvider(ctx).TracingConfig("Ory Kratos")
}
func (p *Config) IsInsecureDevMode(ctx context.Context) bool {
return p.GetProvider(ctx).Bool("dev")
}
func (p *Config) IsBackgroundCourierEnabled(ctx context.Context) bool {
return p.GetProvider(ctx).Bool("watch-courier")
}
func (p *Config) CourierExposeMetricsPort(ctx context.Context) int {
return p.GetProvider(ctx).Int("expose-metrics-port")
}
func (p *Config) SelfServiceFlowVerificationUI(ctx context.Context) *url.URL {
return p.ParseAbsoluteOrRelativeURIOrFail(ctx, ViperKeySelfServiceVerificationUI)
}
func (p *Config) SelfServiceFlowVerificationRequestLifespan(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySelfServiceVerificationRequestLifespan, time.Hour)
}
func (p *Config) SelfServiceFlowVerificationReturnTo(ctx context.Context, defaultReturnTo *url.URL) *url.URL {
return p.GetProvider(ctx).RequestURIF(ViperKeySelfServiceVerificationBrowserDefaultReturnTo, defaultReturnTo)
}
func (p *Config) SelfServiceFlowVerificationAfterHooks(ctx context.Context, strategy string) []SelfServiceHook {
return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceVerificationAfter, strategy))
}
func (p *Config) SelfServiceFlowRecoveryReturnTo(ctx context.Context, defaultReturnTo *url.URL) *url.URL {
return p.GetProvider(ctx).RequestURIF(ViperKeySelfServiceRecoveryBrowserDefaultReturnTo, defaultReturnTo)
}
func (p *Config) SelfServiceFlowRecoveryRequestLifespan(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySelfServiceRecoveryRequestLifespan, time.Hour)
}
func (p *Config) SelfServiceFlowRecoveryNotifyUnknownRecipients(ctx context.Context) bool {
return p.GetProvider(ctx).BoolF(ViperKeySelfServiceRecoveryNotifyUnknownRecipients, false)
}
func (p *Config) SelfServiceFlowLogoutClearBrowserData(ctx context.Context) bool {
return p.GetProvider(ctx).BoolF(ViperKeySelfServiceLogoutClearBrowserData, false)
}
func (p *Config) SelfServiceLinkMethodLifespan(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeyLinkLifespan, time.Hour)
}
func (p *Config) SelfServiceCodeMethodLifespan(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeyCodeLifespan, time.Hour)
}
func (p *Config) SelfServiceCodeMethodMaxSubmissions(ctx context.Context) int {
return p.GetProvider(ctx).IntF(ViperKeyCodeMaxSubmissions, 5)
}
func (p *Config) SelfServiceCodeMethodMissingCredentialFallbackEnabled(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyCodeConfigMissingCredentialFallbackEnabled)
}
func (p *Config) DatabaseCleanupSleepTables(ctx context.Context) time.Duration {
return p.GetProvider(ctx).Duration(ViperKeyDatabaseCleanupSleepTables)
}
func (p *Config) DatabaseCleanupBatchSize(ctx context.Context) int {
return p.GetProvider(ctx).Int(ViperKeyDatabaseCleanupBatchSize)
}
func (p *Config) SelfServiceFlowRecoveryAfterHooks(ctx context.Context, strategy string) []SelfServiceHook {
return p.selfServiceHooks(ctx, HookStrategyKey(ViperKeySelfServiceRecoveryAfter, strategy))
}
func (p *Config) SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter, time.Hour)
}
func (p *Config) SessionSameSiteMode(ctx context.Context) http.SameSite {
if !p.GetProvider(ctx).Exists(ViperKeySessionSameSite) {
return p.CookieSameSiteMode(ctx)
}
switch p.GetProvider(ctx).StringF(ViperKeySessionSameSite, "Lax") {
case "Lax":
return http.SameSiteLaxMode
case "Strict":
return http.SameSiteStrictMode
case "None":
return http.SameSiteNoneMode
}
return http.SameSiteDefaultMode
}
func (p *Config) SessionDomain(ctx context.Context) string {
if !p.GetProvider(ctx).Exists(ViperKeySessionDomain) {
return p.CookieDomain(ctx)
}
return p.GetProvider(ctx).String(ViperKeySessionDomain)
}
func (p *Config) SessionCookieSecure(ctx context.Context) bool {
if !p.GetProvider(ctx).Exists(ViperKeySessionSecure) {
return !p.IsInsecureDevMode(ctx)
}
return p.GetProvider(ctx).Bool(ViperKeySessionSecure)
}
func (p *Config) CookieDomain(ctx context.Context) string {
return p.GetProvider(ctx).String(ViperKeyCookieDomain)
}
func (p *Config) SessionWhoAmIAAL(ctx context.Context) string {
return p.GetProvider(ctx).String(ViperKeySessionWhoAmIAAL)
}
func (p *Config) SessionWhoAmICaching(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeySessionWhoAmICaching)
}
func (p *Config) FeatureFlagFasterSessionExtend(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyFeatureFlagFasterSessionExtend)
}
// LegacyAllowInsecureOrigins restores legacy behavior where wildcard return URLs
// and CORS origins need not be bounded at a registrable domain. Insecure; only
// set by the Ory Network for grandfathered projects. Default false.
func (p *Config) LegacyAllowInsecureOrigins(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyLegacyAllowInsecureOrigins)
}
func (p *Config) SessionWhoAmICachingMaxAge(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySessionWhoAmICachingMaxAge, 0)
}
func (p *Config) UseContinueWithTransitions(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyUseContinueWithTransitions)
}
func (p *Config) ChooseRecoveryAddress(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyChooseRecoveryAddress)
}
func (p *Config) RefreshLoginChooseAddress(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeyRefreshLoginChooseAddress)
}
func (p *Config) SessionRefreshMinTimeLeft(ctx context.Context) time.Duration {
return p.GetProvider(ctx).DurationF(ViperKeySessionRefreshMinTimeLeft, p.SessionLifespan(ctx))
}
func (p *Config) SelfServiceSettingsRequiredAAL(ctx context.Context) string {
return p.GetProvider(ctx).String(ViperKeySelfServiceSettingsRequiredAAL)
}
func (p *Config) CookieSameSiteMode(ctx context.Context) http.SameSite {
switch p.GetProvider(ctx).StringF(ViperKeyCookieSameSite, "Lax") {
case "Lax":
return http.SameSiteLaxMode
case "Strict":
return http.SameSiteStrictMode
case "None":
return http.SameSiteNoneMode
}
return http.SameSiteDefaultMode
}
func (p *Config) SessionPath(ctx context.Context) string {
if !p.GetProvider(ctx).Exists(ViperKeySessionPath) {
return p.CookiePath(ctx)
}
return p.GetProvider(ctx).String(ViperKeySessionPath)
}
func (p *Config) CookiePath(ctx context.Context) string {
return p.GetProvider(ctx).String(ViperKeyCookiePath)
}
func (p *Config) CookieSecure(ctx context.Context) bool {
if !p.GetProvider(ctx).Exists(ViperKeyCookieSecure) {
return !p.IsInsecureDevMode(ctx)
}
return p.GetProvider(ctx).Bool(ViperKeyCookieSecure)
}
func (p *Config) SelfServiceFlowLoginReturnTo(ctx context.Context, strategy string) *url.URL {
return p.selfServiceReturnTo(ctx, ViperKeySelfServiceLoginAfter, strategy)
}
func (p *Config) SelfServiceFlowRegistrationReturnTo(ctx context.Context, strategy string) *url.URL {
return p.selfServiceReturnTo(ctx, ViperKeySelfServiceRegistrationAfter, strategy)
}
func (p *Config) SelfServiceFlowSettingsReturnTo(ctx context.Context, strategy string, defaultReturnTo *url.URL) *url.URL {
return p.GetProvider(ctx).RequestURIF(
ViperKeySelfServiceSettingsAfter+"."+strategy+"."+DefaultBrowserReturnURL,
p.GetProvider(ctx).RequestURIF(ViperKeySelfServiceSettingsAfter+"."+DefaultBrowserReturnURL,
defaultReturnTo,
),
)
}
func (p *Config) selfServiceReturnTo(ctx context.Context, key string, strategy string) *url.URL {
return p.GetProvider(ctx).RequestURIF(
key+"."+strategy+"."+DefaultBrowserReturnURL,
p.GetProvider(ctx).RequestURIF(key+"."+DefaultBrowserReturnURL,
p.SelfServiceBrowserDefaultReturnTo(ctx),
),
)
}
func (p *Config) ConfigVersion(ctx context.Context) string {
return p.GetProvider(ctx).StringF(ViperKeyVersion, UnknownVersion)
}
func (p *Config) PasswordPolicyConfig(ctx context.Context) *PasswordPolicy {
return &PasswordPolicy{
HaveIBeenPwnedHost: p.GetProvider(ctx).StringF(ViperKeyPasswordHaveIBeenPwnedHost, "api.pwnedpasswords.com"),
HaveIBeenPwnedEnabled: p.GetProvider(ctx).BoolF(ViperKeyPasswordHaveIBeenPwnedEnabled, true),
MaxBreaches: uint(p.GetProvider(ctx).Int(ViperKeyPasswordMaxBreaches)), // #nosec G115 -- negative values are prevented by the schema validation
IgnoreNetworkErrors: p.GetProvider(ctx).BoolF(ViperKeyIgnoreNetworkErrors, true),
MinPasswordLength: uint(p.GetProvider(ctx).IntF(ViperKeyPasswordMinLength, 8)), // #nosec G115 -- negative values are prevented by the schema validation
IdentifierSimilarityCheckEnabled: p.GetProvider(ctx).BoolF(ViperKeyPasswordIdentifierSimilarityCheckEnabled, true),
}
}
func (p *Config) WebAuthnForPasswordless(ctx context.Context) bool {
return p.GetProvider(ctx).BoolF(ViperKeyWebAuthnPasswordless, false)
}
func (p *Config) WebAuthnConfig(ctx context.Context) *webauthn.Config {
scheme := p.SelfPublicURL(ctx).Scheme
id := p.GetProvider(ctx).String(ViperKeyWebAuthnRPID)
origin := p.GetProvider(ctx).String(ViperKeyWebAuthnRPOrigin)
origins := p.GetProvider(ctx).StringsF(ViperKeyWebAuthnRPOrigins, []string{cmp.Or(origin, scheme+"://"+id)})
return &webauthn.Config{
RPDisplayName: p.GetProvider(ctx).String(ViperKeyWebAuthnRPDisplayName),
RPID: id,
RPOrigins: origins,
AuthenticatorSelection: protocol.AuthenticatorSelection{
UserVerification: protocol.VerificationDiscouraged,
},
EncodeUserIDAsString: false,
}
}
func (p *Config) PasskeyConfig(ctx context.Context) *webauthn.Config {
scheme := p.SelfPublicURL(ctx).Scheme
id := p.GetProvider(ctx).String(ViperKeyPasskeyRPID)
origins := p.GetProvider(ctx).StringsF(ViperKeyPasskeyRPOrigins, []string{scheme + "://" + id})
residentKey := protocol.ResidentKeyRequirement(
p.GetProvider(ctx).StringF(ViperKeyPasskeyResidentKey, string(protocol.ResidentKeyRequirementRequired)))
requireResidentKey := residentKey == protocol.ResidentKeyRequirementRequired
authSel := protocol.AuthenticatorSelection{
RequireResidentKey: &requireResidentKey,
ResidentKey: residentKey,
UserVerification: protocol.UserVerificationRequirement(
p.GetProvider(ctx).StringF(ViperKeyPasskeyUserVerification, string(protocol.VerificationPreferred))),
}
// Only constrain authenticator attachment when the operator explicitly
// configures it. Omitting the field lets users register either platform
// or cross-platform authenticators, which matches the WebAuthn spec's
// "no preference" behavior.
if attachment := p.GetProvider(ctx).String(ViperKeyPasskeyAuthenticatorAttachment); attachment != "" {
authSel.AuthenticatorAttachment = protocol.AuthenticatorAttachment(attachment)
}
cfg := &webauthn.Config{
RPDisplayName: p.GetProvider(ctx).String(ViperKeyPasskeyRPDisplayName),
RPID: id,
RPOrigins: origins,
AuthenticatorSelection: authSel,
AttestationPreference: protocol.ConveyancePreference(
p.GetProvider(ctx).StringF(ViperKeyPasskeyAttestationPreference, string(protocol.PreferNoAttestation))),
EncodeUserIDAsString: false,
}
if d := p.GetProvider(ctx).Duration(ViperKeyPasskeyRegistrationTimeout); d > 0 {
cfg.Timeouts.Registration = webauthn.TimeoutConfig{
Timeout: d,
TimeoutUVD: d,
}
}
if d := p.GetProvider(ctx).Duration(ViperKeyPasskeyLoginTimeout); d > 0 {
cfg.Timeouts.Login = webauthn.TimeoutConfig{
Timeout: d,
TimeoutUVD: d,
}
}
return cfg
}
type Organization struct {
ID uuid.UUID `koanf:"id"`
Domains []string `koanf:"domains"`
DefaultRegion region.Region `koanf:"default_region"`
SessionLifespan time.Duration `koanf:"session_lifespan"`
}
func (p *Config) Organizations(ctx context.Context) (orgs []Organization) {
if err := p.GetProvider(ctx).Unmarshal(ViperKeyOrganizations, &orgs); err != nil {
return nil
}
return orgs
}
func (p *Config) HasherPasswordHashingAlgorithm(ctx context.Context) string {
configValue := p.GetProvider(ctx).StringF(ViperKeyHasherAlgorithm, DefaultPasswordHashingAlgorithm)
switch configValue {
case "bcrypt":
return configValue
case "argon2":
fallthrough
default:
return configValue
}
}
func (p *Config) CipherAlgorithm(ctx context.Context) string {
configValue := p.GetProvider(ctx).StringF(ViperKeyCipherAlgorithm, DefaultCipherAlgorithm)
switch configValue {
case "noop":
return configValue
case "xchacha20-poly1305":
return configValue
case "aes":
fallthrough
default:
return configValue
}
}
func (p *Config) GetProvider(ctx context.Context) *configx.Provider {
return p.c.Config(ctx, p.p)
}
type SessionTokenizeFormat struct {
TTL time.Duration `koanf:"ttl" json:"ttl"`
ClaimsMapperURL string `koanf:"claims_mapper_url" json:"claims_mapper_url"`
JWKSURL string `koanf:"jwks_url" json:"jwks_url"`
SubjectSource string `koanf:"subject_source" json:"subject_source"`
}
func (p *Config) TokenizeTemplate(ctx context.Context, key string) (_ *SessionTokenizeFormat, err error) {
var result SessionTokenizeFormat
path := ViperKeySessionTokenizerTemplates + "." + key
if !p.GetProvider(ctx).Exists(path) {
return nil, errors.WithStack(herodot.ErrBadRequest().WithReasonf("Unable to find tokenizer template \"%s\".", key))
}
if err := p.GetProvider(ctx).Unmarshal(path, &result); err != nil {
return nil, errors.WithStack(herodot.ErrMisconfiguration().WithReasonf("Unable to decode tokenizer template \"%s\": %s", key, err))
}
return &result, nil
}
func (p *Config) DefaultConsistencyLevel(ctx context.Context) crdbx.ConsistencyLevel {
return crdbx.ConsistencyLevelFromString(p.GetProvider(ctx).String(ViperKeyPreviewDefaultReadConsistencyLevel))
}
func (p *Config) PasswordMigrationHook(ctx context.Context) *PasswordMigrationHook {
hook := &PasswordMigrationHook{
Enabled: p.GetProvider(ctx).BoolF(ViperKeyPasswordMigrationHook+".enabled", false),
}
if !hook.Enabled {
return hook
}
_ = p.GetProvider(ctx).Unmarshal(ViperKeyPasswordMigrationHook+".config", &hook.Config)
return hook
}
func (p *Config) SelfServiceLoginFlowIdentifierFirstEnabled(ctx context.Context) bool {
switch p.GetProvider(ctx).String(ViperKeySelfServiceLoginFlowStyle) {
case "identifier_first":
return true
default:
return false
}
}
func (p *Config) SecurityAccountEnumerationMitigate(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeySecurityAccountEnumerationMitigate)
}
func (p *Config) SecurityDisallowRefInIdentitySchemas(ctx context.Context) bool {
return p.GetProvider(ctx).Bool(ViperKeySecurityDisallowRefInIdentitySchemas)
}