2026-05-20 07:01:08 +00:00
|
|
|
package usecase
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2026-05-20 13:03:59 +00:00
|
|
|
libcrypto "gateway/internal/library/crypto"
|
2026-05-20 23:51:22 +00:00
|
|
|
libmongo "gateway/internal/library/mongo"
|
2026-05-20 07:01:08 +00:00
|
|
|
redislib "gateway/internal/library/redis"
|
|
|
|
|
memberconfig "gateway/internal/model/member/config"
|
|
|
|
|
domrepo "gateway/internal/model/member/domain/repository"
|
|
|
|
|
domusecase "gateway/internal/model/member/domain/usecase"
|
|
|
|
|
"gateway/internal/model/member/repository"
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-20 13:03:59 +00:00
|
|
|
// Module bundles member atomic primitives. Each entry is a single-purpose
|
|
|
|
|
// usecase; composite flows (e.g. "send verification email then mark
|
|
|
|
|
// business_email verified") are assembled at the logic / driver layer and
|
|
|
|
|
// MUST NOT live inside another usecase.
|
2026-05-20 07:01:08 +00:00
|
|
|
type Module struct {
|
2026-05-20 23:51:22 +00:00
|
|
|
OTP domusecase.OTPUseCase
|
|
|
|
|
TOTP domusecase.TOTPUseCase
|
|
|
|
|
Profile domusecase.ProfileUseCase
|
|
|
|
|
Lifecycle domusecase.LifecycleUseCase
|
|
|
|
|
Provisioning domusecase.ProvisioningUseCase
|
|
|
|
|
Tenant domusecase.TenantUseCase
|
2026-05-21 06:45:35 +00:00
|
|
|
VerifyRate domusecase.VerifyRateUseCase
|
2026-05-20 13:03:59 +00:00
|
|
|
|
2026-05-20 23:51:22 +00:00
|
|
|
Members domrepo.MemberRepository
|
|
|
|
|
Tenants domrepo.TenantRepository
|
|
|
|
|
Identities domrepo.IdentityRepository
|
2026-05-20 07:01:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ModuleParam wires member module dependencies.
|
|
|
|
|
type ModuleParam struct {
|
2026-05-20 23:51:22 +00:00
|
|
|
Redis *redislib.Client
|
|
|
|
|
MongoConf *libmongo.Conf
|
|
|
|
|
Config memberconfig.Config
|
|
|
|
|
// Optional overrides for tests.
|
|
|
|
|
Members domrepo.MemberRepository
|
|
|
|
|
Tenants domrepo.TenantRepository
|
|
|
|
|
Identities domrepo.IdentityRepository
|
2026-05-20 13:03:59 +00:00
|
|
|
TOTPProfile domrepo.TOTPProfileRepository
|
2026-05-20 23:51:22 +00:00
|
|
|
UIDGen domrepo.UIDGenerator
|
2026-05-20 07:01:08 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-20 13:03:59 +00:00
|
|
|
// NewModuleFromParam builds member atomic usecases.
|
2026-05-20 07:01:08 +00:00
|
|
|
func NewModuleFromParam(param ModuleParam) (*Module, error) {
|
|
|
|
|
if param.Redis == nil || param.Redis.Zero() == nil {
|
|
|
|
|
return nil, fmt.Errorf("member: redis is required")
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 23:51:22 +00:00
|
|
|
cfg := param.Config.Defaults()
|
2026-05-20 07:01:08 +00:00
|
|
|
otpStore := repository.NewRedisOTPChallengeStore(param.Redis)
|
|
|
|
|
rateStore := repository.NewRedisVerifyRateStore(param.Redis)
|
2026-05-20 23:51:22 +00:00
|
|
|
|
|
|
|
|
members := param.Members
|
|
|
|
|
tenants := param.Tenants
|
|
|
|
|
identities := param.Identities
|
|
|
|
|
uidGen := param.UIDGen
|
|
|
|
|
totpProfile := param.TOTPProfile
|
|
|
|
|
|
|
|
|
|
if param.MongoConf != nil && param.MongoConf.Host != "" {
|
|
|
|
|
if members == nil {
|
|
|
|
|
members = repository.NewMemberRepository(repository.MemberRepositoryParam{Conf: param.MongoConf})
|
|
|
|
|
}
|
|
|
|
|
if tenants == nil {
|
|
|
|
|
tenants = repository.NewTenantRepository(repository.TenantRepositoryParam{Conf: param.MongoConf})
|
|
|
|
|
}
|
|
|
|
|
if identities == nil {
|
|
|
|
|
identities = repository.NewIdentityRepository(repository.IdentityRepositoryParam{Conf: param.MongoConf})
|
|
|
|
|
}
|
|
|
|
|
if totpProfile == nil {
|
|
|
|
|
totpProfile = repository.NewMongoTOTPProfileRepository(param.MongoConf)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if uidGen == nil {
|
|
|
|
|
uidGen = repository.NewRedisUIDGenerator(param.Redis)
|
|
|
|
|
}
|
|
|
|
|
if totpProfile == nil {
|
|
|
|
|
totpProfile = repository.NewMemoryTOTPProfileRepository()
|
2026-05-20 07:01:08 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-20 13:03:59 +00:00
|
|
|
mod := &Module{
|
|
|
|
|
OTP: MustOTPUseCase(OTPUseCaseParam{Store: otpStore, Config: cfg}),
|
2026-05-21 06:45:35 +00:00
|
|
|
VerifyRate: MustVerifyRateUseCase(VerifyRateUseCaseParam{Store: rateStore}),
|
2026-05-20 23:51:22 +00:00
|
|
|
Members: members,
|
|
|
|
|
Tenants: tenants,
|
|
|
|
|
Identities: identities,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if members != nil {
|
|
|
|
|
mod.Profile = MustProfileUseCase(ProfileUseCaseParam{Members: members})
|
|
|
|
|
}
|
|
|
|
|
if members != nil && tenants != nil && uidGen != nil {
|
|
|
|
|
mod.Lifecycle = MustLifecycleUseCase(LifecycleUseCaseParam{
|
|
|
|
|
Members: members,
|
|
|
|
|
Tenants: tenants,
|
|
|
|
|
UIDGen: uidGen,
|
|
|
|
|
})
|
|
|
|
|
mod.Tenant = MustTenantUseCase(TenantUseCaseParam{Tenants: tenants})
|
|
|
|
|
}
|
|
|
|
|
if members != nil && identities != nil && tenants != nil && uidGen != nil {
|
|
|
|
|
mod.Provisioning = MustProvisioningUseCase(ProvisioningUseCaseParam{
|
|
|
|
|
Members: members,
|
|
|
|
|
Identities: identities,
|
|
|
|
|
Tenants: tenants,
|
|
|
|
|
UIDGen: uidGen,
|
|
|
|
|
})
|
2026-05-20 13:03:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if cfg.TOTP.SecretKEK != "" {
|
|
|
|
|
cipher, err := libcrypto.NewAESGCMFromString(cfg.TOTP.SecretKEK)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("member: totp kek: %w", err)
|
|
|
|
|
}
|
|
|
|
|
mod.TOTP = MustTOTPUseCase(TOTPUseCaseParam{
|
|
|
|
|
Profile: totpProfile,
|
|
|
|
|
Enroll: repository.NewRedisTOTPEnrollStore(param.Redis),
|
|
|
|
|
Replay: repository.NewRedisTOTPReplayStore(param.Redis),
|
|
|
|
|
Cipher: cipher,
|
|
|
|
|
Config: cfg,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mod, nil
|
2026-05-20 07:01:08 +00:00
|
|
|
}
|