Spreadsheet-UX op een echte relationele engine
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
/**
* Verifying a licence.
*
* Vendored from Grant, the tool that issues them, and deliberately identical
* to the copy there: nothing imported but `node:crypto`, no dependencies, no
* network, no filesystem. Wellformed embeds its own public key, reads the
* token out of its configuration, and calls `verifyLicence`.
*
* Do not grow this file. Anything Wellformed wants on top of it belongs in
* `index.ts` beside it, so that this one stays a copy that can be replaced
* wholesale when Grant's changes.
*/
import { createHash, createPublicKey, verify as verifySignature, type KeyObject } from 'node:crypto';
/**
* Version tag on every token.
*
* It is the first thing read and the first thing to change if the format ever
* does, so that an old product meeting a new token says "I do not know this
* kind of licence" instead of guessing at the bytes.
*/
export const TOKEN_PREFIX = 'grant1';
/** A licence, as it was signed. */
export interface Licence {
/** Slug of the software this is a licence for, e.g. `wellformed`. */
product: string;
/** Reference as it appears on the deed, e.g. `WF-2026-001`. */
ref: string;
/** Legal name of the party it was granted to. */
to: string;
/** Date of the grant, `YYYY-MM-DD`. */
from: string;
/** `perpetual`, or the `YYYY-MM-DD` on which it ends. */
term: string;
/** Fingerprint of the key that signed it. */
keyId: string;
/** Anything the grant added on top. Free-form, and product-specific. */
note?: string;
}
/** Why a token was not accepted. */
export type LicenceFault =
| 'missing'
| 'malformed'
| 'unknown-format'
| 'bad-signature'
| 'wrong-product'
| 'expired';
export class LicenceError extends Error {
readonly fault: LicenceFault;
constructor(fault: LicenceFault, message: string) {
super(message);
this.name = 'LicenceError';
this.fault = fault;
}
}
export interface VerifyOptions {
/**
* Refuse a licence issued for other software.
*
* Worth setting always. Without it a licence for one product is a licence
* for every product signed by the same key, which is exactly the mistake a
* per-product key pair exists to prevent.
*/
product?: string;
/** Overridable only so that tests can sit at a date. */
now?: Date;
}
/**
* Check a token against one or more public keys and return what it says.
*
* Throws `LicenceError` and returns nothing on failure — there is no partly
* good licence, and a caller that wants to keep running past a bad one should
* have to say so in a `catch`.
*
* Several keys may be passed so that a key can be rotated without invalidating
* what was signed with the old one: keep the retired public key in the list
* until the licences it signed have been reissued.
*/
export function verifyLicence(
token: string | undefined | null,
keys: PublicKeyInput | readonly PublicKeyInput[],
options: VerifyOptions = {},
): Licence {
const raw = (token ?? '').trim();
if (raw === '') throw new LicenceError('missing', 'No licence is set.');
const parts = raw.split('.');
if (parts.length !== 3) {
throw new LicenceError('malformed', 'A licence is three parts separated by dots.');
}
const [prefix, payloadSegment, signatureSegment] = parts as [string, string, string];
if (prefix !== TOKEN_PREFIX) {
throw new LicenceError('unknown-format', `Licence format ${prefix} is not one this version knows.`);
}
// The signature covers the payload segment exactly as it was written, not a
// re-serialisation of the object inside it. That way nothing depends on two
// JSON encoders agreeing about key order or whitespace.
const signed = Buffer.from(payloadSegment, 'ascii');
const signature = Buffer.from(signatureSegment, 'base64url');
const candidates = Array.isArray(keys) ? keys : [keys as PublicKeyInput];
const accepted = candidates.some((key) => {
try {
return verifySignature(null, signed, publicKeyFrom(key), signature);
} catch {
// A key that cannot be read is a key that does not accept this licence.
return false;
}
});
if (!accepted) {
throw new LicenceError('bad-signature', 'This licence was not signed by the key this software trusts.');
}
const licence = decodePayload(payloadSegment);
if (options.product !== undefined && licence.product !== options.product) {
throw new LicenceError(
'wrong-product',
`This is a licence for ${licence.product}, not for ${options.product}.`,
);
}
const expiry = expiryOf(licence);
if (expiry !== null && (options.now ?? new Date()) > expiry) {
throw new LicenceError('expired', `This licence ran to ${licence.term}.`);
}
return licence;
}
/**
* Read a token without checking the signature.
*
* For showing a human what a token claims — in a CLI, in a support mail — and
* for nothing else. Anyone can write anything here. Never branch on it.
*/
export function decodeUnverified(token: string): Licence {
const segment = token.trim().split('.')[1];
if (segment === undefined) throw new LicenceError('malformed', 'A licence is three parts separated by dots.');
return decodePayload(segment);
}
/** The moment a licence stops covering anything, or `null` if it is perpetual. */
export function expiryOf(licence: Licence): Date | null {
if (licence.term === 'perpetual') return null;
const end = new Date(`${licence.term}T23:59:59.999Z`);
if (Number.isNaN(end.getTime())) {
throw new LicenceError('malformed', `Licence term ${licence.term} is not a date.`);
}
return end;
}
/**
* One line naming who holds this, for a log or a settings screen.
*
* `Acme B.V. · WF-2026-001 · since 3 March 2026 · perpetual`
*/
export function describeLicence(licence: Licence): string {
const term = licence.term === 'perpetual' ? 'perpetual' : `until ${formatDate(licence.term)}`;
return `${licence.to} · ${licence.ref} · since ${formatDate(licence.from)} · ${term}`;
}
/** `2026-03-03` as `3 March 2026`, for anything a person reads. */
export function formatDate(iso: string): string {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
if (match === null) return iso;
const [, year, month, day] = match as unknown as [string, string, string, string];
const name = MONTHS[Number(month) - 1] ?? month;
return `${Number(day)} ${name} ${year}`;
}
const MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
] as const;
/**
* A public key, in whichever of the three shapes is at hand.
*
* PEM is what `grant keygen` writes and what a product embeds. The bare
* base64 form is there because a 44-character string survives being pasted
* into a config file, a Dockerfile or a systemd unit, and a PEM block does
* not.
*/
export type PublicKeyInput = string | Buffer | KeyObject;
/** Ed25519 SPKI is a fixed 12-byte header and then the 32-byte key. */
const SPKI_ED25519_HEADER = Buffer.from('302a300506032b6570032100', 'hex');
/** Turn any of the accepted shapes into a key `node:crypto` will use. */
export function publicKeyFrom(value: PublicKeyInput): KeyObject {
if (typeof value === 'object' && !Buffer.isBuffer(value)) return value;
const bytes = Buffer.isBuffer(value) ? value : null;
const text = bytes === null ? String(value).trim() : '';
if (text.includes('BEGIN')) {
return createPublicKey(text);
}
const raw = bytes ?? Buffer.from(text, 'base64');
if (raw.length === 32) {
return createPublicKey({
key: Buffer.concat([SPKI_ED25519_HEADER, raw]),
format: 'der',
type: 'spki',
});
}
return createPublicKey({ key: raw, format: 'der', type: 'spki' });
}
/**
* A short, stable name for a key.
*
* It travels in the token so that a product holding several public keys knows
* which one to reach for, and so that a person can tell two keys apart in a
* log without handling either.
*/
export function fingerprint(key: PublicKeyInput): string {
const der = publicKeyFrom(key).export({ format: 'der', type: 'spki' });
return createHash('sha256').update(der).digest('hex').slice(0, 16);
}
function decodePayload(segment: string): Licence {
let parsed: unknown;
try {
parsed = JSON.parse(Buffer.from(segment, 'base64url').toString('utf8'));
} catch {
throw new LicenceError('malformed', 'The body of this licence is not readable.');
}
if (typeof parsed !== 'object' || parsed === null) {
throw new LicenceError('malformed', 'The body of this licence is not readable.');
}
const body = parsed as Record<string, unknown>;
for (const field of ['product', 'ref', 'to', 'from', 'term', 'key'] as const) {
if (typeof body[field] !== 'string' || body[field] === '') {
throw new LicenceError('malformed', `This licence has no ${field}.`);
}
}
const licence: Licence = {
product: body.product as string,
ref: body.ref as string,
to: body.to as string,
from: body.from as string,
term: body.term as string,
keyId: body.key as string,
};
if (typeof body.note === 'string' && body.note !== '') licence.note = body.note;
return licence;
}