@alchemy.run/boat 0.0.0-stage → 2.0.0-beta.86
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 +201 -0
- package/README.md +70 -2
- package/lib/AiProxy.d.ts +89 -0
- package/lib/AiProxy.d.ts.map +1 -0
- package/lib/AiProxy.js +125 -0
- package/lib/AiProxy.js.map +1 -0
- package/lib/ApiKey.d.ts +105 -0
- package/lib/ApiKey.d.ts.map +1 -0
- package/lib/ApiKey.js +134 -0
- package/lib/ApiKey.js.map +1 -0
- package/lib/AuthProvider.d.ts +71 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +322 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/BoatBindings.d.ts +52 -0
- package/lib/BoatBindings.d.ts.map +1 -0
- package/lib/BoatBindings.js +104 -0
- package/lib/BoatBindings.js.map +1 -0
- package/lib/Bootstrap.d.ts +8 -0
- package/lib/Bootstrap.d.ts.map +1 -0
- package/lib/Bootstrap.js +28 -0
- package/lib/Bootstrap.js.map +1 -0
- package/lib/ConnectSandbox.d.ts +56 -0
- package/lib/ConnectSandbox.d.ts.map +1 -0
- package/lib/ConnectSandbox.js +3 -0
- package/lib/ConnectSandbox.js.map +1 -0
- package/lib/ConnectSandboxHttp.d.ts +21 -0
- package/lib/ConnectSandboxHttp.d.ts.map +1 -0
- package/lib/ConnectSandboxHttp.js +46 -0
- package/lib/ConnectSandboxHttp.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/CreateSandbox.d.ts +134 -0
- package/lib/CreateSandbox.d.ts.map +1 -0
- package/lib/CreateSandbox.js +3 -0
- package/lib/CreateSandbox.js.map +1 -0
- package/lib/CreateSandboxHttp.d.ts +21 -0
- package/lib/CreateSandboxHttp.d.ts.map +1 -0
- package/lib/CreateSandboxHttp.js +93 -0
- package/lib/CreateSandboxHttp.js.map +1 -0
- package/lib/CredentialProfile.d.ts +157 -0
- package/lib/CredentialProfile.d.ts.map +1 -0
- package/lib/CredentialProfile.js +245 -0
- package/lib/CredentialProfile.js.map +1 -0
- package/lib/Credentials.d.ts +15 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +23 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Environment.d.ts +219 -0
- package/lib/Environment.d.ts.map +1 -0
- package/lib/Environment.js +273 -0
- package/lib/Environment.js.map +1 -0
- package/lib/Errors.d.ts +50 -0
- package/lib/Errors.d.ts.map +1 -0
- package/lib/Errors.js +17 -0
- package/lib/Errors.js.map +1 -0
- package/lib/Providers.d.ts +33 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +47 -0
- package/lib/Providers.js.map +1 -0
- package/lib/Sandbox.d.ts +205 -0
- package/lib/Sandbox.d.ts.map +1 -0
- package/lib/Sandbox.js +368 -0
- package/lib/Sandbox.js.map +1 -0
- package/lib/SandboxClient.d.ts +159 -0
- package/lib/SandboxClient.d.ts.map +1 -0
- package/lib/SandboxClient.js +1 -0
- package/lib/SandboxClient.js.map +1 -0
- package/lib/SandboxEvents.d.ts +111 -0
- package/lib/SandboxEvents.d.ts.map +1 -0
- package/lib/SandboxEvents.js +60 -0
- package/lib/SandboxEvents.js.map +1 -0
- package/lib/SandboxEventsLive.d.ts +13 -0
- package/lib/SandboxEventsLive.d.ts.map +1 -0
- package/lib/SandboxEventsLive.js +63 -0
- package/lib/SandboxEventsLive.js.map +1 -0
- package/lib/Snapshot.d.ts +123 -0
- package/lib/Snapshot.d.ts.map +1 -0
- package/lib/Snapshot.js +186 -0
- package/lib/Snapshot.js.map +1 -0
- package/lib/Webhook.d.ts +56 -0
- package/lib/Webhook.d.ts.map +1 -0
- package/lib/Webhook.js +98 -0
- package/lib/Webhook.js.map +1 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +19 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/Declared.d.ts +34 -0
- package/lib/internal/Declared.d.ts.map +1 -0
- package/lib/internal/Declared.js +38 -0
- package/lib/internal/Declared.js.map +1 -0
- package/lib/internal/Org.d.ts +22 -0
- package/lib/internal/Org.d.ts.map +1 -0
- package/lib/internal/Org.js +23 -0
- package/lib/internal/Org.js.map +1 -0
- package/lib/internal/Ownership.d.ts +29 -0
- package/lib/internal/Ownership.d.ts.map +1 -0
- package/lib/internal/Ownership.js +50 -0
- package/lib/internal/Ownership.js.map +1 -0
- package/lib/internal/Program.d.ts +69 -0
- package/lib/internal/Program.d.ts.map +1 -0
- package/lib/internal/Program.js +178 -0
- package/lib/internal/Program.js.map +1 -0
- package/lib/internal/Ref.d.ts +14 -0
- package/lib/internal/Ref.d.ts.map +1 -0
- package/lib/internal/Ref.js +7 -0
- package/lib/internal/Ref.js.map +1 -0
- package/lib/internal/SandboxHandle.d.ts +17 -0
- package/lib/internal/SandboxHandle.d.ts.map +1 -0
- package/lib/internal/SandboxHandle.js +249 -0
- package/lib/internal/SandboxHandle.js.map +1 -0
- package/lib/internal/WebhookDelivery.d.ts +16 -0
- package/lib/internal/WebhookDelivery.d.ts.map +1 -0
- package/lib/internal/WebhookDelivery.js +109 -0
- package/lib/internal/WebhookDelivery.js.map +1 -0
- package/package.json +81 -3
- package/src/AiProxy.ts +171 -0
- package/src/ApiKey.ts +250 -0
- package/src/AuthProvider.ts +472 -0
- package/src/BoatBindings.ts +136 -0
- package/src/Bootstrap.ts +55 -0
- package/src/ConnectSandbox.ts +60 -0
- package/src/ConnectSandboxHttp.ts +59 -0
- package/src/Contributions.ts +9 -0
- package/src/CreateSandbox.ts +143 -0
- package/src/CreateSandboxHttp.ts +115 -0
- package/src/CredentialProfile.ts +350 -0
- package/src/Credentials.ts +33 -0
- package/src/Environment.ts +416 -0
- package/src/Errors.ts +34 -0
- package/src/Providers.ts +71 -0
- package/src/Sandbox.ts +647 -0
- package/src/SandboxClient.ts +199 -0
- package/src/SandboxEvents.ts +125 -0
- package/src/SandboxEventsLive.ts +88 -0
- package/src/Snapshot.ts +284 -0
- package/src/Webhook.ts +163 -0
- package/src/globals.d.ts +2 -0
- package/src/index.ts +18 -0
- package/src/internal/Declared.ts +87 -0
- package/src/internal/Org.ts +31 -0
- package/src/internal/Ownership.ts +75 -0
- package/src/internal/Program.ts +250 -0
- package/src/internal/Ref.ts +15 -0
- package/src/internal/SandboxHandle.ts +425 -0
- package/src/internal/WebhookDelivery.ts +179 -0
package/lib/ApiKey.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import * as boat from "@distilled.cloud/boat-dev/boat";
|
|
2
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
3
|
+
import { isResolved } from "alchemy/Diff";
|
|
4
|
+
import * as Provider from "alchemy/Provider";
|
|
5
|
+
import { Resource } from "alchemy/Resource";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Redacted from "effect/Redacted";
|
|
8
|
+
import { boatName, isGeneratedName } from "./internal/Ownership.js";
|
|
9
|
+
/**
|
|
10
|
+
* A scoped Boat API key: the actions it may take, and the sandboxes or
|
|
11
|
+
* environments it reaches. Give each app or CI system its own key with
|
|
12
|
+
* only what it needs.
|
|
13
|
+
*
|
|
14
|
+
* Boat shows a key once, when it is created, so Alchemy keeps it in state
|
|
15
|
+
* as a `Redacted` value. Changing any prop creates a new key and revokes
|
|
16
|
+
* the old one; destroying the resource revokes it.
|
|
17
|
+
*
|
|
18
|
+
* Creating a key needs a key with `account.admin`, such as the one
|
|
19
|
+
* `alchemy profile edit --add Boat` signs in with. A new key cannot have
|
|
20
|
+
* more actions, more sandboxes or a later expiry than the key creating it.
|
|
21
|
+
* The key a browser sign-in gets lasts a year, so keys it creates expire
|
|
22
|
+
* with it. An expired key is minted again on the next deploy.
|
|
23
|
+
*
|
|
24
|
+
* ### Creating an API key
|
|
25
|
+
* **Example:** A key for CI that manages the sandboxes it creates
|
|
26
|
+
* ```typescript
|
|
27
|
+
* export const Ci = Boat.ApiKey("Ci", { preset: "ci", ttl: "90d" });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* **Example:** A key that only runs commands in one sandbox
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const box = yield* Boat.Sandbox("Box", {});
|
|
33
|
+
* const key = yield* Boat.ApiKey("BoxExec", {
|
|
34
|
+
* actions: ["sandbox.read", "exec", "file.read", "file.write"],
|
|
35
|
+
* sandboxIds: [box.sandboxId],
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @resource
|
|
40
|
+
* @product API key
|
|
41
|
+
*/
|
|
42
|
+
export const ApiKey = Resource("Boat.ApiKey");
|
|
43
|
+
const redact = (value) => Redacted.isRedacted(value) ? value : Redacted.make(value);
|
|
44
|
+
const findById = (apiKeyId) => boat
|
|
45
|
+
.listApiKeys({})
|
|
46
|
+
.pipe(Effect.map((listed) => listed.apiKeys.find((key) => key.id === apiKeyId)));
|
|
47
|
+
/** The props that decide what a key is; Boat can't change any of them on a key. */
|
|
48
|
+
const scopeOf = (props) => JSON.stringify({
|
|
49
|
+
name: props.name,
|
|
50
|
+
preset: props.preset,
|
|
51
|
+
actions: props.actions === undefined ? undefined : [...props.actions].sort(),
|
|
52
|
+
sandboxIds: props.sandboxIds === undefined ? undefined : [...props.sandboxIds].sort(),
|
|
53
|
+
environmentIds: props.environmentIds === undefined ? undefined : [...props.environmentIds].sort(),
|
|
54
|
+
ttl: props.ttl ?? "never",
|
|
55
|
+
});
|
|
56
|
+
const attributesOf = (key, secret) => ({
|
|
57
|
+
apiKeyId: key.id,
|
|
58
|
+
name: key.name,
|
|
59
|
+
key: secret,
|
|
60
|
+
keyPrefix: key.keyPrefix,
|
|
61
|
+
keyLastFour: key.keyLastFour,
|
|
62
|
+
expiresAt: key.expiresAt ?? undefined,
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Seconds until the signing key expires, from Boat's refusal ("…signed
|
|
66
|
+
* with an API key that expires 2027-11-07T16:21:12.757Z…"), less a minute;
|
|
67
|
+
* Boat's 90-day default when the message names no date.
|
|
68
|
+
*/
|
|
69
|
+
const ttlUntilSignerExpires = (message) => {
|
|
70
|
+
const expires = /expires (\S+?Z)/.exec(message ?? "")?.[1];
|
|
71
|
+
const seconds = expires === undefined ? Number.NaN : (Date.parse(expires) - Date.now()) / 1000 - 60;
|
|
72
|
+
return Number.isFinite(seconds) && seconds > 60 ? String(Math.floor(seconds)) : "90d";
|
|
73
|
+
};
|
|
74
|
+
/** Revoke a key; revoking one that is gone succeeds. */
|
|
75
|
+
export const revokeApiKey = (apiKeyId) => boat.revokeApiKey({ apiKeyId }).pipe(Effect.asVoid, Effect.catchTag("NotFound", () => Effect.void));
|
|
76
|
+
export const ApiKeyProvider = () => Provider.effect(ApiKey, Effect.gen(function* () {
|
|
77
|
+
return {
|
|
78
|
+
stables: ["apiKeyId", "key", "keyPrefix", "keyLastFour"],
|
|
79
|
+
diff: Effect.fn(function* ({ news, olds }) {
|
|
80
|
+
if (!isResolved(news) || olds === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (scopeOf(news) !== scopeOf(olds))
|
|
83
|
+
return { action: "replace" };
|
|
84
|
+
return undefined;
|
|
85
|
+
}),
|
|
86
|
+
read: Effect.fn(function* ({ olds, output }) {
|
|
87
|
+
// Boat never returns a key's value after creation, so a key is
|
|
88
|
+
// only usable through state.
|
|
89
|
+
if (output === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
const key = yield* findById(output.apiKeyId);
|
|
92
|
+
if (key === undefined || key.expired === true)
|
|
93
|
+
return undefined;
|
|
94
|
+
const attrs = attributesOf(key, output.key);
|
|
95
|
+
return olds !== undefined || isGeneratedName(key.name) ? attrs : Unowned(attrs);
|
|
96
|
+
}),
|
|
97
|
+
reconcile: Effect.fn(function* ({ id, news, output, session }) {
|
|
98
|
+
const name = yield* boatName(id, news.name ?? output?.name);
|
|
99
|
+
// Observe — a key is usable only while its value is in state.
|
|
100
|
+
const observed = output === undefined ? undefined : yield* findById(output.apiKeyId);
|
|
101
|
+
if (output !== undefined && observed !== undefined && observed.expired !== true) {
|
|
102
|
+
return attributesOf(observed, output.key);
|
|
103
|
+
}
|
|
104
|
+
// Ensure
|
|
105
|
+
yield* session.note(`Creating API key ${name}...`);
|
|
106
|
+
const create = (ttl) => boat.createScopedApiKey({
|
|
107
|
+
name,
|
|
108
|
+
ttl,
|
|
109
|
+
...(news.preset === undefined ? {} : { preset: news.preset }),
|
|
110
|
+
...(news.actions === undefined ? {} : { actions: news.actions }),
|
|
111
|
+
...(news.sandboxIds === undefined ? {} : { sandboxIds: news.sandboxIds }),
|
|
112
|
+
...(news.environmentIds === undefined ? {} : { environmentIds: news.environmentIds }),
|
|
113
|
+
});
|
|
114
|
+
const created = yield* create(news.ttl ?? "never").pipe(
|
|
115
|
+
// Without a `ttl`, the longest Boat allows: as long as the key
|
|
116
|
+
// signing the request, whose expiry Boat names in the refusal.
|
|
117
|
+
Effect.catchTag("ApiKeyOutlivesSigner", (error) => news.ttl === undefined
|
|
118
|
+
? Effect.suspend(() => create(ttlUntilSignerExpires(error.message)))
|
|
119
|
+
: Effect.fail(error)));
|
|
120
|
+
return attributesOf(created.apiKey, redact(created.secret));
|
|
121
|
+
}),
|
|
122
|
+
delete: Effect.fn(function* ({ output }) {
|
|
123
|
+
yield* revokeApiKey(output.apiKeyId);
|
|
124
|
+
}),
|
|
125
|
+
// For nuke. Listed keys carry no value; delete needs only the id.
|
|
126
|
+
list: Effect.fn(function* () {
|
|
127
|
+
const listed = yield* boat.listApiKeys({});
|
|
128
|
+
return listed.apiKeys
|
|
129
|
+
.filter((key) => isGeneratedName(key.name))
|
|
130
|
+
.map((key) => attributesOf(key, Redacted.make("")));
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
}));
|
|
134
|
+
//# sourceMappingURL=ApiKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiKey.js","sources":["../src/ApiKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAgGpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,QAAQ,CAAS,aAAa,CAAC,CAAC;AAEtD,MAAM,MAAM,GAAG,CAAC,KAAyC,EAAE,EAAE,CAC3D,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAE5D,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAE,EAAE,CACpC,IAAI;KACD,WAAW,CAAC,EAAE,CAAC;KACf,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC;AAErF,mFAAmF;AACnF,MAAM,OAAO,GAAG,CAAC,KAAkB,EAAE,EAAE,CACrC,IAAI,CAAC,SAAS,CAAC;IACb,IAAI,EAAE,KAAK,CAAC,IAAI;IAChB,MAAM,EAAE,KAAK,CAAC,MAAM;IACpB,OAAO,EAAE,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE;IAC5E,UAAU,EAAE,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE;IACrF,cAAc,EACZ,KAAK,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE;IACnF,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,OAAO;CAC1B,CAAC,CAAC;AAEL,MAAM,YAAY,GAAG,CAAC,GAAgB,EAAE,MAAiC,EAAoB,EAAE,CAAC,CAAC;IAC/F,QAAQ,EAAE,GAAG,CAAC,EAAE;IAChB,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,GAAG,EAAE,MAAM;IACX,SAAS,EAAE,GAAG,CAAC,SAAS;IACxB,WAAW,EAAE,GAAG,CAAC,WAAW;IAC5B,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS;CACtC,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,qBAAqB,GAAG,CAAC,OAA2B,EAAE,EAAE;IAC5D,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,OAAO,GACX,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC;IACtF,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACxF,CAAC,CAAC;AAEF,wDAAwD;AACxD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAE,EAAE,CAC/C,IAAI,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAClC,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAC/C,CAAC;AAEJ,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,EAAE,CACjC,QAAQ,CAAC,MAAM,CACb,MAAM,EACN,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,OAAO;QACL,OAAO,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,aAAa,CAAC;QACxD,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;YACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAC9D,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAW,CAAC;YAC3E,OAAO,SAAS,CAAC;QACnB,CAAC,CAAC;QACF,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;YACzC,+DAA+D;YAC/D,6BAA6B;YAC7B,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC7C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;gBAAE,OAAO,SAAS,CAAC;YAChE,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5C,OAAO,IAAI,KAAK,SAAS,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAClF,CAAC,CAAC;QACF,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;YAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,MAAM,EAAE,IAAI,CAAC,CAAC;YAE5D,8DAA8D;YAC9D,MAAM,QAAQ,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACrF,IAAI,MAAM,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAChF,OAAO,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5C,CAAC;YAED,SAAS;YACT,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,IAAI,KAAK,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,EAAE,CAC7B,IAAI,CAAC,kBAAkB,CAAC;gBACtB,IAAI;gBACJ,GAAG;gBACH,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC7D,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;gBAChE,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;gBACzE,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;aACtF,CAAC,CAAC;YACL,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC,IAAI;YACrD,+DAA+D;YAC/D,+DAA+D;YAC/D,MAAM,CAAC,QAAQ,CAAC,sBAAsB,EAAE,CAAC,KAAK,EAAE,EAAE,CAChD,IAAI,CAAC,GAAG,KAAK,SAAS;gBACpB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBACpE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CACvB,CACF,CAAC;YACF,OAAO,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9D,CAAC,CAAC;QACF,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE;YACrC,KAAK,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC;QACF,kEAAkE;QAClE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC;YACvB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC3C,OAAO,MAAM,CAAC,OAAO;iBAClB,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;iBAC1C,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC;KACH,CAAC;AACJ,CAAC,CAAC,CACH,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
import type { ChildProcessSpawner } from "effect/process/ChildProcessSpawner";
|
|
3
|
+
import * as Redacted from "effect/Redacted";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
/**
|
|
6
|
+
* Canonical name registered in the AuthProviders registry. Use this key to
|
|
7
|
+
* look up the Boat AuthProvider from inside provider Layers.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BOAT_AUTH_PROVIDER_NAME = "Boat";
|
|
10
|
+
/**
|
|
11
|
+
* A Boat profile entry. Both methods store a Boat API key (`boat_…`):
|
|
12
|
+
*
|
|
13
|
+
* - `browser`: Boat's agent sign-in (https://boat.dev/auth.md). You sign in
|
|
14
|
+
* to boat.dev with GitHub, Google or an email code, type the code Alchemy
|
|
15
|
+
* shows, and Boat issues a key. The identity assertion that comes with it
|
|
16
|
+
* mints the next key when this one expires.
|
|
17
|
+
* - `stored`: you paste a key created with `boat api-keys create` or on the
|
|
18
|
+
* dashboard.
|
|
19
|
+
*
|
|
20
|
+
* Either may pin `org`, the organization Alchemy works in, the way the
|
|
21
|
+
* dashboard's organization select or `boat --org` do: every call bills it
|
|
22
|
+
* (`X-Boat-Org`), and `Boat.Environment` manages its shared environments.
|
|
23
|
+
* Sign-in picks the account's organization (asking only when it belongs
|
|
24
|
+
* to several) and stores its id (`team_…`), which survives renames, with
|
|
25
|
+
* `orgName` for display.
|
|
26
|
+
* Without it, sandboxes bill the account's active wallet (`boat org
|
|
27
|
+
* switch`) and environments are personal.
|
|
28
|
+
*
|
|
29
|
+
* A revoked key fails with Boat's `unauthorized` error; run
|
|
30
|
+
* `alchemy profile edit --reconfigure Boat` to sign in again.
|
|
31
|
+
*/
|
|
32
|
+
export declare const BoatAuthConfigSchema: Schema.Union<readonly [Schema.Struct<{
|
|
33
|
+
readonly method: Schema.Literal<"browser">;
|
|
34
|
+
readonly email: Schema.String;
|
|
35
|
+
readonly apiKey: Schema.String;
|
|
36
|
+
readonly identityAssertion: Schema.optional<Schema.String>;
|
|
37
|
+
/** Epoch millis the key expires at, when Boat says. */
|
|
38
|
+
readonly expires: Schema.optional<Schema.Number>;
|
|
39
|
+
readonly account: Schema.optional<Schema.String>;
|
|
40
|
+
readonly org: Schema.optional<Schema.String>;
|
|
41
|
+
readonly orgName: Schema.optional<Schema.String>;
|
|
42
|
+
}>, Schema.Struct<{
|
|
43
|
+
readonly method: Schema.Literal<"stored">;
|
|
44
|
+
readonly apiKey: Schema.String;
|
|
45
|
+
readonly org: Schema.optional<Schema.String>;
|
|
46
|
+
readonly orgName: Schema.optional<Schema.String>;
|
|
47
|
+
}>]>;
|
|
48
|
+
export type BoatAuthConfig = typeof BoatAuthConfigSchema.Type;
|
|
49
|
+
/** Resolved Boat credentials: an API key sent as a bearer token. */
|
|
50
|
+
export interface BoatResolvedCredentials {
|
|
51
|
+
type: "apiKey";
|
|
52
|
+
apiKey: Redacted.Redacted<string>;
|
|
53
|
+
apiBaseUrl: string;
|
|
54
|
+
/** The organization (wallet) calls bill, sent as `X-Boat-Org`. */
|
|
55
|
+
org?: string;
|
|
56
|
+
source: {
|
|
57
|
+
type: BoatAuthConfig["method"] | "env";
|
|
58
|
+
details?: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Layer that registers the Boat {@link AuthProvider} into the
|
|
63
|
+
* `AuthProviders` registry when built. Included in `Boat.providers()` so
|
|
64
|
+
* `alchemy profile edit` can configure it.
|
|
65
|
+
*
|
|
66
|
+
* Supported methods:
|
|
67
|
+
* - `browser`: Boat's browser sign-in with GitHub or Google (interactive only).
|
|
68
|
+
* - `stored`: a pasted API key (`--method stored --set apiKey=-`).
|
|
69
|
+
*/
|
|
70
|
+
export declare const BoatAuth: Layer.Layer<never, never, import("alchemy/Auth/Registry").AuthProviders | ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/crypto/SecureRandom").SecureRandom>;
|
|
71
|
+
//# sourceMappingURL=AuthProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthProvider.d.ts","sources":["../src/AuthProvider.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,uBAAuB,SAAS,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,oBAAoB;;;;;IAM7B,uDAAuD;;;;;;;;;;IAYzD,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAC;AAG9D,oEAAoE;AACpE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACtE;AAkPD;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ,0NAkIpB,CAAC"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import * as boat from "@distilled.cloud/boat-dev";
|
|
2
|
+
import { DEFAULT_API_BASE_URL, fromApiKey } from "@distilled.cloud/boat-dev/Credentials";
|
|
3
|
+
import * as Login from "@distilled.cloud/boat-dev/Login";
|
|
4
|
+
import { AuthError, AuthProviderLayer, NeedsReauth, refreshHint, } from "alchemy/Auth/AuthProvider";
|
|
5
|
+
import { displayRedacted } from "alchemy/Auth/Credentials";
|
|
6
|
+
import { getEnv, getEnvRedacted, mapPromptCancellation } from "alchemy/Auth/Env";
|
|
7
|
+
import { collectFieldValues, storedSecret, storedValueText, validateFieldValues, } from "alchemy/Auth/StoredAuthProvider";
|
|
8
|
+
import * as Interaction from "alchemy/Interaction";
|
|
9
|
+
import * as Clock from "effect/Clock";
|
|
10
|
+
import * as Duration from "effect/Duration";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
13
|
+
import * as Layer from "effect/Layer";
|
|
14
|
+
import * as Match from "effect/Match";
|
|
15
|
+
import * as Redacted from "effect/Redacted";
|
|
16
|
+
import * as Schema from "effect/Schema";
|
|
17
|
+
/**
|
|
18
|
+
* Canonical name registered in the AuthProviders registry. Use this key to
|
|
19
|
+
* look up the Boat AuthProvider from inside provider Layers.
|
|
20
|
+
*/
|
|
21
|
+
export const BOAT_AUTH_PROVIDER_NAME = "Boat";
|
|
22
|
+
/**
|
|
23
|
+
* A Boat profile entry. Both methods store a Boat API key (`boat_…`):
|
|
24
|
+
*
|
|
25
|
+
* - `browser`: Boat's agent sign-in (https://boat.dev/auth.md). You sign in
|
|
26
|
+
* to boat.dev with GitHub, Google or an email code, type the code Alchemy
|
|
27
|
+
* shows, and Boat issues a key. The identity assertion that comes with it
|
|
28
|
+
* mints the next key when this one expires.
|
|
29
|
+
* - `stored`: you paste a key created with `boat api-keys create` or on the
|
|
30
|
+
* dashboard.
|
|
31
|
+
*
|
|
32
|
+
* Either may pin `org`, the organization Alchemy works in, the way the
|
|
33
|
+
* dashboard's organization select or `boat --org` do: every call bills it
|
|
34
|
+
* (`X-Boat-Org`), and `Boat.Environment` manages its shared environments.
|
|
35
|
+
* Sign-in picks the account's organization (asking only when it belongs
|
|
36
|
+
* to several) and stores its id (`team_…`), which survives renames, with
|
|
37
|
+
* `orgName` for display.
|
|
38
|
+
* Without it, sandboxes bill the account's active wallet (`boat org
|
|
39
|
+
* switch`) and environments are personal.
|
|
40
|
+
*
|
|
41
|
+
* A revoked key fails with Boat's `unauthorized` error; run
|
|
42
|
+
* `alchemy profile edit --reconfigure Boat` to sign in again.
|
|
43
|
+
*/
|
|
44
|
+
export const BoatAuthConfigSchema = Schema.Union([
|
|
45
|
+
Schema.Struct({
|
|
46
|
+
method: Schema.Literal("browser"),
|
|
47
|
+
email: Schema.String,
|
|
48
|
+
apiKey: Schema.String,
|
|
49
|
+
identityAssertion: Schema.optional(Schema.String),
|
|
50
|
+
/** Epoch millis the key expires at, when Boat says. */
|
|
51
|
+
expires: Schema.optional(Schema.Number),
|
|
52
|
+
account: Schema.optional(Schema.String),
|
|
53
|
+
org: Schema.optional(Schema.String),
|
|
54
|
+
orgName: Schema.optional(Schema.String),
|
|
55
|
+
}),
|
|
56
|
+
Schema.Struct({
|
|
57
|
+
method: Schema.Literal("stored"),
|
|
58
|
+
apiKey: Schema.String,
|
|
59
|
+
org: Schema.optional(Schema.String),
|
|
60
|
+
orgName: Schema.optional(Schema.String),
|
|
61
|
+
}),
|
|
62
|
+
]);
|
|
63
|
+
const apiKeyField = {
|
|
64
|
+
name: "apiKey",
|
|
65
|
+
label: "Boat API key",
|
|
66
|
+
description: "Create one with `boat api-keys create` or at https://boat.dev/dashboard.",
|
|
67
|
+
secret: true,
|
|
68
|
+
validate: (value) => (value.trim().startsWith("boat_") ? undefined : "Expected a boat_… key"),
|
|
69
|
+
};
|
|
70
|
+
const orgField = {
|
|
71
|
+
name: "org",
|
|
72
|
+
label: "Boat organization",
|
|
73
|
+
description: "The organization to work in: its id (team_…), its name, or `personal`. Leave it out to follow your active wallet.",
|
|
74
|
+
optional: true,
|
|
75
|
+
};
|
|
76
|
+
/** `--set` fields; interactively the org is picked from a list instead. */
|
|
77
|
+
const keyFields = [apiKeyField, orgField];
|
|
78
|
+
/** A set org, trimmed; blank means the active wallet. */
|
|
79
|
+
const orgOf = (value) => {
|
|
80
|
+
const org = value?.trim();
|
|
81
|
+
return org ? { org } : {};
|
|
82
|
+
};
|
|
83
|
+
const storedConfig = (values) => ({
|
|
84
|
+
method: "stored",
|
|
85
|
+
apiKey: Redacted.value(storedSecret(values.apiKey) ?? Redacted.make("")).trim(),
|
|
86
|
+
...orgOf(storedValueText(values.org)),
|
|
87
|
+
});
|
|
88
|
+
/** Sentinel choice: pin nothing and follow the account's active wallet. */
|
|
89
|
+
const ACTIVE_WALLET = "";
|
|
90
|
+
/**
|
|
91
|
+
* The organization to work in. An account in no organization works
|
|
92
|
+
* personally and one in exactly one works in it, without a question;
|
|
93
|
+
* only an account in several is asked to pick.
|
|
94
|
+
*/
|
|
95
|
+
const chooseOrg = Effect.fn(function* (apiKey, current) {
|
|
96
|
+
const { orgs } = yield* boat.listOrganizations({}).pipe(Effect.provide(Layer.mergeAll(fromApiKey({ apiKey }), FetchHttpClient.layer)), Effect.mapError((cause) => AuthError.make({ message: "Boat: could not list your organizations.", cause })));
|
|
97
|
+
const teams = orgs.filter((org) => org.type === "org");
|
|
98
|
+
if (teams.length === 0)
|
|
99
|
+
return {};
|
|
100
|
+
if (teams.length === 1) {
|
|
101
|
+
const [team] = teams;
|
|
102
|
+
yield* Interaction.info(`Boat: working in ${team.name}.`);
|
|
103
|
+
return { org: team.id, orgName: team.name };
|
|
104
|
+
}
|
|
105
|
+
const active = orgs.find((org) => org.active);
|
|
106
|
+
const org = yield* Interaction.select({
|
|
107
|
+
message: "Boat organization to work in",
|
|
108
|
+
options: [
|
|
109
|
+
...teams.map((team) => ({
|
|
110
|
+
value: team.id,
|
|
111
|
+
label: team.name,
|
|
112
|
+
description: `${team.role ?? "member"}: sandboxes bill it, environments are its shared ones`,
|
|
113
|
+
})),
|
|
114
|
+
{
|
|
115
|
+
value: "personal",
|
|
116
|
+
label: "Personal",
|
|
117
|
+
description: "your own wallet and your personal environments",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
value: ACTIVE_WALLET,
|
|
121
|
+
label: "Follow the active wallet",
|
|
122
|
+
description: `whatever \`boat org switch\` picks (now ${active?.name ?? "Personal"}); environments stay personal`,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
initialValue: current ?? (active?.type === "org" ? active.id : teams[0].id),
|
|
126
|
+
}).pipe(mapPromptCancellation);
|
|
127
|
+
if (org === ACTIVE_WALLET)
|
|
128
|
+
return {};
|
|
129
|
+
const orgName = org === "personal" ? "Personal" : teams.find((team) => team.id === org)?.name;
|
|
130
|
+
return { org, ...(orgName ? { orgName } : {}) };
|
|
131
|
+
});
|
|
132
|
+
const loginFailed = (cause) => AuthError.make({ message: `Boat: browser sign-in failed: ${cause.message}`, cause });
|
|
133
|
+
/** The account a key acts for, as `boat whoami` shows it. */
|
|
134
|
+
const accountOf = (apiKey) => boat.getCurrentUser({}).pipe(Effect.provide(Layer.mergeAll(fromApiKey({ apiKey }), FetchHttpClient.layer)), Effect.map(({ user }) => user.email ?? user.login ?? user.displayName ?? user.id), Effect.mapError((cause) => AuthError.make({ message: "Boat: the new API key was refused.", cause })));
|
|
135
|
+
/** Fresh codes the sign-in hands out before it gives up. */
|
|
136
|
+
const CODE_ROUNDS = 6;
|
|
137
|
+
/** Polls give up after this many attempts: about fifteen minutes at 5s. */
|
|
138
|
+
const POLL_ATTEMPTS = 180;
|
|
139
|
+
const expiryOf = (now, expiresIn) => expiresIn === undefined ? {} : { expires: now + expiresIn * 1000 };
|
|
140
|
+
/**
|
|
141
|
+
* Boat's agent sign-in: register for the account with the user's email,
|
|
142
|
+
* open the claim page, where the user signs in with any method and types the
|
|
143
|
+
* code, then poll until Boat issues a key.
|
|
144
|
+
*/
|
|
145
|
+
const browserLogin = (initialEmail, initialOrg) => Effect.gen(function* () {
|
|
146
|
+
const email = (yield* Interaction.text({
|
|
147
|
+
message: "Email of your Boat account",
|
|
148
|
+
description: "You sign in on boat.dev next, with GitHub, Google or an email code.",
|
|
149
|
+
...(initialEmail ? { initialValue: initialEmail } : {}),
|
|
150
|
+
validate: (value) => (value.includes("@") ? undefined : "Expected an email address"),
|
|
151
|
+
}).pipe(mapPromptCancellation)).trim();
|
|
152
|
+
const http = FetchHttpClient.layer;
|
|
153
|
+
const registered = yield* Login.registerAgent({ email }).pipe(Effect.provide(http), Effect.mapError(loginFailed));
|
|
154
|
+
const poll = (claim, interval, attempt) => Effect.sleep(Duration.seconds(interval)).pipe(Effect.andThen(Login.pollClaim({ claimToken: claim.claimToken, interval }).pipe(Effect.provide(http))), Effect.flatMap((result) => result._tag === "Issued"
|
|
155
|
+
? Effect.succeed(result.key)
|
|
156
|
+
: attempt >= POLL_ATTEMPTS
|
|
157
|
+
? Effect.fail(AuthError.make({ message: "Boat: the sign-in wasn't completed in time." }))
|
|
158
|
+
: poll(claim, result.interval, attempt + 1)));
|
|
159
|
+
// A code lapses after about ten minutes (`expired_token`); the claim
|
|
160
|
+
// then hands out a fresh one, shown in a new sign-in prompt.
|
|
161
|
+
const signIn = (claim, round) => Interaction.browserLogin({
|
|
162
|
+
provider: "Boat",
|
|
163
|
+
url: claim.verificationUri,
|
|
164
|
+
code: claim.userCode,
|
|
165
|
+
waitFor: poll(claim, claim.interval, 1),
|
|
166
|
+
}).pipe(Effect.catchIf((e) => e._tag === "BoatLoginError" && e.code === "expired_token" && round < CODE_ROUNDS, () => Login.refreshClaim({ claimToken: claim.claimToken, email }).pipe(Effect.provide(http), Effect.mapError(loginFailed), Effect.tap(() => Interaction.info("Boat: the code expired; here is a new one.")), Effect.flatMap((next) => signIn(next, round + 1)))), Effect.mapError((e) => (e._tag === "AuthError" ? e : loginFailed(e))));
|
|
167
|
+
const key = yield* signIn(registered, 1);
|
|
168
|
+
const account = yield* accountOf(key.apiKey);
|
|
169
|
+
yield* Interaction.success(`Boat: signed in as ${account ?? email}.`);
|
|
170
|
+
const org = yield* chooseOrg(key.apiKey, initialOrg);
|
|
171
|
+
const now = yield* Clock.currentTimeMillis;
|
|
172
|
+
return {
|
|
173
|
+
method: "browser",
|
|
174
|
+
email,
|
|
175
|
+
apiKey: Redacted.value(key.apiKey),
|
|
176
|
+
...(key.identityAssertion
|
|
177
|
+
? { identityAssertion: Redacted.value(key.identityAssertion) }
|
|
178
|
+
: {}),
|
|
179
|
+
...expiryOf(now, key.expiresIn),
|
|
180
|
+
...(account ? { account } : {}),
|
|
181
|
+
...org,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
/** Mints a new key from the assertion when the stored one is within a minute of expiring. */
|
|
185
|
+
const freshKey = Effect.fn(function* (profileName, config) {
|
|
186
|
+
const now = yield* Clock.currentTimeMillis;
|
|
187
|
+
if (config.expires === undefined || config.expires > now + 60_000)
|
|
188
|
+
return config;
|
|
189
|
+
if (config.identityAssertion === undefined) {
|
|
190
|
+
return yield* NeedsReauth.make({
|
|
191
|
+
provider: BOAT_AUTH_PROVIDER_NAME,
|
|
192
|
+
profile: profileName,
|
|
193
|
+
message: `Boat API key expired. ${refreshHint(BOAT_AUTH_PROVIDER_NAME, profileName)}`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const key = yield* Login.exchangeAssertion({
|
|
197
|
+
identityAssertion: Redacted.make(config.identityAssertion),
|
|
198
|
+
}).pipe(Effect.provide(FetchHttpClient.layer), Effect.mapError((cause) => NeedsReauth.make({
|
|
199
|
+
provider: BOAT_AUTH_PROVIDER_NAME,
|
|
200
|
+
profile: profileName,
|
|
201
|
+
message: `Boat sign-in expired. ${refreshHint(BOAT_AUTH_PROVIDER_NAME, profileName)}`,
|
|
202
|
+
cause,
|
|
203
|
+
})));
|
|
204
|
+
return {
|
|
205
|
+
...config,
|
|
206
|
+
apiKey: Redacted.value(key.apiKey),
|
|
207
|
+
...(key.identityAssertion ? { identityAssertion: Redacted.value(key.identityAssertion) } : {}),
|
|
208
|
+
expires: key.expiresIn === undefined ? undefined : now + key.expiresIn * 1000,
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
const readEnvironment = Effect.gen(function* () {
|
|
212
|
+
const apiKey = yield* getEnvRedacted("BOAT_API_KEY");
|
|
213
|
+
if (apiKey === undefined || Redacted.value(apiKey).trim().length === 0) {
|
|
214
|
+
return yield* AuthError.make({
|
|
215
|
+
message: "Boat CI credentials not found. Set BOAT_API_KEY.",
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
type: "apiKey",
|
|
220
|
+
apiKey: Redacted.make(Redacted.value(apiKey).trim()),
|
|
221
|
+
apiBaseUrl: (yield* getEnv("BOAT_API_BASE_URL")) ?? DEFAULT_API_BASE_URL,
|
|
222
|
+
...orgOf(yield* getEnv("BOAT_ORG")),
|
|
223
|
+
source: { type: "env", details: "BOAT_API_KEY" },
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
const resolve = (config) => ({
|
|
227
|
+
type: "apiKey",
|
|
228
|
+
apiKey: Redacted.make(config.apiKey),
|
|
229
|
+
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
230
|
+
...orgOf(config.org),
|
|
231
|
+
source: {
|
|
232
|
+
type: config.method,
|
|
233
|
+
details: config.method === "browser" ? (config.account ?? config.email) : undefined,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
/**
|
|
237
|
+
* Layer that registers the Boat {@link AuthProvider} into the
|
|
238
|
+
* `AuthProviders` registry when built. Included in `Boat.providers()` so
|
|
239
|
+
* `alchemy profile edit` can configure it.
|
|
240
|
+
*
|
|
241
|
+
* Supported methods:
|
|
242
|
+
* - `browser`: Boat's browser sign-in with GitHub or Google (interactive only).
|
|
243
|
+
* - `stored`: a pasted API key (`--method stored --set apiKey=-`).
|
|
244
|
+
*/
|
|
245
|
+
export const BoatAuth = AuthProviderLayer()(BOAT_AUTH_PROVIDER_NAME, Effect.gen(function* () {
|
|
246
|
+
const pasteKey = collectFieldValues([apiKeyField]).pipe(Effect.map(storedConfig), Effect.flatMap((config) => chooseOrg(Redacted.make(config.apiKey)).pipe(Effect.map((org) => ({ ...config, ...org })))), Effect.tap(() => Interaction.success("Boat: credentials saved.")));
|
|
247
|
+
const configure = (_profileName) => Interaction.select({
|
|
248
|
+
message: "Boat authentication method",
|
|
249
|
+
options: [
|
|
250
|
+
{
|
|
251
|
+
value: "browser",
|
|
252
|
+
label: "Browser sign-in",
|
|
253
|
+
description: "recommended — sign in on boat.dev with GitHub, Google or email",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
value: "stored",
|
|
257
|
+
label: "API key",
|
|
258
|
+
description: "paste a boat_… key",
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
}).pipe(mapPromptCancellation, Effect.flatMap((method) => Match.value(method).pipe(Match.when("browser", () => browserLogin()), Match.when("stored", () => pasteKey), Match.exhaustive)), Effect.mapError((e) => e._tag === "AuthError"
|
|
262
|
+
? e
|
|
263
|
+
: AuthError.make({ message: "Boat: failed to configure credentials", cause: e })));
|
|
264
|
+
/** The browser sign-in needs a person, so only `stored` takes flags. */
|
|
265
|
+
const configureMethods = [
|
|
266
|
+
{ method: "stored", fields: keyFields },
|
|
267
|
+
];
|
|
268
|
+
const configureWith = (_profileName, input) => input.method === "stored"
|
|
269
|
+
? validateFieldValues(BOAT_AUTH_PROVIDER_NAME, keyFields, input.values).pipe(Effect.map(storedConfig), Effect.tap(() => Interaction.success("Boat: credentials saved.")))
|
|
270
|
+
: Effect.fail(AuthError.make({
|
|
271
|
+
message: `Boat: unknown method '${input.method}'. Valid methods: stored. (Browser sign-in is interactive-only.)`,
|
|
272
|
+
}));
|
|
273
|
+
// A key doesn't refresh, so logging in again repeats the browser
|
|
274
|
+
// sign-in; a pasted key stays as it is.
|
|
275
|
+
const login = (_profileName, config) => config.method === "browser"
|
|
276
|
+
? browserLogin(config.email, config.org).pipe(Effect.mapError((e) => e._tag === "AuthError"
|
|
277
|
+
? e
|
|
278
|
+
: AuthError.make({ message: "Boat: login failed", cause: e })))
|
|
279
|
+
: Effect.succeed(config);
|
|
280
|
+
const details = (_profileName, config) => Effect.succeed({
|
|
281
|
+
lines: [
|
|
282
|
+
...(config.method === "browser"
|
|
283
|
+
? [{ key: "account", value: config.account ?? config.email }]
|
|
284
|
+
: []),
|
|
285
|
+
{ key: "apiKey", value: displayRedacted(Redacted.make(config.apiKey)) },
|
|
286
|
+
...(config.org ? [{ key: "org", value: config.orgName ?? config.org }] : []),
|
|
287
|
+
{ key: "source", value: config.method },
|
|
288
|
+
],
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
configSchema: BoatAuthConfigSchema,
|
|
292
|
+
configure,
|
|
293
|
+
configureWith,
|
|
294
|
+
configureMethods,
|
|
295
|
+
login,
|
|
296
|
+
logout: () => Effect.void,
|
|
297
|
+
details,
|
|
298
|
+
read: (profileName, config, updateConfig) => config.method === "stored"
|
|
299
|
+
? Effect.succeed(resolve(config))
|
|
300
|
+
: freshKey(profileName, config).pipe(Effect.tap((fresh) => fresh === config ? Effect.void : (updateConfig?.(fresh) ?? Effect.void)), Effect.map(resolve)),
|
|
301
|
+
readEnvironment,
|
|
302
|
+
environment: [
|
|
303
|
+
{
|
|
304
|
+
name: "BOAT_API_KEY",
|
|
305
|
+
required: true,
|
|
306
|
+
secret: true,
|
|
307
|
+
description: "Boat API key (`boat_…`).",
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "BOAT_API_BASE_URL",
|
|
311
|
+
required: false,
|
|
312
|
+
description: `Boat API URL override. Defaults to ${DEFAULT_API_BASE_URL}.`,
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "BOAT_ORG",
|
|
316
|
+
required: false,
|
|
317
|
+
description: "Boat organization (wallet) to bill: an org id (`team_…`), an org name, or `personal`. Defaults to the account's active wallet.",
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
};
|
|
321
|
+
}));
|
|
322
|
+
//# sourceMappingURL=AuthProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthProvider.js","sources":["../src/AuthProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How Boat bindings reach Boat from any host.
|
|
3
|
+
*
|
|
4
|
+
* Boat has scoped API keys and no identity federation, so every binding
|
|
5
|
+
* mints its own key at deploy, a `Boat.ApiKey` in the host's namespace,
|
|
6
|
+
* with only the actions it needs and, for a declared sandbox, only that
|
|
7
|
+
* sandbox. The key is deleted with the host. The deployer's credentials
|
|
8
|
+
* never reach a host. The binding captures the key as a named runtime
|
|
9
|
+
* Output; inside the deployed host it is read back from the host's env,
|
|
10
|
+
* still `Redacted`. Every runtime call provides `Credentials` from it, so
|
|
11
|
+
* nothing reads `process.env`. The deployer's org and API URL are bound
|
|
12
|
+
* alongside it (`BOAT_ORG`, `BOAT_API_BASE_URL`), so the host's calls bill
|
|
13
|
+
* the same wallet the deploy did.
|
|
14
|
+
*
|
|
15
|
+
* Bindings that create sandboxes at run time label them with the host's
|
|
16
|
+
* `alchemy.stack` / `alchemy.stage` / `alchemy.id`, computed at deploy and
|
|
17
|
+
* bound into the host (`bindOwnerLabels`). Destroying the stack does not
|
|
18
|
+
* delete them.
|
|
19
|
+
*/
|
|
20
|
+
import { Credentials } from "@distilled.cloud/boat-dev/Credentials";
|
|
21
|
+
import type { RuntimeContext } from "alchemy/RuntimeContext";
|
|
22
|
+
import * as Effect from "effect/Effect";
|
|
23
|
+
import type * as HttpClient from "effect/http/HttpClient";
|
|
24
|
+
/** The `alchemy.*` labels on sandboxes a host creates at run time. */
|
|
25
|
+
export type OwnerLabels = Readonly<Record<string, string>>;
|
|
26
|
+
export interface BoatAuth {
|
|
27
|
+
/** Runs a distilled call with the binding's key and an HTTP client. */
|
|
28
|
+
readonly authorize: <A, E>(effect: Effect.Effect<A, E, Credentials | HttpClient.HttpClient>) => Effect.Effect<A, E, RuntimeContext>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Mints the binding's key (`<keyId>` in the host's namespace, with
|
|
32
|
+
* `scope`) and binds it onto the host as `<envName>`, and returns a runtime
|
|
33
|
+
* accessor for it. Without a host (a script) nothing is minted, and calls
|
|
34
|
+
* use the ambient credentials.
|
|
35
|
+
*/
|
|
36
|
+
export declare const bindBoat: (keyId: string, envName: string, scope: {
|
|
37
|
+
name?: import("alchemy/Input").Input<string | undefined>;
|
|
38
|
+
preset?: import("alchemy/Input").Input<import("./ApiKey.ts").ApiKeyPreset | undefined>;
|
|
39
|
+
actions?: import("alchemy/Input").Input<import("./ApiKey.ts").ApiKeyAction[] | undefined>;
|
|
40
|
+
sandboxIds?: import("alchemy/Input").Input<string[] | undefined>;
|
|
41
|
+
environmentIds?: import("alchemy/Input").Input<string[] | undefined>;
|
|
42
|
+
ttl?: import("alchemy/Input").Input<string | undefined>;
|
|
43
|
+
}) => Effect.Effect<{
|
|
44
|
+
authorize: <A, E>(effect: Effect.Effect<A, E, Credentials | HttpClient.HttpClient>) => Effect.Effect<A, E, never>;
|
|
45
|
+
}, never, never>;
|
|
46
|
+
/**
|
|
47
|
+
* Binds the host's `alchemy.stack` / `alchemy.stage` / `alchemy.id` into
|
|
48
|
+
* it, and returns them at run time for `create` to label what it makes.
|
|
49
|
+
* Without a host (a script) there are none.
|
|
50
|
+
*/
|
|
51
|
+
export declare const bindOwnerLabels: Effect.Effect<Effect.Effect<Readonly<Record<string, string>>, never, never>, never, never>;
|
|
52
|
+
//# sourceMappingURL=BoatBindings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BoatBindings.d.ts","sources":["../src/BoatBindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,WAAW,EAAwB,MAAM,uCAAuC,CAAC;AAM1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,KAAK,UAAU,MAAM,wBAAwB,CAAC;AAU1D,sEAAsE;AACtE,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACvB,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC,KAC7D,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;CAC1C;AAED;;;;;GAKG;AACH,eAAO,MAAM,QAAQ;;;;;;;;gBAXE,CAAC,EAAE,CAAC;gBAyDzB,CAAC;AAUH;;;;GAIG;AACH,eAAO,MAAM,eAAe,4FAkB1B,CAAC"}
|