148 lines
4.1 KiB
Go
148 lines
4.1 KiB
Go
package repository
|
|
|
|
import (
|
|
"time"
|
|
|
|
"hyapp-admin-server/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// UserAuthorization is the current server-side authorization state used for
|
|
// every protected request. JWT claims identify the session, but never own RBAC
|
|
// state because role changes must take effect before a long-lived token expires.
|
|
type UserAuthorization struct {
|
|
Username string
|
|
Status string
|
|
Permissions []string
|
|
}
|
|
|
|
type userAuthorizationRow struct {
|
|
Username string
|
|
Status string
|
|
PermissionCode *string
|
|
}
|
|
|
|
func (s *Store) FindUserByUsername(username string) (*model.User, error) {
|
|
var user model.User
|
|
err := s.db.Preload("Roles.Permissions").Preload("TeamRecord").Where("username = ?", username).First(&user).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
func (s *Store) FindUserByID(id uint) (*model.User, error) {
|
|
var user model.User
|
|
err := s.db.Preload("Roles.Permissions").Preload("TeamRecord").First(&user, id).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
func (s *Store) CurrentAuthorizationForUser(userID uint) (UserAuthorization, error) {
|
|
if userID == 0 {
|
|
return UserAuthorization{}, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
var rows []userAuthorizationRow
|
|
// The query starts from admin_users.id and follows the two composite-primary-key
|
|
// relation tables. It therefore resolves live permissions in one indexed query
|
|
// without loading the full user/role models on every protected HTTP request.
|
|
result := s.db.Raw(`
|
|
SELECT admin_users.username,
|
|
admin_users.status,
|
|
admin_permissions.code AS permission_code
|
|
FROM admin_users
|
|
LEFT JOIN admin_user_roles
|
|
ON admin_user_roles.user_id = admin_users.id
|
|
LEFT JOIN admin_role_permissions
|
|
ON admin_role_permissions.role_id = admin_user_roles.role_id
|
|
LEFT JOIN admin_permissions
|
|
ON admin_permissions.id = admin_role_permissions.permission_id
|
|
WHERE admin_users.id = ?
|
|
ORDER BY admin_permissions.code ASC
|
|
`, userID).Scan(&rows)
|
|
if result.Error != nil {
|
|
return UserAuthorization{}, result.Error
|
|
}
|
|
if len(rows) == 0 {
|
|
return UserAuthorization{}, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
authorization := UserAuthorization{
|
|
Username: rows[0].Username,
|
|
Status: rows[0].Status,
|
|
}
|
|
seen := make(map[string]struct{}, len(rows))
|
|
for _, row := range rows {
|
|
if row.PermissionCode == nil || *row.PermissionCode == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[*row.PermissionCode]; ok {
|
|
continue
|
|
}
|
|
seen[*row.PermissionCode] = struct{}{}
|
|
authorization.Permissions = append(authorization.Permissions, *row.PermissionCode)
|
|
}
|
|
return authorization, nil
|
|
}
|
|
|
|
func PermissionsForUser(user model.User) []string {
|
|
set := map[string]struct{}{}
|
|
for _, role := range user.Roles {
|
|
for _, permission := range role.Permissions {
|
|
set[permission.Code] = struct{}{}
|
|
}
|
|
}
|
|
|
|
out := make([]string, 0, len(set))
|
|
for code := range set {
|
|
out = append(out, code)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func (s *Store) TouchUserLogin(id uint) error {
|
|
nowMS := time.Now().UTC().UnixMilli()
|
|
return s.db.Model(&model.User{}).Where("id = ?", id).Updates(map[string]any{
|
|
"last_login_at_ms": &nowMS,
|
|
"updated_at_ms": nowMS,
|
|
}).Error
|
|
}
|
|
|
|
func (s *Store) CreateRefreshToken(token model.RefreshToken) error {
|
|
return s.db.Create(&token).Error
|
|
}
|
|
|
|
func (s *Store) RotateRefreshToken(oldTokenID uint, token model.RefreshToken) error {
|
|
nowMS := time.Now().UTC().UnixMilli()
|
|
return s.db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Create(&token).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Model(&model.RefreshToken{}).
|
|
Where("id = ? AND revoked_at_ms IS NULL", oldTokenID).
|
|
Update("revoked_at_ms", &nowMS).Error
|
|
})
|
|
}
|
|
|
|
func (s *Store) FindRefreshToken(hash string) (*model.RefreshToken, error) {
|
|
var token model.RefreshToken
|
|
err := s.db.Where("token_hash = ? AND revoked_at_ms IS NULL AND expires_at_ms > ?", hash, time.Now().UTC().UnixMilli()).First(&token).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &token, nil
|
|
}
|
|
|
|
func (s *Store) RevokeRefreshToken(hash string) error {
|
|
nowMS := time.Now().UTC().UnixMilli()
|
|
return s.db.Model(&model.RefreshToken{}).Where("token_hash = ? AND revoked_at_ms IS NULL", hash).Update("revoked_at_ms", &nowMS).Error
|
|
}
|