Security Guards (XyGuard)
XyGuard is the high-performance declarative guard registry of XyPriss. It enables centralized, standardized security enforcement for routes and route groups with native TypeScript type-safety.
Architecture and Core Concepts
Unlike traditional middleware, guards rely on a static global registry (XyGuard) and a standardized return-type protocol. They run before handler initialization and are fully inspectable in the route registry.
XyGuard (static registry)
resolvers: Map<string, GuardResolver>createGuardMiddleware()
guards: RouteGuard[] (Array syntax)guards: BuiltInGuards (Object syntax)Route Execution Handler
true.Guard Signatures
XyGuard resolvers support two distinct signature patterns based on whether they accept configuration options.
1. Direct / Boolean Guard Signature
Used for simple guards (e.g. authenticated: true or inline functions). Receives (req, res) or (req, ctx).
import { XyPrisRequest, XyPrisResponse } from "xypriss";
export const authGuard = async (req: XyPrisRequest, res: XyPrisResponse) => {
if (!req.session?.userId) {
return "Unauthorized: Bearer token or session missing";
}
return true;
};2. Configurable Guard Signature (with Options)
Used when parameters are passed (e.g. roles: ["admin"]). Receives (req, options, ctx).
import { XyPrisRequest, XyGuardContext } from "xypriss";
export const rolesGuard = async (
req: XyPrisRequest,
requiredRoles: string[],
ctx: XyGuardContext
) => {
const userRole = req.user?.role;
if (!userRole || !requiredRoles.includes(userRole)) {
return "Forbidden: Insufficient roles";
}
return true;
};XyGuard.define() — Registering Resolvers
Register your guard logic globally using XyGuard.define() during application startup before defining routes.
import { XyGuard, XyPrisRequest, XyPrisResponse, XyGuardContext } from "xypriss";
// 1. Authentication guard
XyGuard.define("authenticated", async (req: XyPrisRequest, res: XyPrisResponse) => {
const token = req.headers["authorization"]?.split(" ")[1];
if (!token) return "Missing authorization header";
try {
(req as any).user = await verifyJwt(token);
return true;
} catch {
return "Invalid or expired token";
}
});
// 2. Role-based guard (receives options parameter)
XyGuard.define("roles", (req: XyPrisRequest, requiredRoles: string[], ctx: XyGuardContext) => {
const userRole = (req as any).user?.role;
if (!userRole || !requiredRoles.includes(userRole)) {
return `Role '${userRole}' is insufficient. Required: ${requiredRoles.join(", ")}`;
}
return true;
});
// 3. Permission guard
XyGuard.define("permissions", (req: XyPrisRequest, required: string[]) => {
const userPerms = (req as any).user?.permissions || [];
const missing = required.filter((p) => !userPerms.includes(p));
return missing.length === 0 ? true : `Missing permissions: ${missing.join(", ")}`;
});
// 4. Custom guard — IP Whitelist
XyGuard.define("ipWhitelist", (req: XyPrisRequest) => {
const allowedIps = ["127.0.0.1", "192.168.1.100"];
return allowedIps.includes(req.ip ?? "") ? true : `IP ${req.ip} is not allowed`;
});Applying Guards
Declarative Object Syntax (Recommended)
Pass guard configurations in the guards object of route options.
import { router } from "./router";
// Single guard
router.get("/profile", { guards: { authenticated: true } }, async (req, res) => {
res.json({ user: (req as any).user });
});
// Multi-guard combination
router.post(
"/admin/users",
{
guards: {
authenticated: true,
roles: ["admin", "super-admin"],
ipWhitelist: true,
},
},
async (req, res) => {
res.status(201).json({ message: "User created successfully" });
},
);Inline Array Syntax
Alternatively, pass an array of inline guard functions:
router.get("/dashboard", { guards: [authGuard, checkSubscriptionGuard] }, (req, res) => {
res.json({ status: "active" });
});Group-Level Guards & Inheritance
Guards applied to a route group via router.group() automatically apply to all child routes inside that group. Guards cascade in hierarchical order:
router.group(
{
prefix: "/admin",
guards: {
authenticated: true,
roles: ["admin"],
},
},
(adminRouter) => {
// Inherits 'authenticated' and 'roles: ["admin"]'
adminRouter.get("/dashboard", dashboardHandler);
// Inherits parent group guards AND adds 'permissions: ["reports:export"]'
adminRouter.get(
"/reports/export",
{ guards: { permissions: ["reports:export"] } },
exportHandler,
);
},
);Return Protocol & HTTP Behavior
The table below details how return values from resolvers translate directly into HTTP responses:
| Return Value | Guard Type / Context | HTTP Status | JSON Response Body |
|---|---|---|---|
| true / void | All | Access Granted | Request continues to handler |
| false | authenticated | 401 Unauthorized | { success: false, error: "Unauthorized: Authentication required" } |
| false | roles | 403 Forbidden | { success: false, error: "Forbidden: Insufficient roles" } |
| false | permissions / Custom / Array | 403 Forbidden | { success: false, error: "Forbidden: Access denied" } |
| "custom error" | authenticated / Custom / Array | 401 Unauthorized | { success: false, error: "custom error" } |
| "custom error" | roles / permissions | 403 Forbidden | { success: false, error: "custom error" } |
| throw Error | All | 500 Server Error | { success: false, error: "Internal Server Error during guard check" } |
string over false to provide clear, actionable feedback to API clients.v1 → v2 Migration (Removal of `custom`)
In XyPriss v2, the legacy custom property inside the guards object has been removed and is now typed as never.
Legacy Syntax (v1 — Deprecated / Removed)
// NO LONGER WORKS IN V2
router.get("/data", {
guards: {
authenticated: true,
custom: [myInlineGuard], // Type error: 'custom' is typed as 'never'
},
}, handler);V2 Recommended Migration Options
// Option A: Use array for inline guards
router.get("/data", { guards: [myInlineGuard] }, handler);
// Option B: Register named guard via XyGuard.define
XyGuard.define("myGuard", myInlineGuard);
router.get("/data", {
guards: { authenticated: true, myGuard: true },
}, handler);TypeScript Auto-completion via Declaration Merging
Augment the CustomGuards interface in your project to enable native IDE autocompletion for custom guard keys:
// src/types/xypriss.d.ts
declare module "xypriss" {
interface CustomGuards {
ipWhitelist?: boolean;
plan?: "free" | "starter" | "premium" | "enterprise";
apiKey?: boolean;
}
}
// IDE now validates custom keys and values:
router.get(
"/premium-feature",
{
guards: {
authenticated: true,
plan: "premium", // Auto-completed & type-checked!
},
},
handler,
);Advanced Practical Patterns
1. Async Database Verification Guard
XyGuard.define("subscriptionActive", async (req) => {
const user = (req as any).user;
if (!user) return false;
const sub = await db.subscriptions.findOne({
userId: user.id,
status: "active",
});
if (!sub) return "Subscription expired or invalid";
(req as any).subscription = sub;
return true;
});2. Daily User Quota Guard
XyGuard.define("dailyExportQuota", async (req) => {
const user = (req as any).user;
const key = `quota:${user.id}:${new Date().toISOString().slice(0, 10)}`;
const count = await redis.incr(key);
const limit = user.plan === "premium" ? 1000 : 50;
if (count > limit) {
return `Daily quota reached (${limit} exports/day)`;
}
return true;
});3. Feature Flag / Beta Guard
XyGuard.define("betaTesterOnly", (req) => {
const user = (req as any).user;
if (user?.isBetaTester) return true;
return "This feature is restricted to beta testers";
});4. Pre-Handler Request Validation
XyGuard.define("requireJsonBody", (req) => {
const ct = req.headers["content-type"] ?? "";
if (!ct.includes("application/json")) {
return "Content-Type: application/json is required";
}
if (!req.body) return "Request body cannot be empty";
return true;
});Guards vs Middleware
| Middleware | Guards (XyGuard) | |
|---|---|---|
| Declaration | Imperative (app.use) | Declarative (inline or via XyGuard.define) |
| Inspection Visibility | No | Yes (visible in route inspection) |
| Return Protocol | Manually call next() | Standardized (true / false / "string") |
| Execution Timing | During request processing chain | Before main handler initializes |
Explore Go-native rate limiting, multi-window XTRS shields, response caching, and lifecycle hooks.
