76 lines
2.9 KiB
Go
76 lines
2.9 KiB
Go
// Package authz holds the one authorization rule shared by orders and
|
|
// cartridge_batches: both tables carry an assigned_master_id column, and
|
|
// both need the identical policy applied to it — not just from order/
|
|
// cartridge's own handlers but from every other package that acts on a
|
|
// specific order or batch (inventory, cash, document, diagnosis). Kept as a
|
|
// single package rather than duplicated per-domain logic so the rule and
|
|
// its query can't drift between call sites.
|
|
package authz
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// CanAccessAssigned reports whether a staff member may read or act on a
|
|
// record that has an assigned_master_id column. Gated on the "unscoped"
|
|
// permission (see core's migrations/007_unscoped_permission.sql) rather than
|
|
// the literal "master" role name — a custom role without "unscoped" is just
|
|
// as restricted as a built-in master, and owner/manager only keep full
|
|
// oversight because that migration seeded "unscoped" onto them. A staff
|
|
// member with "unscoped" always can; without it, only an unassigned record
|
|
// (nil, not yet claimed by anyone) or one already assigned to them is
|
|
// accessible.
|
|
func CanAccessAssigned(permissions []string, assignedMasterID *string, staffID string) bool {
|
|
if hasUnscoped(permissions) {
|
|
return true
|
|
}
|
|
return assignedMasterID == nil || *assignedMasterID == staffID
|
|
}
|
|
|
|
func hasUnscoped(permissions []string) bool {
|
|
for _, p := range permissions {
|
|
if p == "unscoped" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CheckOrderAccess loads an order's current assigned_master_id and applies
|
|
// CanAccessAssigned, returning a ready-to-send fiber error (404 if the order
|
|
// doesn't exist, 403 if it's assigned to a different master) or nil.
|
|
func CheckOrderAccess(ctx context.Context, db *pgxpool.Pool, orderID string, permissions []string, staffID string) error {
|
|
var assignedMasterID *string
|
|
err := db.QueryRow(ctx, `SELECT assigned_master_id FROM orders WHERE id = $1::uuid`, orderID).Scan(&assignedMasterID)
|
|
if err != nil {
|
|
if err == pgx.ErrNoRows {
|
|
return fiber.NewError(404, "order not found")
|
|
}
|
|
return fiber.NewError(500, "internal error")
|
|
}
|
|
if !CanAccessAssigned(permissions, assignedMasterID, staffID) {
|
|
return fiber.NewError(403, "not assigned to you")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckBatchAccess is CheckOrderAccess for cartridge_batches.
|
|
func CheckBatchAccess(ctx context.Context, db *pgxpool.Pool, batchID string, permissions []string, staffID string) error {
|
|
var assignedMasterID *string
|
|
err := db.QueryRow(ctx, `SELECT assigned_master_id FROM cartridge_batches WHERE id = $1::uuid`, batchID).Scan(&assignedMasterID)
|
|
if err != nil {
|
|
if err == pgx.ErrNoRows {
|
|
return fiber.NewError(404, "batch not found")
|
|
}
|
|
return fiber.NewError(500, "internal error")
|
|
}
|
|
if !CanAccessAssigned(permissions, assignedMasterID, staffID) {
|
|
return fiber.NewError(403, "not assigned to you")
|
|
}
|
|
return nil
|
|
}
|