@ondewo/csi-client-typescript
v5.6.2
Published
ONDEWO Conversation System Integration (CSI) Client library for Typescript
Maintainers
Readme
Overview
@ondewo/csi-client-typescript is a compiled version of the ONDEWO CSI API using the ONDEWO PROTO COMPILER. Here you can find the CSI API documentation.
ONDEWO APIs use Protocol Buffers version 3 (proto3) as their Interface Definition Language (IDL) to define the API interface and the structure of the payload messages. The same interface definition is used for gRPC versions of the API in all languages.
Setup
Using NPM:
npm i --save @ondewo/csi-client-typescriptUsing GitHub:
git clone https://github.com/ondewo/ondewo-csi-client-typescript.git ## Clone repository
cd ondewo-csi-client-typescript ## Change into repo-directoy
make setup_developer_environment_locally ## Install dependenciesPackage structure
npm
├── api
│ ├── google
│ │ ├── api
│ │ │ ├── annotations_pb.d.ts
│ │ │ └── annotations_pb.js
│ │ ├── protobuf
│ │ │ ├── any_pb.d.ts
│ │ │ ├── any_pb.js
│ │ │ ├── empty_pb.d.ts
│ │ │ ├── empty_pb.js
│ │ │ ├── field_mask_pb.d.ts
│ │ │ ├── field_mask_pb.js
│ │ │ ├── struct_pb.d.ts
│ │ │ ├── struct_pb.js
│ │ │ ├── timestamp_pb.d.ts
│ │ │ └── timestamp_pb.js
│ │ ├── rpc
│ │ │ ├── status_pb.d.ts
│ │ │ └── status_pb.js
│ │ └── type
│ │ ├── latlng_pb.d.ts
│ │ └── latlng_pb.js
│ └── ondewo
│ ├── csi
│ │ ├── conversation_grpc_web_pb.d.ts
│ │ ├── conversation_grpc_web_pb.js
│ │ ├── conversation_pb.d.ts
│ │ └── conversation_pb.js
│ ├── nlu
│ │ ├── agent_grpc_web_pb.d.ts
│ │ ├── agent_grpc_web_pb.js
│ │ ├── agent_pb.d.ts
│ │ ├── agent_pb.js
│ │ ├── aiservices_grpc_web_pb.d.ts
│ │ ├── aiservices_grpc_web_pb.js
│ │ ├── aiservices_pb.d.ts
│ │ ├── aiservices_pb.js
│ │ ├── ...
│ ├── s2t
│ │ ├── speech-to-text_grpc_web_pb.d.ts
│ │ ├── speech-to-text_grpc_web_pb.js
│ │ ├── speech-to-text_pb.d.ts
│ │ └── speech-to-text_pb.js
│ └── t2s
│ ├── text-to-speech_grpc_web_pb.d.ts
│ ├── text-to-speech_grpc_web_pb.js
│ ├── text-to-speech_pb.d.ts
│ └── text-to-speech_pb.js
├── LICENSE
├── package.json
├── public-api.d.ts
└── README.mdTLS, mutual TLS and certificates
This package is a gRPC-web client. Its generated clients send every call through the browser's XMLHttpRequest
to a gRPC-web proxy (Envoy) in front of the ONDEWO service, so TLS is the browser's TLS: the browser verifies the
server certificate against its own (operating system / browser) trust store, and a client certificate for mutual TLS
can only come from the browser's own certificate store. Page code cannot hand a CA certificate, a client certificate
or a private key to the browser, so this SDK takes none of them; never ship a private key to a browser.
createGrpcWebEndpoint turns host / port / useSecureChannel into the hostname URL and the client options
every generated *Client / *PromiseClient takes:
| Mode | createGrpcWebEndpoint config | Where the certificates live |
|--------------------------------|---------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Plaintext (not for production) | useSecureChannel: false | none; builds http://host:port and logs a warning naming host:port |
| TLS, publicly trusted server | useSecureChannel: true (the default) | the server certificate chains to a CA the browser already trusts |
| TLS, private CA | useSecureChannel: true | install the CA (ca.pem) in the operating system or browser trust store |
| Mutual TLS | useSecureChannel: true, plus withCredentials: true cross-origin | install the client certificate and key (client.p12) in the operating system or browser certificate store; the proxy requests it and verifies it against its CA |
Rules the code enforces:
- A config carrying
grpcCert,grpcClientCertorgrpcClientKey(or the Python spellingsgrpc_cert,grpc_client_cert,grpc_client_key) with a non-empty value throws anErrornaming the field, instead of silently ignoring a certificate you meant to use. Empty values are ignored, so a config ported from another ONDEWO SDK with blank TLS fields still works. hostis a bare host name or IP address (no scheme, credentials, path or port); a bare IPv6 literal is bracketed (::1becomeshttps://[::1]:50051).portis an integer 1-65535 (number or numeric string).useSecureChannelandwithCredentialsmust be booleans: parse environment strings yourself ('false'is refused, not read astrue).useSecureChannel: falselogs a warning naminghost:portthroughconsole.warn, or through the logger passed as the second argument. No error message renders a value of a refused field or the host of a refused URL.withCredentials: trueis gRPC-web's option for cross-origin calls: only then does the browser send cookies, HTTP authentication and its TLS client certificate to a proxy on another origin. A same-origin proxy does not need it.
import { createGrpcWebEndpoint } from '@ondewo/csi-client-typescript/auth/offlineTokenProvider';
import { ConversationsPromiseClient } from '@ondewo/csi-client-typescript/api/ondewo/csi/conversation_grpc_web_pb';
const endpoint = createGrpcWebEndpoint({
host: 'csi.example.com',
port: 443,
withCredentials: true // only for mutual TLS against a proxy on another origin
});
const client = new ConversationsPromiseClient(endpoint.hostname, null, endpoint.options);Node.js. The generated clients need XMLHttpRequest, which Node.js does not provide (a call fails with
XMLHttpRequest is not defined), so this package's gRPC calls run in browsers only; in Node.js only the Keycloak
login helper is usable. There is therefore no Node.js path for a custom CA or a client certificate in this SDK: for
a server-side client use the ONDEWO Python SDK, or generate a native @grpc/grpc-js client from the
API protos and pass your PEM files to credentials.createSsl(ca, clientKey, clientCert).
The proxy side of mutual TLS
The browser only offers a client certificate when the TLS server asks for one. With Envoy as the gRPC-web proxy:
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
'@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
require_client_certificate: true
common_tls_context:
tls_certificates:
- certificate_chain: { filename: /etc/envoy/certs/server.pem }
private_key: { filename: /etc/envoy/certs/server.key }
validation_context:
trusted_ca: { filename: /etc/envoy/certs/ca.pem }For a cross-origin page the CORS policy must allow credentials with an explicit origin (allow_credentials: true;
Access-Control-Allow-Origin: * is rejected by the browser for a credentialed request). Envoy may in turn connect to
the ONDEWO service over TLS or mutual TLS with its own (upstream) certificate.
A test PKI with openssl
A CA, a server certificate with SANs, and a client certificate with the clientAuth extended key usage, bundled as
PKCS#12 for import into a browser or operating system certificate store. For tests only: the keys are unencrypted.
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -days 365 \
-subj "/CN=Test CA" -keyout ca.key -out ca.pem
printf 'subjectAltName=DNS:localhost,IP:127.0.0.1\nextendedKeyUsage=serverAuth\n' > server.ext
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-subj "/CN=localhost" -keyout server.key -out server.csr
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -days 365 \
-extfile server.ext -out server.pem
printf 'extendedKeyUsage=clientAuth\n' > client.ext
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-subj "/CN=my-client" -keyout client.key -out client.csr
openssl x509 -req -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -days 365 \
-extfile client.ext -out client.pem
openssl pkcs12 -export -in client.pem -inkey client.key -certfile ca.pem -name my-client -out client.p12
chmod 600 *.key client.p12
openssl verify -CAfile ca.pem server.pem client.pemEnvoy uses server.pem / server.key and trusts ca.pem for its clients; the browser trusts ca.pem and imports
client.p12.
TLS security notes
- The private key of a client certificate belongs in the operating system / browser certificate store, never in page
code, a bundle,
localStorageor a config file served to the browser. This SDK refuses one rather than carry it. createGrpcWebEndpointreturns only the URL and{ withCredentials }; logging it reveals no secret.- The Keycloak tokens are secrets too:
JSON.stringify(provider),console.log(provider)andutil.inspect(provider)of theOfflineTokenProvider(also nested in another object) render the access and refresh tokens as***REDACTED***(getAuthorizationHeader()still returns the real one). Do not log theAuthorizationheader yourself. withCredentials: truealso sends the page's cookies for the proxy's origin; restrict the proxy's allowed origins.
TLS troubleshooting
grpc-web reports a failed TLS connection only as a generic error (the browser hides the TLS cause from JavaScript); the cause is in the browser's developer tools (Console / Network tab):
net::ERR_CERT_AUTHORITY_INVALID: the server certificate does not chain to a CA the browser trusts. Install the CA in the trust store, or use a publicly trusted certificate.net::ERR_CERT_COMMON_NAME_INVALID: the host you connect to is not among the certificate's subject alternative names. Connect by a name in the SAN, or reissue the certificate (an IP needs anIP:SAN).net::ERR_BAD_SSL_CLIENT_AUTH_CERT/net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED: the proxy requires a client certificate and the browser offered none, or one not signed by the proxy'strusted_ca. Importclient.p12, pick it when the browser asks, and checkopenssl verify -CAfile ca.pem client.pem.- Mixed content blocked: an
https://page cannot call anhttp://endpoint; useuseSecureChannel: true. - CORS error only with
withCredentials: true: the proxy answers withAccess-Control-Allow-Origin: *or withoutAccess-Control-Allow-Credentials: true.
