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
|
# Go duration format
|
||||||
JWT_EXPIRY=168h
|
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_HOST=
|
||||||
MAILER_SMTP_PORT=
|
MAILER_SMTP_PORT=
|
||||||
MAILER_IMAP_HOST=
|
MAILER_IMAP_HOST=
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ func New(cfg *config.Config) (*App, error) {
|
||||||
cfg.JWTExpiry,
|
cfg.JWTExpiry,
|
||||||
cfg.TokenMagicLinkExpiry,
|
cfg.TokenMagicLinkExpiry,
|
||||||
cfg.IsProduction(),
|
cfg.IsProduction(),
|
||||||
|
cfg.DisableRegistration,
|
||||||
)
|
)
|
||||||
inviteService := service.NewInviteService(invitationRepository, spaceRepository, userRepository, emailService, auditLogService)
|
inviteService := service.NewInviteService(invitationRepository, spaceRepository, userRepository, emailService, auditLogService)
|
||||||
recurringEventService := service.NewRecurringEventService(recurringEventRepository, transactionService, accountService)
|
recurringEventService := service.NewRecurringEventService(recurringEventRepository, transactionService, accountService)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ type Config struct {
|
||||||
JWTExpiry time.Duration
|
JWTExpiry time.Duration
|
||||||
TokenMagicLinkExpiry time.Duration
|
TokenMagicLinkExpiry time.Duration
|
||||||
|
|
||||||
|
DisableRegistration bool
|
||||||
|
|
||||||
MailerSMTPHost string
|
MailerSMTPHost string
|
||||||
MailerSMTPPort int
|
MailerSMTPPort int
|
||||||
MailerIMAPHost string
|
MailerIMAPHost string
|
||||||
|
|
@ -58,6 +60,8 @@ func Load(version string) *Config {
|
||||||
JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default
|
JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default
|
||||||
TokenMagicLinkExpiry: envDuration("TOKEN_MAGIC_LINK_EXPIRY", 10*time.Minute),
|
TokenMagicLinkExpiry: envDuration("TOKEN_MAGIC_LINK_EXPIRY", 10*time.Minute),
|
||||||
|
|
||||||
|
DisableRegistration: envBool("DISABLE_REGISTRATION", false),
|
||||||
|
|
||||||
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
|
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
|
||||||
MailerSMTPPort: envInt("MAILER_SMTP_PORT", 587),
|
MailerSMTPPort: envInt("MAILER_SMTP_PORT", 587),
|
||||||
MailerIMAPHost: envString("MAILER_IMAP_HOST", ""),
|
MailerIMAPHost: envString("MAILER_IMAP_HOST", ""),
|
||||||
|
|
@ -120,6 +124,19 @@ func envInt(key string, def int) int {
|
||||||
return int(i)
|
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 {
|
func envDuration(key string, def time.Duration) time.Duration {
|
||||||
value, ok := os.LookupEnv(key)
|
value, ok := os.LookupEnv(key)
|
||||||
if !ok || value == "" {
|
if !ok || value == "" {
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,23 @@ func (h *authHandler) SendMagicLink(w http.ResponseWriter, r *http.Request) {
|
||||||
err = h.authService.SendMagicLink(email)
|
err = h.authService.SendMagicLink(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("magic link send failed", "error", err, "email", email)
|
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" {
|
if r.URL.Query().Get("resend") == "true" {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ func newTestAuthHandler(dbi testutil.DBInfo) *authHandler {
|
||||||
spaceSvc := service.NewSpaceService(spaceRepo)
|
spaceSvc := service.NewSpaceService(spaceRepo)
|
||||||
accountSvc := service.NewAccountService(accountRepo)
|
accountSvc := service.NewAccountService(accountRepo)
|
||||||
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
|
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)
|
inviteSvc := service.NewInviteService(inviteRepo, spaceRepo, userRepo, emailSvc, nil)
|
||||||
return NewAuthHandler(authSvc, inviteSvc, spaceSvc)
|
return NewAuthHandler(authSvc, inviteSvc, spaceSvc)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func newTestSettingsHandler(dbi testutil.DBInfo) (*settingsHandler, *service.Aut
|
||||||
spaceSvc := service.NewSpaceService(spaceRepo)
|
spaceSvc := service.NewSpaceService(spaceRepo)
|
||||||
accountSvc := service.NewAccountService(accountRepo)
|
accountSvc := service.NewAccountService(accountRepo)
|
||||||
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
|
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)
|
userSvc := service.NewUserService(userRepo)
|
||||||
return NewSettingsHandler(authSvc, userSvc), authSvc
|
return NewSettingsHandler(authSvc, userSvc), authSvc
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func newTestApp(dbi testutil.DBInfo) *app.App {
|
||||||
spaceSvc := service.NewSpaceService(spaceRepo)
|
spaceSvc := service.NewSpaceService(spaceRepo)
|
||||||
accountSvc := service.NewAccountService(accountRepo)
|
accountSvc := service.NewAccountService(accountRepo)
|
||||||
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
|
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)
|
userSvc := service.NewUserService(userRepo)
|
||||||
inviteSvc := service.NewInviteService(inviteRepo, spaceRepo, userRepo, emailSvc, nil)
|
inviteSvc := service.NewInviteService(inviteRepo, spaceRepo, userRepo, emailSvc, nil)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ var (
|
||||||
ErrEmailNotVerified = errors.New("email not verified")
|
ErrEmailNotVerified = errors.New("email not verified")
|
||||||
ErrInvalidEmail = errors.New("invalid email address")
|
ErrInvalidEmail = errors.New("invalid email address")
|
||||||
ErrNameRequired = errors.New("name is required")
|
ErrNameRequired = errors.New("name is required")
|
||||||
|
ErrRegistrationDisabled = errors.New("registration is disabled")
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthService struct {
|
type AuthService struct {
|
||||||
|
|
@ -41,6 +42,7 @@ type AuthService struct {
|
||||||
jwtExpiry time.Duration
|
jwtExpiry time.Duration
|
||||||
tokenMagicLinkExpiry time.Duration
|
tokenMagicLinkExpiry time.Duration
|
||||||
isProduction bool
|
isProduction bool
|
||||||
|
disableRegistration bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthService(
|
func NewAuthService(
|
||||||
|
|
@ -53,6 +55,7 @@ func NewAuthService(
|
||||||
jwtExpiry time.Duration,
|
jwtExpiry time.Duration,
|
||||||
tokenMagicLinkExpiry time.Duration,
|
tokenMagicLinkExpiry time.Duration,
|
||||||
isProduction bool,
|
isProduction bool,
|
||||||
|
disableRegistration bool,
|
||||||
) *AuthService {
|
) *AuthService {
|
||||||
return &AuthService{
|
return &AuthService{
|
||||||
emailService: emailService,
|
emailService: emailService,
|
||||||
|
|
@ -64,6 +67,7 @@ func NewAuthService(
|
||||||
jwtExpiry: jwtExpiry,
|
jwtExpiry: jwtExpiry,
|
||||||
tokenMagicLinkExpiry: tokenMagicLinkExpiry,
|
tokenMagicLinkExpiry: tokenMagicLinkExpiry,
|
||||||
isProduction: isProduction,
|
isProduction: isProduction,
|
||||||
|
disableRegistration: disableRegistration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,6 +239,10 @@ func (s *AuthService) SendMagicLink(email string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// User doesn't exist - create a new passwordless account
|
// User doesn't exist - create a new passwordless account
|
||||||
if errors.Is(err, repository.ErrUserNotFound) {
|
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()
|
now := time.Now()
|
||||||
user = &model.User{
|
user = &model.User{
|
||||||
ID: uuid.NewString(),
|
ID: uuid.NewString(),
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ func newTestAuthService(dbi testutil.DBInfo) *AuthService {
|
||||||
cfg.JWTExpiry,
|
cfg.JWTExpiry,
|
||||||
cfg.TokenMagicLinkExpiry,
|
cfg.TokenMagicLinkExpiry,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue