package store import ( "context" "atlas9.dev/c/core" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib" "atlas9.dev/c/demo/lib/access" ) // SqliteSystemGrantStore is the platform (operator/staff) access plane: system // grants live in their own table, with no tenant/path, managed via system-scoped // caps and resolved separately from tenant grants. type SqliteSystemGrantStore struct { db dbi.DBI guard access.Guard roles lib.Roles } var _ access.SystemStore = (*SqliteSystemGrantStore)(nil) func NewSqliteSystemGrantStore(db dbi.DBI, guard access.Guard, roles lib.Roles) *SqliteSystemGrantStore { return &SqliteSystemGrantStore{db: db, guard: guard, roles: roles} } func (s *SqliteSystemGrantStore) Add(ctx context.Context, g iam.SystemGrant) error { if err := s.guard.System(ctx, iam.CapSystemGrantsAdd); err != nil { return err } _, err := s.db.Exec(ctx, ` INSERT INTO system_grants (principal, role) VALUES ($1, $2) ON CONFLICT (principal, role) DO NOTHING `, g.Principal, g.Role) return err } func (s *SqliteSystemGrantStore) Remove(ctx context.Context, g iam.SystemGrant) error { if err := s.guard.System(ctx, iam.CapSystemGrantsRemove); err != nil { return err } _, err := s.db.Exec(ctx, ` DELETE FROM system_grants WHERE principal = $1 AND role = $2 `, g.Principal, g.Role) return err } func (s *SqliteSystemGrantStore) List(ctx context.Context, page core.PageReq) (core.Page[iam.SystemGrant], error) { var out core.Page[iam.SystemGrant] if err := s.guard.System(ctx, iam.CapSystemGrantsList); err != nil { return out, err } limit := page.Limit if limit <= 0 { limit = 100 } rows, err := s.db.Query(ctx, ` SELECT principal, role FROM system_grants ORDER BY principal, role LIMIT $1 `, limit) if err != nil { return out, err } defer rows.Close() for rows.Next() { var g iam.SystemGrant if err := rows.Scan(&g.Principal, &g.Role); err != nil { return out, err } out.Items = append(out.Items, g) } return out, rows.Err() } // Load resolves the principal's system roles into platform-wide (system) caps. // No guard: it runs during auth resolution, like the access store's loaders. func (s *SqliteSystemGrantStore) Load(ctx context.Context, principal core.ID) (access.Access, error) { var out access.Access rows, err := s.db.Query(ctx, `SELECT role FROM system_grants WHERE principal = $1`, principal) if err != nil { return out, err } defer rows.Close() for rows.Next() { var role string if err := rows.Scan(&role); err != nil { return out, err } caps, _ := s.roles.Caps(role) out = out.WithSystem(caps...) } return out, rows.Err() }