1import { assert, describe, it } from "@effect/vitest";
2import * as Duration from "effect/Duration";
3import * as Effect from "effect/Effect";
4import * as Fiber from "effect/Fiber";
5import * as Layer from "effect/Layer";
6import * as Ref from "effect/Ref";
7import * as HttpClient from "effect/http/HttpClient";
8import * as HttpClientRequest from "effect/http/HttpClientRequest";
9import * as HttpClientResponse from "effect/http/HttpClientResponse";
10import { PRIMARY_LOCAL_ENVIRONMENT_ID } from "@t3tools/contracts";
11import {
12 DESKTOP_BOOTSTRAP_TOKEN_WINDOW_MS,
13 currentDesktopBootstrapToken,
14} from "@t3tools/shared/desktopBootstrapToken";
15import * as TestClock from "effect/testing/TestClock";
16
17import * as DesktopBackendPool from "./DesktopBackendPool.ts";
18import * as DesktopLocalEnvironmentAuth from "./DesktopLocalEnvironmentAuth.ts";
19
20const config = {
21 executablePath: "/electron",
22 entryPath: "/server/bin.mjs",
23 cwd: "/server",
24 env: {},
25 bootstrap: {
26 mode: "desktop",
27 noBrowser: true,
28 port: 3773,
29 t3Home: "/tmp/t3",
30 host: "127.0.0.1",
31 desktopBootstrapToken: "desktop-bootstrap-token",
32 tailscaleServeEnabled: false,
33 tailscaleServePort: 443,
34 },
35 httpBaseUrl: new URL("http://127.0.0.1:3773"),
36 captureOutput: true,
37};
38
39describe("DesktopLocalEnvironmentAuth", () => {
40 it.effect("exchanges the desktop bootstrap credential only once", () =>
41 Effect.gen(function* () {
42 const requestCount = yield* Ref.make(0);
43 const layerHttpClient = Layer.succeed(
44 HttpClient.HttpClient,
45 HttpClient.make((request) =>
46 Ref.update(requestCount, (count) => count + 1).pipe(
47 Effect.as(
48 HttpClientResponse.fromWeb(
49 request,
50 new Response(
51 JSON.stringify({
52 access_token: "desktop-bearer-token",
53 issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
54 token_type: "Bearer",
55 expires_in: 3600,
56 scope: "orchestration:read",
57 }),
58 { status: 200, headers: { "content-type": "application/json" } },
59 ),
60 ),
61 ),
62 ),
63 ),
64 );
65 const layerPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
66 list: Effect.succeed([
67 {
68 id: PRIMARY_LOCAL_ENVIRONMENT_ID,
69 label: Effect.succeed("Windows"),
70 currentConfig: Effect.succeedSome(config),
71 },
72 ]),
73 } as unknown as DesktopBackendPool.DesktopBackendPool["Service"]);
74 const layerTest = DesktopLocalEnvironmentAuth.layer.pipe(
75 Layer.provide(Layer.mergeAll(layerPool, layerHttpClient)),
76 );
77
78 const [first, second] = yield* Effect.gen(function* () {
79 const auth = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth;
80 return yield* Effect.all([auth.getBearerToken, auth.getBearerToken]);
81 }).pipe(Effect.provide(layerTest));
82
83 assert.strictEqual(first, "desktop-bearer-token");
84 assert.strictEqual(second, "desktop-bearer-token");
85 assert.strictEqual(yield* Ref.get(requestCount), 1);
86 }),
87 );
88
89 it.effect(
90 "exchanges the current window's token when the backend was launched with a secret",
91 () =>
92 Effect.gen(function* () {
93 const presented = yield* Ref.make<string | null>(null);
94 const httpClientLayer = Layer.succeed(
95 HttpClient.HttpClient,
96 HttpClient.make((request) => {
97 const body =
98 request.body._tag === "Uint8Array" ? new TextDecoder().decode(request.body.body) : "";
99 return Ref.set(presented, new URLSearchParams(body).get("subject_token")).pipe(
100 Effect.as(
101 HttpClientResponse.fromWeb(
102 request,
103 new Response(
104 JSON.stringify({
105 access_token: "desktop-bearer-token",
106 issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
107 token_type: "Bearer",
108 expires_in: 3600,
109 scope: "orchestration:read",
110 }),
111 { status: 200, headers: { "content-type": "application/json" } },
112 ),
113 ),
114 ),
115 );
116 }),
117 );
118 const poolLayer = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
119 list: Effect.succeed([
120 {
121 id: PRIMARY_LOCAL_ENVIRONMENT_ID,
122 label: Effect.succeed("Windows"),
123 currentConfig: Effect.succeedSome({
124 ...config,
125 bootstrap: { ...config.bootstrap, desktopBootstrapSecret: "desktop-secret" },
126 }),
127 },
128 ]),
129 } as unknown as DesktopBackendPool.DesktopBackendPool["Service"]);
130
131 // The first exchange happens a day after launch, past the launch token's windows.
132 yield* TestClock.setTime(DESKTOP_BOOTSTRAP_TOKEN_WINDOW_MS * 2 + 1);
133 yield* Effect.gen(function* () {
134 const auth = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth;
135 return yield* auth.getBearerToken;
136 }).pipe(
137 Effect.provide(
138 DesktopLocalEnvironmentAuth.layer.pipe(
139 Layer.provide(Layer.mergeAll(poolLayer, httpClientLayer)),
140 ),
141 ),
142 );
143
144 assert.strictEqual(
145 yield* Ref.get(presented),
146 currentDesktopBootstrapToken("desktop-secret", DESKTOP_BOOTSTRAP_TOKEN_WINDOW_MS * 2 + 1),
147 );
148 }).pipe(Effect.provide(TestClock.layer())),
149 );
150
151 const tokenResponse = (request: HttpClientRequest.HttpClientRequest) =>
152 HttpClientResponse.fromWeb(
153 request,
154 new Response(
155 JSON.stringify({
156 access_token: "desktop-bearer-token",
157 issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
158 token_type: "Bearer",
159 expires_in: 3600,
160 scope: "orchestration:read",
161 }),
162 { status: 200, headers: { "content-type": "application/json" } },
163 ),
164 );
165 const layerPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, {
166 list: Effect.succeed([
167 {
168 id: PRIMARY_LOCAL_ENVIRONMENT_ID,
169 label: Effect.succeed("Windows"),
170 currentConfig: Effect.succeedSome(config),
171 },
172 ]),
173 } as unknown as DesktopBackendPool.DesktopBackendPool["Service"]);
174 // Answers the first `failures` exchanges with `failure`, then with a token.
175 const makeExchange = (failures: number, failure: () => Response) =>
176 Effect.gen(function* () {
177 const requestCount = yield* Ref.make(0);
178 const layerHttpClient = Layer.succeed(
179 HttpClient.HttpClient,
180 HttpClient.make((request) =>
181 Ref.updateAndGet(requestCount, (count) => count + 1).pipe(
182 Effect.map((count) =>
183 count <= failures
184 ? HttpClientResponse.fromWeb(request, failure())
185 : tokenResponse(request),
186 ),
187 ),
188 ),
189 );
190 const auth = yield* DesktopLocalEnvironmentAuth.DesktopLocalEnvironmentAuth.pipe(
191 Effect.provide(
192 DesktopLocalEnvironmentAuth.layer.pipe(
193 Layer.provide(Layer.mergeAll(layerPool, layerHttpClient)),
194 ),
195 ),
196 );
197 return { auth, requestCount };
198 });
199
200 it.effect("retries a backend that is still starting", () =>
201 Effect.gen(function* () {
202 const { auth, requestCount } = yield* makeExchange(
203 2,
204 () => new Response("", { status: 503 }),
205 );
206
207 const fiber = yield* auth.getBearerToken.pipe(Effect.forkChild);
208 yield* TestClock.adjust(Duration.seconds(1));
209
210 assert.strictEqual(yield* Fiber.join(fiber), "desktop-bearer-token");
211 assert.strictEqual(yield* Ref.get(requestCount), 3);
212 }).pipe(Effect.provide(TestClock.layer())),
213 );
214
215 it.effect("does not retry a rejected bootstrap credential", () =>
216 Effect.gen(function* () {
217 const { auth, requestCount } = yield* makeExchange(1, () =>
218 Response.json(
219 {
220 _tag: "EnvironmentAuthInvalidError",
221 code: "auth_invalid",
222 reason: "invalid_credential",
223 traceId: "trace-1",
224 },
225 { status: 401 },
226 ),
227 );
228
229 const fiber = yield* auth.getBearerToken.pipe(Effect.flip, Effect.forkChild);
230 yield* TestClock.adjust(Duration.seconds(1));
231 const error = yield* Fiber.join(fiber);
232
233 assert.strictEqual(error._tag, "DesktopLocalEnvironmentAuthSessionBootstrapError");
234 assert.strictEqual(yield* Ref.get(requestCount), 1);
235 }).pipe(Effect.provide(TestClock.layer())),
236 );
237});