@usine/gateway 0.0.0-stage → 0.2.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 +661 -0
- package/README.md +200 -2
- package/dist/admin/api.d.ts +146 -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 +84 -0
- package/dist/attachment/attachment.d.ts.map +1 -0
- package/dist/attachment/attachment.js +67 -0
- package/dist/attachment/attachment.js.map +1 -0
- package/dist/attachment/store.d.ts +34 -0
- package/dist/attachment/store.d.ts.map +1 -0
- package/dist/attachment/store.js +133 -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 +131 -0
- package/dist/caller/caller.js.map +1 -0
- package/dist/chatgpt/auth-file.d.ts +47 -0
- package/dist/chatgpt/auth-file.d.ts.map +1 -0
- package/dist/chatgpt/auth-file.js +132 -0
- package/dist/chatgpt/auth-file.js.map +1 -0
- package/dist/chatgpt/logins.d.ts +50 -0
- package/dist/chatgpt/logins.d.ts.map +1 -0
- package/dist/chatgpt/logins.js +303 -0
- package/dist/chatgpt/logins.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 +27 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +27 -0
- package/dist/gateway.js.map +1 -0
- package/dist/github/github.d.ts +54 -0
- package/dist/github/github.d.ts.map +1 -0
- package/dist/github/github.js +112 -0
- package/dist/github/github.js.map +1 -0
- package/dist/github/rename.d.ts +30 -0
- package/dist/github/rename.d.ts.map +1 -0
- package/dist/github/rename.js +83 -0
- package/dist/github/rename.js.map +1 -0
- package/dist/llm/llm.d.ts +113 -0
- package/dist/llm/llm.d.ts.map +1 -0
- package/dist/llm/llm.js +144 -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 +113 -0
- package/dist/node/environment.d.ts.map +1 -0
- package/dist/node/environment.js +140 -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 +28 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +81 -0
- package/dist/node/node.js.map +1 -0
- package/dist/node/reload.d.ts +27 -0
- package/dist/node/reload.d.ts.map +1 -0
- package/dist/node/reload.js +170 -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 +29 -0
- package/dist/proxy/proxy.d.ts.map +1 -0
- package/dist/proxy/proxy.js +297 -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,84 @@
|
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { DateTime, Effect, Schema } from "effect";
|
|
3
|
+
/**
|
|
4
|
+
* The ID of an attachment, unchanged by renewals. A new attach for the same Worker and
|
|
5
|
+
* repository gets a new ID, so an earlier caller token is never accepted again.
|
|
6
|
+
*/
|
|
7
|
+
export declare const AttachmentId: Schema.brand<Schema.String, "AttachmentId">;
|
|
8
|
+
export type AttachmentId = typeof AttachmentId.Type;
|
|
9
|
+
/** Returns a new attachment ID of 128 random bits from the platform's cryptographic generator. */
|
|
10
|
+
export declare const newAttachmentId: Effect.Effect<string & import("effect/Brand").Brand<"AttachmentId">, never, never>;
|
|
11
|
+
declare const InstallationCredential_base: Schema.Class<InstallationCredential, Schema.TaggedStruct<"InstallationCredential", {
|
|
12
|
+
readonly token: Schema.Redacted<Schema.NonEmptyString>;
|
|
13
|
+
}>, {}>;
|
|
14
|
+
/** A GitHub App installation token added to the Worker's requests for the repository. */
|
|
15
|
+
export declare class InstallationCredential extends InstallationCredential_base {
|
|
16
|
+
}
|
|
17
|
+
declare const AnonymousCredential_base: Schema.Class<AnonymousCredential, Schema.TaggedStruct<"AnonymousCredential", {}>, {}>;
|
|
18
|
+
/**
|
|
19
|
+
* No credential: requests are forwarded unchanged, so only anonymous GitHub access works, such
|
|
20
|
+
* as reading a public repository.
|
|
21
|
+
*/
|
|
22
|
+
export declare class AnonymousCredential extends AnonymousCredential_base {
|
|
23
|
+
}
|
|
24
|
+
/** The credential added to a Worker's GitHub requests for an attached repository. */
|
|
25
|
+
export declare const AttachmentCredential: Schema.Union<readonly [typeof InstallationCredential, typeof AnonymousCredential]>;
|
|
26
|
+
export type AttachmentCredential = typeof AttachmentCredential.Type;
|
|
27
|
+
declare const Attachment_base: Schema.Class<Attachment, Schema.Struct<{
|
|
28
|
+
readonly id: Schema.brand<Schema.String, "AttachmentId">;
|
|
29
|
+
readonly worker: Schema.brand<Schema.String, "WorkerName">;
|
|
30
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
31
|
+
readonly credential: Schema.Union<readonly [typeof InstallationCredential, typeof AnonymousCredential]>;
|
|
32
|
+
readonly expiresAt: Schema.DateTimeUtc;
|
|
33
|
+
}>, {}>;
|
|
34
|
+
/**
|
|
35
|
+
* A grant to one Worker VM for one repository. Until `expiresAt`, the Gateway adds `credential`
|
|
36
|
+
* to the VM's requests for that repository and accepts the attachment's caller token.
|
|
37
|
+
* `expiresAt` is the token's expiry. A Worker and repository can have several attachments, one
|
|
38
|
+
* per Attempt.
|
|
39
|
+
*/
|
|
40
|
+
export declare class Attachment extends Attachment_base {
|
|
41
|
+
/** Returns whether the attachment grants access at `now`. */
|
|
42
|
+
isLiveAt(now: DateTime.Utc): boolean;
|
|
43
|
+
}
|
|
44
|
+
declare const LiveAttachment_base: Schema.Class<LiveAttachment, Schema.TaggedStruct<"LiveAttachment", {
|
|
45
|
+
readonly attachment: typeof Attachment;
|
|
46
|
+
readonly newNames: Schema.$Array<Schema.brand<Schema.String, "RepositoryName">>;
|
|
47
|
+
}>, {}>;
|
|
48
|
+
/**
|
|
49
|
+
* A lookup result for an active attachment. `newNames` are the names GitHub's permanent
|
|
50
|
+
* redirects gave the attached repository after a rename or transfer.
|
|
51
|
+
*/
|
|
52
|
+
export declare class LiveAttachment extends LiveAttachment_base {
|
|
53
|
+
/** Returns whether `repository` is the attached repository, by its attached name or a new name. */
|
|
54
|
+
serves(repository: RepositoryName): boolean;
|
|
55
|
+
}
|
|
56
|
+
declare const ExpiredAttachment_base: Schema.Class<ExpiredAttachment, Schema.TaggedStruct<"ExpiredAttachment", {
|
|
57
|
+
readonly expiredAt: Schema.DateTimeUtc;
|
|
58
|
+
}>, {}>;
|
|
59
|
+
/** A lookup result for an attachment whose token expired without renewal. */
|
|
60
|
+
export declare class ExpiredAttachment extends ExpiredAttachment_base {
|
|
61
|
+
}
|
|
62
|
+
declare const MissingAttachment_base: Schema.Class<MissingAttachment, Schema.TaggedStruct<"MissingAttachment", {}>, {}>;
|
|
63
|
+
/** A lookup result for an ID that isn't in the store because it was detached or never attached on this Gateway. */
|
|
64
|
+
export declare class MissingAttachment extends MissingAttachment_base {
|
|
65
|
+
}
|
|
66
|
+
export type AttachmentLookup = LiveAttachment | ExpiredAttachment | MissingAttachment;
|
|
67
|
+
declare const AttachmentAlreadyExpired_base: Schema.Class<AttachmentAlreadyExpired, Schema.TaggedStruct<"AttachmentAlreadyExpired", {
|
|
68
|
+
readonly worker: Schema.brand<Schema.String, "WorkerName">;
|
|
69
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
70
|
+
readonly expiresAt: Schema.DateTimeUtc;
|
|
71
|
+
}>, import("effect/Cause").YieldableError>;
|
|
72
|
+
/** The error when an attach request has an expired token. */
|
|
73
|
+
export declare class AttachmentAlreadyExpired extends AttachmentAlreadyExpired_base {
|
|
74
|
+
}
|
|
75
|
+
declare const AttachmentConflict_base: Schema.Class<AttachmentConflict, Schema.TaggedStruct<"AttachmentConflict", {
|
|
76
|
+
readonly id: Schema.brand<Schema.String, "AttachmentId">;
|
|
77
|
+
readonly worker: Schema.brand<Schema.String, "WorkerName">;
|
|
78
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
79
|
+
}>, import("effect/Cause").YieldableError>;
|
|
80
|
+
/** The error when a renewal uses an attachment ID that belongs to another Worker or repository. */
|
|
81
|
+
export declare class AttachmentConflict extends AttachmentConflict_base {
|
|
82
|
+
}
|
|
83
|
+
export {};
|
|
84
|
+
//# sourceMappingURL=attachment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../src/attachment/attachment.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAEjF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGlD;;;GAGG;AACH,eAAO,MAAM,YAAY,6CAEW,CAAC;AACrC,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC;AAEpD,kGAAkG;AAClG,eAAO,MAAM,eAAe,oFAE3B,CAAC;;;;AAEF,yFAAyF;AACzF,qBAAa,sBAAuB,SAAQ,2BAG3C;CAAG;;AAEJ;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,wBAGxC;CAAG;AAEJ,qFAAqF;AACrF,eAAO,MAAM,oBAAoB,oFAA8D,CAAC;AAChG,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAC;;;;;;;;AAEpE;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,eAM9B;IACA,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,GAAG,OAAO,CAEnC;CACF;;;;;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,mBAGlC;IACA,mGAAmG;IACnG,MAAM,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAK1C;CACF;;;;AAED,6EAA6E;AAC7E,qBAAa,iBAAkB,SAAQ,sBAGtC;CAAG;;AAEJ,mHAAmH;AACnH,qBAAa,iBAAkB,SAAQ,sBAGtC;CAAG;AAEJ,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;;;;;;AAEtF,6DAA6D;AAC7D,qBAAa,wBAAyB,SAAQ,6BAG7C;CAAG;;;;;;AAEJ,mGAAmG;AACnG,qBAAa,kBAAmB,SAAQ,uBAGvC;CAAG"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { canonicalRepositoryName, RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { WorkerName } from "@usine/core/worker";
|
|
3
|
+
import { DateTime, Effect, Schema } from "effect";
|
|
4
|
+
import { Base64Url } from "effect/encoding";
|
|
5
|
+
/**
|
|
6
|
+
* The ID of an attachment, unchanged by renewals. A new attach for the same Worker and
|
|
7
|
+
* repository gets a new ID, so an earlier caller token is never accepted again.
|
|
8
|
+
*/
|
|
9
|
+
export const AttachmentId = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}$/u, { message: "Expected a 22-character attachment ID" })).pipe(Schema.brand("AttachmentId"));
|
|
10
|
+
/** Returns a new attachment ID of 128 random bits from the platform's cryptographic generator. */
|
|
11
|
+
export const newAttachmentId = Effect.sync(() => AttachmentId.make(Base64Url.encode(crypto.getRandomValues(new Uint8Array(16)))));
|
|
12
|
+
/** A GitHub App installation token added to the Worker's requests for the repository. */
|
|
13
|
+
export class InstallationCredential extends Schema.TaggedClass()("InstallationCredential", { token: Schema.Redacted(Schema.NonEmptyString) }) {
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* No credential: requests are forwarded unchanged, so only anonymous GitHub access works, such
|
|
17
|
+
* as reading a public repository.
|
|
18
|
+
*/
|
|
19
|
+
export class AnonymousCredential extends Schema.TaggedClass()("AnonymousCredential", {}) {
|
|
20
|
+
}
|
|
21
|
+
/** The credential added to a Worker's GitHub requests for an attached repository. */
|
|
22
|
+
export const AttachmentCredential = Schema.Union([InstallationCredential, AnonymousCredential]);
|
|
23
|
+
/**
|
|
24
|
+
* A grant to one Worker VM for one repository. Until `expiresAt`, the Gateway adds `credential`
|
|
25
|
+
* to the VM's requests for that repository and accepts the attachment's caller token.
|
|
26
|
+
* `expiresAt` is the token's expiry. A Worker and repository can have several attachments, one
|
|
27
|
+
* per Attempt.
|
|
28
|
+
*/
|
|
29
|
+
export class Attachment extends Schema.Class("@usine/gateway/Attachment")({
|
|
30
|
+
id: AttachmentId,
|
|
31
|
+
worker: WorkerName,
|
|
32
|
+
repository: RepositoryName,
|
|
33
|
+
credential: AttachmentCredential,
|
|
34
|
+
expiresAt: Schema.DateTimeUtc,
|
|
35
|
+
}) {
|
|
36
|
+
/** Returns whether the attachment grants access at `now`. */
|
|
37
|
+
isLiveAt(now) {
|
|
38
|
+
return DateTime.isLessThan(now, this.expiresAt);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A lookup result for an active attachment. `newNames` are the names GitHub's permanent
|
|
43
|
+
* redirects gave the attached repository after a rename or transfer.
|
|
44
|
+
*/
|
|
45
|
+
export class LiveAttachment extends Schema.TaggedClass()("LiveAttachment", {
|
|
46
|
+
attachment: Attachment,
|
|
47
|
+
newNames: Schema.Array(RepositoryName),
|
|
48
|
+
}) {
|
|
49
|
+
/** Returns whether `repository` is the attached repository, by its attached name or a new name. */
|
|
50
|
+
serves(repository) {
|
|
51
|
+
const wanted = canonicalRepositoryName(repository);
|
|
52
|
+
return [this.attachment.repository, ...this.newNames].some((name) => canonicalRepositoryName(name) === wanted);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** A lookup result for an attachment whose token expired without renewal. */
|
|
56
|
+
export class ExpiredAttachment extends Schema.TaggedClass()("ExpiredAttachment", { expiredAt: Schema.DateTimeUtc }) {
|
|
57
|
+
}
|
|
58
|
+
/** A lookup result for an ID that isn't in the store because it was detached or never attached on this Gateway. */
|
|
59
|
+
export class MissingAttachment extends Schema.TaggedClass()("MissingAttachment", {}) {
|
|
60
|
+
}
|
|
61
|
+
/** The error when an attach request has an expired token. */
|
|
62
|
+
export class AttachmentAlreadyExpired extends Schema.TaggedError()("AttachmentAlreadyExpired", { worker: WorkerName, repository: RepositoryName, expiresAt: Schema.DateTimeUtc }) {
|
|
63
|
+
}
|
|
64
|
+
/** The error when a renewal uses an attachment ID that belongs to another Worker or repository. */
|
|
65
|
+
export class AttachmentConflict extends Schema.TaggedError()("AttachmentConflict", { id: AttachmentId, worker: WorkerName, repository: RepositoryName }) {
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=attachment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../src/attachment/attachment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
import { type Attachment, AttachmentAlreadyExpired, AttachmentConflict, type AttachmentId, type AttachmentLookup } from "./attachment.ts";
|
|
4
|
+
declare const Attachments_base: Context.ServiceClass<Attachments, "@usine/gateway/Attachments", {
|
|
5
|
+
/**
|
|
6
|
+
* Stores an attachment, or replaces the credential and expiry of the one with the same ID.
|
|
7
|
+
* Fails with `AttachmentConflict` if the ID belongs to another Worker or repository.
|
|
8
|
+
*/
|
|
9
|
+
readonly attach: (attachment: Attachment) => Effect.Effect<void, AttachmentAlreadyExpired | AttachmentConflict>;
|
|
10
|
+
/** Removes the attachment. Returns `false` if it didn't exist. */
|
|
11
|
+
readonly detach: (id: AttachmentId) => Effect.Effect<boolean>;
|
|
12
|
+
/** Returns the active attachments, earliest expiry first. */
|
|
13
|
+
readonly list: () => Effect.Effect<ReadonlyArray<Attachment>>;
|
|
14
|
+
/** Returns whether the attachment is active, expired, or missing. */
|
|
15
|
+
readonly find: (id: AttachmentId) => Effect.Effect<AttachmentLookup>;
|
|
16
|
+
/**
|
|
17
|
+
* Records `name` as the attached repository's new name, from GitHub's permanent redirect of a
|
|
18
|
+
* request for it, so the attachment serves paths that name it until detach or expiry. Does
|
|
19
|
+
* nothing if the attachment isn't live or already serves `name`.
|
|
20
|
+
*/
|
|
21
|
+
readonly learnNewName: (id: AttachmentId, name: RepositoryName) => Effect.Effect<void>;
|
|
22
|
+
/** Removes expired attachments and returns the number removed. */
|
|
23
|
+
readonly sweep: () => Effect.Effect<number>;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* The in-memory attachment store, keyed by attachment ID. A Gateway restart clears it; the
|
|
27
|
+
* executor re-attaches with the same IDs when it renews or restarts.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Attachments extends Attachments_base {
|
|
30
|
+
/** An empty store that removes expired attachments every minute while the layer is in scope. */
|
|
31
|
+
static readonly layer: Layer.Layer<Attachments, never, never>;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/attachment/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACtF,OAAO,EAAE,OAAO,EAAkB,MAAM,EAAE,KAAK,EAAc,MAAM,QAAQ,CAAC;AAC5E,OAAO,EACL,KAAK,UAAU,EACf,wBAAwB,EACxB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAItB,MAAM,iBAAiB,CAAC;;IA4BrB;;;OAGG;qBACc,CACf,UAAU,EAAE,UAAU,KACnB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,wBAAwB,GAAG,kBAAkB,CAAC;IACvE,kEAAkE;qBACjD,CAAC,EAAE,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;IAC7D,6DAA6D;mBAC9C,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAC7D,qEAAqE;mBACtD,CAAC,EAAE,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC;IACpE;;;;OAIG;2BACoB,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IACtF,kEAAkE;oBAClD,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;;AA3B/C;;;GAGG;AACH,qBAAa,WAAY,SAAQ,gBAyBA;IAC/B,gGAAgG;IAChG,MAAM,CAAC,QAAQ,CAAC,KAAK,yCA4InB;CACH"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { canonicalRepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { Context, Data, DateTime, Effect, Layer, Match, Ref } from "effect";
|
|
3
|
+
import { AttachmentAlreadyExpired, AttachmentConflict, ExpiredAttachment, LiveAttachment, MissingAttachment, } from "./attachment.js";
|
|
4
|
+
const SWEEP_INTERVAL = "1 minute";
|
|
5
|
+
const samePair = (left, right) => left.worker === right.worker &&
|
|
6
|
+
canonicalRepositoryName(left.repository) === canonicalRepositoryName(right.repository);
|
|
7
|
+
const Learning = Data.taggedEnum();
|
|
8
|
+
/**
|
|
9
|
+
* The in-memory attachment store, keyed by attachment ID. A Gateway restart clears it; the
|
|
10
|
+
* executor re-attaches with the same IDs when it renews or restarts.
|
|
11
|
+
*/
|
|
12
|
+
export class Attachments extends Context.Service()("@usine/gateway/Attachments") {
|
|
13
|
+
/** An empty store that removes expired attachments every minute while the layer is in scope. */
|
|
14
|
+
static layer = Layer.effect(Attachments, Effect.gen(function* () {
|
|
15
|
+
const entries = yield* Ref.make(new Map());
|
|
16
|
+
const attach = Effect.fn("Attachments.attach")(function* (attachment) {
|
|
17
|
+
const { id, worker, repository, expiresAt } = attachment;
|
|
18
|
+
yield* Effect.annotateCurrentSpan({ attachment: id, worker, repository });
|
|
19
|
+
const now = yield* DateTime.now;
|
|
20
|
+
if (!attachment.isLiveAt(now))
|
|
21
|
+
return yield* new AttachmentAlreadyExpired({ worker, repository, expiresAt });
|
|
22
|
+
const outcome = yield* Ref.modify(entries, (current) => {
|
|
23
|
+
const previous = current.get(id);
|
|
24
|
+
if (previous !== undefined && !samePair(previous.attachment, attachment))
|
|
25
|
+
return ["conflict", current];
|
|
26
|
+
return [
|
|
27
|
+
previous === undefined ? "attached" : "renewed",
|
|
28
|
+
new Map(current).set(id, { attachment, newNames: previous?.newNames ?? [] }),
|
|
29
|
+
];
|
|
30
|
+
});
|
|
31
|
+
if (outcome === "conflict")
|
|
32
|
+
return yield* new AttachmentConflict({ id, worker, repository });
|
|
33
|
+
yield* Effect.logInfo(outcome === "attached" ? "Attached a GitHub credential" : "Renewed a GitHub credential", {
|
|
34
|
+
attachment: id,
|
|
35
|
+
worker,
|
|
36
|
+
repository,
|
|
37
|
+
credential: attachment.credential._tag,
|
|
38
|
+
expiresAt: DateTime.formatIso(expiresAt),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
const detach = Effect.fn("Attachments.detach")(function* (id) {
|
|
42
|
+
yield* Effect.annotateCurrentSpan({ attachment: id });
|
|
43
|
+
const removed = yield* Ref.modify(entries, (current) => {
|
|
44
|
+
const entry = current.get(id);
|
|
45
|
+
if (entry === undefined)
|
|
46
|
+
return [entry, current];
|
|
47
|
+
const next = new Map(current);
|
|
48
|
+
next.delete(id);
|
|
49
|
+
return [entry.attachment, next];
|
|
50
|
+
});
|
|
51
|
+
if (removed === undefined) {
|
|
52
|
+
yield* Effect.logInfo("Nothing to detach", { attachment: id });
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
yield* Effect.logInfo("Detached a GitHub credential", {
|
|
56
|
+
attachment: id,
|
|
57
|
+
worker: removed.worker,
|
|
58
|
+
repository: removed.repository,
|
|
59
|
+
});
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
const list = Effect.fn("Attachments.list")(function* () {
|
|
63
|
+
const now = yield* DateTime.now;
|
|
64
|
+
const current = yield* Ref.get(entries);
|
|
65
|
+
return [...current.values()]
|
|
66
|
+
.map(({ attachment }) => attachment)
|
|
67
|
+
.filter((attachment) => attachment.isLiveAt(now))
|
|
68
|
+
.sort((left, right) => DateTime.toEpochMillis(left.expiresAt) - DateTime.toEpochMillis(right.expiresAt));
|
|
69
|
+
});
|
|
70
|
+
const find = Effect.fn("Attachments.find")(function* (id) {
|
|
71
|
+
const now = yield* DateTime.now;
|
|
72
|
+
const entry = (yield* Ref.get(entries)).get(id);
|
|
73
|
+
if (entry === undefined)
|
|
74
|
+
return new MissingAttachment();
|
|
75
|
+
return entry.attachment.isLiveAt(now)
|
|
76
|
+
? new LiveAttachment(entry)
|
|
77
|
+
: new ExpiredAttachment({ expiredAt: entry.attachment.expiresAt });
|
|
78
|
+
});
|
|
79
|
+
const learnNewName = Effect.fn("Attachments.learnNewName")(function* (id, name) {
|
|
80
|
+
yield* Effect.annotateCurrentSpan({ attachment: id, newName: name });
|
|
81
|
+
const now = yield* DateTime.now;
|
|
82
|
+
const learning = yield* Ref.modify(entries, (current) => {
|
|
83
|
+
const entry = current.get(id);
|
|
84
|
+
if (entry === undefined || !entry.attachment.isLiveAt(now))
|
|
85
|
+
return [Learning.Kept({ reason: "not-live" }), current];
|
|
86
|
+
if (new LiveAttachment(entry).serves(name))
|
|
87
|
+
return [Learning.Kept({ reason: "already-served" }), current];
|
|
88
|
+
return [
|
|
89
|
+
Learning.Learned({ attachment: entry.attachment }),
|
|
90
|
+
new Map(current).set(id, { ...entry, newNames: [...entry.newNames, name] }),
|
|
91
|
+
];
|
|
92
|
+
});
|
|
93
|
+
yield* Match.valueTags(learning, {
|
|
94
|
+
Learned: ({ attachment }) => Effect.logInfo("Learned the new name of an attached repository", {
|
|
95
|
+
attachment: id,
|
|
96
|
+
worker: attachment.worker,
|
|
97
|
+
repository: attachment.repository,
|
|
98
|
+
newName: name,
|
|
99
|
+
}),
|
|
100
|
+
Kept: ({ reason }) => Effect.logDebug("Kept the names an attachment serves", {
|
|
101
|
+
attachment: id,
|
|
102
|
+
newName: name,
|
|
103
|
+
reason,
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
const sweep = Effect.fn("Attachments.sweep")(function* () {
|
|
108
|
+
const now = yield* DateTime.now;
|
|
109
|
+
const expired = yield* Ref.modify(entries, (current) => {
|
|
110
|
+
const gone = [...current.values()]
|
|
111
|
+
.map(({ attachment }) => attachment)
|
|
112
|
+
.filter((attachment) => !attachment.isLiveAt(now));
|
|
113
|
+
if (gone.length === 0)
|
|
114
|
+
return [gone, current];
|
|
115
|
+
const next = new Map(current);
|
|
116
|
+
for (const { id } of gone)
|
|
117
|
+
next.delete(id);
|
|
118
|
+
return [gone, next];
|
|
119
|
+
});
|
|
120
|
+
for (const { id, worker, repository, expiresAt } of expired)
|
|
121
|
+
yield* Effect.logInfo("Forgot an expired GitHub credential", {
|
|
122
|
+
attachment: id,
|
|
123
|
+
worker,
|
|
124
|
+
repository,
|
|
125
|
+
expiredAt: DateTime.formatIso(expiresAt),
|
|
126
|
+
});
|
|
127
|
+
return expired.length;
|
|
128
|
+
});
|
|
129
|
+
yield* Effect.forever(Effect.sleep(SWEEP_INTERVAL).pipe(Effect.andThen(sweep()))).pipe(Effect.forkScoped);
|
|
130
|
+
return { attach, detach, list, find, learnNewName, sweep };
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/attachment/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option, Schema } from "effect";
|
|
2
|
+
import { Headers } from "effect/http";
|
|
3
|
+
import { GatewaySettings } from "../settings.ts";
|
|
4
|
+
declare const CallerClaims_base: Schema.Class<CallerClaims, Schema.Struct<{
|
|
5
|
+
readonly attachment: Schema.brand<Schema.String, "AttachmentId">;
|
|
6
|
+
readonly worker: Schema.brand<Schema.String, "WorkerName">;
|
|
7
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
8
|
+
}>, {}>;
|
|
9
|
+
/**
|
|
10
|
+
* The claims in a caller token: its attachment ID, Worker, and repository. Because the
|
|
11
|
+
* attachment ID is included, a token from an earlier attachment is rejected after a new attach.
|
|
12
|
+
*/
|
|
13
|
+
export declare class CallerClaims extends CallerClaims_base {
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The token a Worker VM sends instead of a real credential. It's issued with each attachment
|
|
17
|
+
* and accepted only while that attachment is active.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CallerToken: Schema.Redacted<Schema.NonEmptyString>;
|
|
20
|
+
export type CallerToken = typeof CallerToken.Type;
|
|
21
|
+
declare const CallerTokenRejected_base: Schema.Class<CallerTokenRejected, Schema.TaggedStruct<"CallerTokenRejected", {
|
|
22
|
+
readonly reason: Schema.Literals<readonly ["malformed", "bad-signature"]>;
|
|
23
|
+
}>, import("effect/Cause").YieldableError>;
|
|
24
|
+
/** The error for a caller token the Gateway didn't issue: `malformed` if the format is wrong, `bad-signature` if it wasn't signed with the Gateway's key. */
|
|
25
|
+
export declare class CallerTokenRejected extends CallerTokenRejected_base {
|
|
26
|
+
}
|
|
27
|
+
/** The header for tools that can add one, such as git with `http.extraHeader`. */
|
|
28
|
+
export declare const GATEWAY_TOKEN_HEADER = "x-usine-gateway-token";
|
|
29
|
+
declare const CallerTokens_base: Context.ServiceClass<CallerTokens, "@usine/gateway/CallerTokens", {
|
|
30
|
+
readonly issue: (claims: CallerClaims) => Effect.Effect<CallerToken>;
|
|
31
|
+
readonly verify: (token: CallerToken) => Effect.Effect<CallerClaims, CallerTokenRejected>;
|
|
32
|
+
/**
|
|
33
|
+
* Removes caller tokens from headers for a host outside the Gateway: always the Gateway
|
|
34
|
+
* header, and `Authorization` or `x-api-key` only if the value verifies as a caller token.
|
|
35
|
+
*/
|
|
36
|
+
readonly withoutCallerTokens: (headers: Headers.Headers) => Effect.Effect<Headers.Headers>;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Issues and verifies caller tokens of the form `ugw1.<claims>.<HMAC-SHA256>` under the
|
|
40
|
+
* Gateway's caller key. The same attachment always yields the same token, so no token table
|
|
41
|
+
* is kept.
|
|
42
|
+
*/
|
|
43
|
+
export declare class CallerTokens extends CallerTokens_base {
|
|
44
|
+
static readonly layer: Layer.Layer<CallerTokens, never, GatewaySettings>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns the credential in an `Authorization` or `Proxy-Authorization` value: the `Bearer` or
|
|
48
|
+
* `token` parameter, or the `Basic` password (the user name if the password is empty).
|
|
49
|
+
*/
|
|
50
|
+
export declare const tokenFromAuthorization: (value: string) => Option.Option<CallerToken>;
|
|
51
|
+
/**
|
|
52
|
+
* Returns the caller token from the request headers, checking the Gateway header, then
|
|
53
|
+
* `Authorization` (gh, Codex, git, and Claude Code with `ANTHROPIC_AUTH_TOKEN` or
|
|
54
|
+
* `CLAUDE_CODE_OAUTH_TOKEN`), then `x-api-key` (Claude Code with `ANTHROPIC_API_KEY`).
|
|
55
|
+
*/
|
|
56
|
+
export declare const presentedToken: (headers: Headers.Headers) => Option.Option<CallerToken>;
|
|
57
|
+
/** Returns `headers` without any header that can contain a caller credential. */
|
|
58
|
+
export declare const withoutCallerCredentials: (headers: Headers.Headers) => Headers.Headers;
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=caller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.d.ts","sourceRoot":"","sources":["../../src/caller/caller.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAoB,MAAM,EAAE,MAAM,QAAQ,CAAC;AAElF,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;;;;;;AAEjD;;;GAGG;AACH,qBAAa,YAAa,SAAQ,iBAIhC;CAAG;AAEL;;;GAGG;AACH,eAAO,MAAM,WAAW,wCAAyC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAC;;;;AAElD,6JAA6J;AAC7J,qBAAa,mBAAoB,SAAQ,wBAGxC;CAAG;AAEJ,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;;oBAgCxC,CAAC,MAAM,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;qBACnD,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,mBAAmB,CAAC;IACzF;;;OAGG;kCAC2B,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;;AAd9F;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,iBAWA;IAChC,MAAM,CAAC,QAAQ,CAAC,KAAK,oDAsFnB;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,UAAW,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAgB/E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,YAAa,OAAO,CAAC,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAahF,CAAC;AAEJ,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,YAAa,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,OACqB,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
2
|
+
import { WorkerName } from "@usine/core/worker";
|
|
3
|
+
import { Context, Effect, Layer, Option, Redacted, Result, Schema } from "effect";
|
|
4
|
+
import { Base64, Base64Url } from "effect/encoding";
|
|
5
|
+
import { Headers } from "effect/http";
|
|
6
|
+
import { AttachmentId } from "../attachment/attachment.js";
|
|
7
|
+
import { GatewaySettings } from "../settings.js";
|
|
8
|
+
/**
|
|
9
|
+
* The claims in a caller token: its attachment ID, Worker, and repository. Because the
|
|
10
|
+
* attachment ID is included, a token from an earlier attachment is rejected after a new attach.
|
|
11
|
+
*/
|
|
12
|
+
export class CallerClaims extends Schema.Class("@usine/gateway/CallerClaims")({
|
|
13
|
+
attachment: AttachmentId,
|
|
14
|
+
worker: WorkerName,
|
|
15
|
+
repository: RepositoryName,
|
|
16
|
+
}) {
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The token a Worker VM sends instead of a real credential. It's issued with each attachment
|
|
20
|
+
* and accepted only while that attachment is active.
|
|
21
|
+
*/
|
|
22
|
+
export const CallerToken = Schema.Redacted(Schema.NonEmptyString);
|
|
23
|
+
/** The error for a caller token the Gateway didn't issue: `malformed` if the format is wrong, `bad-signature` if it wasn't signed with the Gateway's key. */
|
|
24
|
+
export class CallerTokenRejected extends Schema.TaggedError()("CallerTokenRejected", { reason: Schema.Literals(["malformed", "bad-signature"]) }) {
|
|
25
|
+
}
|
|
26
|
+
/** The header for tools that can add one, such as git with `http.extraHeader`. */
|
|
27
|
+
export const GATEWAY_TOKEN_HEADER = "x-usine-gateway-token";
|
|
28
|
+
/** The headers that can contain a caller credential; all are removed before forwarding. */
|
|
29
|
+
const CALLER_CREDENTIAL_HEADERS = [
|
|
30
|
+
GATEWAY_TOKEN_HEADER,
|
|
31
|
+
"authorization",
|
|
32
|
+
"x-api-key",
|
|
33
|
+
"proxy-authorization",
|
|
34
|
+
];
|
|
35
|
+
const TOKEN_FORMAT = /^ugw1\.([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]+)$/u;
|
|
36
|
+
const encoder = new TextEncoder();
|
|
37
|
+
const toArrayBuffer = (value) => {
|
|
38
|
+
const buffer = new ArrayBuffer(value.byteLength);
|
|
39
|
+
new Uint8Array(buffer).set(value);
|
|
40
|
+
return buffer;
|
|
41
|
+
};
|
|
42
|
+
const ClaimsJson = Schema.fromJsonString(CallerClaims);
|
|
43
|
+
const encodeClaims = Schema.encodeSync(ClaimsJson);
|
|
44
|
+
const decodeClaims = Schema.decodeUnknownOption(ClaimsJson);
|
|
45
|
+
/**
|
|
46
|
+
* Issues and verifies caller tokens of the form `ugw1.<claims>.<HMAC-SHA256>` under the
|
|
47
|
+
* Gateway's caller key. The same attachment always yields the same token, so no token table
|
|
48
|
+
* is kept.
|
|
49
|
+
*/
|
|
50
|
+
export class CallerTokens extends Context.Service()("@usine/gateway/CallerTokens") {
|
|
51
|
+
static layer = Layer.effect(CallerTokens, Effect.gen(function* () {
|
|
52
|
+
const { callerKey } = yield* GatewaySettings;
|
|
53
|
+
const key = yield* Effect.promise(() => crypto.subtle.importKey("raw", toArrayBuffer(encoder.encode(Redacted.value(callerKey))), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]));
|
|
54
|
+
const issue = Effect.fn("CallerTokens.issue")(function* (claims) {
|
|
55
|
+
yield* Effect.annotateCurrentSpan({
|
|
56
|
+
attachment: claims.attachment,
|
|
57
|
+
worker: claims.worker,
|
|
58
|
+
repository: claims.repository,
|
|
59
|
+
});
|
|
60
|
+
const signed = `ugw1.${Base64Url.encode(encodeClaims(claims))}`;
|
|
61
|
+
const mac = yield* Effect.promise(() => crypto.subtle.sign("HMAC", key, toArrayBuffer(encoder.encode(signed))));
|
|
62
|
+
return Redacted.make(`${signed}.${Base64Url.encode(new Uint8Array(mac))}`);
|
|
63
|
+
});
|
|
64
|
+
const verify = Effect.fn("CallerTokens.verify")(function* (token) {
|
|
65
|
+
const match = TOKEN_FORMAT.exec(Redacted.value(token));
|
|
66
|
+
const payload = match?.[1];
|
|
67
|
+
const mac = Option.fromNullishOr(match?.[2]).pipe(Option.flatMap((value) => Result.getSuccess(Base64Url.decode(value))));
|
|
68
|
+
if (payload === undefined || Option.isNone(mac))
|
|
69
|
+
return yield* new CallerTokenRejected({ reason: "malformed" });
|
|
70
|
+
const authentic = yield* Effect.promise(() => crypto.subtle.verify("HMAC", key, toArrayBuffer(mac.value), toArrayBuffer(encoder.encode(`ugw1.${payload}`))));
|
|
71
|
+
if (!authentic)
|
|
72
|
+
return yield* new CallerTokenRejected({ reason: "bad-signature" });
|
|
73
|
+
const claims = Result.getSuccess(Base64Url.decodeString(payload)).pipe(Option.flatMap(decodeClaims));
|
|
74
|
+
if (Option.isNone(claims))
|
|
75
|
+
return yield* new CallerTokenRejected({ reason: "malformed" });
|
|
76
|
+
yield* Effect.annotateCurrentSpan({
|
|
77
|
+
attachment: claims.value.attachment,
|
|
78
|
+
worker: claims.value.worker,
|
|
79
|
+
repository: claims.value.repository,
|
|
80
|
+
});
|
|
81
|
+
return claims.value;
|
|
82
|
+
});
|
|
83
|
+
const isCallerToken = (token) => Option.match(token, {
|
|
84
|
+
onNone: () => Effect.succeed(false),
|
|
85
|
+
onSome: (candidate) => verify(candidate).pipe(Effect.as(true), Effect.orElseSucceed(() => false)),
|
|
86
|
+
});
|
|
87
|
+
const withoutCallerTokens = Effect.fn("CallerTokens.withoutCallerTokens")(function* (headers) {
|
|
88
|
+
const remaining = Headers.remove(headers, GATEWAY_TOKEN_HEADER);
|
|
89
|
+
const authorization = Headers.get(remaining, "authorization").pipe(Option.flatMap(tokenFromAuthorization));
|
|
90
|
+
const apiKey = Headers.get(remaining, "x-api-key").pipe(Option.map((value) => Redacted.make(value.trim())));
|
|
91
|
+
const withoutAuthorization = (yield* isCallerToken(authorization))
|
|
92
|
+
? Headers.remove(remaining, "authorization")
|
|
93
|
+
: remaining;
|
|
94
|
+
return (yield* isCallerToken(apiKey))
|
|
95
|
+
? Headers.remove(withoutAuthorization, "x-api-key")
|
|
96
|
+
: withoutAuthorization;
|
|
97
|
+
});
|
|
98
|
+
return { issue, verify, withoutCallerTokens };
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns the credential in an `Authorization` or `Proxy-Authorization` value: the `Bearer` or
|
|
103
|
+
* `token` parameter, or the `Basic` password (the user name if the password is empty).
|
|
104
|
+
*/
|
|
105
|
+
export const tokenFromAuthorization = (value) => {
|
|
106
|
+
const match = /^(\S+)\s+(\S+)\s*$/u.exec(value.trim());
|
|
107
|
+
const scheme = match?.[1]?.toLowerCase();
|
|
108
|
+
const parameter = match?.[2];
|
|
109
|
+
if (scheme === undefined || parameter === undefined)
|
|
110
|
+
return Option.none();
|
|
111
|
+
if (scheme === "bearer" || scheme === "token")
|
|
112
|
+
return Option.some(Redacted.make(parameter));
|
|
113
|
+
if (scheme !== "basic")
|
|
114
|
+
return Option.none();
|
|
115
|
+
return Result.getSuccess(Base64.decodeString(parameter)).pipe(Option.flatMap((pair) => {
|
|
116
|
+
const separator = pair.indexOf(":");
|
|
117
|
+
const user = separator === -1 ? pair : pair.slice(0, separator);
|
|
118
|
+
const password = separator === -1 ? "" : pair.slice(separator + 1);
|
|
119
|
+
const secret = password === "" ? user : password;
|
|
120
|
+
return secret === "" ? Option.none() : Option.some(Redacted.make(secret));
|
|
121
|
+
}));
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Returns the caller token from the request headers, checking the Gateway header, then
|
|
125
|
+
* `Authorization` (gh, Codex, git, and Claude Code with `ANTHROPIC_AUTH_TOKEN` or
|
|
126
|
+
* `CLAUDE_CODE_OAUTH_TOKEN`), then `x-api-key` (Claude Code with `ANTHROPIC_API_KEY`).
|
|
127
|
+
*/
|
|
128
|
+
export const presentedToken = (headers) => Headers.get(headers, GATEWAY_TOKEN_HEADER).pipe(Option.filter((value) => value.trim() !== ""), Option.map((value) => Redacted.make(value.trim())), Option.orElse(() => Headers.get(headers, "authorization").pipe(Option.flatMap(tokenFromAuthorization))), Option.orElse(() => Headers.get(headers, "x-api-key").pipe(Option.filter((value) => value.trim() !== ""), Option.map((value) => Redacted.make(value.trim())))));
|
|
129
|
+
/** Returns `headers` without any header that can contain a caller credential. */
|
|
130
|
+
export const withoutCallerCredentials = (headers) => CALLER_CREDENTIAL_HEADERS.reduce((remaining, name) => Headers.remove(remaining, name), headers);
|
|
131
|
+
//# sourceMappingURL=caller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.js","sourceRoot":"","sources":["../../src/caller/caller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAClF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;GAGG;AACH,MAAM,OAAO,YAAa,SAAQ,MAAM,CAAC,KAAK,CAAe,6BAA6B,CAAC,CAAC;IAC1F,UAAU,EAAE,YAAY;IACxB,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,cAAc;CAC3B,CAAC;CAAG;AAEL;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;AAGlE,6JAA6J;AAC7J,MAAM,OAAO,mBAAoB,SAAQ,MAAM,CAAC,WAAW,EAAuB,CAChF,qBAAqB,EACrB,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC,EAAE,CAC5D;CAAG;AAEJ,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,2FAA2F;AAC3F,MAAM,yBAAyB,GAAG;IAChC,oBAAoB;IACpB,eAAe;IACf,WAAW;IACX,qBAAqB;CACb,CAAC;AAEX,MAAM,YAAY,GAAG,6CAA6C,CAAC;AAEnE,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,MAAM,aAAa,GAAG,CAAC,KAAiB,EAAe,EAAE;IACvD,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACjD,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;AACvD,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AACnD,MAAM,YAAY,GAAG,MAAM,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,OAAO,YAAa,SAAQ,OAAO,CAAC,OAAO,EAW9C,CAAC,6BAA6B,CAAC;IAChC,MAAM,CAAU,KAAK,GAAG,KAAK,CAAC,MAAM,CAClC,YAAY,EACZ,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC;QAC7C,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CACrC,MAAM,CAAC,MAAM,CAAC,SAAS,CACrB,KAAK,EACL,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EACxD,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,EAAE,QAAQ,CAAC,CACnB,CACF,CAAC;QAEF,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAoB;YAC3E,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC;gBAChC,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,UAAU,EAAE,MAAM,CAAC,UAAU;aAC9B,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YAChE,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CACrC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CACvE,CAAC;YACF,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAkB;YAC3E,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YACvD,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC/C,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CACtE,CAAC;YACF,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;gBAC7C,OAAO,KAAK,CAAC,CAAC,IAAI,mBAAmB,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;YACjE,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAC3C,MAAM,CAAC,MAAM,CAAC,MAAM,CAClB,MAAM,EACN,GAAG,EACH,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,EACxB,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAC,CACjD,CACF,CAAC;YACF,IAAI,CAAC,SAAS;gBAAE,OAAO,KAAK,CAAC,CAAC,IAAI,mBAAmB,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;YACnF,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CACpE,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAC7B,CAAC;YACF,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;gBAAE,OAAO,KAAK,CAAC,CAAC,IAAI,mBAAmB,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;YAC1F,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC;gBAChC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;gBACnC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;gBAC3B,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;aACpC,CAAC,CAAC;YACH,OAAO,MAAM,CAAC,KAAK,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,MAAM,aAAa,GAAG,CAAC,KAAiC,EAAE,EAAE,CAC1D,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE;YAClB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YACnC,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,CACpB,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CACpB,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EACf,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAClC;SACJ,CAAC,CAAC;QAEL,MAAM,mBAAmB,GAAG,MAAM,CAAC,EAAE,CAAC,kCAAkC,CAAC,CAAC,QAAQ,CAAC,EACjF,OAAwB;YAExB,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;YAChE,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,IAAI,CAChE,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,CACvC,CAAC;YACF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,IAAI,CACrD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CACnD,CAAC;YACF,MAAM,oBAAoB,GAAG,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;gBAChE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC;gBAC5C,CAAC,CAAC,SAAS,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBACnC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,WAAW,CAAC;gBACnD,CAAC,CAAC,oBAAoB,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAChD,CAAC,CAAC,CACH,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAa,EAA8B,EAAE;IAClF,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,MAAM,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IAC1E,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5F,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IAC7C,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACtB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;QACjD,OAAO,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAC,CACH,CAAC;AACJ,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAwB,EAA8B,EAAE,CACrF,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,IAAI,CAC7C,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,EAClD,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CACjB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC,CACnF,EACD,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CACjB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,IAAI,CACpC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CACnD,CACF,CACF,CAAC;AAEJ,iFAAiF;AACjF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,OAAwB,EAAmB,EAAE,CACpF,yBAAyB,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DateTime, Effect, FileSystem, Option, Schema } from "effect";
|
|
2
|
+
declare const ChatGptAuthFileError_base: Schema.Class<ChatGptAuthFileError, Schema.TaggedStruct<"ChatGptAuthFileError", {
|
|
3
|
+
readonly path: Schema.String;
|
|
4
|
+
readonly operation: Schema.Literals<readonly ["read", "write"]>;
|
|
5
|
+
readonly message: Schema.String;
|
|
6
|
+
}>, import("effect/Cause").YieldableError>;
|
|
7
|
+
/**
|
|
8
|
+
* The error when a ChatGPT login's `auth.json` can't be read or written, or isn't a Codex
|
|
9
|
+
* ChatGPT login. The message never contains a token.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ChatGptAuthFileError extends ChatGptAuthFileError_base {
|
|
12
|
+
}
|
|
13
|
+
declare const ChatGptTokens_base: Schema.Class<ChatGptTokens, Schema.Struct<{
|
|
14
|
+
readonly accessToken: Schema.Redacted<Schema.NonEmptyString>;
|
|
15
|
+
readonly refreshToken: Schema.Redacted<Schema.NonEmptyString>;
|
|
16
|
+
/** The ChatGPT account or workspace ID, sent as `ChatGPT-Account-ID`. */
|
|
17
|
+
readonly accountId: Schema.Option<Schema.NonEmptyString>;
|
|
18
|
+
/** The access token's `exp` claim, if it has one. */
|
|
19
|
+
readonly expiresAt: Schema.Option<Schema.DateTimeUtc>;
|
|
20
|
+
/** When the tokens were last refreshed, from `last_refresh`. */
|
|
21
|
+
readonly lastRefresh: Schema.Option<Schema.DateTimeUtc>;
|
|
22
|
+
}>, {}>;
|
|
23
|
+
/** A ChatGPT login's tokens as the Gateway holds them in memory. */
|
|
24
|
+
export declare class ChatGptTokens extends ChatGptTokens_base {
|
|
25
|
+
}
|
|
26
|
+
/** Returns the expiry in a JWT's `exp` claim, in seconds since the epoch. */
|
|
27
|
+
export declare const jwtExpiry: (jwt: string) => Option.Option<DateTime.Utc>;
|
|
28
|
+
/** Returns the ChatGPT account ID in an ID token's claims. */
|
|
29
|
+
export declare const idTokenAccount: (idToken: string) => Option.Option<string>;
|
|
30
|
+
/** Reads and parses the Codex `auth.json` at `path`. */
|
|
31
|
+
export declare const readAuthFile: (path: string) => Effect.Effect<ChatGptTokens, ChatGptAuthFileError, FileSystem.FileSystem>;
|
|
32
|
+
/**
|
|
33
|
+
* Writes rotated tokens into the Codex `auth.json` at `path` and sets `last_refresh`, keeping
|
|
34
|
+
* its other fields. Writes a temporary file with mode 0600 next to it and renames it over the
|
|
35
|
+
* file, so a reader sees either file whole, and removes the temporary file if that fails or is
|
|
36
|
+
* interrupted. Returns `false`, writing nothing, if the file's refresh token isn't one `owns`
|
|
37
|
+
* accepts, such as a login the operator copied in that the next reload reads.
|
|
38
|
+
*/
|
|
39
|
+
export declare const writeAuthFile: (path: string, tokens: ChatGptTokens, idToken: Option.Option<string>, owns: (refreshToken: string) => boolean) => Effect.Effect<boolean, ChatGptAuthFileError, FileSystem.FileSystem>;
|
|
40
|
+
/**
|
|
41
|
+
* Checks that the Gateway can write the rotated tokens next to the auth file at `path`, by
|
|
42
|
+
* creating and removing a file there, so a missing write permission shows before the first
|
|
43
|
+
* refresh.
|
|
44
|
+
*/
|
|
45
|
+
export declare const checkAuthFileWritable: (path: string) => Effect.Effect<void, ChatGptAuthFileError, FileSystem.FileSystem>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=auth-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-file.d.ts","sourceRoot":"","sources":["../../src/chatgpt/auth-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAQ,UAAU,EAAE,MAAM,EAAoB,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;;;AAG9F;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,yBAOzC;CAAG;;;;IAQF,yEAAyE;;IAEzE,qDAAqD;;IAErD,gEAAgE;;;AARlE,oEAAoE;AACpE,qBAAa,aAAc,SAAQ,kBASjC;CAAG;AAmCL,6EAA6E;AAC7E,eAAO,MAAM,SAAS,QAAS,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAI/D,CAAC;AAEJ,8DAA8D;AAC9D,eAAO,MAAM,cAAc,YAAa,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAMlE,CAAC;AAiCJ,wDAAwD;AACxD,eAAO,MAAM,YAAY,6FAWvB,CAAC;AAKH;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,6FAIH,MAAM,KAAK,OAAO,wEAuCvC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,oFAiBhC,CAAC"}
|