Skip to content

Commit

Permalink
feat(web): user one-time password preferences
Browse files Browse the repository at this point in the history
This allows administrators to configure a list of Time-based One-Time Password parameters that users can pick from the web UI during registrations.

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
  • Loading branch information
james-d-elliott committed Mar 4, 2024
1 parent c0dbdd9 commit 87d2a34
Show file tree
Hide file tree
Showing 70 changed files with 1,951 additions and 574 deletions.
15 changes: 15 additions & 0 deletions docs/data/configkeys.json
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,21 @@
"secret": false,
"env": "AUTHELIA_TOTP_SECRET_SIZE"
},
{
"path": "totp.allowed_algorithms",
"secret": false,
"env": "AUTHELIA_TOTP_ALLOWED_ALGORITHMS"
},
{
"path": "totp.allowed_digits",
"secret": false,
"env": "AUTHELIA_TOTP_ALLOWED_DIGITS"
},
{
"path": "totp.allowed_periods",
"secret": false,
"env": "AUTHELIA_TOTP_ALLOWED_PERIODS"
},
{
"path": "duo_api.disable",
"secret": false,
Expand Down
36 changes: 36 additions & 0 deletions docs/static/schemas/latest/json-schema/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -3137,6 +3137,42 @@
"title": "Secret Size",
"description": "The secret size for generated TOTP keys",
"default": 32
},
"allowed_algorithms": {
"items": {
"type": "string",
"enum": [
"SHA1",
"SHA256",
"SHA512"
]
},
"type": "array",
"title": "Allowed Algorithms",
"description": "List of algorithms the user is allowed to select in addition to the default"
},
"allowed_digits": {
"items": {
"type": "integer",
"enum": [
6,
8
]
},
"type": "array",
"title": "Allowed Digits",
"description": "List of digits the user is allowed to select in addition to the default",
"default": [
6
]
},
"allowed_periods": {
"items": {
"type": "integer"
},
"type": "array",
"title": "Allowed Periods",
"description": "List of periods the user is allowed to select in addition to the default"
}
},
"additionalProperties": false,
Expand Down
36 changes: 36 additions & 0 deletions docs/static/schemas/v4.38/json-schema/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -3137,6 +3137,42 @@
"title": "Secret Size",
"description": "The secret size for generated TOTP keys",
"default": 32
},
"allowed_algorithms": {
"items": {
"type": "string",
"enum": [
"SHA1",
"SHA256",
"SHA512"
]
},
"type": "array",
"title": "Allowed Algorithms",
"description": "List of algorithms the user is allowed to select in addition to the default"
},
"allowed_digits": {
"items": {
"type": "integer",
"enum": [
6,
8
]
},
"type": "array",
"title": "Allowed Digits",
"description": "List of digits the user is allowed to select in addition to the default",
"default": [
6
]
},
"allowed_periods": {
"items": {
"type": "integer"
},
"type": "array",
"title": "Allowed Periods",
"description": "List of periods the user is allowed to select in addition to the default"
}
},
"additionalProperties": false,
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/storage_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@ func (ctx *CmdCtx) StorageUserTOTPGenerateRunE(cmd *cobra.Command, args []string

totpProvider := totp.NewTimeBasedProvider(ctx.config.TOTP)

if c, err = totpProvider.GenerateCustom(args[0], ctx.config.TOTP.Algorithm, secret, ctx.config.TOTP.Digits, ctx.config.TOTP.Period, ctx.config.TOTP.SecretSize); err != nil {
if c, err = totpProvider.GenerateCustom(args[0], ctx.config.TOTP.DefaultAlgorithm, secret, uint(ctx.config.TOTP.DefaultDigits), uint(ctx.config.TOTP.DefaultPeriod), uint(ctx.config.TOTP.SecretSize)); err != nil {
return err
}

Expand Down
3 changes: 3 additions & 0 deletions internal/configuration/schema/keys.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 21 additions & 14 deletions internal/configuration/schema/totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,30 @@ package schema

// TOTP represents the configuration related to TOTP options.
type TOTP struct {
Disable bool `koanf:"disable" json:"disable" jsonschema:"default=false,title=Disable" jsonschema_description:"Disables the TOTP 2FA functionality"`
Issuer string `koanf:"issuer" json:"issuer" jsonschema:"default=Authelia,title=Issuer" jsonschema_description:"The issuer value for generated TOTP keys"`
Algorithm string `koanf:"algorithm" json:"algorithm" jsonschema:"default=SHA1,enum=SHA1,enum=SHA256,enum=SHA512,title=Algorithm" jsonschema_description:"The algorithm value for generated TOTP keys"`
Digits uint `koanf:"digits" json:"digits" jsonschema:"default=6,enum=6,enum=8,title=Digits" jsonschema_description:"The digits value for generated TOTP keys"`
Period uint `koanf:"period" json:"period" jsonschema:"default=30,title=Period" jsonschema_description:"The period value for generated TOTP keys"`
Skew *uint `koanf:"skew" json:"skew" jsonschema:"default=1,title=Skew" jsonschema_description:"The permitted skew for generated TOTP keys"`
SecretSize uint `koanf:"secret_size" json:"secret_size" jsonschema:"default=32,minimum=20,title=Secret Size" jsonschema_description:"The secret size for generated TOTP keys"`
Disable bool `koanf:"disable" json:"disable" jsonschema:"default=false,title=Disable" jsonschema_description:"Disables the TOTP 2FA functionality"`
Issuer string `koanf:"issuer" json:"issuer" jsonschema:"default=Authelia,title=Issuer" jsonschema_description:"The issuer value for generated TOTP keys"`
DefaultAlgorithm string `koanf:"algorithm" json:"algorithm" jsonschema:"default=SHA1,enum=SHA1,enum=SHA256,enum=SHA512,title=Algorithm" jsonschema_description:"The algorithm value for generated TOTP keys"`
DefaultDigits int `koanf:"digits" json:"digits" jsonschema:"default=6,enum=6,enum=8,title=Digits" jsonschema_description:"The digits value for generated TOTP keys"`
DefaultPeriod int `koanf:"period" json:"period" jsonschema:"default=30,title=Period" jsonschema_description:"The period value for generated TOTP keys"`
Skew *int `koanf:"skew" json:"skew" jsonschema:"default=1,title=Skew" jsonschema_description:"The permitted skew for generated TOTP keys"`
SecretSize int `koanf:"secret_size" json:"secret_size" jsonschema:"default=32,minimum=20,title=Secret Size" jsonschema_description:"The secret size for generated TOTP keys"`

AllowedAlgorithms []string `koanf:"allowed_algorithms" json:"allowed_algorithms" jsonschema:"title=Allowed Algorithms,enum=SHA1,enum=SHA256,enum=SHA512" jsonschema_description:"List of algorithms the user is allowed to select in addition to the default"`
AllowedDigits []int `koanf:"allowed_digits" json:"allowed_digits" jsonschema:"title=Allowed Digits,enum=6,enum=8,default=6" jsonschema_description:"List of digits the user is allowed to select in addition to the default"`
AllowedPeriods []int `koanf:"allowed_periods" json:"allowed_periods" jsonschema:"title=Allowed Periods" jsonschema_description:"List of periods the user is allowed to select in addition to the default"`
}

var defaultOtpSkew = uint(1)
var defaultTOTPSkew = 1

// DefaultTOTPConfiguration represents default configuration parameters for TOTP generation.
var DefaultTOTPConfiguration = TOTP{
Issuer: "Authelia",
Algorithm: TOTPAlgorithmSHA1,
Digits: 6,
Period: 30,
Skew: &defaultOtpSkew,
SecretSize: TOTPSecretSizeDefault,
Issuer: "Authelia",
DefaultAlgorithm: TOTPAlgorithmSHA1,
DefaultDigits: 6,
DefaultPeriod: 30,
Skew: &defaultTOTPSkew,
SecretSize: TOTPSecretSizeDefault,
AllowedAlgorithms: []string{TOTPAlgorithmSHA1},
AllowedDigits: []int{6},
AllowedPeriods: []int{30},
}
11 changes: 7 additions & 4 deletions internal/configuration/validator/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,13 @@ const (

// TOTP Error constants.
const (
errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of %s but it's configured as '%s'"
errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it's configured as '%d'"
errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it's configured as '%d'"
errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it's configured as '%d'" //nolint:gosec
errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of %s but it's configured as '%s'"
errFmtTOTPInvalidAllowedAlgorithm = "totp: option 'allowed_algorithm' must be one of %s but one of the values is '%s'"
errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it's configured as '%d'"
errFmtTOTPInvalidAllowedPeriod = "totp: option 'allowed_periods' option must be 15 or more but one of the values is '%d'"
errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it's configured as '%d'"
errFmtTOTPInvalidAllowedDigit = "totp: option 'allowed_digits' must only have the values 6 or 8 but one of the values is '%d'"
errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it's configured as '%d'" //nolint:gosec
)

// Storage Error constants.
Expand Down
94 changes: 74 additions & 20 deletions internal/configuration/validator/totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)

// ValidateTOTP validates and update TOTP configuration.
// ValidateTOTP validates and updates TOTP configuration.
func ValidateTOTP(config *schema.Configuration, validator *schema.StructValidator) {
if config.TOTP.Disable {
return
Expand All @@ -18,35 +18,89 @@ func ValidateTOTP(config *schema.Configuration, validator *schema.StructValidato
config.TOTP.Issuer = schema.DefaultTOTPConfiguration.Issuer
}

if config.TOTP.Algorithm == "" {
config.TOTP.Algorithm = schema.DefaultTOTPConfiguration.Algorithm
validateTOTPValueSetAlgorithm(config, validator)
validateTOTPValueSetPeriod(config, validator)
validateTOTPValueSetDigits(config, validator)

if config.TOTP.Skew == nil {
config.TOTP.Skew = schema.DefaultTOTPConfiguration.Skew
}

if config.TOTP.SecretSize == 0 {
config.TOTP.SecretSize = schema.DefaultTOTPConfiguration.SecretSize
} else if config.TOTP.SecretSize < schema.TOTPSecretSizeMinimum {
validator.Push(fmt.Errorf(errFmtTOTPInvalidSecretSize, schema.TOTPSecretSizeMinimum, config.TOTP.SecretSize))
}
}

func validateTOTPValueSetAlgorithm(config *schema.Configuration, validator *schema.StructValidator) {
if config.TOTP.DefaultAlgorithm == "" {
config.TOTP.DefaultAlgorithm = schema.DefaultTOTPConfiguration.DefaultAlgorithm
} else {
config.TOTP.Algorithm = strings.ToUpper(config.TOTP.Algorithm)
config.TOTP.DefaultAlgorithm = strings.ToUpper(config.TOTP.DefaultAlgorithm)

if !utils.IsStringInSlice(config.TOTP.Algorithm, schema.TOTPPossibleAlgorithms) {
validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strJoinOr(schema.TOTPPossibleAlgorithms), config.TOTP.Algorithm))
if !utils.IsStringInSlice(config.TOTP.DefaultAlgorithm, schema.TOTPPossibleAlgorithms) {
validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strJoinOr(schema.TOTPPossibleAlgorithms), config.TOTP.DefaultAlgorithm))
}
}

if config.TOTP.Period == 0 {
config.TOTP.Period = schema.DefaultTOTPConfiguration.Period
} else if config.TOTP.Period < 15 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidPeriod, config.TOTP.Period))
for i, algorithm := range config.TOTP.AllowedAlgorithms {
config.TOTP.AllowedAlgorithms[i] = strings.ToUpper(algorithm)

if !utils.IsStringInSlice(config.TOTP.AllowedAlgorithms[i], schema.TOTPPossibleAlgorithms) {
validator.Push(fmt.Errorf(errFmtTOTPInvalidAllowedAlgorithm, strJoinOr(schema.TOTPPossibleAlgorithms), config.TOTP.AllowedAlgorithms[i]))
}
}

if config.TOTP.Digits == 0 {
config.TOTP.Digits = schema.DefaultTOTPConfiguration.Digits
} else if config.TOTP.Digits != 6 && config.TOTP.Digits != 8 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidDigits, config.TOTP.Digits))
if !utils.IsStringInSlice(config.TOTP.DefaultAlgorithm, config.TOTP.AllowedAlgorithms) {
config.TOTP.AllowedAlgorithms = append(config.TOTP.AllowedAlgorithms, config.TOTP.DefaultAlgorithm)
}
}

if config.TOTP.Skew == nil {
config.TOTP.Skew = schema.DefaultTOTPConfiguration.Skew
func validateTOTPValueSetPeriod(config *schema.Configuration, validator *schema.StructValidator) {
if config.TOTP.DefaultPeriod == 0 {
config.TOTP.DefaultPeriod = schema.DefaultTOTPConfiguration.DefaultPeriod
} else if config.TOTP.DefaultPeriod < 15 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidPeriod, config.TOTP.DefaultPeriod))
}

if config.TOTP.SecretSize == 0 {
config.TOTP.SecretSize = schema.DefaultTOTPConfiguration.SecretSize
} else if config.TOTP.SecretSize < schema.TOTPSecretSizeMinimum {
validator.Push(fmt.Errorf(errFmtTOTPInvalidSecretSize, schema.TOTPSecretSizeMinimum, config.TOTP.SecretSize))
var hasDefaultPeriod bool

for _, period := range config.TOTP.AllowedPeriods {
if period < 15 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidAllowedPeriod, period))
}

if period == config.TOTP.DefaultPeriod {
hasDefaultPeriod = true
}
}

if !hasDefaultPeriod {
config.TOTP.AllowedPeriods = append(config.TOTP.AllowedPeriods, config.TOTP.DefaultPeriod)
}
}

func validateTOTPValueSetDigits(config *schema.Configuration, validator *schema.StructValidator) {
if config.TOTP.DefaultDigits == 0 {
config.TOTP.DefaultDigits = schema.DefaultTOTPConfiguration.DefaultDigits
} else if config.TOTP.DefaultDigits != 6 && config.TOTP.DefaultDigits != 8 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidDigits, config.TOTP.DefaultDigits))
}

var hasDefaultDigits bool

for _, digits := range config.TOTP.AllowedDigits {
if digits != 6 && digits != 8 {
validator.Push(fmt.Errorf(errFmtTOTPInvalidAllowedDigit, config.TOTP.DefaultDigits))
}

if digits == config.TOTP.DefaultDigits {
hasDefaultDigits = true
}
}

if !hasDefaultDigits {
config.TOTP.AllowedDigits = append(config.TOTP.AllowedDigits, config.TOTP.DefaultDigits)
}
}

0 comments on commit 87d2a34

Please sign in to comment.