feat(09-02): add admin create and reset-password commands

- Commands hash with bcrypt, validate role codes, and revoke tokens on reset
- Generated app main appends cabana.RuntimeCommands exactly once
This commit is contained in:
Jakub Zych
2026-09-24 17:56:34 +02:00
parent d27f442c49
commit 5f218977e4
3 changed files with 213 additions and 6 deletions

View File

@@ -5,8 +5,8 @@ import (
"path/filepath"
"strings"
"testing"
"time"
"git.golem15.com/golem15/summercms/cabana"
"git.golem15.com/golem15/summercms/party"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
@@ -169,7 +169,7 @@ func TestBackendAdminWinterRow(t *testing.T) {
if err := Migrate(gdb, nil); err != nil {
t.Fatal(err)
}
var role cabana.BackendUserRole
var role winterRole
if err := gdb.Where("code = ?", "developer").First(&role).Error; err != nil {
t.Fatal(err)
}
@@ -181,7 +181,7 @@ func TestBackendAdminWinterRow(t *testing.T) {
VALUES ('Ada', 'Lovelace', 'ada', 'Ada@Example.Test', 'winter-hash', 'act', 'persist', 'reset', '{"backend.manage_access":1}', TRUE, FALSE, NULL, NULL, NULL, NOW(), NOW())`).Error; err != nil {
t.Fatal(err)
}
var user cabana.BackendUser
var user winterUser
if err := gdb.Preload("Role").Where("login = ?", "ada").First(&user).Error; err != nil {
t.Fatal(err)
}
@@ -202,10 +202,10 @@ func TestBackendAdminWinterRow(t *testing.T) {
if err := gdb.Delete(&user).Error; err != nil {
t.Fatal(err)
}
if err := gdb.Where("login = ?", "ada").First(&cabana.BackendUser{}).Error; err == nil {
if err := gdb.Where("login = ?", "ada").First(&winterUser{}).Error; err == nil {
t.Fatal("soft-deleted backend user remained visible")
}
if err := gdb.Unscoped().Where("login = ?", "ada").First(&cabana.BackendUser{}).Error; err != nil {
if err := gdb.Unscoped().Where("login = ?", "ada").First(&winterUser{}).Error; err != nil {
t.Fatalf("unscoped load: %v", err)
}
if err := gdb.Exec(`INSERT INTO backend_user_roles (name, code, is_system, created_at, updated_at) VALUES ('Editor A', 'shared', FALSE, NOW(), NOW())`).Error; err != nil {
@@ -216,6 +216,37 @@ func TestBackendAdminWinterRow(t *testing.T) {
}
}
// winterUser mirrors cabana.BackendUser's column contract without importing
// cabana. Cabana commands call lagoon.OpenFromApp, so a lagoon test cannot
// import cabana.
type winterRole struct {
ID uint `gorm:"column:id;primaryKey"`
Name string `gorm:"column:name"`
Code string `gorm:"column:code"`
IsSystem bool `gorm:"column:is_system"`
}
func (winterRole) TableName() string { return "backend_user_roles" }
type winterUser struct {
ID uint `gorm:"column:id;primaryKey"`
FirstName string `gorm:"column:first_name"`
LastName string `gorm:"column:last_name"`
Login string `gorm:"column:login"`
Email string `gorm:"column:email"`
Password string `gorm:"column:password"`
Permissions string `gorm:"column:permissions"`
IsActivated bool `gorm:"column:is_activated"`
IsSuperuser bool `gorm:"column:is_superuser"`
RoleID *uint `gorm:"column:role_id"`
LastLogin *time.Time `gorm:"column:last_login"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at"`
TokensValidAfter *time.Time `gorm:"column:tokens_valid_after"`
Role winterRole
}
func (winterUser) TableName() string { return "backend_users" }
type systemRole struct {
id int
system bool