Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update default config, adjust and re-generate json schema #1556

Merged
merged 8 commits into from
Aug 7, 2024
12 changes: 4 additions & 8 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ a user/password, so a minimal configuration in your configuration file (`backend
your own `*.yaml` file) could contain the following:

```yaml
passcode:
email_delivery:
enabled: true
email:
from_address: [email protected]
from_name: Example Application
Expand All @@ -189,13 +190,8 @@ service:
```

In a production setting you would rather use a self-hosted SMTP server or a managed service like AWS SES. In that case
you need to supply the `passcode.smtp.host`, `passcode.smtp.port` as well as the `passcode.smtp.user`,
`passcode.smtp.password` settings according to your server/service settings.

> **Note** The `passcode.smtp.host` configuration entry is required for the service to start up.
> Only a check for a non-empty string value will be performed. Also: SMTP-connection related values are not
> verified, i.e. the application may start but no emails will be sent and your users might not be able to log in if
> the provided values do not describe an existing SMTP server.
you need to supply the `email_delivery.smtp.host`, `email_delivery.smtp.port` as well as the `email_delivery.smtp.user`,
`email_delivery.smtp.password` settings according to your server/service settings.

### Configure JSON Web Key Set generation

Expand Down
86 changes: 19 additions & 67 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
zeroLogger "github.com/rs/zerolog/log"
"github.com/teamhanko/hanko/backend/ee/saml/config"
"golang.org/x/exp/slices"
)
Expand All @@ -32,10 +31,10 @@ type Config struct {
// options, to new ones. If set to `false`, these values have to be set manually if non-default values should be
// used.
ConvertLegacyConfig bool `yaml:"convert_legacy_config" json:"convert_legacy_config,omitempty" koanf:"convert_legacy_config" split_words:"true" jsonschema:"default=false"`
// `database configures database connection settings.
// `database` configures database connection settings.
Database Database `yaml:"database" json:"database,omitempty" koanf:"database" jsonschema:"title=database"`
// `debug`, if set to `true`, adds additional debugging information to flow API responses.
Debug bool `yaml:"debug" json:"debug,omitempty" koanf:"debug"`
Debug bool `yaml:"debug" json:"debug,omitempty" koanf:"debug" jsonschema:"default=false"`
// `email` configures how email addresses of user accounts are acquired and used.
Email Email `yaml:"email" json:"email,omitempty" koanf:"email" jsonschema:"title=email"`
// `email_delivery` configures how outgoing mails are delivered.
Expand Down Expand Up @@ -115,7 +114,8 @@ func Load(cfgFile *string) (*Config, error) {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}

if err := envconfig.Process("", c); err != nil {
err = envconfig.Process("", c)
if err != nil {
return nil, fmt.Errorf("failed to load config from env vars: %w", err)
}

Expand Down Expand Up @@ -146,10 +146,6 @@ func (c *Config) Validate() error {
return fmt.Errorf("failed to validate smtp settings: %w", err)
}
}
err = c.Passcode.Validate()
if err != nil {
return fmt.Errorf("failed to validate passcode settings: %w", err)
}
err = c.Database.Validate()
if err != nil {
return fmt.Errorf("failed to validate database settings: %w", err)
Expand Down Expand Up @@ -219,11 +215,11 @@ func (s *Service) Validate() error {

type Password struct {
// `acquire_on_registration` configures how users are prompted creating a password on registration.
AcquireOnRegistration string `yaml:"acquire_on_registration" json:"acquire_on_registration,omitempty" koanf:"acquire_on_registration" split_words:"true" jsonschema:"default=never,enum=always,enum=conditional,enum=never"`
AcquireOnRegistration string `yaml:"acquire_on_registration" json:"acquire_on_registration,omitempty" koanf:"acquire_on_registration" split_words:"true" jsonschema:"default=always,enum=always,enum=conditional,enum=never"`
// `acquire_on_login` configures how users are prompted creating a password on login.
AcquireOnLogin string `yaml:"acquire_on_login" json:"acquire_on_login,omitempty" koanf:"acquire_on_login" split_words:"true" jsonschema:"default=always,enum=always,enum=conditional,enum=never"`
AcquireOnLogin string `yaml:"acquire_on_login" json:"acquire_on_login,omitempty" koanf:"acquire_on_login" split_words:"true" jsonschema:"default=never,enum=always,enum=conditional,enum=never"`
// `enabled` determines whether passwords are enabled or disabled.
Enabled bool `yaml:"enabled" json:"enabled,omitempty" koanf:"enabled" jsonschema:"default=false"`
Enabled bool `yaml:"enabled" json:"enabled,omitempty" koanf:"enabled" jsonschema:"default=true"`
// `min_length` determines the minimum password length.
MinLength int `yaml:"min_length" json:"min_length,omitempty" koanf:"min_length" split_words:"true" jsonschema:"default=8"`
// Deprecated. Use `min_length` instead.
Expand Down Expand Up @@ -343,10 +339,10 @@ func (s *ServerSettings) Validate() error {
type WebauthnTimeouts struct {
// `registration` determines the time, in milliseconds, that the client is willing to wait for the credential
// creation request to the WebAuthn API to complete.
Registration int `yaml:"registration" json:"registration,omitempty" koanf:"registration" jsonschema:"default=60000"`
Registration int `yaml:"registration" json:"registration,omitempty" koanf:"registration" jsonschema:"default=600000"`
// `login` determines the time, in milliseconds, that the client is willing to wait for the credential
// request to the WebAuthn API to complete.
Login int `yaml:"login" json:"login,omitempty" koanf:"login" jsonschema:"default=60000"`
Login int `yaml:"login" json:"login,omitempty" koanf:"login" jsonschema:"default=600000"`
}

// WebauthnSettings defines the settings for the webauthn authentication mechanism
Expand Down Expand Up @@ -427,7 +423,7 @@ type RelyingParty struct {

// SMTP Server Settings for sending passcodes
type SMTP struct {
Host string `yaml:"host" json:"host,omitempty" koanf:"host"`
Host string `yaml:"host" json:"host,omitempty" koanf:"host" jsonschema:"default=localhost"`
Port string `yaml:"port" json:"port,omitempty" koanf:"port" jsonschema:"default=465"`
User string `yaml:"user" json:"user,omitempty" koanf:"user"`
Password string `yaml:"password" json:"password,omitempty" koanf:"password"`
Expand All @@ -443,35 +439,9 @@ func (s *SMTP) Validate() error {
return nil
}

type PasscodeEmail struct {
// Deprecated. Use `email_delivery.from_address` instead.
FromAddress string `yaml:"from_address" json:"from_address,omitempty" koanf:"from_address" split_words:"true" jsonschema:"[email protected]"`
// Deprecated. Use `email_delivery.from_name` instead.
FromName string `yaml:"from_name" json:"from_name,omitempty" koanf:"from_name" split_words:"true" jsonschema:"default=Hanko"`
}

func (e *PasscodeEmail) Validate() error {
if len(strings.TrimSpace(e.FromAddress)) == 0 {
return errors.New("from_address must not be empty")
}
return nil
}

type Passcode struct {
// Deprecated. See child properties for suggested replacements.
Email PasscodeEmail `yaml:"email" json:"email,omitempty" koanf:"email"`
// Deprecated. Use `email.passcode_ttl` instead.
TTL int `yaml:"ttl" json:"ttl,omitempty" koanf:"ttl" jsonschema:"default=300"`
// Deprecated. Use `email_delivery.smtp` instead.
Smtp SMTP `yaml:"smtp" json:"smtp,omitempty" koanf:"smtp,omitempty" required:"false" envconfig:"smtp,omitempty"`
}

func (p *Passcode) Validate() error {
err := p.Email.Validate()
if err != nil {
return fmt.Errorf("failed to validate email settings: %w", err)
}
return nil
}

type Database struct {
Expand Down Expand Up @@ -562,7 +532,7 @@ type Session struct {
// `lifespan` determines how long a session token (JWT) is valid. It must be a (possibly signed) sequence of decimal
// numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Lifespan string `yaml:"lifespan" json:"lifespan,omitempty" koanf:"lifespan" jsonschema:"default=1h"`
Lifespan string `yaml:"lifespan" json:"lifespan,omitempty" koanf:"lifespan" jsonschema:"default=12h"`
}

func (s *Session) Validate() error {
Expand Down Expand Up @@ -876,8 +846,6 @@ func (c *Config) convertLegacyConfig() {
c.Email.PasscodeTtl = c.Passcode.TTL

c.EmailDelivery.SMTP = c.Smtp
c.EmailDelivery.FromName = c.Passcode.Email.FromName
c.EmailDelivery.FromAddress = c.Passcode.Email.FromAddress

c.Password.MinLength = c.Password.MinPasswordLength

Expand All @@ -888,8 +856,6 @@ func (c *Config) convertLegacyConfig() {
}

func (c *Config) PostProcess() error {
c.arrangeSmtpSettings()

if c.ConvertLegacyConfig {
c.convertLegacyConfig()
}
Expand All @@ -912,20 +878,6 @@ func (c *Config) PostProcess() error {
return nil
}

func (c *Config) arrangeSmtpSettings() {
if !c.EmailDelivery.Enabled {
return
}
if c.Passcode.Smtp.Validate() == nil {
if c.Smtp.Validate() == nil {
zeroLogger.Warn().Msg("Both root smtp and passcode.smtp are set. Using smtp settings from root configuration")
return
}

c.Smtp = c.Passcode.Smtp
}
}

type LoggerConfig struct {
// `log_health_and_metrics` determines whether requests of the `/health` and `/metrics` endpoints are logged.
LogHealthAndMetrics bool `yaml:"log_health_and_metrics,omitempty" json:"log_health_and_metrics" koanf:"log_health_and_metrics" jsonschema:"default=true"`
Expand All @@ -949,7 +901,7 @@ type Passkey struct {
// `enabled` determines whether users can create or authenticate with passkeys.
Enabled bool `yaml:"enabled" json:"enabled,omitempty" koanf:"enabled" jsonschema:"default=true"`
// `limit` defines the maximum number of passkeys a user can have.
Limit int `yaml:"limit" json:"limit,omitempty" koanf:"limit" jsonschema:"default=100"`
Limit int `yaml:"limit" json:"limit,omitempty" koanf:"limit" jsonschema:"default=10"`
// `optional` determines whether users must create a passkey when prompted. The last remaining passkey cannot be
// deleted if passkeys are required (`optional: false`).
//
Expand Down Expand Up @@ -1029,13 +981,13 @@ type Email struct {
// `enabled` determines whether emails are enabled.
Enabled bool `yaml:"enabled" json:"enabled,omitempty" koanf:"enabled" jsonschema:"default=true"`
// 'limit' determines the maximum number of emails a user can register.
Limit int `yaml:"limit" json:"limit,omitempty" koanf:"limit" jsonschema:"default=100"`
Limit int `yaml:"limit" json:"limit,omitempty" koanf:"limit" jsonschema:"default=5"`
// `max_length` specifies the maximum allowed length of an email address.
MaxLength int `yaml:"max_length" json:"max_length,omitempty" koanf:"max_length" jsonschema:"default=100"`
// `optional` determines whether users must provide an email when prompted.
// There must always be at least one email address associated with an account. The primary email address cannot be
// deleted if emails are required (`optional`: false`).
Optional bool `yaml:"optional" json:"optional,omitempty" koanf:"optional" jsonschema:"default=true"`
Optional bool `yaml:"optional" json:"optional,omitempty" koanf:"optional" jsonschema:"default=false"`
// `passcode_ttl` specifies, in seconds, how long a passcode is valid for.
PasscodeTtl int `yaml:"passcode_ttl" json:"passcode_ttl,omitempty" koanf:"passcode_ttl" jsonschema:"default=300"`
// `require_verification` determines whether newly created emails must be verified by providing a passcode sent
Expand All @@ -1051,17 +1003,17 @@ type Email struct {
type Username struct {
// `acquire_on_login` determines whether users, provided that they do not already have set a username,
// are prompted to provide a username on login.
AcquireOnLogin bool `yaml:"acquire_on_login" json:"acquire_on_login,omitempty" koanf:"acquire_on_login" split_words:"true" jsonschema:"default=false"`
AcquireOnLogin bool `yaml:"acquire_on_login" json:"acquire_on_login,omitempty" koanf:"acquire_on_login" split_words:"true" jsonschema:"default=true"`
// `acquire_on_registration` determines whether users are prompted to provide a username on registration.
AcquireOnRegistration bool `yaml:"acquire_on_registration" json:"acquire_on_registration,omitempty" koanf:"acquire_on_registration" split_words:"true" jsonschema:"default=false"`
AcquireOnRegistration bool `yaml:"acquire_on_registration" json:"acquire_on_registration,omitempty" koanf:"acquire_on_registration" split_words:"true" jsonschema:"default=true"`
// `enabled` determines whether users can set a unique username.
//
// Usernames can contain letters (a-z,A-Z), numbers (0-9), and underscores.
Enabled bool `yaml:"enabled" json:"enabled,omitempty" koanf:"enabled" jsonschema:"default=true"`
Enabled bool `yaml:"enabled" json:"enabled,omitempty" koanf:"enabled" jsonschema:"default=false"`
// `max_length` specifies the maximum allowed length of a username.
MaxLength int `yaml:"max_length" json:"max_length,omitempty" koanf:"max_length" jsonschema:"default=100"`
MaxLength int `yaml:"max_length" json:"max_length,omitempty" koanf:"max_length" jsonschema:"default=32"`
// `min_length` specifies the minimum length of a username.
MinLength int `yaml:"min_length" json:"min_length,omitempty" koanf:"min_length" split_words:"true" jsonschema:"default=8"`
MinLength int `yaml:"min_length" json:"min_length,omitempty" koanf:"min_length" split_words:"true" jsonschema:"default=3"`
// `optional` determines whether users must provide a username when prompted. The username can only be changed but
// not deleted if usernames are required (`optional: false`).
Optional bool `yaml:"optional" json:"optional,omitempty" koanf:"optional" jsonschema:"default=true"`
Expand Down
136 changes: 89 additions & 47 deletions backend/config/config.yaml
Original file line number Diff line number Diff line change
@@ -1,68 +1,110 @@
account:
allow_deletion: true
allow_signup: true
convert_legacy_config: false
database:
user: hanko
password: hanko
host: localhost
port: "5432"
dialect: postgres
smtp:
host: localhost
port: 2500
server:
public:
cors:
allow_origins:
- http://localhost:63342
- http://localhost:8000/
passcode:
email:
from_address: [email protected]
secrets:
keys:
- abcedfghijklmnopqrstuvwxyz
service:
name: Hanko Authentication Service
webauthn:
relying_party:
id: localhost
origins:
- http://localhost:63342
- http://localhost:8000
password:
enabled: true
optional: false
acquire_on_registration: conditional
acquire_on_login: conditional
recovery: true
passkey:
enabled: true
optional: false
acquire_on_registration: conditional
acquire_on_login: conditional
attestation_preference: direct
limit: 100
debug: false
email:
enabled: true
optional: false
acquire_on_registration: true
acquire_on_login: true
acquire_on_login: false
require_verification: true
limit: 100
limit: 5
use_as_login_identifier: true
max_length: 100
use_for_authentication: true
passcode_ttl: 300
username:
email_delivery:
enabled: true
from_address: [email protected]
from_name: Hanko
smtp:
host: localhost
port: "2500"
log:
log_health_and_metrics: true
passkey:
enabled: true
optional: true
acquire_on_registration: always
acquire_on_login: always
attestation_preference: direct
user_verification: preferred
limit: 10
password:
enabled: true
optional: false
acquire_on_registration: always
acquire_on_login: never
recovery: true
min_length: 8
rate_limiter:
enabled: true
store: in_memory
passcode_limits:
tokens: 3
interval: 1m
password_limits:
tokens: 3
interval: 1m
token_limits:
tokens: 3
interval: 1m
saml:
enabled: false
secrets:
keys:
- abcedfghijklmnopqrstuvwxyz
server:
public:
cors:
allow_origins:
- http://localhost:63342
- http://localhost:8888
- http://localhost:8000
service:
name: Hanko Authentication Service
session:
lifespan: 12h
enable_auth_token_header: false
third_party:
providers:
apple:
enabled: false
discord:
enabled: false
github:
enabled: false
google:
enabled: false
linkedin:
enabled: false
microsoft:
enabled: false
username:
enabled: false
optional: true
acquire_on_registration: true
acquire_on_login: true
use_as_login_identifier: true
max_length: 100
rate_limiter:
min_length: 3
max_length: 32
webauthn:
timeouts:
registration: 600000
login: 600000
relying_party:
id: localhost
origins:
- http://localhost:63342
- http://localhost:8888
- http://localhost:8000
webhooks:
enabled: false
email_delivery:
from_address: [email protected]
smtp:
host: localhost
port: 2500
convert_legacy_config: false
allow_time_expiration: false
Loading
Loading