Add random password generation for new users

- Move GenerateRandomPassword to auth package for reuse
- Make password optional in user creation (generates 16-char if empty)
- Return generated password in response for user to copy
- Add Generate/Copy UI with show/hide toggle
This commit is contained in:
Christoph Haas 2026-03-29 14:52:59 +02:00
parent 7cdc561dcc
commit 8b2d2649ac
4 changed files with 80 additions and 20 deletions

View file

@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"git.workaround.org/chaas/imc/backend/internal/auth"
"git.workaround.org/chaas/imc/backend/internal/db"
"git.workaround.org/chaas/imc/backend/internal/mail"
"github.com/gin-gonic/gin"
@ -22,7 +23,7 @@ func NewUserHandler(database *db.DB) *UserHandler {
type CreateUserRequest struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
Password string `json:"password"`
Quota int64 `json:"quota"`
}
@ -169,7 +170,11 @@ func (h *UserHandler) Create(c *gin.Context) {
return
}
passwordHash := "{BLF-CRYPT}" + req.Password
password := req.Password
if password == "" {
password = auth.GenerateRandomPassword(16)
}
passwordHash := "{BLF-CRYPT}" + password
err = h.db.CreateUser(ctx, domain.ID, req.Email, passwordHash, req.Quota)
if err != nil {
@ -178,7 +183,7 @@ func (h *UserHandler) Create(c *gin.Context) {
return
}
Created(c, map[string]string{"message": "user created"})
Created(c, map[string]interface{}{"message": "user created", "password": password})
}
func (h *UserHandler) Update(c *gin.Context) {

View file

@ -3,8 +3,10 @@
package auth
import (
"errors" // standard errors package
"time" // time handling
"crypto/rand" // cryptographically secure random number generator
"errors" // standard errors package
"log" // logging
"time" // time handling
"github.com/golang-jwt/jwt/v5" // JWT library for token handling
"golang.org/x/crypto/bcrypt" // bcrypt for secure password hashing
@ -125,3 +127,16 @@ func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// GenerateRandomPassword creates a cryptographically secure random password.
func GenerateRandomPassword(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
result := make([]byte, length)
if _, err := rand.Read(result); err != nil {
log.Fatalf("Failed to generate random password: %v", err)
}
for i := range result {
result[i] = charset[int(result[i])%len(charset)]
}
return string(result)
}

View file

@ -25,7 +25,7 @@ func (d *DB) GetUserByEmail(ctx context.Context, email string) (imcdb.VirtualUse
}
func (d *DB) CreateUser(ctx context.Context, domainID uint32, email, passwordHash string, quota int64) error {
quotaNull := sql.NullInt64{Int64: quota, Valid: quota > 0}
quotaNull := sql.NullInt64{Int64: quota, Valid: true}
return d.Queries.CreateUser(ctx, imcdb.CreateUserParams{
DomainID: domainID,
Email: email,
@ -42,7 +42,7 @@ func (d *DB) UpdateUserPassword(ctx context.Context, id uint32, passwordHash str
}
func (d *DB) UpdateUserQuota(ctx context.Context, id uint32, quota int64) error {
quotaNull := sql.NullInt64{Int64: quota, Valid: quota > 0}
quotaNull := sql.NullInt64{Int64: quota, Valid: true}
return d.Queries.UpdateUserQuota(ctx, imcdb.UpdateUserQuotaParams{
Quota: quotaNull,
ID: id,