@usine/gateway 0.0.0-stage → 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +112 -2
- package/dist/admin/api.d.ts +206 -0
- package/dist/admin/api.d.ts.map +1 -0
- package/dist/admin/api.js +105 -0
- package/dist/admin/api.js.map +1 -0
- package/dist/admin/server.d.ts +10 -0
- package/dist/admin/server.d.ts.map +1 -0
- package/dist/admin/server.js +79 -0
- package/dist/admin/server.js.map +1 -0
- package/dist/attachment/attachment.d.ts +77 -0
- package/dist/attachment/attachment.d.ts.map +1 -0
- package/dist/attachment/attachment.js +57 -0
- package/dist/attachment/attachment.js.map +1 -0
- package/dist/attachment/store.d.ts +27 -0
- package/dist/attachment/store.d.ts.map +1 -0
- package/dist/attachment/store.js +101 -0
- package/dist/attachment/store.js.map +1 -0
- package/dist/caller/caller.d.ts +60 -0
- package/dist/caller/caller.d.ts.map +1 -0
- package/dist/caller/caller.js +130 -0
- package/dist/caller/caller.js.map +1 -0
- package/dist/egress/client-hello.d.ts +120 -0
- package/dist/egress/client-hello.d.ts.map +1 -0
- package/dist/egress/client-hello.js +155 -0
- package/dist/egress/client-hello.js.map +1 -0
- package/dist/egress/egress.d.ts +66 -0
- package/dist/egress/egress.d.ts.map +1 -0
- package/dist/egress/egress.js +114 -0
- package/dist/egress/egress.js.map +1 -0
- package/dist/gateway.d.ts +23 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +23 -0
- package/dist/gateway.js.map +1 -0
- package/dist/github/github.d.ts +43 -0
- package/dist/github/github.d.ts.map +1 -0
- package/dist/github/github.js +88 -0
- package/dist/github/github.js.map +1 -0
- package/dist/llm/llm.d.ts +64 -0
- package/dist/llm/llm.d.ts.map +1 -0
- package/dist/llm/llm.js +92 -0
- package/dist/llm/llm.js.map +1 -0
- package/dist/node/egress-proxy.d.ts +30 -0
- package/dist/node/egress-proxy.d.ts.map +1 -0
- package/dist/node/egress-proxy.js +491 -0
- package/dist/node/egress-proxy.js.map +1 -0
- package/dist/node/environment.d.ts +108 -0
- package/dist/node/environment.d.ts.map +1 -0
- package/dist/node/environment.js +113 -0
- package/dist/node/environment.js.map +1 -0
- package/dist/node/main.d.ts +3 -0
- package/dist/node/main.d.ts.map +1 -0
- package/dist/node/main.js +13 -0
- package/dist/node/main.js.map +1 -0
- package/dist/node/node.d.ts +29 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +82 -0
- package/dist/node/node.js.map +1 -0
- package/dist/node/reload.d.ts +26 -0
- package/dist/node/reload.d.ts.map +1 -0
- package/dist/node/reload.js +142 -0
- package/dist/node/reload.js.map +1 -0
- package/dist/proxy/forward.d.ts +12 -0
- package/dist/proxy/forward.d.ts.map +1 -0
- package/dist/proxy/forward.js +44 -0
- package/dist/proxy/forward.js.map +1 -0
- package/dist/proxy/proxy.d.ts +28 -0
- package/dist/proxy/proxy.d.ts.map +1 -0
- package/dist/proxy/proxy.js +189 -0
- package/dist/proxy/proxy.js.map +1 -0
- package/dist/settings.d.ts +52 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +27 -0
- package/dist/settings.js.map +1 -0
- package/dist/upstream.d.ts +5 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +6 -0
- package/dist/upstream.js.map +1 -0
- package/package.json +58 -3
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Data, Option } from "effect";
|
|
2
|
+
/** The maximum bytes the forward proxy buffers while waiting for a complete ClientHello. */
|
|
3
|
+
export const CLIENT_HELLO_LIMIT = 16 * 1024;
|
|
4
|
+
const HANDSHAKE_RECORD = 0x16;
|
|
5
|
+
const CLIENT_HELLO = 0x01;
|
|
6
|
+
const SERVER_NAME = 0x0000;
|
|
7
|
+
const HOST_NAME = 0x00;
|
|
8
|
+
const ENCRYPTED_CLIENT_HELLO = 0xfe0d;
|
|
9
|
+
const MAX_RECORD_LENGTH = 1 << 14;
|
|
10
|
+
export const ClientHelloRead = Data.taggedEnum();
|
|
11
|
+
const notTls = (detail) => ClientHelloRead.NotTls({ detail });
|
|
12
|
+
const u16 = (bytes, at) => ((bytes[at] ?? 0) << 8) | (bytes[at + 1] ?? 0);
|
|
13
|
+
/** Returns the `host_name` entry of a `server_name` extension (RFC 6066, section 3). */
|
|
14
|
+
const hostName = (data) => {
|
|
15
|
+
if (data.length < 2 || u16(data, 0) !== data.length - 2)
|
|
16
|
+
return Option.none();
|
|
17
|
+
let at = 2;
|
|
18
|
+
while (at + 3 <= data.length) {
|
|
19
|
+
const type = data[at];
|
|
20
|
+
const length = u16(data, at + 1);
|
|
21
|
+
const start = at + 3;
|
|
22
|
+
if (start + length > data.length)
|
|
23
|
+
return Option.none();
|
|
24
|
+
if (type === HOST_NAME && length > 0)
|
|
25
|
+
return Option.some(new TextDecoder().decode(data.subarray(start, start + length)));
|
|
26
|
+
at = start + length;
|
|
27
|
+
}
|
|
28
|
+
return Option.none();
|
|
29
|
+
};
|
|
30
|
+
/** Parses a ClientHello body (RFC 8446, section 4.1.2). */
|
|
31
|
+
const clientHelloBody = (body) => {
|
|
32
|
+
let at = 2 + 32;
|
|
33
|
+
if (at + 1 > body.length)
|
|
34
|
+
return notTls("ClientHello is truncated");
|
|
35
|
+
at += 1 + (body[at] ?? 0);
|
|
36
|
+
if (at + 2 > body.length)
|
|
37
|
+
return notTls("ClientHello is truncated");
|
|
38
|
+
at += 2 + u16(body, at);
|
|
39
|
+
if (at + 1 > body.length)
|
|
40
|
+
return notTls("ClientHello is truncated");
|
|
41
|
+
at += 1 + (body[at] ?? 0);
|
|
42
|
+
if (at > body.length)
|
|
43
|
+
return notTls("ClientHello is truncated");
|
|
44
|
+
if (at === body.length)
|
|
45
|
+
return ClientHelloRead.ClientHello({ serverName: Option.none(), encryptedClientHello: false });
|
|
46
|
+
if (at + 2 > body.length)
|
|
47
|
+
return notTls("ClientHello extensions are truncated");
|
|
48
|
+
const end = at + 2 + u16(body, at);
|
|
49
|
+
if (end > body.length)
|
|
50
|
+
return notTls("ClientHello extensions are truncated");
|
|
51
|
+
at += 2;
|
|
52
|
+
let serverName = Option.none();
|
|
53
|
+
let encryptedClientHello = false;
|
|
54
|
+
while (at + 4 <= end) {
|
|
55
|
+
const type = u16(body, at);
|
|
56
|
+
const length = u16(body, at + 2);
|
|
57
|
+
const start = at + 4;
|
|
58
|
+
if (start + length > end)
|
|
59
|
+
return notTls("a ClientHello extension is truncated");
|
|
60
|
+
if (type === SERVER_NAME && Option.isNone(serverName))
|
|
61
|
+
serverName = hostName(body.subarray(start, start + length));
|
|
62
|
+
if (type === ENCRYPTED_CLIENT_HELLO)
|
|
63
|
+
encryptedClientHello = true;
|
|
64
|
+
at = start + length;
|
|
65
|
+
}
|
|
66
|
+
return ClientHelloRead.ClientHello({ serverName, encryptedClientHello });
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Reads the TLS ClientHello at the start of `bytes`, joining handshake records if the client
|
|
70
|
+
* split it. For `Incomplete`, `needed` is the smallest byte count that can change the result.
|
|
71
|
+
*/
|
|
72
|
+
export const readClientHello = (bytes) => {
|
|
73
|
+
const chunks = [];
|
|
74
|
+
let handshakeLength = 0;
|
|
75
|
+
let at = 0;
|
|
76
|
+
for (;;) {
|
|
77
|
+
if (bytes.length > at && bytes[at] !== HANDSHAKE_RECORD)
|
|
78
|
+
return notTls("the first bytes are not a TLS handshake record");
|
|
79
|
+
if (bytes.length < at + 5)
|
|
80
|
+
return ClientHelloRead.Incomplete({ needed: at + 5 });
|
|
81
|
+
if (bytes[at + 1] !== 0x03)
|
|
82
|
+
return notTls("the record version is not TLS");
|
|
83
|
+
const length = u16(bytes, at + 3);
|
|
84
|
+
if (length === 0 || length > MAX_RECORD_LENGTH)
|
|
85
|
+
return notTls("the record length is invalid");
|
|
86
|
+
if (bytes.length < at + 5 + length)
|
|
87
|
+
return ClientHelloRead.Incomplete({ needed: at + 5 + length });
|
|
88
|
+
chunks.push(bytes.subarray(at + 5, at + 5 + length));
|
|
89
|
+
handshakeLength += length;
|
|
90
|
+
at += 5 + length;
|
|
91
|
+
if (handshakeLength < 4)
|
|
92
|
+
continue;
|
|
93
|
+
const handshake = new Uint8Array(handshakeLength);
|
|
94
|
+
let filled = 0;
|
|
95
|
+
for (const chunk of chunks) {
|
|
96
|
+
handshake.set(chunk, filled);
|
|
97
|
+
filled += chunk.length;
|
|
98
|
+
}
|
|
99
|
+
if (handshake[0] !== CLIENT_HELLO)
|
|
100
|
+
return notTls("the handshake is not a ClientHello");
|
|
101
|
+
const messageLength = ((handshake[1] ?? 0) << 16) | u16(handshake, 2);
|
|
102
|
+
if (messageLength > CLIENT_HELLO_LIMIT)
|
|
103
|
+
return notTls("the ClientHello is too large");
|
|
104
|
+
if (handshakeLength >= 4 + messageLength)
|
|
105
|
+
return clientHelloBody(handshake.subarray(4, 4 + messageLength));
|
|
106
|
+
const rest = at + 5 + (4 + messageLength - handshakeLength);
|
|
107
|
+
if (bytes.length < rest)
|
|
108
|
+
return ClientHelloRead.Incomplete({ needed: rest });
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* A buffer for a tunnel's first bytes that parses only when the byte count the last parse
|
|
113
|
+
* needed is reached. A ClientHello longer than 16 KiB is read as `NotTls`.
|
|
114
|
+
*/
|
|
115
|
+
export class ClientHelloReader {
|
|
116
|
+
#chunks = [];
|
|
117
|
+
#length = 0;
|
|
118
|
+
#needed = 1;
|
|
119
|
+
#parses = 0;
|
|
120
|
+
/** Adds bytes from the client and returns the current read result. */
|
|
121
|
+
push(chunk) {
|
|
122
|
+
this.#chunks.push(chunk);
|
|
123
|
+
this.#length += chunk.length;
|
|
124
|
+
if (this.#length < this.#needed)
|
|
125
|
+
return ClientHelloRead.Incomplete({ needed: this.#needed });
|
|
126
|
+
this.#parses += 1;
|
|
127
|
+
const read = readClientHello(this.bytes);
|
|
128
|
+
if (read._tag !== "Incomplete")
|
|
129
|
+
return read;
|
|
130
|
+
if (read.needed > CLIENT_HELLO_LIMIT)
|
|
131
|
+
return notTls("the ClientHello is larger than 16 KiB");
|
|
132
|
+
this.#needed = read.needed;
|
|
133
|
+
return read;
|
|
134
|
+
}
|
|
135
|
+
/** All bytes collected so far, in order; the proxy writes them upstream once the tunnel opens. */
|
|
136
|
+
get bytes() {
|
|
137
|
+
if (this.#chunks.length !== 1) {
|
|
138
|
+
const joined = new Uint8Array(this.#length);
|
|
139
|
+
let at = 0;
|
|
140
|
+
for (const chunk of this.#chunks) {
|
|
141
|
+
joined.set(chunk, at);
|
|
142
|
+
at += chunk.length;
|
|
143
|
+
}
|
|
144
|
+
this.#chunks = [joined];
|
|
145
|
+
}
|
|
146
|
+
return this.#chunks[0] ?? new Uint8Array(0);
|
|
147
|
+
}
|
|
148
|
+
/** The number of parses so far. */
|
|
149
|
+
get parses() {
|
|
150
|
+
return this.#parses;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Returns `host` lowercased and without a trailing dot, for comparing TLS and CONNECT host names. */
|
|
154
|
+
export const comparableHost = (host) => host.toLowerCase().replace(/\.$/u, "");
|
|
155
|
+
//# sourceMappingURL=client-hello.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-hello.js","sourceRoot":"","sources":["../../src/egress/client-hello.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Result, Schema } from "effect";
|
|
2
|
+
/** A lowercase DNS host name without a trailing dot. */
|
|
3
|
+
export declare const HostName: Schema.brand<Schema.String, "HostName">;
|
|
4
|
+
export type HostName = typeof HostName.Type;
|
|
5
|
+
export declare const Port: Schema.brand<Schema.Int, "Port">;
|
|
6
|
+
export type Port = typeof Port.Type;
|
|
7
|
+
declare const EgressRule_base: Schema.Class<EgressRule, Schema.Struct<{
|
|
8
|
+
readonly host: Schema.brand<Schema.String, "HostName">;
|
|
9
|
+
readonly match: Schema.Literals<readonly ["exact", "subdomains"]>;
|
|
10
|
+
readonly ports: Schema.NonEmptyArray<Schema.brand<Schema.Int, "Port">>;
|
|
11
|
+
}>, {}>;
|
|
12
|
+
/**
|
|
13
|
+
* An allowlist line. `exact` permits only that host; `subdomains` permits every name below the
|
|
14
|
+
* domain (`*.pythonhosted.org`) but not the domain itself.
|
|
15
|
+
*/
|
|
16
|
+
export declare class EgressRule extends EgressRule_base {
|
|
17
|
+
/** Returns whether this rule permits `host` on any port. */
|
|
18
|
+
covers(host: HostName): boolean;
|
|
19
|
+
}
|
|
20
|
+
declare const EgressAllowlistError_base: Schema.Class<EgressAllowlistError, Schema.TaggedStruct<"EgressAllowlistError", {
|
|
21
|
+
readonly line: Schema.Int;
|
|
22
|
+
readonly message: Schema.String;
|
|
23
|
+
}>, import("effect/Cause").YieldableError>;
|
|
24
|
+
/** The error for an unreadable allowlist file, with the line and the problem. */
|
|
25
|
+
export declare class EgressAllowlistError extends EgressAllowlistError_base {
|
|
26
|
+
}
|
|
27
|
+
declare const EgressPermitted_base: Schema.Class<EgressPermitted, Schema.TaggedStruct<"EgressPermitted", {
|
|
28
|
+
readonly host: Schema.brand<Schema.String, "HostName">;
|
|
29
|
+
readonly port: Schema.brand<Schema.Int, "Port">;
|
|
30
|
+
readonly rule: typeof EgressRule;
|
|
31
|
+
}>, {}>;
|
|
32
|
+
/** A permitted destination and the rule that permits it. */
|
|
33
|
+
export declare class EgressPermitted extends EgressPermitted_base {
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The reason a destination is refused: unlisted host or port, an IP address, or a name that
|
|
37
|
+
* resolves to a private, loopback, or link-local address. For a tunnel, also: the first bytes
|
|
38
|
+
* aren't a TLS ClientHello, or the ClientHello has no server name, hides it with ECH, or has a
|
|
39
|
+
* different server name than the CONNECT host.
|
|
40
|
+
*/
|
|
41
|
+
export declare const EgressRefusalReason: Schema.Literals<readonly ["host-not-allowed", "port-not-allowed", "address-literal", "private-address", "not-tls", "server-name-missing", "server-name-mismatch"]>;
|
|
42
|
+
export type EgressRefusalReason = typeof EgressRefusalReason.Type;
|
|
43
|
+
declare const EgressRefused_base: Schema.Class<EgressRefused, Schema.TaggedStruct<"EgressRefused", {
|
|
44
|
+
readonly host: Schema.String;
|
|
45
|
+
readonly port: Schema.Int;
|
|
46
|
+
readonly reason: Schema.Literals<readonly ["host-not-allowed", "port-not-allowed", "address-literal", "private-address", "not-tls", "server-name-missing", "server-name-mismatch"]>;
|
|
47
|
+
}>, {}>;
|
|
48
|
+
/** A refused destination. `host` is the raw host the client requested. */
|
|
49
|
+
export declare class EgressRefused extends EgressRefused_base {
|
|
50
|
+
}
|
|
51
|
+
export type EgressDecision = EgressPermitted | EgressRefused;
|
|
52
|
+
declare const EgressAllowlist_base: Schema.Class<EgressAllowlist, Schema.Struct<{
|
|
53
|
+
readonly rules: Schema.$Array<typeof EgressRule>;
|
|
54
|
+
}>, {}>;
|
|
55
|
+
/** The hosts Workers can connect to through the forward proxy. Unlisted hosts and all IP addresses are refused. */
|
|
56
|
+
export declare class EgressAllowlist extends EgressAllowlist_base {
|
|
57
|
+
/** Returns the decision for a connection to `host` on `port`. */
|
|
58
|
+
decide(host: string, port: number): EgressDecision;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parses an allowlist file with one rule per line: `host` or `*.domain`, optionally with
|
|
62
|
+
* `:port` (443 and 80 by default). `#` starts a comment and blank lines are skipped.
|
|
63
|
+
*/
|
|
64
|
+
export declare const parseEgressAllowlist: (text: string) => Result.Result<EgressAllowlist, EgressAllowlistError>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=egress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"egress.d.ts","sourceRoot":"","sources":["../../src/egress/egress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhD,wDAAwD;AACxD,eAAO,MAAM,QAAQ,yCAOW,CAAC;AACjC,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAE5C,eAAO,MAAM,IAAI,kCAEhB,CAAC;AACF,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC;;;;;;AAKpC;;;GAGG;AACH,qBAAa,UAAW,SAAQ,eAI9B;IACA,4DAA4D;IAC5D,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAE9B;CACF;;;;;AAED,iFAAiF;AACjF,qBAAa,oBAAqB,SAAQ,yBAGzC;CAAG;;;;;;AAEJ,4DAA4D;AAC5D,qBAAa,eAAgB,SAAQ,oBAInC;CAAG;AAEL;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,oKAQ9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAC;;;;;;AAElE,0EAA0E;AAC1E,qBAAa,aAAc,SAAQ,kBAIjC;CAAG;AAEL,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;;;;AAO7D,mHAAmH;AACnH,qBAAa,eAAgB,SAAQ,oBAEC;IACpC,iEAAiE;IACjE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CAYjD;CACF;AA6BD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,SACzB,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,oBAAoB,CAWrD,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Option, Result, Schema } from "effect";
|
|
2
|
+
/** A lowercase DNS host name without a trailing dot. */
|
|
3
|
+
export const HostName = Schema.String.check(Schema.isPattern(/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u, {
|
|
4
|
+
message: "Expected a lowercase DNS host name",
|
|
5
|
+
})).pipe(Schema.brand("HostName"));
|
|
6
|
+
export const Port = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65_535 })).pipe(Schema.brand("Port"));
|
|
7
|
+
/** The ports for an allowlist line without a port: HTTPS, and HTTP for apt mirrors. */
|
|
8
|
+
const DEFAULT_PORTS = [Port.make(443), Port.make(80)];
|
|
9
|
+
/**
|
|
10
|
+
* An allowlist line. `exact` permits only that host; `subdomains` permits every name below the
|
|
11
|
+
* domain (`*.pythonhosted.org`) but not the domain itself.
|
|
12
|
+
*/
|
|
13
|
+
export class EgressRule extends Schema.Class("@usine/gateway/EgressRule")({
|
|
14
|
+
host: HostName,
|
|
15
|
+
match: Schema.Literals(["exact", "subdomains"]),
|
|
16
|
+
ports: Schema.NonEmptyArray(Port),
|
|
17
|
+
}) {
|
|
18
|
+
/** Returns whether this rule permits `host` on any port. */
|
|
19
|
+
covers(host) {
|
|
20
|
+
return this.match === "exact" ? host === this.host : host.endsWith(`.${this.host}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** The error for an unreadable allowlist file, with the line and the problem. */
|
|
24
|
+
export class EgressAllowlistError extends Schema.TaggedError()("EgressAllowlistError", { line: Schema.Int, message: Schema.String }) {
|
|
25
|
+
}
|
|
26
|
+
/** A permitted destination and the rule that permits it. */
|
|
27
|
+
export class EgressPermitted extends Schema.TaggedClass()("EgressPermitted", {
|
|
28
|
+
host: HostName,
|
|
29
|
+
port: Port,
|
|
30
|
+
rule: EgressRule,
|
|
31
|
+
}) {
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The reason a destination is refused: unlisted host or port, an IP address, or a name that
|
|
35
|
+
* resolves to a private, loopback, or link-local address. For a tunnel, also: the first bytes
|
|
36
|
+
* aren't a TLS ClientHello, or the ClientHello has no server name, hides it with ECH, or has a
|
|
37
|
+
* different server name than the CONNECT host.
|
|
38
|
+
*/
|
|
39
|
+
export const EgressRefusalReason = Schema.Literals([
|
|
40
|
+
"host-not-allowed",
|
|
41
|
+
"port-not-allowed",
|
|
42
|
+
"address-literal",
|
|
43
|
+
"private-address",
|
|
44
|
+
"not-tls",
|
|
45
|
+
"server-name-missing",
|
|
46
|
+
"server-name-mismatch",
|
|
47
|
+
]);
|
|
48
|
+
/** A refused destination. `host` is the raw host the client requested. */
|
|
49
|
+
export class EgressRefused extends Schema.TaggedClass()("EgressRefused", {
|
|
50
|
+
host: Schema.String,
|
|
51
|
+
port: Schema.Int,
|
|
52
|
+
reason: EgressRefusalReason,
|
|
53
|
+
}) {
|
|
54
|
+
}
|
|
55
|
+
const IPV4_LITERAL = /^\d{1,3}(?:\.\d{1,3}){3}$/u;
|
|
56
|
+
const isHostName = Schema.is(HostName);
|
|
57
|
+
const isPort = Schema.is(Port);
|
|
58
|
+
/** The hosts Workers can connect to through the forward proxy. Unlisted hosts and all IP addresses are refused. */
|
|
59
|
+
export class EgressAllowlist extends Schema.Class("@usine/gateway/EgressAllowlist")({ rules: Schema.Array(EgressRule) }) {
|
|
60
|
+
/** Returns the decision for a connection to `host` on `port`. */
|
|
61
|
+
decide(host, port) {
|
|
62
|
+
const normalized = host.toLowerCase().replace(/\.$/u, "");
|
|
63
|
+
if (IPV4_LITERAL.test(normalized) || normalized.includes(":"))
|
|
64
|
+
return new EgressRefused({ host, port, reason: "address-literal" });
|
|
65
|
+
if (!isHostName(normalized) || !isPort(port))
|
|
66
|
+
return new EgressRefused({ host, port, reason: "host-not-allowed" });
|
|
67
|
+
const covering = this.rules.filter((rule) => rule.covers(normalized));
|
|
68
|
+
if (covering.length === 0)
|
|
69
|
+
return new EgressRefused({ host, port, reason: "host-not-allowed" });
|
|
70
|
+
const rule = covering.find((candidate) => candidate.ports.includes(port));
|
|
71
|
+
return rule === undefined
|
|
72
|
+
? new EgressRefused({ host, port, reason: "port-not-allowed" })
|
|
73
|
+
: new EgressPermitted({ host: normalized, port, rule });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const parseLine = (text, line) => {
|
|
77
|
+
const match = /^(\*\.)?([^:\s]+)(?::(\d+))?$/u.exec(text);
|
|
78
|
+
const host = match?.[2]?.toLowerCase();
|
|
79
|
+
if (match === null || host === undefined || !isHostName(host) || IPV4_LITERAL.test(host))
|
|
80
|
+
return Result.fail(new EgressAllowlistError({
|
|
81
|
+
line,
|
|
82
|
+
message: `Expected a host name, or *. and a domain, optionally followed by :port, got "${text}"`,
|
|
83
|
+
}));
|
|
84
|
+
const port = Option.fromNullishOr(match[3]).pipe(Option.map(Number));
|
|
85
|
+
if (Option.isSome(port) && !isPort(port.value))
|
|
86
|
+
return Result.fail(new EgressAllowlistError({ line, message: `Port ${port.value} is not between 1 and 65535` }));
|
|
87
|
+
return Result.succeed(new EgressRule({
|
|
88
|
+
host,
|
|
89
|
+
match: match[1] === undefined ? "exact" : "subdomains",
|
|
90
|
+
ports: Option.match(port, {
|
|
91
|
+
onNone: () => DEFAULT_PORTS,
|
|
92
|
+
onSome: (value) => [Port.make(value)],
|
|
93
|
+
}),
|
|
94
|
+
}));
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Parses an allowlist file with one rule per line: `host` or `*.domain`, optionally with
|
|
98
|
+
* `:port` (443 and 80 by default). `#` starts a comment and blank lines are skipped.
|
|
99
|
+
*/
|
|
100
|
+
export const parseEgressAllowlist = (text) => {
|
|
101
|
+
const rules = [];
|
|
102
|
+
const lines = text.split(/\r?\n/u);
|
|
103
|
+
for (const [index, raw] of lines.entries()) {
|
|
104
|
+
const content = raw.replace(/#.*$/u, "").trim();
|
|
105
|
+
if (content === "")
|
|
106
|
+
continue;
|
|
107
|
+
const parsed = parseLine(content, index + 1);
|
|
108
|
+
if (Result.isFailure(parsed))
|
|
109
|
+
return Result.fail(parsed.failure);
|
|
110
|
+
rules.push(parsed.success);
|
|
111
|
+
}
|
|
112
|
+
return Result.succeed(new EgressAllowlist({ rules }));
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=egress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"egress.js","sourceRoot":"","sources":["../../src/egress/egress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { Attachments } from "./attachment/store.ts";
|
|
3
|
+
import { CallerTokens } from "./caller/caller.ts";
|
|
4
|
+
import { SettingsInForce } from "./settings.ts";
|
|
5
|
+
export { adminRoutes } from "./admin/server.ts";
|
|
6
|
+
export { AnonymousCredential, Attachment, AttachmentAlreadyExpired, AttachmentConflict, AttachmentCredential, AttachmentId, type AttachmentLookup, ExpiredAttachment, InstallationCredential, LiveAttachment, MissingAttachment, } from "./attachment/attachment.ts";
|
|
7
|
+
export { Attachments } from "./attachment/store.ts";
|
|
8
|
+
export { CallerClaims, CallerToken, CallerTokenRejected, CallerTokens, GATEWAY_TOKEN_HEADER, tokenFromAuthorization, } from "./caller/caller.ts";
|
|
9
|
+
export { type EgressDecision, EgressAllowlist, EgressAllowlistError, EgressPermitted, EgressRefusalReason, EgressRefused, EgressRule, HostName, parseEgressAllowlist, Port, } from "./egress/egress.ts";
|
|
10
|
+
export { GITHUB_ORIGINS, type GitHubOrigins } from "./github/github.ts";
|
|
11
|
+
export { AnthropicApiKey, AnthropicCredential, AnthropicProvider, AnthropicSubscriptionToken, type LlmProviders, OpenAiProvider, } from "./llm/llm.ts";
|
|
12
|
+
export { endToEndHeaders } from "./proxy/forward.ts";
|
|
13
|
+
export { GatewayProxy, REFUSAL_HEADER, RefusalReason } from "./proxy/proxy.ts";
|
|
14
|
+
export { GatewayDomain } from "@usine/core/worker";
|
|
15
|
+
export { AdminToken, CallerKey, GatewaySettings, gatewayHosts, type ReloadableSettings, SettingsInForce, } from "./settings.ts";
|
|
16
|
+
export { UpstreamBase } from "./upstream.ts";
|
|
17
|
+
/**
|
|
18
|
+
* The Gateway's shared state for one process: the attachment store, the caller token signer,
|
|
19
|
+
* and the current settings. Provide this layer once to the reverse proxy, forward proxy, admin
|
|
20
|
+
* API, and reload.
|
|
21
|
+
*/
|
|
22
|
+
export declare const gatewayServices: Layer.Layer<Attachments | CallerTokens | SettingsInForce, never, import("./settings.ts").GatewaySettings>;
|
|
23
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,EACd,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,cAAc,EACnB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,QAAQ,EACR,oBAAoB,EACpB,IAAI,GACL,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,0BAA0B,EAC1B,KAAK,YAAY,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,EACZ,KAAK,kBAAkB,EACvB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,eAAe,2GAI3B,CAAC"}
|
package/dist/gateway.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { Attachments } from "./attachment/store.js";
|
|
3
|
+
import { CallerTokens } from "./caller/caller.js";
|
|
4
|
+
import { SettingsInForce } from "./settings.js";
|
|
5
|
+
export { adminRoutes } from "./admin/server.js";
|
|
6
|
+
export { AnonymousCredential, Attachment, AttachmentAlreadyExpired, AttachmentConflict, AttachmentCredential, AttachmentId, ExpiredAttachment, InstallationCredential, LiveAttachment, MissingAttachment, } from "./attachment/attachment.js";
|
|
7
|
+
export { Attachments } from "./attachment/store.js";
|
|
8
|
+
export { CallerClaims, CallerToken, CallerTokenRejected, CallerTokens, GATEWAY_TOKEN_HEADER, tokenFromAuthorization, } from "./caller/caller.js";
|
|
9
|
+
export { EgressAllowlist, EgressAllowlistError, EgressPermitted, EgressRefusalReason, EgressRefused, EgressRule, HostName, parseEgressAllowlist, Port, } from "./egress/egress.js";
|
|
10
|
+
export { GITHUB_ORIGINS } from "./github/github.js";
|
|
11
|
+
export { AnthropicApiKey, AnthropicCredential, AnthropicProvider, AnthropicSubscriptionToken, OpenAiProvider, } from "./llm/llm.js";
|
|
12
|
+
export { endToEndHeaders } from "./proxy/forward.js";
|
|
13
|
+
export { GatewayProxy, REFUSAL_HEADER, RefusalReason } from "./proxy/proxy.js";
|
|
14
|
+
export { GatewayDomain } from "@usine/core/worker";
|
|
15
|
+
export { AdminToken, CallerKey, GatewaySettings, gatewayHosts, SettingsInForce, } from "./settings.js";
|
|
16
|
+
export { UpstreamBase } from "./upstream.js";
|
|
17
|
+
/**
|
|
18
|
+
* The Gateway's shared state for one process: the attachment store, the caller token signer,
|
|
19
|
+
* and the current settings. Provide this layer once to the reverse proxy, forward proxy, admin
|
|
20
|
+
* API, and reload.
|
|
21
|
+
*/
|
|
22
|
+
export const gatewayServices = Layer.mergeAll(Attachments.layer, CallerTokens.layer, SettingsInForce.layer);
|
|
23
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EAEZ,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,EACd,iBAAiB,GAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,QAAQ,EACR,oBAAoB,EACpB,IAAI,GACL,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAsB,MAAM,oBAAoB,CAAC;AACxE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,0BAA0B,EAE1B,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,EAEZ,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC,QAAQ,CAC3C,WAAW,CAAC,KAAK,EACjB,YAAY,CAAC,KAAK,EAClB,eAAe,CAAC,KAAK,CACtB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
import { Headers } from "effect/unstable/http";
|
|
4
|
+
import type { AttachmentCredential } from "../attachment/attachment.ts";
|
|
5
|
+
import { UpstreamBase } from "../upstream.ts";
|
|
6
|
+
/** The upstream origins for GitHub requests: `web` for git smart HTTP, `api` for REST and GraphQL, `uploads` for release asset uploads. */
|
|
7
|
+
export interface GitHubOrigins {
|
|
8
|
+
readonly web: UpstreamBase;
|
|
9
|
+
readonly api: UpstreamBase;
|
|
10
|
+
readonly uploads: UpstreamBase;
|
|
11
|
+
}
|
|
12
|
+
/** The github.com origins. */
|
|
13
|
+
export declare const GITHUB_ORIGINS: GitHubOrigins;
|
|
14
|
+
/**
|
|
15
|
+
* The host a GitHub request was sent to. `gateway` is `git.<domain>`, which serves git at
|
|
16
|
+
* `/OWNER/REPO.git` and the API under `/api/` like GitHub Enterprise Server, so `gh` works with
|
|
17
|
+
* `GH_HOST` set to it. The others are GitHub's own hosts, used when `gh` sends requests through
|
|
18
|
+
* its `http_unix_socket`, for example to follow a URL from an API response.
|
|
19
|
+
*/
|
|
20
|
+
export type GitHubHost = "gateway" | "web" | "api" | "uploads";
|
|
21
|
+
/** GitHub's host names and what each serves. */
|
|
22
|
+
export declare const GITHUB_HOSTS: ReadonlyMap<string, GitHubHost>;
|
|
23
|
+
/**
|
|
24
|
+
* A forwarded GitHub request: git smart HTTP for a repository, or an API call. `repository` is
|
|
25
|
+
* the repository in the path; GraphQL and calls such as `/user` have none.
|
|
26
|
+
*/
|
|
27
|
+
export interface GitHubRoute {
|
|
28
|
+
readonly protocol: "git" | "api";
|
|
29
|
+
readonly upstream: string;
|
|
30
|
+
readonly repository: Option.Option<RepositoryName>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the upstream URL and repository for a GitHub request. Returns `None` for paths
|
|
34
|
+
* outside git smart HTTP, Git LFS, and the API, such as web pages.
|
|
35
|
+
*/
|
|
36
|
+
export declare const githubRoute: (origins: GitHubOrigins, host: GitHubHost, pathname: string, search: string) => Option.Option<GitHubRoute>;
|
|
37
|
+
/**
|
|
38
|
+
* Adds the attachment's credential to a GitHub request whose caller credential was removed: as
|
|
39
|
+
* the `x-access-token` password for git, or as a bearer token for the API. An anonymous
|
|
40
|
+
* attachment adds nothing.
|
|
41
|
+
*/
|
|
42
|
+
export declare const withGitHubCredential: (route: GitHubRoute, credential: AttachmentCredential, headers: Headers.Headers) => Headers.Headers;
|
|
43
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/github/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAY,MAAM,EAAoB,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,2IAA2I;AAC3I,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAED,8BAA8B;AAC9B,eAAO,MAAM,cAAc,EAAE,aAI5B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/D,gDAAgD;AAChD,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAIvD,CAAC;AAEH;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;CACpD;AAuCD;;;GAGG;AACH,eAAO,MAAM,WAAW,YACb,aAAa,QAChB,UAAU,YACN,MAAM,UACR,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,WAAW,CAiB3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,UACxB,WAAW,cACN,oBAAoB,WACvB,OAAO,CAAC,OAAO,KACvB,OAAO,CAAC,OAeV,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { Encoding, Option, Redacted, Schema } from "effect";
|
|
3
|
+
import { Headers } from "effect/unstable/http";
|
|
4
|
+
import { UpstreamBase } from "../upstream.js";
|
|
5
|
+
/** The github.com origins. */
|
|
6
|
+
export const GITHUB_ORIGINS = {
|
|
7
|
+
web: UpstreamBase.make("https://github.com"),
|
|
8
|
+
api: UpstreamBase.make("https://api.github.com"),
|
|
9
|
+
uploads: UpstreamBase.make("https://uploads.github.com"),
|
|
10
|
+
};
|
|
11
|
+
/** GitHub's host names and what each serves. */
|
|
12
|
+
export const GITHUB_HOSTS = new Map([
|
|
13
|
+
["github.com", "web"],
|
|
14
|
+
["api.github.com", "api"],
|
|
15
|
+
["uploads.github.com", "uploads"],
|
|
16
|
+
]);
|
|
17
|
+
const GIT_PATH = /^\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?\/(?:info\/refs|git-upload-pack|git-receive-pack|info\/lfs(?:\/[^?#]*)?)$/u;
|
|
18
|
+
const REPOSITORY_PATH = /^\/repos\/([^/]+)\/([^/]+)(?:\/|$)/u;
|
|
19
|
+
const decodeRepository = Schema.decodeUnknownOption(RepositoryName);
|
|
20
|
+
const gitRoute = (origins, pathname, search) => {
|
|
21
|
+
const match = GIT_PATH.exec(pathname);
|
|
22
|
+
if (match === null)
|
|
23
|
+
return Option.none();
|
|
24
|
+
return Option.map(decodeRepository(`${match[1]}/${match[2]}`), (repository) => ({
|
|
25
|
+
protocol: "git",
|
|
26
|
+
upstream: `${origins.web}${pathname}${search}`,
|
|
27
|
+
repository: Option.some(repository),
|
|
28
|
+
}));
|
|
29
|
+
};
|
|
30
|
+
/** Returns the route for an API path, or `None` for a `/repos/OWNER/REPO` path whose segments aren't a valid repository name. */
|
|
31
|
+
const apiRoute = (base, rest, search) => {
|
|
32
|
+
const path = rest === "" ? "/" : rest;
|
|
33
|
+
const match = REPOSITORY_PATH.exec(path);
|
|
34
|
+
const repository = match === null
|
|
35
|
+
? Option.some(Option.none())
|
|
36
|
+
: Option.map(decodeRepository(`${match[1]}/${match[2]}`), Option.some);
|
|
37
|
+
return Option.map(repository, (named) => ({
|
|
38
|
+
protocol: "api",
|
|
39
|
+
upstream: `${base}${path}${search}`,
|
|
40
|
+
repository: named,
|
|
41
|
+
}));
|
|
42
|
+
};
|
|
43
|
+
const afterPrefix = (pathname, prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)
|
|
44
|
+
? Option.some(pathname.slice(prefix.length))
|
|
45
|
+
: Option.none();
|
|
46
|
+
/**
|
|
47
|
+
* Returns the upstream URL and repository for a GitHub request. Returns `None` for paths
|
|
48
|
+
* outside git smart HTTP, Git LFS, and the API, such as web pages.
|
|
49
|
+
*/
|
|
50
|
+
export const githubRoute = (origins, host, pathname, search) => {
|
|
51
|
+
switch (host) {
|
|
52
|
+
case "web":
|
|
53
|
+
return gitRoute(origins, pathname, search);
|
|
54
|
+
case "api":
|
|
55
|
+
return apiRoute(origins.api, pathname, search);
|
|
56
|
+
case "uploads":
|
|
57
|
+
return apiRoute(origins.uploads, pathname, search);
|
|
58
|
+
case "gateway": {
|
|
59
|
+
if (pathname === "/api/graphql")
|
|
60
|
+
return apiRoute(origins.api, "/graphql", search);
|
|
61
|
+
const rest = afterPrefix(pathname, "/api/v3");
|
|
62
|
+
if (Option.isSome(rest))
|
|
63
|
+
return apiRoute(origins.api, rest.value, search);
|
|
64
|
+
const upload = afterPrefix(pathname, "/api/uploads");
|
|
65
|
+
if (Option.isSome(upload))
|
|
66
|
+
return apiRoute(origins.uploads, upload.value, search);
|
|
67
|
+
return gitRoute(origins, pathname, search);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Adds the attachment's credential to a GitHub request whose caller credential was removed: as
|
|
73
|
+
* the `x-access-token` password for git, or as a bearer token for the API. An anonymous
|
|
74
|
+
* attachment adds nothing.
|
|
75
|
+
*/
|
|
76
|
+
export const withGitHubCredential = (route, credential, headers) => {
|
|
77
|
+
switch (credential._tag) {
|
|
78
|
+
case "AnonymousCredential":
|
|
79
|
+
return headers;
|
|
80
|
+
case "InstallationCredential": {
|
|
81
|
+
const token = Redacted.value(credential.token);
|
|
82
|
+
return Headers.set(headers, "authorization", route.protocol === "git"
|
|
83
|
+
? `Basic ${Encoding.encodeBase64(`x-access-token:${token}`)}`
|
|
84
|
+
: `Bearer ${token}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/github/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { GatewayCredentialKind } from "@usine/core/worker";
|
|
2
|
+
import { Option, Schema } from "effect";
|
|
3
|
+
import { Headers } from "effect/unstable/http";
|
|
4
|
+
declare const AnthropicApiKey_base: Schema.Class<AnthropicApiKey, Schema.TaggedStruct<"AnthropicApiKey", {
|
|
5
|
+
readonly key: Schema.Redacted<Schema.NonEmptyString>;
|
|
6
|
+
}>, {}>;
|
|
7
|
+
/** An Anthropic API key, sent upstream as `x-api-key`. */
|
|
8
|
+
export declare class AnthropicApiKey extends AnthropicApiKey_base {
|
|
9
|
+
}
|
|
10
|
+
declare const AnthropicSubscriptionToken_base: Schema.Class<AnthropicSubscriptionToken, Schema.TaggedStruct<"AnthropicSubscriptionToken", {
|
|
11
|
+
readonly token: Schema.Redacted<Schema.NonEmptyString>;
|
|
12
|
+
}>, {}>;
|
|
13
|
+
/**
|
|
14
|
+
* A Claude subscription token from `claude setup-token`, sent upstream as a bearer token with
|
|
15
|
+
* the OAuth beta flag. Anthropic doesn't document swapping a placeholder for one through a
|
|
16
|
+
* proxy; `test/placeholder-swap` checks that it works.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AnthropicSubscriptionToken extends AnthropicSubscriptionToken_base {
|
|
19
|
+
}
|
|
20
|
+
/** The credential added to Anthropic requests. */
|
|
21
|
+
export declare const AnthropicCredential: Schema.Union<readonly [typeof AnthropicApiKey, typeof AnthropicSubscriptionToken]>;
|
|
22
|
+
export type AnthropicCredential = typeof AnthropicCredential.Type;
|
|
23
|
+
declare const AnthropicProvider_base: Schema.Class<AnthropicProvider, Schema.TaggedStruct<"AnthropicProvider", {
|
|
24
|
+
readonly upstream: Schema.brand<Schema.String, "UpstreamBase">;
|
|
25
|
+
readonly credential: Schema.Union<readonly [typeof AnthropicApiKey, typeof AnthropicSubscriptionToken]>;
|
|
26
|
+
}>, {}>;
|
|
27
|
+
/** Claude Code's upstream. On the Worker, `ANTHROPIC_BASE_URL` is `http://llm.<domain>/anthropic`. */
|
|
28
|
+
export declare class AnthropicProvider extends AnthropicProvider_base {
|
|
29
|
+
}
|
|
30
|
+
declare const OpenAiProvider_base: Schema.Class<OpenAiProvider, Schema.TaggedStruct<"OpenAiProvider", {
|
|
31
|
+
readonly upstream: Schema.brand<Schema.String, "UpstreamBase">;
|
|
32
|
+
readonly key: Schema.Redacted<Schema.NonEmptyString>;
|
|
33
|
+
}>, {}>;
|
|
34
|
+
/**
|
|
35
|
+
* An OpenAI-compatible upstream for Codex. On the Worker, the `model_provider` `base_url` is
|
|
36
|
+
* `http://llm.<domain>/openai/v1`. The key is sent as a bearer token.
|
|
37
|
+
*/
|
|
38
|
+
export declare class OpenAiProvider extends OpenAiProvider_base {
|
|
39
|
+
}
|
|
40
|
+
/** The LLM upstreams the Gateway forwards to. Requests for a provider without a credential are refused. */
|
|
41
|
+
export interface LlmProviders {
|
|
42
|
+
readonly anthropic: Option.Option<AnthropicProvider>;
|
|
43
|
+
readonly openai: Option.Option<OpenAiProvider>;
|
|
44
|
+
}
|
|
45
|
+
export declare const LlmProviderName: Schema.Literals<readonly ["anthropic", "openai"]>;
|
|
46
|
+
export type LlmProviderName = typeof LlmProviderName.Type;
|
|
47
|
+
/** An LLM request's provider and the path after its prefix. */
|
|
48
|
+
export interface LlmTarget {
|
|
49
|
+
readonly provider: LlmProviderName;
|
|
50
|
+
readonly rest: string;
|
|
51
|
+
}
|
|
52
|
+
/** Returns the provider from the first path segment; for example, `/anthropic/v1/messages` is Anthropic's `/v1/messages`. */
|
|
53
|
+
export declare const llmTarget: (pathname: string) => Option.Option<LlmTarget>;
|
|
54
|
+
/** Returns the credential kind sent to `provider`; always an API key for OpenAI. */
|
|
55
|
+
export declare const credentialKind: (provider: AnthropicProvider | OpenAiProvider) => GatewayCredentialKind;
|
|
56
|
+
/** Returns the configured provider for `name` as a single forwardable value. */
|
|
57
|
+
export declare const configuredProvider: (providers: LlmProviders, name: LlmProviderName) => Option.Option<AnthropicProvider | OpenAiProvider>;
|
|
58
|
+
/**
|
|
59
|
+
* Adds the provider's credential to headers without the caller's credential. For a
|
|
60
|
+
* subscription token, also merges the OAuth beta flag into the harness's flags.
|
|
61
|
+
*/
|
|
62
|
+
export declare const withProviderCredential: (provider: AnthropicProvider | OpenAiProvider, headers: Headers.Headers) => Headers.Headers;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=llm.d.ts.map
|