Files
zitadel/backend/v3/domain/session_challenge_util.go
T
Gayathri VijayanandGitHub f99142bc4a feat: implement otp sms session challenge (#11919)
# Which Problems Are Solved

This PR adds the OTP SMS Challenge needed for CreateSession API
https://github.com/zitadel/zitadel/issues/11035

# How the Problems Are Solved

- Introduce `OTPSMSChallengeCommand` (validate/execute/events) for
session creation checks
- Introduce `GetOTPCryptoGeneratorConfigWithDefault` to fetch
per-instance secret generator settings
- Add unit tests

# Additional Changes

Refactor tests in `session_check_recovery_code_test.go` to make the
helper functions reusable in `session_challenge_otp_sms_test.go`

# Additional Context
Related to https://github.com/zitadel/zitadel/issues/11035
2026-03-26 11:33:44 +00:00

77 lines
2.3 KiB
Go

package domain
import (
"context"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/zerrors"
)
type newOTPCodeFunc func(g crypto.Generator) (*crypto.CryptoValue, string, error)
type OTPType int64
const (
OTPTypeSMS OTPType = iota
OTPTypeEmail
)
func GetOTPCryptoGeneratorConfigWithDefault(ctx context.Context, instanceID string, opts *InvokeOpts, defaultConfig *crypto.GeneratorConfig, otpType OTPType) (*crypto.GeneratorConfig, error) {
if defaultConfig == nil {
return nil, zerrors.ThrowInternal(nil, "DOM-3AcM0U", "missing default config")
}
if opts == nil || opts.secretGeneratorSettingsRepo == nil {
return defaultConfig, nil
}
settingsRepo := opts.secretGeneratorSettingsRepo
cfg, err := settingsRepo.Get(
ctx,
opts.DB(),
database.WithCondition(
settingsRepo.UniqueCondition(instanceID, nil, SettingTypeSecretGenerator, SettingStateActive),
),
)
if err := handleGetError(err, "DOM-x7Yd3E", "SecretGeneratorSettings"); err != nil {
return nil, err // todo: or return defaultConfig?
}
if cfg.State != SettingStateActive {
return defaultConfig, nil
}
var attrs SecretGeneratorAttrsWithExpiry
switch otpType {
case OTPTypeSMS:
if cfg.OTPSMS == nil {
return defaultConfig, nil
}
attrs = cfg.OTPSMS.SecretGeneratorAttrsWithExpiry
case OTPTypeEmail:
if cfg.OTPEmail == nil {
return defaultConfig, nil
}
attrs = cfg.OTPEmail.SecretGeneratorAttrsWithExpiry
default:
return nil, zerrors.ThrowInternal(nil, "DOM-3AcM0U", "invalid otp type")
}
return &crypto.GeneratorConfig{
Length: getValueOrDefault(attrs.Length, defaultConfig.Length),
Expiry: getValueOrDefault(attrs.Expiry, defaultConfig.Expiry),
IncludeLowerLetters: getValueOrDefault(attrs.IncludeLowerLetters, defaultConfig.IncludeLowerLetters),
IncludeUpperLetters: getValueOrDefault(attrs.IncludeUpperLetters, defaultConfig.IncludeUpperLetters),
IncludeDigits: getValueOrDefault(attrs.IncludeDigits, defaultConfig.IncludeDigits),
IncludeSymbols: getValueOrDefault(attrs.IncludeSymbols, defaultConfig.IncludeSymbols),
}, nil
}
// getValueOrDefault safely dereferences a pointer or returns a default value
func getValueOrDefault[T any](ptr *T, defaultVal T) T {
if ptr != nil {
return *ptr
}
return defaultVal
}