97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
package users
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"github.com/ultisuite/ulti-backend/internal/auth"
|
|
"github.com/ultisuite/ulti-backend/internal/permission"
|
|
)
|
|
|
|
var ErrLastPlatformAdmin = errors.New("cannot remove the last platform admin")
|
|
|
|
// AccountState is the persisted user lifecycle used for auth and admin APIs.
|
|
type AccountState struct {
|
|
Status string
|
|
PlatformAdmin bool
|
|
}
|
|
|
|
// GetAccountState loads status and platform_admin for an external_id.
|
|
func GetAccountState(ctx context.Context, db *pgxpool.Pool, externalID string) (AccountState, error) {
|
|
if db == nil || externalID == "" {
|
|
return AccountState{Status: "active"}, nil
|
|
}
|
|
var state AccountState
|
|
err := db.QueryRow(ctx, `
|
|
SELECT status, platform_admin FROM users WHERE external_id = $1
|
|
`, externalID).Scan(&state.Status, &state.PlatformAdmin)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return AccountState{Status: "active"}, nil
|
|
}
|
|
if err != nil {
|
|
return AccountState{}, err
|
|
}
|
|
return state, nil
|
|
}
|
|
|
|
// ApplyAccountGroups adjusts OIDC groups from the persisted account role.
|
|
func ApplyAccountGroups(ctx context.Context, db *pgxpool.Pool, claims *auth.Claims) error {
|
|
if claims == nil || db == nil {
|
|
return nil
|
|
}
|
|
state, err := GetAccountState(ctx, db, claims.Sub)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch permission.DeriveAccountRole(state.PlatformAdmin, state.Status) {
|
|
case permission.AccountRoleGuest:
|
|
claims.Groups = permission.WithGuestAccess(claims.Groups)
|
|
case permission.AccountRoleSuspended:
|
|
// Auth middleware rejects disabled accounts before handlers run.
|
|
default:
|
|
claims.Groups = permission.WithSuiteDefaults(claims.Groups)
|
|
}
|
|
if state.PlatformAdmin {
|
|
claims.Groups = permission.WithPlatformAdmin(claims.Groups)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CountPlatformAdmins returns active platform admins.
|
|
func CountPlatformAdmins(ctx context.Context, db *pgxpool.Pool) (int64, error) {
|
|
if db == nil {
|
|
return 0, fmt.Errorf("database not configured")
|
|
}
|
|
var count int64
|
|
err := db.QueryRow(ctx, `
|
|
SELECT COUNT(*) FROM users
|
|
WHERE platform_admin = true AND status = 'active'
|
|
`).Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
// RevokePlatformAdmin clears platform_admin for external_id.
|
|
func RevokePlatformAdmin(ctx context.Context, db *pgxpool.Pool, externalID string) error {
|
|
if db == nil {
|
|
return fmt.Errorf("database not configured")
|
|
}
|
|
if externalID == "" {
|
|
return fmt.Errorf("missing external id")
|
|
}
|
|
tag, err := db.Exec(ctx, `
|
|
UPDATE users SET platform_admin = false, updated_at = NOW()
|
|
WHERE external_id = $1
|
|
`, externalID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return pgx.ErrNoRows
|
|
}
|
|
return nil
|
|
}
|