@actana/sdk 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,211 @@
1
+ import type { CoreRegistrationBlob } from "./core-registration-blob.ts";
2
+ export { CORE_PAIRING_REDEEM_PATH, type CorePairingClientInfo, type CorePairingRedeemRequest, type CorePairingRedeemResponse, type CorePairingRefusalBody, } from "./core-pairing-wire.ts";
3
+ /**
4
+ * Why a pairing attempt did not produce a blob.
5
+ *
6
+ * The list is what a caller can *act* on, and it stops where the Core's own
7
+ * answers stop. `refused` covers a wrong code, an expired session, one already
8
+ * redeemed and one whose attempts are spent, because the Core answers all four
9
+ * with one status and one body on purpose (`core-pairing-routes.ts`, "every
10
+ * refusal is the same refusal"): telling them apart on the wire would tell an
11
+ * attacker whether a session exists and whether their last guess was closer.
12
+ * A client that reported four different things would be inventing three of
13
+ * them. The distinction the operator needs is in the Core's audit log, which
14
+ * is where #282 put it deliberately.
15
+ */
16
+ export type CorePairingFailure =
17
+ /** The address could not be read as a Core's HTTPS or core-link address. */
18
+ "bad-address"
19
+ /** The code was not eight characters, or named no pairing session. */
20
+ | "bad-code"
21
+ /** The expected fingerprint was not a SHA-256 fingerprint. */
22
+ | "bad-fingerprint"
23
+ /** Nothing answered at that address, or the dial timed out. */
24
+ | "unreachable"
25
+ /** The Core presented a chain with no CA in it — nothing to pin. */
26
+ | "no-ca-presented"
27
+ /** No fingerprint was given to check against. The code was not sent. */
28
+ | "fingerprint-unconfirmed"
29
+ /** The presented CA is not the expected one. The code was not sent. */
30
+ | "fingerprint-mismatch"
31
+ /**
32
+ * The expected CA, on an address its certificate does not cover.
33
+ *
34
+ * Its own failure rather than a mismatch, because it is not an attack and
35
+ * saying so sends the operator hunting for one: a Core's server certificate
36
+ * covers the host it was set up for plus loopback, so reaching a Core over a
37
+ * second interface, a tunnel or a DNS name added later fails here with the
38
+ * fingerprint matching perfectly.
39
+ */
40
+ | "hostname-mismatch"
41
+ /** The expected CA, and a certificate that is expired or otherwise unusable. */
42
+ | "certificate-invalid"
43
+ /** Wrong, expired, already redeemed, or out of attempts — see above. */
44
+ | "refused"
45
+ /** Too many attempts from this client, too fast. Try again later. */
46
+ | "rate-limited"
47
+ /** The Core would not accept the request itself — a bug on this side. */
48
+ | "rejected"
49
+ /** There is no pairing endpoint on that Core. */
50
+ | "not-pairable"
51
+ /** The Core failed while handling the redemption. */
52
+ | "core-error"
53
+ /** A 200 that was not a redemption response. */
54
+ | "malformed-response";
55
+ /** Everything a failure knows beyond its {@link CorePairingFailure}. */
56
+ export type CorePairingErrorDetail = {
57
+ /** The HTTP status, when the failure came from one. */
58
+ status?: number;
59
+ /** `retry-after`, in seconds, on a `rate-limited` failure. */
60
+ retryAfterSeconds?: number;
61
+ /** The Core's own refusal code, when it sent one. */
62
+ coreCode?: string;
63
+ /** The fingerprint the caller was told to expect. */
64
+ expectedFingerprint?: string;
65
+ /** The fingerprint the Core actually presented. */
66
+ presentedFingerprint?: string;
67
+ /** The CA the Core presented, PEM — for a UI that wants to show it. */
68
+ presentedCaCert?: string;
69
+ /** The TLS or OpenSSL code behind a dial failure, e.g. `CERT_HAS_EXPIRED`. */
70
+ tlsCode?: string;
71
+ };
72
+ /**
73
+ * A pairing attempt that did not produce a blob.
74
+ *
75
+ * One class with a {@link CorePairingFailure} rather than a class per failure:
76
+ * the CLI (#285) and the Panel (#286) both switch on the reason to write a
77
+ * sentence, and a `switch` over a union is checked by the compiler where a
78
+ * chain of `instanceof` is not.
79
+ */
80
+ export declare class CorePairingError extends Error {
81
+ readonly name = "CorePairingError";
82
+ /** Which failure this is. Switch on it; do not read the message. */
83
+ readonly failure: CorePairingFailure;
84
+ /** Whatever the failure knows beyond its reason. */
85
+ readonly detail: CorePairingErrorDetail;
86
+ constructor(failure: CorePairingFailure, message: string, detail?: CorePairingErrorDetail, options?: {
87
+ cause?: unknown;
88
+ });
89
+ }
90
+ /** What a bootstrap dial learns about a Core before anything is trusted. */
91
+ export type CorePairingIdentity = {
92
+ /** SHA-256 over the CA's DER, colon-separated uppercase hex. */
93
+ fingerprint: string;
94
+ /** The PEM CA certificate that fingerprint is of. */
95
+ caCert: string;
96
+ /** The host that was dialled. */
97
+ host: string;
98
+ /** The port that was dialled. */
99
+ port: number;
100
+ /** `https://host:port` — where a redemption would be posted. */
101
+ httpsOrigin: string;
102
+ };
103
+ /** How long a dial or a redemption may take before it is called unreachable. */
104
+ export declare const DEFAULT_PAIRING_TIMEOUT_MS = 15000;
105
+ /**
106
+ * Dial a Core and report the CA it presents — **without a code to send**.
107
+ *
108
+ * This is the first-contact mode, and the reason it is a separate function
109
+ * rather than a flag is that it takes no code: a UI that shows the operator's
110
+ * fingerprint beside the Core's, and asks a human whether they match, cannot
111
+ * leak a secret it was never given. {@link pairWithCore} calls it too, so the
112
+ * fingerprint a caller confirms is computed by the same code that later
113
+ * enforces it.
114
+ *
115
+ * The dial is unverified, because at this point in the flow there is nothing to
116
+ * verify against — that is what the fingerprint the operator read out is for.
117
+ * Nothing is sent on this connection and it is closed as soon as the chain has
118
+ * been read.
119
+ */
120
+ export declare function fetchCorePairingIdentity(opts: {
121
+ /** `host:port`, `https://host:port` or `wss://host:port`. */
122
+ address: string;
123
+ /** Defaults to {@link DEFAULT_PAIRING_TIMEOUT_MS}. */
124
+ timeoutMs?: number;
125
+ }): Promise<CorePairingIdentity>;
126
+ export type PairWithCoreOptions = {
127
+ /** The Core's address: `host:port`, `https://host:port` or `wss://host:port`. */
128
+ address: string;
129
+ /**
130
+ * The pairing code the operator read out — `XXXX-XXXX`, in any case and with
131
+ * or without the hyphen.
132
+ *
133
+ * A code names a session, and the Core will not go looking for which one, so
134
+ * the session id has to travel with it. Either pass it as `sessionId`, or
135
+ * pass a single `<sessionId>:<XXXX-XXXX>` string here and this reads both out
136
+ * of it. Which of the two an operator is given is #280's to settle across
137
+ * `actana pair new` (#283) and `actana core pair` (#285); both forms parse
138
+ * here so that neither is a change to this module.
139
+ */
140
+ code: string;
141
+ /** The pairing session the code belongs to, when `code` does not carry it. */
142
+ sessionId?: string;
143
+ /**
144
+ * The CA fingerprint the operator read out, in any of the forms a human
145
+ * copies it in: colon-separated or not, upper or lower case.
146
+ *
147
+ * **Absent is not "skip the check".** With no fingerprint this function
148
+ * refuses with `fingerprint-unconfirmed` and reports the presented one in the
149
+ * error, having sent no code — see {@link fetchCorePairingIdentity}.
150
+ */
151
+ expectedCaFingerprint?: string | null;
152
+ /** What this machine calls itself, for the operator's `actana pair ls`. */
153
+ label?: string;
154
+ /** This machine's platform, e.g. `process.platform`. */
155
+ platform?: string;
156
+ /** Defaults to {@link DEFAULT_PAIRING_TIMEOUT_MS}, per connection. */
157
+ timeoutMs?: number;
158
+ };
159
+ /**
160
+ * Pair with a Core and return the credential it issued.
161
+ *
162
+ * The result is a {@link CoreRegistrationBlob} and nothing downstream can tell
163
+ * it from a hand-carried one: `coreConnectionFromBlob` unpacks it,
164
+ * `httpsBaseUrlFor` derives the HTTPS origin, the PEMs go into the mTLS
165
+ * handshake and the bearer into the `auth` frame — exactly as they do today.
166
+ * `clientKey` is the key generated on this machine a few lines above; it was
167
+ * never sent, and the Core has never seen it.
168
+ *
169
+ * Throws {@link CorePairingError} for everything that is not a blob.
170
+ */
171
+ export declare function pairWithCore(opts: PairWithCoreOptions): Promise<CoreRegistrationBlob>;
172
+ /** A pairing code, and the session it names. */
173
+ export type PairingTicket = {
174
+ sessionId: string;
175
+ code: string;
176
+ };
177
+ /**
178
+ * Read a ticket out of what a human typed.
179
+ *
180
+ * The code is checked for *shape* — eight alphanumerics, hyphens and spaces
181
+ * ignored — and not against the Core's alphabet. That is a deliberate stop:
182
+ * the alphabet is an internal of `packages/shared` (which this package may not
183
+ * import) and mirroring it here would be a copy free to drift, which ADR 0025
184
+ * D3 is about. What the shape check buys is worth having on its own: a code
185
+ * that could not be right whatever the alphabet is never spends one of the
186
+ * five attempts the operator's session has.
187
+ */
188
+ export declare function parsePairingTicket(input: string, sessionId?: string): PairingTicket;
189
+ /** A Core's address, in the two forms this module needs it. */
190
+ type CoreAddress = {
191
+ host: string;
192
+ port: number;
193
+ httpsOrigin: string;
194
+ };
195
+ /**
196
+ * Read `host:port`, `https://host:port` or `wss://host:port`.
197
+ *
198
+ * `ws://` and `http://` are refused rather than upgraded: there is no
199
+ * certificate on a plaintext dial, so there is no fingerprint to check, and
200
+ * pairing over one would be the silent unverified exchange this module exists
201
+ * to make impossible. A caller that meant the secure port should say so.
202
+ */
203
+ export declare function parseCoreAddress(address: string): CoreAddress;
204
+ /**
205
+ * A fingerprint as this module compares them: colon-separated uppercase hex,
206
+ * which is the form `actana pair new` prints and a human copies.
207
+ */
208
+ export declare function parseFingerprint(input: string): string;
209
+ /** SHA-256 over a certificate's DER, in the form {@link parseFingerprint} yields. */
210
+ export declare function fingerprintOf(der: Uint8Array): string;
211
+ //# sourceMappingURL=core-pairing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-pairing.d.ts","sourceRoot":"","sources":["../src/core-pairing.ts"],"names":[],"mappings":"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"}