diff --git a/README.md b/README.md index 43d3043..ac0ff25 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ new PromptClient({ host, token, tls = false, natsUrl, natsToken, url }) | `url` | a full `priompt://@host:port` string; defaults to `PRIOMPT_URL` | | `token` | sent as `authorization: Bearer `; explicit value wins over the url's token | | `tls` | use TLS to reach the server | +| `caCert` | path to the CA certificate that signed the server's cert — required for a private CA, which is the usual self-hosted case | +| `clientCert` / `clientKey` | paths to a client certificate and key, for a server started with `-client-ca` (mTLS). Both or neither | | `natsUrl` | broker endpoint for `subscribe()`; may carry the credential as `nats://@host:4222` | | `natsToken` | broker credential, if you would rather pass it separately; wins over the url's | diff --git a/client.js b/client.js index c7b136d..159d35a 100644 --- a/client.js +++ b/client.js @@ -50,7 +50,7 @@ class PromptClient { // One connection string covers local/self-host/cloud: pass url or set // PRIOMPT_URL (priompt://@host:port). An explicit host wins; an // explicit token overrides the URL's token. - constructor({ host, token, tls = false, natsUrl, natsToken, url } = {}) { + constructor({ host, token, tls = false, caCert, clientCert, clientKey, natsUrl, natsToken, url } = {}) { if (!host) { const raw = url || process.env.PRIOMPT_URL; if (raw) { @@ -61,7 +61,31 @@ class PromptClient { } if (!host) throw new Error("PromptClient needs host, url, or PRIOMPT_URL"); const pkg = loadService(); - const creds = tls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure(); + // caCert, clientCert and clientKey are file paths. + // + // Without caCert this client could only reach a server whose certificate + // chains to a public root — but a self-hosted Priompt is normally fronted by + // a private CA, which is exactly what the Go CLI's -ca-cert and the Python + // client's ca_cert exist for. This client had neither, so TLS deployments + // were simply unreachable from Node. + // + // clientCert/clientKey are for a server started with -client-ca, which + // refuses connections lacking a certificate signed by that CA before + // authentication runs at all. + let creds; + if (tls) { + const fs = require("fs"); + if (Boolean(clientCert) !== Boolean(clientKey)) { + throw new Error("clientCert and clientKey must be given together"); + } + creds = grpc.credentials.createSsl( + caCert ? fs.readFileSync(caCert) : null, + clientKey ? fs.readFileSync(clientKey) : null, + clientCert ? fs.readFileSync(clientCert) : null + ); + } else { + creds = grpc.credentials.createInsecure(); + } this._stub = new pkg.PromptService(host, creds); this._meta = new grpc.Metadata(); if (token) this._meta.add("authorization", `Bearer ${token}`);