feat: disable registration
This commit is contained in:
parent
ab74d46c28
commit
39330ce821
17 changed files with 179 additions and 132 deletions
|
|
@ -12,6 +12,9 @@ JWT_SECRET=
|
|||
# Go duration format
|
||||
JWT_EXPIRY=168h
|
||||
|
||||
# Set to true to block new account creation via magic link. Existing users can still log in.
|
||||
DISABLE_REGISTRATION=false
|
||||
|
||||
MAILER_SMTP_HOST=
|
||||
MAILER_SMTP_PORT=
|
||||
MAILER_IMAP_HOST=
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ func New(cfg *config.Config) (*App, error) {
|
|||
cfg.JWTExpiry,
|
||||
cfg.TokenMagicLinkExpiry,
|
||||
cfg.IsProduction(),
|
||||
cfg.DisableRegistration,
|
||||
)
|
||||
inviteService := service.NewInviteService(invitationRepository, spaceRepository, userRepository, emailService, auditLogService)
|
||||
recurringEventService := service.NewRecurringEventService(recurringEventRepository, transactionService, accountService)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ type Config struct {
|
|||
JWTExpiry time.Duration
|
||||
TokenMagicLinkExpiry time.Duration
|
||||
|
||||
DisableRegistration bool
|
||||
|
||||
MailerSMTPHost string
|
||||
MailerSMTPPort int
|
||||
MailerIMAPHost string
|
||||
|
|
@ -58,6 +60,8 @@ func Load(version string) *Config {
|
|||
JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default
|
||||
TokenMagicLinkExpiry: envDuration("TOKEN_MAGIC_LINK_EXPIRY", 10*time.Minute),
|
||||
|
||||
DisableRegistration: envBool("DISABLE_REGISTRATION", false),
|
||||
|
||||
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
|
||||
MailerSMTPPort: envInt("MAILER_SMTP_PORT", 587),
|
||||
MailerIMAPHost: envString("MAILER_IMAP_HOST", ""),
|
||||
|
|
@ -120,6 +124,19 @@ func envInt(key string, def int) int {
|
|||
return int(i)
|
||||
}
|
||||
|
||||
func envBool(key string, def bool) bool {
|
||||
value, ok := os.LookupEnv(key)
|
||||
if !ok || value == "" {
|
||||
return def
|
||||
}
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
slog.Warn("config invalid bool, using default", "key", key, "value", value, "default", def)
|
||||
return def
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func envDuration(key string, def time.Duration) time.Duration {
|
||||
value, ok := os.LookupEnv(key)
|
||||
if !ok || value == "" {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,23 @@ func (h *authHandler) SendMagicLink(w http.ResponseWriter, r *http.Request) {
|
|||
err = h.authService.SendMagicLink(email)
|
||||
if err != nil {
|
||||
slog.Warn("magic link send failed", "error", err, "email", email)
|
||||
|
||||
if errors.Is(err, service.ErrRegistrationDisabled) {
|
||||
msg := "Registration is disabled. Please contact an administrator if you need an account."
|
||||
if r.URL.Query().Get("resend") == "true" {
|
||||
ui.RenderToast(w, r, toast.Toast(toast.Props{
|
||||
Title: "Magic link not sent",
|
||||
Description: msg,
|
||||
Variant: toast.VariantError,
|
||||
Icon: true,
|
||||
Dismissible: true,
|
||||
Duration: 5000,
|
||||
}))
|
||||
return
|
||||
}
|
||||
ui.Render(w, r, pages.Auth(msg))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if r.URL.Query().Get("resend") == "true" {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func newTestAuthHandler(dbi testutil.DBInfo) *authHandler {
|
|||
spaceSvc := service.NewSpaceService(spaceRepo)
|
||||
accountSvc := service.NewAccountService(accountRepo)
|
||||
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
|
||||
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
|
||||
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false, false)
|
||||
inviteSvc := service.NewInviteService(inviteRepo, spaceRepo, userRepo, emailSvc, nil)
|
||||
return NewAuthHandler(authSvc, inviteSvc, spaceSvc)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func newTestSettingsHandler(dbi testutil.DBInfo) (*settingsHandler, *service.Aut
|
|||
spaceSvc := service.NewSpaceService(spaceRepo)
|
||||
accountSvc := service.NewAccountService(accountRepo)
|
||||
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
|
||||
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
|
||||
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false, false)
|
||||
userSvc := service.NewUserService(userRepo)
|
||||
return NewSettingsHandler(authSvc, userSvc), authSvc
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func newTestApp(dbi testutil.DBInfo) *app.App {
|
|||
spaceSvc := service.NewSpaceService(spaceRepo)
|
||||
accountSvc := service.NewAccountService(accountRepo)
|
||||
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
|
||||
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
|
||||
authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false, false)
|
||||
userSvc := service.NewUserService(userRepo)
|
||||
inviteSvc := service.NewInviteService(inviteRepo, spaceRepo, userRepo, emailSvc, nil)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ var (
|
|||
ErrEmailNotVerified = errors.New("email not verified")
|
||||
ErrInvalidEmail = errors.New("invalid email address")
|
||||
ErrNameRequired = errors.New("name is required")
|
||||
ErrRegistrationDisabled = errors.New("registration is disabled")
|
||||
)
|
||||
|
||||
type AuthService struct {
|
||||
|
|
@ -41,6 +42,7 @@ type AuthService struct {
|
|||
jwtExpiry time.Duration
|
||||
tokenMagicLinkExpiry time.Duration
|
||||
isProduction bool
|
||||
disableRegistration bool
|
||||
}
|
||||
|
||||
func NewAuthService(
|
||||
|
|
@ -53,6 +55,7 @@ func NewAuthService(
|
|||
jwtExpiry time.Duration,
|
||||
tokenMagicLinkExpiry time.Duration,
|
||||
isProduction bool,
|
||||
disableRegistration bool,
|
||||
) *AuthService {
|
||||
return &AuthService{
|
||||
emailService: emailService,
|
||||
|
|
@ -64,6 +67,7 @@ func NewAuthService(
|
|||
jwtExpiry: jwtExpiry,
|
||||
tokenMagicLinkExpiry: tokenMagicLinkExpiry,
|
||||
isProduction: isProduction,
|
||||
disableRegistration: disableRegistration,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,6 +239,10 @@ func (s *AuthService) SendMagicLink(email string) error {
|
|||
if err != nil {
|
||||
// User doesn't exist - create a new passwordless account
|
||||
if errors.Is(err, repository.ErrUserNotFound) {
|
||||
if s.disableRegistration {
|
||||
slog.Info("registration disabled, refusing to create new user", "email", email)
|
||||
return ErrRegistrationDisabled
|
||||
}
|
||||
now := time.Now()
|
||||
user = &model.User{
|
||||
ID: uuid.NewString(),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ func newTestAuthService(dbi testutil.DBInfo) *AuthService {
|
|||
cfg.JWTExpiry,
|
||||
cfg.TokenMagicLinkExpiry,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue