camomile
v1.0.1
Published
Node.js HTTP image proxy to route images through SSL
Downloads
88
Readme
camomile
camomile is a Node.js HTTP proxy to route images through SSL, compatible with unified plugins, to safely embed user content on the web.
Contents
- What is this?
- When should I use this?
- Install
- Use
- API
- Examples
- Compatibility
- Contribute
- Acknowledgments
- License
What is this?
This is a Node.js HTTP proxy to route images through SSL, integrable in any Node.js server such as Express, Koa, Fastify, or Next.js.
camomile works together with rehype-github-image, which does the following at build time:
- find all insecure HTTP image URLs in content
- generate HMAC signature of each URL
- replace the URL with a signed URL containing the encoded URL and HMAC
When a user visits your app and views the content:
- their browser requests the URLs going to your server
- camomile validates the HMAC, decodes the URL, requests the content from the origin server without sensitive headers, and streams it to the client
When should I use this?
Use this when you want to embed user content on the web in a safe way. Sometimes user content is served over HTTP, which is not secure:
An HTTPS page that includes content fetched using cleartext HTTP is called a mixed content page. Pages like this are only partially encrypted, leaving the unencrypted content accessible to sniffers and man-in-the-middle attackers.
— MDN
This also prevents information about your users leaking to other servers.
Install
This package is ESM only. In Node.js (version 18+), install with npm:
npm install camomile
Use
import process from 'node:process'
import {Camomile} from 'camomile'
const secret = process.env.CAMOMILE_SECRET
if (!secret) throw new Error('Missing `CAMOMILE_SECRET` in environment')
const server = new Camomile({secret})
server.listen({host: '127.0.0.1', port: 1080})
API
This package exports the identifier
Camomile
.
It exports the TypeScript type
Options
.
There is no default export.
new Camomile(options)
Create a new camomile server with options.
Parameters
options
(Options
, required) — configuration
Returns
Server.
Options
Configuration (TypeScript type).
Fields
maxSize
(number
, default:100 * 1024 * 1024
) — max size in bytes per resource to download; a413
is sent if the resource is larger than the maximum sizesecret
(string
, required) — HMAC key to decrypt the URLs and used byrehype-github-image
serverName
(string
, default:'camomile'
) — server name sent inVia
Examples
Example: integrate camomile into Express
import process from 'node:process'
import {Camomile} from 'camomile'
import express from 'express'
const secret = process.env.CAMOMILE_SECRET
if (!secret) throw new Error('Missing `CAMOMILE_SECRET` in environment')
const uploadApp = express()
const camomile = new Camomile({secret})
uploadApp.all('*', camomile.handle.bind(camomile))
const host = '127.0.0.1'
const port = 1080
const app = express()
app.use('/uploads', uploadApp)
app.listen(port, host)
console.log('Listening on `http://' + host + ':' + port + '/uploads/`')
Example: integrate camomile into Koa
import process from 'node:process'
import {Camomile} from 'camomile'
import Koa from 'koa'
const secret = process.env.CAMOMILE_SECRET
if (!secret) throw new Error('Missing `CAMOMILE_SECRET` in environment')
const camomile = new Camomile({secret})
const port = 1080
const app = new Koa()
app.use(function (ctx, next) {
if (/^\/files\/.+/.test(ctx.path.toLowerCase())) {
return camomile.handle(ctx.req, ctx.res)
}
return next()
})
app.listen(port)
Example: integrate camomile into Fastify
import process from 'node:process'
import {Camomile} from 'camomile'
import createFastify from 'fastify'
const secret = process.env.CAMOMILE_SECRET
if (!secret) throw new Error('Missing `CAMOMILE_SECRET` in environment')
const fastify = createFastify({logger: true})
const camomile = new Camomile({secret})
/**
* Add `content-type` so fastify forewards without a parser to the leave body untouched.
*
* @see https://www.fastify.io/docs/latest/Reference/ContentTypeParser/
*/
fastify.addContentTypeParser(
'application/offset+octet-stream',
function (request, payload, done) {
done(null)
}
)
/**
* Use camomile to handle preparation and filehandling requests.
* `.raw` gets the raw Node HTTP request and response objects.
*
* @see https://www.fastify.io/docs/latest/Reference/Request/
* @see https://www.fastify.io/docs/latest/Reference/Reply/#raw
*/
fastify.all('/files', function (request, response) {
camomile.handle(request.raw, response.raw)
})
fastify.all('/files/*', function (request, response) {
camomile.handle(request.raw, response.raw)
})
fastify.listen({port: 3000}, function (error) {
if (error) {
fastify.log.error(error)
process.exit(1)
}
})
Example: integrate camomile into Next.js
Attach the camomile server handler to a Next.js route handler in an optional catch-all route file
/pages/api/upload/[[...file]].ts
import process from 'node:process'
import {Camomile} from 'camomile'
import type {NextApiRequest, NextApiResponse} from 'next'
const secret = process.env.CAMOMILE_SECRET
if (!secret) throw new Error('Missing `CAMOMILE_SECRET` in environment')
/**
* Important: this tells Next.js not to parse the body, as camomile requires
* @see https://nextjs.org/docs/api-routes/request-helpers
*/
export const config = {api: {bodyParser: false}}
const camomile = new Camomile({secret})
export default async function handler(
request: NextApiRequest,
response: NextApiResponse
) {
return camomile.handle(request, response)
}
Compatibility
Projects maintained by the unified collective are compatible with maintained versions of Node.js.
When we cut a new major release,
we drop support for unmaintained versions of Node.
This means we try to keep the current release line,
camomile@^1
,
compatible with Node.js 18.
Contribute
See contributing.md
in
rehypejs/.github
for ways
to get started.
See support.md
for ways to get help.
This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.
For info on how to submit a security report, see our security policy.
Acknowledgments
In 2010 GitHub introduced camo,
a similar server in CoffeeScript,
which is now deprecated and in public archive.
This project is a spiritual successor to camo
.
A lot of inspiration was also taken from go-camo
,
which is a modern and maintained image proxy in Go.
Thanks to @kytta for the npm package name camomile
!