t3code-cli 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2 -2
- package/dist/index.js +12 -2
- package/dist/{runtime-15tR27tv.js → runtime-KQVRmRk-.js} +196 -132
- package/dist/src/auth/local-base-dir.d.ts +7 -0
- package/dist/src/auth/local-origin.d.ts +18 -0
- package/dist/src/auth/local-token.d.ts +26 -0
- package/dist/src/auth/local.d.ts +4 -7
- package/dist/src/auth/transport.d.ts +6 -3
- package/dist/src/auth/type.d.ts +12 -0
- package/dist/src/connection/error.d.ts +8 -0
- package/dist/src/connection/layer.d.ts +3 -0
- package/dist/src/connection/node.d.ts +2 -0
- package/dist/src/connection/service.d.ts +13 -0
- package/dist/src/connection/type.d.ts +10 -0
- package/dist/src/connection.d.ts +5 -0
- package/dist/src/index.d.ts +6 -2
- package/dist/src/rpc/error.d.ts +4 -3
- package/dist/src/rpc/layer.d.ts +5 -4
- package/dist/src/runtime.d.ts +5 -2
- package/package.json +1 -1
- package/src/auth/local-base-dir.ts +22 -0
- package/src/auth/local-origin.ts +62 -0
- package/src/auth/local-token.ts +302 -0
- package/src/auth/local.ts +12 -325
- package/src/auth/transport.ts +17 -11
- package/src/auth/type.ts +15 -0
- package/src/connection/error.ts +9 -0
- package/src/connection/layer.ts +10 -0
- package/src/connection/node.ts +12 -0
- package/src/connection/service.ts +26 -0
- package/src/connection/type.ts +12 -0
- package/src/connection.ts +9 -0
- package/src/index.ts +25 -0
- package/src/rpc/error.ts +4 -11
- package/src/rpc/layer.ts +25 -15
- package/src/runtime.ts +42 -2
package/src/auth/local.ts
CHANGED
|
@@ -1,30 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AuthAdministrativeScopes,
|
|
3
|
-
AuthSessionId,
|
|
4
|
-
type AuthEnvironmentScope,
|
|
5
|
-
} from "#t3tools/contracts";
|
|
6
1
|
import * as Context from "effect/Context";
|
|
7
|
-
import * as Crypto from "effect/Crypto";
|
|
8
|
-
import * as DateTime from "effect/DateTime";
|
|
9
2
|
import * as Effect from "effect/Effect";
|
|
10
|
-
import * as Encoding from "effect/Encoding";
|
|
11
|
-
import * as Filter from "effect/Filter";
|
|
12
|
-
import * as FileSystem from "effect/FileSystem";
|
|
13
3
|
import * as Layer from "effect/Layer";
|
|
14
|
-
import * as Path from "effect/Path";
|
|
15
|
-
import * as Predicate from "effect/Predicate";
|
|
16
|
-
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
17
4
|
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
AuthLocalDatabaseError,
|
|
23
|
-
AuthLocalError,
|
|
24
|
-
AuthLocalSecretError,
|
|
25
|
-
AuthLocalSigningError,
|
|
26
|
-
} from "./error.ts";
|
|
27
|
-
import { decodeAuthLocalRuntimeStateFromJson } from "./schema.ts";
|
|
5
|
+
import { AuthLocalError } from "./error.ts";
|
|
6
|
+
import { T3LocalAuthOrigin } from "./local-origin.ts";
|
|
7
|
+
import { T3LocalAuthToken } from "./local-token.ts";
|
|
28
8
|
import type { LocalAuthInput, LocalAuthResult } from "./type.ts";
|
|
29
9
|
|
|
30
10
|
export class T3LocalAuth extends Context.Service<
|
|
@@ -35,220 +15,22 @@ export class T3LocalAuth extends Context.Service<
|
|
|
35
15
|
>()("t3cli/T3LocalAuth") {}
|
|
36
16
|
|
|
37
17
|
export const makeT3LocalAuth = Effect.fn("makeT3LocalAuth")(function* () {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const environment = yield* Environment;
|
|
41
|
-
const crypto = yield* Crypto.Crypto;
|
|
42
|
-
const sqlClientFactory = yield* SqlClientFactory;
|
|
43
|
-
|
|
44
|
-
function resolveLocalBaseDir(input: string | undefined) {
|
|
45
|
-
const envBaseDir = environment.env["T3CODE_HOME"];
|
|
46
|
-
const raw = input ?? envBaseDir;
|
|
47
|
-
if (raw === undefined || raw.length === 0) {
|
|
48
|
-
return path.join(environment.homeDir, ".t3");
|
|
49
|
-
}
|
|
50
|
-
if (raw === "~") {
|
|
51
|
-
return environment.homeDir;
|
|
52
|
-
}
|
|
53
|
-
if (raw.startsWith("~/") || raw.startsWith("~\\")) {
|
|
54
|
-
return path.join(environment.homeDir, raw.slice(2));
|
|
55
|
-
}
|
|
56
|
-
return path.resolve(environment.cwd, raw);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function resolveLocalOrigin(input: { readonly baseDir: string; readonly origin?: string }) {
|
|
60
|
-
return Effect.gen(function* () {
|
|
61
|
-
if (input.origin !== undefined) {
|
|
62
|
-
return yield* normalizeLocalOrigin(input.origin);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const runtimeStatePath = path.join(input.baseDir, "userdata", "server-runtime.json");
|
|
66
|
-
const raw = yield* fs.readFileString(runtimeStatePath).pipe(
|
|
67
|
-
Effect.mapError(
|
|
68
|
-
(error) =>
|
|
69
|
-
new AuthLocalError({
|
|
70
|
-
message: `local runtime state not found: ${runtimeStatePath}. Make sure T3 Code is running with Network access enabled, or pass --origin manually.`,
|
|
71
|
-
cause: error,
|
|
72
|
-
}),
|
|
73
|
-
),
|
|
74
|
-
);
|
|
75
|
-
const state = yield* decodeAuthLocalRuntimeStateFromJson(raw).pipe(
|
|
76
|
-
Effect.mapError(
|
|
77
|
-
(error) =>
|
|
78
|
-
new AuthLocalError({ message: "local runtime state has invalid shape", cause: error }),
|
|
79
|
-
),
|
|
80
|
-
);
|
|
81
|
-
return yield* normalizeLocalOrigin(state.origin);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function readSigningSecret(secretPath: string) {
|
|
86
|
-
return fs.readFile(secretPath).pipe(
|
|
87
|
-
Effect.map((bytes) => Uint8Array.from(bytes)),
|
|
88
|
-
Effect.catchFilter(Filter.reason("PlatformError", "NotFound"), () =>
|
|
89
|
-
Effect.succeed(undefined),
|
|
90
|
-
),
|
|
91
|
-
Effect.mapError(
|
|
92
|
-
(error) =>
|
|
93
|
-
new AuthLocalSecretError({
|
|
94
|
-
message: `failed to read signing secret: ${secretPath}`,
|
|
95
|
-
cause: error,
|
|
96
|
-
}),
|
|
97
|
-
),
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const readRequiredSigningSecret = Effect.fn("readRequiredSigningSecret")(function* (
|
|
102
|
-
secretsDir: string,
|
|
103
|
-
) {
|
|
104
|
-
const secretPath = path.join(secretsDir, `${signingSecretName}.bin`);
|
|
105
|
-
const secret = yield* readSigningSecret(secretPath);
|
|
106
|
-
if (secret === undefined) {
|
|
107
|
-
return yield* Effect.fail(
|
|
108
|
-
new AuthLocalSecretError({
|
|
109
|
-
message: `local signing secret not found: ${secretPath}`,
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
return secret;
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const hmacSha256 = (secret: Uint8Array, payload: Uint8Array) =>
|
|
117
|
-
Effect.gen(function* () {
|
|
118
|
-
const key =
|
|
119
|
-
secret.byteLength > sha256BlockSize ? yield* crypto.digest("SHA-256", secret) : secret;
|
|
120
|
-
const block = new Uint8Array(sha256BlockSize);
|
|
121
|
-
block.set(key);
|
|
122
|
-
const outerPad = block.map((byte) => byte ^ 0x5c);
|
|
123
|
-
const innerPad = block.map((byte) => byte ^ 0x36);
|
|
124
|
-
const innerHash = yield* crypto.digest("SHA-256", concatBytes(innerPad, payload));
|
|
125
|
-
return yield* crypto.digest("SHA-256", concatBytes(outerPad, innerHash));
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const signPayload = (payload: string, secret: Uint8Array) =>
|
|
129
|
-
hmacSha256(secret, new TextEncoder().encode(payload)).pipe(
|
|
130
|
-
Effect.map(Encoding.encodeBase64Url),
|
|
131
|
-
Effect.mapError(
|
|
132
|
-
(error) =>
|
|
133
|
-
new AuthLocalSigningError({
|
|
134
|
-
operation: "sign",
|
|
135
|
-
message: "failed to sign local auth payload",
|
|
136
|
-
cause: error,
|
|
137
|
-
}),
|
|
138
|
-
),
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
function openAuthDatabase(dbPath: string) {
|
|
142
|
-
return sqlClientFactory.sqliteClient({ filename: dbPath }).pipe(
|
|
143
|
-
Effect.catchTag("SqlError", (error) =>
|
|
144
|
-
Effect.fail(
|
|
145
|
-
new AuthLocalDatabaseError({
|
|
146
|
-
operation: "connect",
|
|
147
|
-
message: error.message,
|
|
148
|
-
}),
|
|
149
|
-
),
|
|
150
|
-
),
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const provideAuthDatabase =
|
|
155
|
-
(dbPath: string) =>
|
|
156
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
157
|
-
Effect.gen(function* () {
|
|
158
|
-
const sql = yield* openAuthDatabase(dbPath);
|
|
159
|
-
return yield* effect.pipe(Effect.provideService(SqlClient.SqlClient, sql));
|
|
160
|
-
}).pipe(Effect.scoped);
|
|
161
|
-
|
|
162
|
-
const issueLocalDatabaseSession = Effect.fn("issueLocalDatabaseSession")(function* (
|
|
163
|
-
input: LocalDatabaseSessionInput,
|
|
164
|
-
) {
|
|
165
|
-
const secret = yield* readRequiredSigningSecret(input.secretsDir);
|
|
166
|
-
const issuedAt = yield* DateTime.now;
|
|
167
|
-
const expiresAt = DateTime.add(issuedAt, { milliseconds: defaultSessionTtlMs });
|
|
168
|
-
const sessionId = yield* crypto.randomUUIDv4.pipe(
|
|
169
|
-
Effect.map((id) => AuthSessionId.make(id)),
|
|
170
|
-
Effect.mapError(
|
|
171
|
-
(error) =>
|
|
172
|
-
new AuthLocalSecretError({
|
|
173
|
-
message: "failed to generate auth session id",
|
|
174
|
-
cause: error,
|
|
175
|
-
}),
|
|
176
|
-
),
|
|
177
|
-
);
|
|
178
|
-
const scopes = [...AuthAdministrativeScopes];
|
|
179
|
-
const claims: LocalSessionClaims = {
|
|
180
|
-
v: 1,
|
|
181
|
-
kind: "session",
|
|
182
|
-
sid: sessionId,
|
|
183
|
-
sub: input.subject,
|
|
184
|
-
scopes,
|
|
185
|
-
method: "bearer-access-token",
|
|
186
|
-
iat: DateTime.toEpochMillis(issuedAt),
|
|
187
|
-
exp: DateTime.toEpochMillis(expiresAt),
|
|
188
|
-
};
|
|
189
|
-
const encodedPayload = Encoding.encodeBase64Url(JSON.stringify(claims));
|
|
190
|
-
const token = `${encodedPayload}.${yield* signPayload(encodedPayload, secret)}`;
|
|
191
|
-
yield* insertAuthSession({
|
|
192
|
-
sessionId,
|
|
193
|
-
subject: input.subject,
|
|
194
|
-
scopes,
|
|
195
|
-
label: input.label,
|
|
196
|
-
issuedAt: DateTime.formatIso(issuedAt),
|
|
197
|
-
expiresAt: DateTime.formatIso(expiresAt),
|
|
198
|
-
}).pipe(
|
|
199
|
-
provideAuthDatabase(input.dbPath),
|
|
200
|
-
Effect.catchTag("SqlError", (error) =>
|
|
201
|
-
Effect.fail(
|
|
202
|
-
new AuthLocalDatabaseError({
|
|
203
|
-
operation: Predicate.isTagged(error.reason, "ConnectionError") ? "connect" : "query",
|
|
204
|
-
message: error.message,
|
|
205
|
-
}),
|
|
206
|
-
),
|
|
207
|
-
),
|
|
208
|
-
);
|
|
209
|
-
return {
|
|
210
|
-
token,
|
|
211
|
-
role: input.role,
|
|
212
|
-
expiresAt: DateTime.formatIso(expiresAt),
|
|
213
|
-
};
|
|
214
|
-
});
|
|
18
|
+
const origin = yield* T3LocalAuthOrigin;
|
|
19
|
+
const token = yield* T3LocalAuthToken;
|
|
215
20
|
|
|
216
21
|
const local = Effect.fn("T3LocalAuthLive.local")(function* (input: LocalAuthInput) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
if (input.subject.length === 0) {
|
|
223
|
-
return yield* Effect.fail(
|
|
224
|
-
new AuthLocalError({ message: "local auth subject cannot be empty" }),
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const baseDir = resolveLocalBaseDir(input.baseDir);
|
|
229
|
-
const session = yield* issueLocalDatabaseSession({
|
|
230
|
-
dbPath: path.join(baseDir, "userdata", "state.sqlite"),
|
|
231
|
-
secretsDir: path.join(baseDir, "userdata", "secrets"),
|
|
232
|
-
role: input.role,
|
|
233
|
-
label: input.label,
|
|
234
|
-
subject: input.subject,
|
|
235
|
-
}).pipe(
|
|
236
|
-
Effect.mapError(
|
|
237
|
-
(error) =>
|
|
238
|
-
new AuthLocalError({ message: `local auth failed: ${error.message}`, cause: error }),
|
|
239
|
-
),
|
|
240
|
-
);
|
|
241
|
-
const url = yield* resolveLocalOrigin({
|
|
242
|
-
baseDir,
|
|
22
|
+
const created = yield* token.create(input);
|
|
23
|
+
const url = yield* origin.resolve({
|
|
24
|
+
baseDir: created.baseDir,
|
|
243
25
|
...(input.origin !== undefined ? { origin: input.origin } : {}),
|
|
244
26
|
});
|
|
245
27
|
return {
|
|
246
28
|
url,
|
|
247
|
-
token:
|
|
248
|
-
role:
|
|
249
|
-
expiresAt:
|
|
29
|
+
token: created.token,
|
|
30
|
+
role: created.role,
|
|
31
|
+
expiresAt: created.expiresAt,
|
|
250
32
|
source: "local" as const,
|
|
251
|
-
baseDir,
|
|
33
|
+
baseDir: created.baseDir,
|
|
252
34
|
};
|
|
253
35
|
});
|
|
254
36
|
|
|
@@ -256,98 +38,3 @@ export const makeT3LocalAuth = Effect.fn("makeT3LocalAuth")(function* () {
|
|
|
256
38
|
});
|
|
257
39
|
|
|
258
40
|
export const T3LocalAuthLive = Layer.effect(T3LocalAuth, makeT3LocalAuth());
|
|
259
|
-
|
|
260
|
-
function insertAuthSession(input: InsertAuthSessionInput) {
|
|
261
|
-
return Effect.gen(function* () {
|
|
262
|
-
const sql = yield* SqlClient.SqlClient;
|
|
263
|
-
yield* sql`PRAGMA busy_timeout = 5000;`;
|
|
264
|
-
yield* sql`PRAGMA foreign_keys = ON;`;
|
|
265
|
-
const columns = yield* sql<{ readonly name: string }>`PRAGMA table_info(auth_sessions)`;
|
|
266
|
-
if (!columns.some((column) => column.name === "scopes")) {
|
|
267
|
-
return yield* Effect.fail(
|
|
268
|
-
new AuthLocalDatabaseError({
|
|
269
|
-
operation: "schema",
|
|
270
|
-
message: "local auth database is missing scoped auth_sessions schema",
|
|
271
|
-
}),
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
yield* sql`
|
|
275
|
-
INSERT INTO auth_sessions (
|
|
276
|
-
session_id,
|
|
277
|
-
subject,
|
|
278
|
-
scopes,
|
|
279
|
-
method,
|
|
280
|
-
client_label,
|
|
281
|
-
client_ip_address,
|
|
282
|
-
client_user_agent,
|
|
283
|
-
client_device_type,
|
|
284
|
-
client_os,
|
|
285
|
-
client_browser,
|
|
286
|
-
issued_at,
|
|
287
|
-
expires_at,
|
|
288
|
-
revoked_at
|
|
289
|
-
)
|
|
290
|
-
VALUES (
|
|
291
|
-
${input.sessionId},
|
|
292
|
-
${input.subject},
|
|
293
|
-
${JSON.stringify(input.scopes)},
|
|
294
|
-
${"bearer-access-token"},
|
|
295
|
-
${input.label},
|
|
296
|
-
NULL,
|
|
297
|
-
NULL,
|
|
298
|
-
${"bot"},
|
|
299
|
-
NULL,
|
|
300
|
-
NULL,
|
|
301
|
-
${input.issuedAt},
|
|
302
|
-
${input.expiresAt},
|
|
303
|
-
NULL
|
|
304
|
-
)
|
|
305
|
-
`;
|
|
306
|
-
return undefined;
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function normalizeLocalOrigin(origin: string) {
|
|
311
|
-
return normalizeHttpBaseUrl(origin).pipe(
|
|
312
|
-
Effect.mapError((error) => new AuthLocalError({ message: error.message, cause: error })),
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
type LocalSessionClaims = {
|
|
317
|
-
readonly v: 1;
|
|
318
|
-
readonly kind: "session";
|
|
319
|
-
readonly sid: AuthSessionId;
|
|
320
|
-
readonly sub: string;
|
|
321
|
-
readonly scopes: ReadonlyArray<AuthEnvironmentScope>;
|
|
322
|
-
readonly method: "bearer-access-token";
|
|
323
|
-
readonly iat: number;
|
|
324
|
-
readonly exp: number;
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
type LocalDatabaseSessionInput = {
|
|
328
|
-
readonly dbPath: string;
|
|
329
|
-
readonly secretsDir: string;
|
|
330
|
-
readonly role: LocalAuthInput["role"];
|
|
331
|
-
readonly label: string;
|
|
332
|
-
readonly subject: string;
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
type InsertAuthSessionInput = {
|
|
336
|
-
readonly sessionId: AuthSessionId;
|
|
337
|
-
readonly subject: string;
|
|
338
|
-
readonly scopes: ReadonlyArray<AuthEnvironmentScope>;
|
|
339
|
-
readonly label: string;
|
|
340
|
-
readonly issuedAt: string;
|
|
341
|
-
readonly expiresAt: string;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const defaultSessionTtlMs = 30 * 24 * 60 * 60 * 1000;
|
|
345
|
-
const signingSecretName = "server-signing-key";
|
|
346
|
-
const sha256BlockSize = 64;
|
|
347
|
-
|
|
348
|
-
function concatBytes(first: Uint8Array, second: Uint8Array) {
|
|
349
|
-
const bytes = new Uint8Array(first.byteLength + second.byteLength);
|
|
350
|
-
bytes.set(first);
|
|
351
|
-
bytes.set(second, first.byteLength);
|
|
352
|
-
return bytes;
|
|
353
|
-
}
|
package/src/auth/transport.ts
CHANGED
|
@@ -9,7 +9,6 @@ import * as Effect from "effect/Effect";
|
|
|
9
9
|
import * as Layer from "effect/Layer";
|
|
10
10
|
import { HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http";
|
|
11
11
|
|
|
12
|
-
import type { ResolvedConfig } from "../config/service.ts";
|
|
13
12
|
import { toHttpEndpointUrl } from "../config/url.ts";
|
|
14
13
|
import { AuthTransportError } from "./error.ts";
|
|
15
14
|
import {
|
|
@@ -21,6 +20,11 @@ import {
|
|
|
21
20
|
decodeAuthWebSocketTicketResult,
|
|
22
21
|
} from "./schema.ts";
|
|
23
22
|
|
|
23
|
+
export type AuthTransportConnection = {
|
|
24
|
+
readonly url: string;
|
|
25
|
+
readonly token: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
24
28
|
export class T3AuthTransport extends Context.Service<
|
|
25
29
|
T3AuthTransport,
|
|
26
30
|
{
|
|
@@ -29,10 +33,10 @@ export class T3AuthTransport extends Context.Service<
|
|
|
29
33
|
readonly credential: string;
|
|
30
34
|
}) => Effect.Effect<AuthBearerBootstrapResult, AuthTransportError>;
|
|
31
35
|
readonly getSession: (
|
|
32
|
-
|
|
36
|
+
connection: AuthTransportConnection,
|
|
33
37
|
) => Effect.Effect<AuthSessionState, AuthTransportError>;
|
|
34
38
|
readonly issueWebSocketTicket: (
|
|
35
|
-
|
|
39
|
+
connection: AuthTransportConnection,
|
|
36
40
|
) => Effect.Effect<AuthWebSocketTicketResult, AuthTransportError>;
|
|
37
41
|
}
|
|
38
42
|
>()("t3cli/T3AuthTransport") {}
|
|
@@ -91,9 +95,11 @@ const makeT3AuthTransport = Effect.fn("makeT3AuthTransport")(function* () {
|
|
|
91
95
|
} satisfies AuthBearerBootstrapResult;
|
|
92
96
|
});
|
|
93
97
|
|
|
94
|
-
const getSession = Effect.fn("AuthTransport.getSession")(function* (
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
const getSession = Effect.fn("AuthTransport.getSession")(function* (
|
|
99
|
+
connection: AuthTransportConnection,
|
|
100
|
+
) {
|
|
101
|
+
const url = yield* makeHttpEndpointUrl(connection.url, "/api/auth/session");
|
|
102
|
+
const request = HttpClientRequest.get(url).pipe(authenticatedRequest(connection));
|
|
97
103
|
const response = yield* client.execute(request).pipe(
|
|
98
104
|
Effect.catchTags({
|
|
99
105
|
HttpClientError: (error) =>
|
|
@@ -124,10 +130,10 @@ const makeT3AuthTransport = Effect.fn("makeT3AuthTransport")(function* () {
|
|
|
124
130
|
});
|
|
125
131
|
|
|
126
132
|
const issueWebSocketTicket = Effect.fn("AuthTransport.issueWebSocketTicket")(function* (
|
|
127
|
-
|
|
133
|
+
connection: AuthTransportConnection,
|
|
128
134
|
) {
|
|
129
|
-
const url = yield* makeHttpEndpointUrl(
|
|
130
|
-
const request = HttpClientRequest.post(url).pipe(authenticatedRequest(
|
|
135
|
+
const url = yield* makeHttpEndpointUrl(connection.url, "/api/auth/websocket-ticket");
|
|
136
|
+
const request = HttpClientRequest.post(url).pipe(authenticatedRequest(connection));
|
|
131
137
|
const response = yield* client.execute(request).pipe(
|
|
132
138
|
Effect.catchTags({
|
|
133
139
|
HttpClientError: (error) =>
|
|
@@ -167,8 +173,8 @@ const makeT3AuthTransport = Effect.fn("makeT3AuthTransport")(function* () {
|
|
|
167
173
|
export const T3AuthTransportLive = Layer.effect(T3AuthTransport, makeT3AuthTransport());
|
|
168
174
|
|
|
169
175
|
const authenticatedRequest =
|
|
170
|
-
(
|
|
171
|
-
request.pipe(HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(
|
|
176
|
+
(connection: AuthTransportConnection) => (request: HttpClientRequest.HttpClientRequest) =>
|
|
177
|
+
request.pipe(HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(connection.token));
|
|
172
178
|
|
|
173
179
|
function makeHttpEndpointUrl(baseUrl: string, path: string) {
|
|
174
180
|
return toHttpEndpointUrl(baseUrl, path).pipe(
|
package/src/auth/type.ts
CHANGED
|
@@ -27,6 +27,21 @@ export type LocalAuthInput = {
|
|
|
27
27
|
readonly subject: string;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
export type LocalAuthTokenInput = Omit<LocalAuthInput, "origin">;
|
|
31
|
+
|
|
32
|
+
export type LocalAuthOriginInput = {
|
|
33
|
+
readonly baseDir?: string;
|
|
34
|
+
readonly origin?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type LocalAuthTokenResult = {
|
|
38
|
+
readonly token: string;
|
|
39
|
+
readonly role: AuthSessionRole;
|
|
40
|
+
readonly expiresAt: string;
|
|
41
|
+
readonly source: "local";
|
|
42
|
+
readonly baseDir: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
30
45
|
export type LocalAuthResult = {
|
|
31
46
|
readonly url: string;
|
|
32
47
|
readonly token: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
|
|
3
|
+
import { T3AuthTransportLive } from "../auth/transport.ts";
|
|
4
|
+
import { T3RpcLive } from "../rpc/layer.ts";
|
|
5
|
+
|
|
6
|
+
export function makeT3CodeRpcLayer() {
|
|
7
|
+
return T3RpcLive.pipe(Layer.provide(T3AuthTransportLive));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const T3CodeRpcLayer = makeT3CodeRpcLayer();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
|
|
3
|
+
import * as NodeSocket from "@effect/platform-node/NodeSocket";
|
|
4
|
+
|
|
5
|
+
import { T3CodeRpcLayer } from "./layer.ts";
|
|
6
|
+
|
|
7
|
+
const NodeConnectionPlatformLayer = Layer.mergeAll(
|
|
8
|
+
NodeHttpClient.layerUndici,
|
|
9
|
+
NodeSocket.layerWebSocketConstructor,
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const T3CodeNodeRpcLayer = T3CodeRpcLayer.pipe(Layer.provide(NodeConnectionPlatformLayer));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
|
|
5
|
+
import type { T3CodeConnectionError } from "./error.ts";
|
|
6
|
+
import type { T3CodeConnection } from "./type.ts";
|
|
7
|
+
|
|
8
|
+
export class T3CodeConnectionProvider extends Context.Service<
|
|
9
|
+
T3CodeConnectionProvider,
|
|
10
|
+
{
|
|
11
|
+
readonly get: Effect.Effect<T3CodeConnection, T3CodeConnectionError>;
|
|
12
|
+
}
|
|
13
|
+
>()("t3cli/T3CodeConnectionProvider") {}
|
|
14
|
+
|
|
15
|
+
export function makeT3CodeConnectionProvider(
|
|
16
|
+
get: Effect.Effect<T3CodeConnection, T3CodeConnectionError>,
|
|
17
|
+
): T3CodeConnectionProvider["Service"] {
|
|
18
|
+
return { get };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function T3CodeConnectionProviderLive(connection: T3CodeConnection) {
|
|
22
|
+
return Layer.succeed(
|
|
23
|
+
T3CodeConnectionProvider,
|
|
24
|
+
makeT3CodeConnectionProvider(Effect.succeed(connection)),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { T3CodeConnectionError } from "./connection/error.ts";
|
|
2
|
+
export { T3CodeRpcLayer, makeT3CodeRpcLayer } from "./connection/layer.ts";
|
|
3
|
+
export { T3CodeNodeRpcLayer } from "./connection/node.ts";
|
|
4
|
+
export {
|
|
5
|
+
T3CodeConnectionProvider,
|
|
6
|
+
T3CodeConnectionProviderLive,
|
|
7
|
+
makeT3CodeConnectionProvider,
|
|
8
|
+
} from "./connection/service.ts";
|
|
9
|
+
export type { T3CodeAuth, T3CodeConnection, T3CodeOrigin } from "./connection/type.ts";
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,16 @@ export type { ApplicationError } from "./application/error.ts";
|
|
|
9
9
|
export { T3Auth } from "./auth/service.ts";
|
|
10
10
|
export { T3AuthLive, makeT3Auth } from "./auth/layer.ts";
|
|
11
11
|
export { T3LocalAuth, T3LocalAuthLive, makeT3LocalAuth } from "./auth/local.ts";
|
|
12
|
+
export {
|
|
13
|
+
T3LocalAuthOrigin,
|
|
14
|
+
T3LocalAuthOriginLive,
|
|
15
|
+
makeT3LocalAuthOrigin,
|
|
16
|
+
} from "./auth/local-origin.ts";
|
|
17
|
+
export {
|
|
18
|
+
T3LocalAuthToken,
|
|
19
|
+
T3LocalAuthTokenLive,
|
|
20
|
+
makeT3LocalAuthToken,
|
|
21
|
+
} from "./auth/local-token.ts";
|
|
12
22
|
export {
|
|
13
23
|
T3AuthPairing,
|
|
14
24
|
T3AuthPairingLive,
|
|
@@ -30,7 +40,10 @@ export type {
|
|
|
30
40
|
AuthConfigInput,
|
|
31
41
|
AuthSessionRole,
|
|
32
42
|
LocalAuthInput,
|
|
43
|
+
LocalAuthOriginInput,
|
|
33
44
|
LocalAuthResult,
|
|
45
|
+
LocalAuthTokenInput,
|
|
46
|
+
LocalAuthTokenResult,
|
|
34
47
|
PairingUrl,
|
|
35
48
|
PairResult,
|
|
36
49
|
} from "./auth/type.ts";
|
|
@@ -39,6 +52,16 @@ export type { ResolvedConfig, StoredConfig } from "./config/service.ts";
|
|
|
39
52
|
export { T3ConfigLive, makeT3Config } from "./config/layer.ts";
|
|
40
53
|
export { ConfigError, UrlError } from "./config/error.ts";
|
|
41
54
|
export type { ConfigServiceError } from "./config/error.ts";
|
|
55
|
+
export {
|
|
56
|
+
makeT3CodeConnectionProvider,
|
|
57
|
+
makeT3CodeRpcLayer,
|
|
58
|
+
T3CodeConnectionProvider,
|
|
59
|
+
T3CodeConnectionProviderLive,
|
|
60
|
+
T3CodeNodeRpcLayer,
|
|
61
|
+
T3CodeRpcLayer,
|
|
62
|
+
T3CodeConnectionError,
|
|
63
|
+
} from "./connection.ts";
|
|
64
|
+
export type { T3CodeAuth, T3CodeConnection, T3CodeOrigin } from "./connection.ts";
|
|
42
65
|
export { Environment } from "./environment/service.ts";
|
|
43
66
|
export type { EnvironmentShape } from "./environment/service.ts";
|
|
44
67
|
export { NodeEnvironmentLive } from "./environment/layer.ts";
|
|
@@ -49,6 +72,8 @@ export {
|
|
|
49
72
|
T3AuthPairingLayer,
|
|
50
73
|
T3AuthTransportLayer,
|
|
51
74
|
T3LocalAuthLayer,
|
|
75
|
+
T3LocalAuthOriginLayer,
|
|
76
|
+
T3LocalAuthTokenLayer,
|
|
52
77
|
} from "./runtime.ts";
|
|
53
78
|
export { SqlClientFactory } from "./sql/service.ts";
|
|
54
79
|
export type { SqlClientFactoryShape, SqliteClientConfig } from "./sql/service.ts";
|
package/src/rpc/error.ts
CHANGED
|
@@ -9,13 +9,9 @@ import {
|
|
|
9
9
|
import { HttpClientError } from "effect/unstable/http";
|
|
10
10
|
import { RpcClientError } from "effect/unstable/rpc";
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
AuthPairingUrlError,
|
|
16
|
-
AuthTransportError,
|
|
17
|
-
} from "../auth/error.ts";
|
|
18
|
-
import { ConfigError, UrlError } from "../config/error.ts";
|
|
12
|
+
import { AuthTransportError } from "../auth/error.ts";
|
|
13
|
+
import { T3CodeConnectionError } from "../connection/error.ts";
|
|
14
|
+
import { UrlError } from "../config/error.ts";
|
|
19
15
|
|
|
20
16
|
const RpcErrorCauseSchema = Schema.Union([
|
|
21
17
|
RpcClientError.RpcClientError,
|
|
@@ -24,11 +20,8 @@ const RpcErrorCauseSchema = Schema.Union([
|
|
|
24
20
|
OrchestrationDispatchCommandError,
|
|
25
21
|
OrchestrationGetSnapshotError,
|
|
26
22
|
ServerSettingsError,
|
|
27
|
-
AuthConfigError,
|
|
28
|
-
AuthLocalError,
|
|
29
|
-
AuthPairingUrlError,
|
|
30
23
|
AuthTransportError,
|
|
31
|
-
|
|
24
|
+
T3CodeConnectionError,
|
|
32
25
|
HttpClientError.HttpClientErrorSchema,
|
|
33
26
|
UrlError,
|
|
34
27
|
Schema.instanceOf(Schema.SchemaError),
|