/gemini

Gemini protocol server & client.

Primary LanguageJavaScriptISC LicenseISC

gemini

Gemini protocol server & client.

npm version build status ISC-licensed minimum Node.js version chat with me on Gitter support me on Patreon

Installation

npm install @derhuerst/gemini

Usage

Server

The following code assumes that you have a valid SSL certificate & key.

const {createServer, DEFAULT_PORT} = require('@derhuerst/gemini')

const handleRequest = (req, res) => {
	if (req.path === '/foo') {
		if (!req.clientFingerprint) {
			return res.requestTransientClientCert('/foo is secret!')
		}
		res.write('foo')
		res.end('!')
	} else if (req.path === '/bar') {
		res.redirect('/foo')
	} else {
		res.gone()
	}
}

const server = createGeminiServer({
	cert: , // certificate (+ chain)
	key: , // private key
	passphrase: , // passphrase, if the key is encrypted
}, handleRequest)

server.listen(DEFAULT_PORT)
server.on('error', console.error)

Client

const request = require('@derhuerst/gemini/client')

request('/bar', (err, res) => {
	if (err) {
		console.error(err)
		process.exit(1)
	}

	console.log(res.statusCode, res.statusMessage)
	if (res.meta) console.log(res.meta)
	res.pipe(process.stdout)
})

TOFU-style client certificates

Interactive clients for human users MUST inform users that such a session has been requested and require the user to approve generation of such a certificate. Transient certificates MUST NOT be generated automatically. – Gemini spec, section 1.4.3

If is up to you how to implement that approval process. As an example, we're going to build a simple CLI prompt:

const {createInterface} = require('readline')

const letUserConfirmClientCertUsage = ({host, reason}, cb) => {
	const prompt = createInterface({
		input: process.stdin,
		output: process.stdout,
		history: 0,
	})
	prompt.question(`Send client cert to ${host}? Server says: "${reason}". y/n > `, (confirmed) => {
		prompt.close()
		cb(confirmed === 'y' || confirmed === 'Y')
	})
}

request('/foo', {
	// opt into client certificates
	useClientCerts: true,
	letUserConfirmClientCertUsage,
}, cb)

API

const createServer = require('@derhuerst/gemini/server')
createServer(opt = {}, onRequest)

opt extends the following defaults:

{
	// SSL certificate & key
	cert: null, key: null, passphrase: null,
	// additional options to be passed into `tls.createServer`
	tlsOpt: {},
}

const request = require('@derhuerst/gemini/client')
request(pathOrUrl, opt = {}, cb)

opt extends the following defaults:

{
	// follow redirects automatically
	followRedirects: false,
	// client certificates
	useClientCerts: false,
	letUserConfirmClientCertUsage: null,
	clientCertStore: defaultClientCertStore,
	// additional options to be passed into `tls.connect`
	tlsOpt: {},
}

const connect = require('@derhuerst/gemini/connect')
connect(opt = {}, cb)

opt extends the following defaults:

{
	hostname: '127.0.0.1',
	port: 1965,
	// client certificate
	cert: null, key: null, passphrase: null,
	// additional options to be passed into `tls.connect`
	tlsOpt: {},
}

Contributing

If you have a question or need support using gemini, please double-check your code and setup first. If you think you have found a bug or want to propose a feature, use the issues page.