golem15.user registers jwt.auth, golem15.fonoteka mounts GET /_fonoteka/api/v1/genres through that guard, and a smoke test reads the 15 migrated rows with a test-only token. Co-authored-by: Cursor <cursoragent@cursor.com>
57 lines
1.5 KiB
Go
57 lines
1.5 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/summercms/backpack"
|
|
"git.golem15.com/golem15/summercms/bouncer"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// User is the minimal users row the JWT guard loads.
|
|
type User struct {
|
|
ID uint `gorm:"column:id;primaryKey"`
|
|
Email string `gorm:"column:email"`
|
|
Password string `gorm:"column:password"`
|
|
MustChangePassword bool `gorm:"column:must_change_password"`
|
|
}
|
|
|
|
func (User) TableName() string { return "users" }
|
|
|
|
type gormUsers struct {
|
|
app *backpack.App
|
|
}
|
|
|
|
func (g gormUsers) FindByID(ctx context.Context, id uint) (*bouncer.Principal, error) {
|
|
if g.app == nil {
|
|
return nil, fmt.Errorf("golem15.user: app is nil")
|
|
}
|
|
gdb, ok := g.app.Lookup[*gorm.DB]()
|
|
if !ok || gdb == nil {
|
|
return nil, fmt.Errorf("golem15.user: gorm handle is missing")
|
|
}
|
|
var row User
|
|
err := gdb.WithContext(ctx).Where("id = ?", id).Take(&row).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &bouncer.Principal{ID: row.ID, MustChangePassword: row.MustChangePassword}, nil
|
|
}
|
|
|
|
func jwtSecret(app *backpack.App) (string, error) {
|
|
if app == nil || app.Config == nil {
|
|
return "", fmt.Errorf("golem15.user: jwt.secret is empty (set SUMMER_GOLEM15__USER__JWT__SECRET)")
|
|
}
|
|
secret := strings.TrimSpace(app.Config.String("golem15.user.jwt.secret"))
|
|
if secret == "" {
|
|
return "", fmt.Errorf("golem15.user: jwt.secret is empty (set SUMMER_GOLEM15__USER__JWT__SECRET)")
|
|
}
|
|
return secret, nil
|
|
}
|