Express utilities and decorators for building web applications with Inversify dependency injection container
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Authentication and authorization system supporting custom auth providers, user principals, and role-based access control. The system integrates seamlessly with dependency injection and provides flexible authentication patterns for Express applications.
Interface representing an authenticated user with support for authentication, role-based, and resource-based authorization.
/**
* Represents an authenticated user principal with authorization capabilities
*/
interface Principal<T = unknown> {
/** User details/data of generic type T */
details: T;
/**
* Checks if the user is authenticated
* @returns Promise resolving to authentication status
*/
isAuthenticated(): Promise<boolean>;
/**
* Checks if the user has a specific role (role-based authorization)
* @param role - Role name to check
* @returns Promise resolving to authorization status
*/
isInRole(role: string): Promise<boolean>;
/**
* Checks if the user owns a specific resource (content-based authorization)
* @param resourceId - Resource identifier to check ownership
* @returns Promise resolving to ownership status
*/
isResourceOwner(resourceId: unknown): Promise<boolean>;
}Interface for implementing custom authentication providers that integrate with the Inversify container.
/**
* Interface for authentication providers that resolve user principals from requests
*/
interface AuthProvider {
/**
* Extracts and authenticates user from HTTP request
* @param req - Express request object
* @param res - Express response object
* @param next - Express next function
* @returns Promise resolving to authenticated user principal
*/
getUser(req: Request, res: Response, next: NextFunction): Promise<Principal>;
}HTTP context provides access to the authenticated user and request-scoped container for each request.
/**
* HTTP context providing request-scoped access to container and user information
*/
interface HttpContext<T = unknown> {
/** Child container created for this specific request */
container: interfaces.Container;
/** Express request object */
request: Request;
/** Express response object */
response: Response;
/** Authenticated user principal */
user: Principal<T>;
}Usage Examples:
import { injectable } from "inversify";
import {
AuthProvider,
Principal,
controller,
httpGet,
principal,
BaseHttpController
} from "inversify-express-utils";
// Custom Principal Implementation
class UserPrincipal implements Principal<{ id: number; name: string; roles: string[] }> {
constructor(public details: { id: number; name: string; roles: string[] }) {}
async isAuthenticated(): Promise<boolean> {
return this.details.id > 0;
}
async isInRole(role: string): Promise<boolean> {
return this.details.roles.includes(role);
}
async isResourceOwner(resourceId: unknown): Promise<boolean> {
// Example: check if user owns the resource
if (typeof resourceId === "object" && resourceId && "userId" in resourceId) {
return (resourceId as any).userId === this.details.id;
}
return false;
}
}
// Custom Auth Provider Implementation
@injectable()
class JwtAuthProvider implements AuthProvider {
async getUser(req: Request, res: Response, next: NextFunction): Promise<Principal> {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
// Return unauthenticated principal
return new UserPrincipal({ id: 0, name: "anonymous", roles: [] });
}
try {
// Validate JWT token (pseudo-code)
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await this.userService.findById(decoded.sub);
return new UserPrincipal({
id: user.id,
name: user.name,
roles: user.roles
});
} catch (error) {
// Return unauthenticated principal on invalid token
return new UserPrincipal({ id: 0, name: "anonymous", roles: [] });
}
}
}
// Using Authentication in Controllers
@controller("/protected")
class ProtectedController extends BaseHttpController {
@httpGet("/profile")
async getProfile(@principal() user: Principal) {
if (!(await user.isAuthenticated())) {
return this.json({ error: "Authentication required" }, 401);
}
return this.ok({
user: user.details,
authenticated: true
});
}
@httpGet("/admin")
async adminOnly(@principal() user: Principal) {
if (!(await user.isAuthenticated())) {
return this.json({ error: "Authentication required" }, 401);
}
if (!(await user.isInRole("admin"))) {
return this.json({ error: "Admin access required" }, 403);
}
return this.ok({ message: "Welcome admin!" });
}
@httpGet("/documents/:id")
async getDocument(
@requestParam("id") docId: string,
@principal() user: Principal
) {
const document = await this.documentService.findById(docId);
if (!document) {
return this.notFound();
}
// Check if user owns the document
if (!(await user.isResourceOwner({ userId: document.ownerId }))) {
return this.json({ error: "Access denied" }, 403);
}
return this.ok(document);
}
}Integrating authentication with the InversifyExpressServer.
// Server constructor with auth provider
class InversifyExpressServer {
constructor(
container: interfaces.Container,
customRouter?: Router | null,
routingConfig?: RoutingConfig | null,
customApp?: Application | null,
authProvider?: (new () => AuthProvider) | null,
forceControllers?: boolean
);
}Usage Examples:
import { Container } from "inversify";
import { InversifyExpressServer } from "inversify-express-utils";
// Setup container and register auth provider
const container = new Container();
container.bind<AuthProvider>("AuthProvider").to(JwtAuthProvider);
// Create server with auth provider
const server = new InversifyExpressServer(
container,
null, // customRouter
null, // routingConfig
null, // customApp
JwtAuthProvider // authProvider constructor
);
// Configure middleware
server.setConfig((app) => {
app.use(express.json());
app.use(cors());
});
// Build and start server
const app = server.build();
app.listen(3000, () => {
console.log("Server started with authentication");
});Access authentication information through HTTP context in controllers.
Usage Examples:
@controller("/context")
class ContextController extends BaseHttpController {
@httpGet("/user-info")
async getUserInfo() {
const { user, request } = this.httpContext;
return this.ok({
isAuthenticated: await user.isAuthenticated(),
userDetails: user.details,
requestPath: request.path,
timestamp: Date.now()
});
}
@httpPost("/check-permissions")
async checkPermissions(@requestBody() { resource, action }: any) {
const { user } = this.httpContext;
if (!(await user.isAuthenticated())) {
return this.json({ error: "Authentication required" }, 401);
}
const hasPermission = await this.permissionService.checkPermission(
user.details,
resource,
action
);
if (!hasPermission) {
return this.json({ error: "Insufficient permissions" }, 403);
}
return this.ok({
message: "Permission granted",
resource,
action
});
}
}Default principal implementation for unauthenticated requests when no auth provider is configured.
// Default anonymous principal returned when no auth provider is set
const anonymousPrincipal: Principal = {
details: null,
isAuthenticated: async () => false,
isInRole: async (_role: string) => false,
isResourceOwner: async (_resourceId: unknown) => false
};Authentication-related type constants for dependency injection.
const TYPE = {
/** Symbol for AuthProvider binding */
AuthProvider: Symbol.for('AuthProvider'),
/** Symbol for Controller binding */
Controller: Symbol.for('Controller'),
/** Symbol for HttpContext binding */
HttpContext: Symbol.for('HttpContext')
};