fix(auth): single-user deployment by default, no open registration

Registration logic redesigned:
- Empty DB (first-time setup): registration always open, no config needed
- After first user exists: registration closed by default
- Multi-user opt-in: set REGISTRATION_ENABLED=true + MAX_USERS=N in .env

Config defaults changed:
- RegistrationEnabled: true → false (closed after first user)
- MaxUsers: 10 → 1 (single-user deployment default)

This eliminates the confusion of multiple users appearing in a personal
deployment where Telegram is bound to a single admin account.
This commit is contained in:
tinkle-community
2026-03-08 18:32:50 +08:00
parent 97f309c9b5
commit b2ce123df1
2 changed files with 26 additions and 21 deletions

View File

@@ -3152,14 +3152,33 @@ func (s *Server) handleLogout(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Logged out"})
}
// handleRegister Handle user registration request
// handleRegister Handle user registration request.
// Registration is only open when no users exist yet (first-time setup) OR when
// explicitly enabled via REGISTRATION_ENABLED=true env var AND within MAX_USERS limit.
func (s *Server) handleRegister(c *gin.Context) {
// Check if registration is allowed
if !config.Get().RegistrationEnabled {
c.JSON(http.StatusForbidden, gin.H{"error": "Registration is disabled"})
userCount, err := s.store.User().Count()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check user count"})
return
}
// First-time setup: allow registration when DB is empty, regardless of config.
firstTimeSetup := userCount == 0
if !firstTimeSetup {
// After first user exists: require explicit opt-in via REGISTRATION_ENABLED=true.
if !config.Get().RegistrationEnabled {
c.JSON(http.StatusForbidden, gin.H{"error": "Registration is disabled"})
return
}
// Enforce max users limit.
maxUsers := config.Get().MaxUsers
if maxUsers > 0 && userCount >= maxUsers {
c.JSON(http.StatusForbidden, gin.H{"error": "Maximum number of users reached"})
return
}
}
var req struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6"`
@@ -3171,26 +3190,12 @@ func (s *Server) handleRegister(c *gin.Context) {
}
// Check if email already exists
_, err := s.store.User().GetByEmail(req.Email)
_, err = s.store.User().GetByEmail(req.Email)
if err == nil {
c.JSON(http.StatusConflict, gin.H{"error": "Email already registered"})
return
}
// Check max users limit (only for new users)
maxUsers := config.Get().MaxUsers
if maxUsers > 0 {
userCount, err := s.store.User().Count()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check user count"})
return
}
if userCount >= maxUsers {
c.JSON(http.StatusForbidden, gin.H{"error": "Not on whitelist"})
return
}
}
// Generate password hash
passwordHash, err := auth.HashPassword(req.Password)
if err != nil {

View File

@@ -55,8 +55,8 @@ type Config struct {
func Init() {
cfg := &Config{
APIServerPort: 8080,
RegistrationEnabled: true,
MaxUsers: 10, // Default: 10 users allowed
RegistrationEnabled: false, // Default: closed after first user registers (first-time setup always allowed)
MaxUsers: 1, // Default: single-user deployment
ExperienceImprovement: true, // Default: enabled to help improve the product
// Database defaults
DBType: "sqlite",