A middleware for secure Deno web applications Elmedeno is a fully customizable security middleware for the major Deno web frameworks. Elmedeno is based totally on code from the Snelm middleware for Deno and conseguentially from the Helmet middleware for NodeJS. Elmedeno currently has built in support and examples for the following Deno web frameworks:
Elmedeno is very easy to use 🎉. You simply import Elmedeno, instanciate it with the framework you want to use and then pass the request and response objects from your chosen framework into the Elmedeno function. Elmedeno will return the original response object with various headers set to improve security without having to set any configurations. For example, to use Elmedeno with the Fresh framework:
// Importing fresh
import { Application } from "https://deno.land/x/fresh/mod.ts";
// Importing Elmedeno
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
// Configuring Elmedeno for the Fresh framework
const elmedeno = new Elmedeno("fresh");
// Using Elmedeno Middleware for Fresh
export async function handler(req: Request, ctx: MiddlewareHandlerContext) {
const res: Response = await elmedeno.protect(req, await ctx.next());
return res;
}
Elmedeno is fully customizable, and uses very similar configurations as the underlying components used in the NodeJS helmet middleware. Elmedeno includes the following components with the following keys to configure them. You can follow any of these links to the respective NPM repositories to learn more about what each component does and what configuration options are available:
- X-Permitted-Cross-Domain-Policies Middleware
->
crossDomain
- Content Security Policy Middleware
->
csp
- DNS Prefetch Control Middleware
->
dnsPrefetchControl
- Dont Sniff Mimetype Middleware
->
dontSniffMimetype
- Expect-CT Middleware ->
expectCt
- Feature Policy Middleware
->
featurePolicy
- Frameguard Middleware ->
frameguard
- Hide X-Powered-By Middleware
->
hidePoweredBy
- HTTP Strict Transport Security Middleware
->
hsts
- Internet Explorer Restrict Untrusted HTML Middleware
->
ieNoOpen
- Referrer Policy Middleware
->
referrerPolicy
- X-XSS-Protection Middleware
->
xssProtection
Any individual component can be disabled by setting its key to null
. For
example, if we wanted to remove the Hide X-Powered-By Middleware, we can
configure Elmedeno like so:
const elmedeno = new Elmedeno("oak", {
hidePoweredBy: null,
});
Or if we wanted to disable all of the components:
const elmedeno = new Elmedeno("pogo", {
crossDomain: null,
csp: null,
dnsPrefetchControl: null,
dontSniffMimetype: null,
expectCt: null,
featurePolicy: null,
frameguard: null,
hidePoweredBy: null,
hsts: null,
ieNoOpen: null,
referrerPolicy: null,
xssProtection: null,
});
We can configure components using the same inputs as the middleware they are based on. For example, the Referrer Policy Middleware accepts parameters to change the referrer policy:
const elmedeno = new Elmedeno("oak", {
referrerPolicy: {
policy: "same-origin",
},
});
Finally, below is an example of setting many configurations to various components within Elmedeno to demonstrate some of the configurations for the components:
// crossDomain config
const crossDomainConfig = {
permittedPolicies: "none",
};
// csp config
const cspConfig = {
// Specify directives as normal.
directives: {
defaultSrc: ["'self'", "default.com"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["style.com"],
fontSrc: ["'self'", "fonts.com"],
imgSrc: ["img.com", "data:"],
sandbox: ["allow-forms", "allow-scripts"],
reportUri: "/report-violation",
objectSrc: ["'none'"],
upgradeInsecureRequests: true,
workerSrc: false, // This is not set.
},
// This module will detect common mistakes in your directives and throw errors
// if it finds any. To disable this, enable "loose mode".
loose: false,
// Set to true if you only want browsers to report errors, not block them.
// You may also set this to a function(req, res) in order to decide dynamically
// whether to use reportOnly mode, e.g., to allow for a dynamic kill switch.
reportOnly: false,
// Set to true if you want to blindly set all headers: Content-Security-Policy,
// X-WebKit-CSP, and X-Content-Security-Policy.
setAllHeaders: false,
// Set to true if you want to disable CSP on Android where it can be buggy.
disableAndroid: false,
// Set to false if you want to completely disable any user-agent sniffing.
// This may make the headers less compatible but it will be much faster.
// This defaults to `true`.
browserSniff: true,
};
// dnsPrefetchControl config
const dnsPrefetchControlConfig = {
allow: true,
};
// expectCt config
const expectCtConfig = {
enforce: true,
maxAge: 30,
reportUri: "https://example.com/report",
};
// featurePolicy config
const featurePolicyConfig = {
features: {
fullscreen: ["'self'"],
vibrate: ["'none'"],
payment: ["example.com"],
syncXhr: ["'none'"],
},
};
// frameguard config
const frameguardConfig = {
action: "allow-from",
domain: "https://example.com",
};
// hidePoweredBy config
const hidePoweredByConfig = {
setTo: "PHP 4.2.0",
};
// hsts config
const hstsConfig = {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
};
// referrerPolicy config
const referrerPolicyConfig = {
policy: "same-origin",
};
// xssProtection config
const xssProtectionConfig = {
setOnOldIE: true,
reportUri: "/report-xss-violation",
mode: null,
};
// Adding configuration to elmedeno
const elmedeno = new Elmedeno("pogo", {
crossDomain: crossDomainConfig,
csp: cspConfig,
dnsPrefetchControl: dnsPrefetchControlConfig,
expectCt: expectCtConfig,
featurePolicy: featurePolicyConfig,
frameguard: frameguardConfig,
hidePoweredBy: hidePoweredByConfig,
hsts: hstsConfig,
referrerPolicy: referrerPolicyConfig,
xssProtection: xssProtectionConfig,
});
Here there is a list of basic usages with the supported frameworks:
Elmedeno Middleware example for Oak:
import { Application } from "https://deno.land/x/oak/mod.ts";
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
const app = new Application();
// Configuring Elmedeno for the Oak framework
const elmedeno = new Elmedeno("oak");
// Elmedeno Middleware for Oak
app.use(async (ctx, next) => {
ctx.response = await elmedeno.protect(ctx.request, ctx.response);
next();
});
app.use((ctx) => {
ctx.response.body = "Oak";
});
await app.listen({ port: 8000 });
Elmedeno Middleware example for ABC:
import { Application, MiddlewareFunc } from "https://deno.land/x/abc/mod.ts";
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
const app = new Application();
// Configuring Elmedeno for ABC
const elmedeno = new Elmedeno("abc");
// Elmedeno middleware for ABC
const ElmedenoMiddleware: MiddlewareFunc = (next) =>
async (c) => {
await elmedeno.protect(c.request, c.response);
return next(c);
};
// Adding the Elmedeno middleware to your web application
app.use(ElmedenoMiddleware);
app.get("/", (c) => {
return "Abc";
}).start({ port: 8080 });
Elmedeno Middleware example for Alosaur:
import { App, Area, Controller, Get } from "https://deno.land/x/alosaur/mod.ts";
import { Middleware } from "https://deno.land/x/alosaur/src/decorator/Middleware.ts";
import { MiddlewareTarget } from "https://deno.land/x/alosaur/src/models/middleware-target.ts";
import { HttpContext } from "https://deno.land/x/alosaur/src/models/http-context.ts";
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
// Configuring Elmedeno for Alosaur
const elmedeno = new Elmedeno("alosaur");
// Elmedeno Middleware for Alosaur
@Middleware(new RegExp("/"))
export class ElmedenoMiddleware implements MiddlewareTarget {
onPreRequest(context: HttpContext) {
return new Promise<void>(async (resolve, reject) => {
await elmedeno.protect(context.request, context.response);
resolve();
});
}
onPostRequest(context: HttpContext) {
return new Promise<void>((resolve, reject) => {
resolve();
});
}
}
@Controller("/")
export class MainController {
@Get("")
text() {
return "Alosaur";
}
}
// Declare module
@Area({
controllers: [MainController],
})
export class MainArea {}
// Create Alosaur application
const app = new App({
areas: [MainArea],
// Adding the Elmedeno Middleware to the application
middlewares: [ElmedenoMiddleware],
});
app.listen();
As Pogo does not support middleware currently, you'll have to call elmedeno within the individual routes:
import pogo from "https://deno.land/x/pogo/main.ts";
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
const server = pogo.server({ port: 55555 });
// Configuring Elmedeno for Pogo
const elmedeno = new Elmedeno("pogo");
server.router.get("/", async (request, _handler) => {
request.response = await elmedeno.protect(request, request.response);
request.response.body = "Pogo";
return request.response;
});
server.start();
import Aqua from "https://deno.land/x/aqua/aqua.ts";
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
const app = new Aqua(8000);
// Configuring Elmedeno for Aqua
const elmedeno = new Elmedeno("aqua");
// Elmedeno Middleware for Aqua
app.register(async (request, response) => {
response = await elmedeno.protect(request, response);
return response;
});
app.get("/", (request, response) => {
return "Aqua";
});
import { App, Request, Response } from "https://deno.land/x/attain/mod.ts";
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
const app = new App();
// Configuring Elmedeno for Attain
const elmedeno = new Elmedeno("attain");
// Elmedeno middleware for Attain
const elmedenoMiddleware = async (req: Request, res: Response) => {
res = await elmedeno.protect(req, res);
};
// Adding the middleware
app.use(elmedenoMiddleware, (req, res) => {
res.status(200).send({ status: "Good" });
});
app.listen({ port: 3500 });
import { MiddlewareHandlerContext } from "https://deno.land/x/fresh/server.ts";
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
// Configuring Elmedeno for the Fresh framework
const elmedeno = new Elmedeno("fresh");
// Elmedeno Middleware for Fresh
export async function handler(req: Request, ctx: MiddlewareHandlerContext) {
const res: Response = await elmedeno.protect(req, await ctx.next());
return res;
}
import express from "https://esm.sh/express";
import { Elmedeno } from "https://deno.land/x/elmedeno/mod.ts";
// Configuring Elmedeno for the Opine framework
const elmedeno = new Elmedeno("opine");
const app = opine();
const elmedenoMiddleware: RequestHandler = async function (
req: OpineRequest,
res: OpineResponse,
next: () => void,
) {
await elmedeno.protect(req, res);
next();
};
app.use(elmedenoMiddleware);
app.get("/", (req, res) => {
res.end("Hello from Elmedeno and Opine on port 8000");
});
app.listen(8000);
Parts of the software is adapted from other modules. The software ported here have preserved their individual licenses and copyrights. All of the modules, including those directly adapted are licensed under the MIT License.
All additional work is under copyright of the Emeldeno author. All rights reserved.