condoms
v1.0.2
Published
TypeSafe route protection
Downloads
5
Readme
Condoms
TypeSafe route protection for your web applications.
Installation
npm install condoms
# or
yarn add condoms
# or
pnpm add condomsUsage
Condoms provides a simple, type-safe way to protect routes in your web application.
import { createCondom } from "condoms";
// Create a context provider function
const getContext = async () => {
return {
session: await getCurrentSession(), // Your session management
routes: {
login: "/login",
dashboard: "/dashboard",
},
methods: {
// Pass in one or more method(s)
redirect: (route) => {
// Your redirect implementation
window.location.href = route;
},
},
};
};
// Create your protection function with a default condom
const protect = createCondom(getContext, {
check: (ctx) => !ctx.session.user, // Return true if redirect is needed
redirectTo: (ctx) => ctx.routes.login,
method: (ctx) => ctx.methods.redirect(ctx.route), // Define how the check-redirect is handled
});
// Use it in your route handlers
async function dashboardRouteHandler() {
// Default callback is used when arguments are not provided
// This will redirect to login if user is not authenticated
const redirect = await protect();
if (redirect) return; // User was redirected
// Continue with protected route logic
renderDashboard();
}
// You can also provide custom checks
async function adminRouteHandler() {
const redirect = await protect(({ defaultCheck }) => [
defaultCheck, // First check authentication
{
// Then check admin permissions
check: (ctx) => !ctx.session.user?.isAdmin,
redirectTo: (ctx) => ctx.routes.dashboard,
method: (ctx) => ctx.methods.redirect(ctx.route),
},
]);
if (redirect) return; // User was redirected
// Continue with admin route logic
renderAdminPanel();
}Next.js (App Router) example:
// ~/auth/utils.ts
import { createCondom } from "condoms";
import { verifySession } from "~/auth/server";
import { forbidden, redirect } from "next/navigation";
import { REDIRECT_URLS } from "~/constants/urls";
export const protect = await createCondom(
async () => ({
session: await verifySession(),
methods: {
redirect,
forbidden,
},
routes: REDIRECT_URLS,
}),
{
// Default condom
check: ({ session }) => !session?.user,
redirectTo: ({ routes }) => routes.unauthed,
method: ({ methods, route }) => methods.redirect(route),
}
);Use the your protection in a layout:
// layout.tsx
import { protect } from "~/auth/utils/routes";
export default async function SetupLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
await protect(({ defaultCheck }) => [
defaultCheck,
{
check: ({ session }) => !!session?.user.hasDefaults,
redirectTo: ({ routes }) => routes.authed,
method: ({ methods, route }) => methods.redirect(route),
},
]);
return <>{children}</>;
}API
createCondom(getContext, defaultCheck)
Creates a route protection function.
getContext: A function that returns a promise resolving to a context object withsession,methods, androutes. The context object can have any shape you want, as long as it satisfies the minimum requirements.defaultCheck: The default check to run if no custom checks are provided. Type of defaultCheck is derived from provided context.
protect([checksGenerator])
The function returned by createCondom.
checksGenerator(optional): A function that receives the default check and returns an array of checks to run in sequence.
Type Reference
Context Object
The context object can have any shape you want, as long as it includes the minimum required properties:
type ProtectRouteContext<
TSession = any, // Your session type
TMethods extends Record<string, Function> = any, // Your methods type
TRoutes extends Record<string, string> = any // Your routes type
> = {
session: TSession; // Your session object (can be any shape)
methods: TMethods; // Methods for handling redirects/responses
routes: TRoutes; // Routes for redirection targets
};Check Object
Both the default check and custom checks should conform to this structure:
type DefaultCheck<TContext extends ProtectRouteContext = any> = {
// Return true if redirect is needed
check: (ctx: TContext) => Promise<boolean> | boolean;
// Return the route to redirect to
redirectTo: (ctx: TContext) => string;
// Define how the check-redirect is handled
method: (ctx: TContext & { route: string }) => any;
};ChecksGenerator Function
The function passed to protect() should have this signature:
type ChecksGenerator<T extends DefaultCheck> = (params: {
defaultCheck: T;
}) => Array<T>;License
ISC
