Licence server and client for my paid apps
  • TypeScript 60%
  • Go 30.3%
  • JavaScript 9.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jonathan Warykowski 41199666c5
All checks were successful
CI / go (push) Successful in 1m1s
CI / worker (push) Successful in 28s
chore: spell-check comments only, as in the other repos (#5)
2026-10-15 11:51:09 +00:00
.github/workflows chore: linting and formatting (#4) 2026-10-15 11:37:05 +00:00
testdata feat: bind each licence to the computer it was activated on (#2) 2026-10-14 13:30:58 +00:00
worker chore: linting and formatting (#4) 2026-10-15 11:37:05 +00:00
.gitignore feat: licence server and Go client for the paid apps 2026-10-14 23:34:29 +11:00
.golangci.yml chore: spell-check comments only, as in the other repos (#5) 2026-10-15 11:51:09 +00:00
go.mod feat: bind each licence to the computer it was activated on (#2) 2026-10-14 13:30:58 +00:00
go.sum feat: bind each licence to the computer it was activated on (#2) 2026-10-14 13:30:58 +00:00
licence.go chore: linting and formatting (#4) 2026-10-15 11:37:05 +00:00
licence_test.go chore: linting and formatting (#4) 2026-10-15 11:37:05 +00:00
README.md chore: linting and formatting (#4) 2026-10-15 11:37:05 +00:00

licence

Licences for the paid apps (keybars Pro, later shepherd Pro), sold through Polar.

  • worker/: the Cloudflare Worker at licence.jwarykowski.dev. An app sends the customer's Polar key once per machine to POST /v1/<product>/activate; the Worker activates it with Polar, checks the key is for that product, and returns a token signed with the product's Ed25519 key. POST /v1/<product>/deactivate frees the machine.
  • The Go package: apps verify tokens offline, and activate and deactivate.
var Pro = licence.Product{ID: "keybars-pro", Keys: []string{publicKey}}

path, _ := Pro.Path()
l, _, err := Pro.Load(path)
pro := err == nil && l.Covers(buildDate)

A licence covers releases built until updatesUntil (a year after purchase), on the computer it was activated on: the token carries an HMAC of the machine ID (/etc/machine-id, macOS's IOPlatformUUID, Windows' MachineGuid), and Load returns ErrOtherMachine for a copied licence file. Activating again there takes one of the key's machines. LICENCE_SERVER points the client at another server.

Adding a product

  1. In Polar, make the product with a licence-key benefit.
  2. Add it to worker/src/products.ts and its benefit ID to BENEFITS in worker/wrangler.jsonc.
  3. npm run keygen <product> writes the private key to ~/.config/licence-signing/ and prints the public key for the app.
  4. npx wrangler secret put SIGNING_KEY_<PRODUCT> --env="" < <that file>, then npm run deploy.

Sandbox

npm run deploy:sandbox runs the Worker against Polar's sandbox on workers.dev, with its own Polar IDs (env.sandbox in wrangler.jsonc) and its own signing keys (npm run keygen <product> sandbox). Sandbox purchases are free, so release builds must never trust a sandbox key.

To replace a signing key, ship the new public key in the app's Keys before uploading the new secret.

Development

go test ./...
golangci-lint run ./...   # .golangci.yml; golangci-lint fmt ./... formats
cd worker && npm ci && npm run check && npm test
npm run lint && npm run fmt:check   # oxlint and oxfmt; npm run fmt formats
npm run fixture   # regenerate testdata/ after changing the token format