| author | |
| committer | |
| log | 3ea6adf17d5528d9c90ed53b6dead650d5931941 |
| tree | 6cd08b21cc8f01f4f99f4606ff4a330ca0eef7f5 |
| parent | a56496c7f20d53369ef87101681ee2a99b3a4337 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>4 files changed, 188 insertions(+), 25 deletions(-)
infra/relay/src/environments/EnvironmentConnector.test.ts+47-3| ... | @@ -22,6 +22,7 @@ import * as Redacted from "effect/Redacted"; | ... | @@ -22,6 +22,7 @@ import * as Redacted from "effect/Redacted"; |
| 22 | import * as Result from "effect/Result"; | 22 | import * as Result from "effect/Result"; |
| 23 | import * as Schema from "effect/Schema"; | 23 | import * as Schema from "effect/Schema"; |
| 24 | import * as TestClock from "effect/testing/TestClock"; | 24 | import * as TestClock from "effect/testing/TestClock"; |
| 25 | import * as Tracer from "effect/Tracer"; | ||
| 25 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; | 26 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; |
| 26 | 27 | ||
| 27 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; | 28 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; |
| ... | @@ -50,6 +51,9 @@ const decodeHealthRequestBody = Schema.decodeUnknownSync( | ... | @@ -50,6 +51,9 @@ const decodeHealthRequestBody = Schema.decodeUnknownSync( |
| 50 | const decodeMintRequestBody = Schema.decodeUnknownSync( | 51 | const decodeMintRequestBody = Schema.decodeUnknownSync( |
| 51 | Schema.fromJsonString(RelayCloudMintCredentialRequest), | 52 | Schema.fromJsonString(RelayCloudMintCredentialRequest), |
| 52 | ); | 53 | ); |
| 54 | const isEnvironmentConnectNotAuthorized = Schema.is( | ||
| 55 | EnvironmentConnector.EnvironmentConnectNotAuthorized, | ||
| 56 | ); | ||
| 53 | 57 | ||
| 54 | function requestBodyText(request: HttpClientRequest.HttpClientRequest): string { | 58 | function requestBodyText(request: HttpClientRequest.HttpClientRequest): string { |
| 55 | return request.body._tag === "Uint8Array" ? new TextDecoder().decode(request.body.body) : "{}"; | 59 | return request.body._tag === "Uint8Array" ? new TextDecoder().decode(request.body.body) : "{}"; |
| ... | @@ -280,7 +284,13 @@ describe("EnvironmentConnector", () => { | ... | @@ -280,7 +284,13 @@ describe("EnvironmentConnector", () => { |
| 280 | 284 | ||
| 281 | expect(Result.isFailure(result)).toBe(true); | 285 | expect(Result.isFailure(result)).toBe(true); |
| 282 | if (Result.isFailure(result)) { | 286 | if (Result.isFailure(result)) { |
| 283 | expect(result.failure).toBeInstanceOf(EnvironmentConnector.EnvironmentConnectNotAuthorized); | 287 | expect(isEnvironmentConnectNotAuthorized(result.failure)).toBe(true); |
| 288 | if (isEnvironmentConnectNotAuthorized(result.failure)) { | ||
| 289 | expect(result.failure).toMatchObject({ | ||
| 290 | operation: "status", | ||
| 291 | reason: "endpoint_provider_not_managed", | ||
| 292 | }); | ||
| 293 | } | ||
| 284 | } | 294 | } |
| 285 | expect(requestCount).toBe(0); | 295 | expect(requestCount).toBe(0); |
| 286 | }).pipe( | 296 | }).pipe( |
| ... | @@ -300,6 +310,14 @@ describe("EnvironmentConnector", () => { | ... | @@ -300,6 +310,14 @@ describe("EnvironmentConnector", () => { |
| 300 | 310 | ||
| 301 | it.effect("rejects stale managed endpoints before sending a mint request", () => { | 311 | it.effect("rejects stale managed endpoints before sending a mint request", () => { |
| 302 | let requestCount = 0; | 312 | let requestCount = 0; |
| 313 | const spans: Array<Tracer.NativeSpan> = []; | ||
| 314 | const tracer = Tracer.make({ | ||
| 315 | span: (options) => { | ||
| 316 | const span = new Tracer.NativeSpan(options); | ||
| 317 | spans.push(span); | ||
| 318 | return span; | ||
| 319 | }, | ||
| 320 | }); | ||
| 303 | const execute = () => | 321 | const execute = () => |
| 304 | Effect.sync(() => { | 322 | Effect.sync(() => { |
| 305 | requestCount += 1; | 323 | requestCount += 1; |
| ... | @@ -318,8 +336,27 @@ describe("EnvironmentConnector", () => { | ... | @@ -318,8 +336,27 @@ describe("EnvironmentConnector", () => { |
| 318 | 336 | ||
| 319 | expect(Result.isFailure(result)).toBe(true); | 337 | expect(Result.isFailure(result)).toBe(true); |
| 320 | if (Result.isFailure(result)) { | 338 | if (Result.isFailure(result)) { |
| 321 | expect(result.failure).toBeInstanceOf(EnvironmentConnector.EnvironmentConnectNotAuthorized); | 339 | expect(isEnvironmentConnectNotAuthorized(result.failure)).toBe(true); |
| 340 | if (isEnvironmentConnectNotAuthorized(result.failure)) { | ||
| 341 | expect(result.failure).toMatchObject({ | ||
| 342 | operation: "connect", | ||
| 343 | reason: "managed_endpoint_mismatch", | ||
| 344 | }); | ||
| 345 | } | ||
| 322 | } | 346 | } |
| 347 | const resolutionSpan = spans.find( | ||
| 348 | (span) => span.name === "relay.environment_connector.resolve_managed_endpoint", | ||
| 349 | ); | ||
| 350 | expect(Object.fromEntries(resolutionSpan?.attributes ?? [])).toMatchObject({ | ||
| 351 | "relay.authorization.allocation_hostname": "env.example.test", | ||
| 352 | "relay.authorization.allocation_has_ready_at": true, | ||
| 353 | "relay.authorization.allocation_has_tunnel_id": true, | ||
| 354 | "relay.authorization.allocation_has_dns_record_id": true, | ||
| 355 | "relay.authorization.linked_http_base_url": "https://attacker.example.test/", | ||
| 356 | "relay.authorization.linked_ws_base_url": "wss://attacker.example.test/ws", | ||
| 357 | "relay.authorization.resolved_http_base_url": "https://env.example.test/", | ||
| 358 | "relay.authorization.resolved_ws_base_url": "wss://env.example.test/ws", | ||
| 359 | }); | ||
| 323 | expect(requestCount).toBe(0); | 360 | expect(requestCount).toBe(0); |
| 324 | }).pipe( | 361 | }).pipe( |
| 325 | Effect.provide( | 362 | Effect.provide( |
| ... | @@ -333,6 +370,7 @@ describe("EnvironmentConnector", () => { | ... | @@ -333,6 +370,7 @@ describe("EnvironmentConnector", () => { |
| 333 | }), | 370 | }), |
| 334 | }), | 371 | }), |
| 335 | ), | 372 | ), |
| 373 | Effect.provideService(Tracer.Tracer, tracer), | ||
| 336 | ); | 374 | ); |
| 337 | }); | 375 | }); |
| 338 | 376 | ||
| ... | @@ -355,7 +393,13 @@ describe("EnvironmentConnector", () => { | ... | @@ -355,7 +393,13 @@ describe("EnvironmentConnector", () => { |
| 355 | 393 | ||
| 356 | expect(Result.isFailure(result)).toBe(true); | 394 | expect(Result.isFailure(result)).toBe(true); |
| 357 | if (Result.isFailure(result)) { | 395 | if (Result.isFailure(result)) { |
| 358 | expect(result.failure).toBeInstanceOf(EnvironmentConnector.EnvironmentConnectNotAuthorized); | 396 | expect(isEnvironmentConnectNotAuthorized(result.failure)).toBe(true); |
| 397 | if (isEnvironmentConnectNotAuthorized(result.failure)) { | ||
| 398 | expect(result.failure).toMatchObject({ | ||
| 399 | operation: "status", | ||
| 400 | reason: "managed_endpoint_allocation_not_ready", | ||
| 401 | }); | ||
| 402 | } | ||
| 359 | } | 403 | } |
| 360 | expect(requestCount).toBe(0); | 404 | expect(requestCount).toBe(0); |
| 361 | }).pipe( | 405 | }).pipe( |
infra/relay/src/environments/EnvironmentConnector.ts+132-12| ... | @@ -40,15 +40,54 @@ import { FetchHttpClient, HttpClient } from "effect/unstable/http"; | ... | @@ -40,15 +40,54 @@ import { FetchHttpClient, HttpClient } from "effect/unstable/http"; |
| 40 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; | 40 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; |
| 41 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; | 41 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; |
| 42 | import * as RelayConfiguration from "../Config.ts"; | 42 | import * as RelayConfiguration from "../Config.ts"; |
| 43 | import { isManagedEndpointHostname } from "../deploymentConfig.ts"; | ||
| 44 | |||
| 45 | export const EnvironmentConnectNotAuthorizedReason = Schema.Literals([ | ||
| 46 | "client_proof_key_thumbprint_missing", | ||
| 47 | "environment_link_not_found", | ||
| 48 | "endpoint_provider_not_managed", | ||
| 49 | "managed_endpoint_allocation_not_found", | ||
| 50 | "managed_endpoint_base_domain_not_configured", | ||
| 51 | "managed_endpoint_allocation_not_ready", | ||
| 52 | "managed_endpoint_hostname_invalid", | ||
| 53 | "managed_endpoint_mismatch", | ||
| 54 | ]); | ||
| 55 | export type EnvironmentConnectNotAuthorizedReason = | ||
| 56 | typeof EnvironmentConnectNotAuthorizedReason.Type; | ||
| 57 | |||
| 58 | function environmentConnectNotAuthorizedReasonMessage( | ||
| 59 | reason: EnvironmentConnectNotAuthorizedReason, | ||
| 60 | ): string { | ||
| 61 | switch (reason) { | ||
| 62 | case "client_proof_key_thumbprint_missing": | ||
| 63 | return "the client proof key thumbprint is missing"; | ||
| 64 | case "environment_link_not_found": | ||
| 65 | return "no active environment link was found"; | ||
| 66 | case "endpoint_provider_not_managed": | ||
| 67 | return "the linked endpoint is not relay-managed"; | ||
| 68 | case "managed_endpoint_allocation_not_found": | ||
| 69 | return "no managed endpoint allocation was found"; | ||
| 70 | case "managed_endpoint_base_domain_not_configured": | ||
| 71 | return "the managed endpoint base domain is not configured"; | ||
| 72 | case "managed_endpoint_allocation_not_ready": | ||
| 73 | return "the managed endpoint allocation is incomplete"; | ||
| 74 | case "managed_endpoint_hostname_invalid": | ||
| 75 | return "the managed endpoint hostname is invalid"; | ||
| 76 | case "managed_endpoint_mismatch": | ||
| 77 | return "the linked endpoint does not match its managed allocation"; | ||
| 78 | } | ||
| 79 | } | ||
| 43 | 80 | ||
| 44 | export class EnvironmentConnectNotAuthorized extends Schema.TaggedErrorClass<EnvironmentConnectNotAuthorized>()( | 81 | export class EnvironmentConnectNotAuthorized extends Schema.TaggedErrorClass<EnvironmentConnectNotAuthorized>()( |
| 45 | "EnvironmentConnectNotAuthorized", | 82 | "EnvironmentConnectNotAuthorized", |
| 46 | { | 83 | { |
| 47 | environmentId: Schema.String, | 84 | environmentId: Schema.String, |
| 85 | operation: Schema.Literals(["connect", "status"]), | ||
| 86 | reason: EnvironmentConnectNotAuthorizedReason, | ||
| 48 | }, | 87 | }, |
| 49 | ) { | 88 | ) { |
| 50 | override get message(): string { | 89 | override get message(): string { |
| 51 | return `Environment '${this.environmentId}' is not authorized to connect`; | 90 | return `Environment '${this.environmentId}' is not authorized for ${this.operation}: ${environmentConnectNotAuthorizedReasonMessage(this.reason)}`; |
| 52 | } | 91 | } |
| 53 | } | 92 | } |
| 54 | 93 | ||
| ... | @@ -257,30 +296,91 @@ const make = Effect.gen(function* () { | ... | @@ -257,30 +296,91 @@ const make = Effect.gen(function* () { |
| 257 | const resolveManagedEndpoint = Effect.fn("relay.environment_connector.resolve_managed_endpoint")( | 296 | const resolveManagedEndpoint = Effect.fn("relay.environment_connector.resolve_managed_endpoint")( |
| 258 | function* (input: { | 297 | function* (input: { |
| 259 | readonly userId: string; | 298 | readonly userId: string; |
| 299 | readonly operation: "connect" | "status"; | ||
| 260 | readonly link: EnvironmentLinks.RelayLinkedEnvironmentRecord; | 300 | readonly link: EnvironmentLinks.RelayLinkedEnvironmentRecord; |
| 261 | }) { | 301 | }) { |
| 262 | if (input.link.endpoint.providerKind !== "cloudflare_tunnel") { | 302 | if (input.link.endpoint.providerKind !== "cloudflare_tunnel") { |
| 303 | yield* Effect.annotateCurrentSpan({ | ||
| 304 | "relay.authorization.endpoint_provider_kind": input.link.endpoint.providerKind, | ||
| 305 | }); | ||
| 263 | return yield* new EnvironmentConnectNotAuthorized({ | 306 | return yield* new EnvironmentConnectNotAuthorized({ |
| 264 | environmentId: input.link.environmentId, | 307 | environmentId: input.link.environmentId, |
| 308 | operation: input.operation, | ||
| 309 | reason: "endpoint_provider_not_managed", | ||
| 265 | }); | 310 | }); |
| 266 | } | 311 | } |
| 267 | const allocation = yield* allocations.get({ | 312 | const allocation = yield* allocations.get({ |
| 268 | userId: input.userId, | 313 | userId: input.userId, |
| 269 | environmentId: input.link.environmentId, | 314 | environmentId: input.link.environmentId, |
| 270 | }); | 315 | }); |
| 271 | const endpoint = allocation | 316 | if (!allocation) { |
| 272 | ? ManagedEndpointAllocations.resolveReadyManagedEndpoint({ | 317 | return yield* new EnvironmentConnectNotAuthorized({ |
| 273 | allocation, | 318 | environmentId: input.link.environmentId, |
| 274 | baseDomain: settings.managedEndpointBaseDomain, | 319 | operation: input.operation, |
| 275 | }) | 320 | reason: "managed_endpoint_allocation_not_found", |
| 276 | : null; | 321 | }); |
| 322 | } | ||
| 323 | const allocationAttributes = { | ||
| 324 | "relay.authorization.allocation_hostname": allocation.hostname, | ||
| 325 | "relay.authorization.allocation_has_ready_at": allocation.readyAt !== null, | ||
| 326 | "relay.authorization.allocation_has_tunnel_id": allocation.tunnelId !== null, | ||
| 327 | "relay.authorization.allocation_has_dns_record_id": allocation.dnsRecordId !== null, | ||
| 328 | } as const; | ||
| 329 | if (!settings.managedEndpointBaseDomain) { | ||
| 330 | yield* Effect.annotateCurrentSpan(allocationAttributes); | ||
| 331 | return yield* new EnvironmentConnectNotAuthorized({ | ||
| 332 | environmentId: input.link.environmentId, | ||
| 333 | operation: input.operation, | ||
| 334 | reason: "managed_endpoint_base_domain_not_configured", | ||
| 335 | }); | ||
| 336 | } | ||
| 337 | if ( | ||
| 338 | allocation.readyAt === null || | ||
| 339 | allocation.tunnelId === null || | ||
| 340 | allocation.dnsRecordId === null | ||
| 341 | ) { | ||
| 342 | yield* Effect.annotateCurrentSpan(allocationAttributes); | ||
| 343 | return yield* new EnvironmentConnectNotAuthorized({ | ||
| 344 | environmentId: input.link.environmentId, | ||
| 345 | operation: input.operation, | ||
| 346 | reason: "managed_endpoint_allocation_not_ready", | ||
| 347 | }); | ||
| 348 | } | ||
| 349 | if (!isManagedEndpointHostname(allocation.hostname, settings.managedEndpointBaseDomain)) { | ||
| 350 | yield* Effect.annotateCurrentSpan({ | ||
| 351 | ...allocationAttributes, | ||
| 352 | "relay.authorization.managed_endpoint_base_domain": settings.managedEndpointBaseDomain, | ||
| 353 | }); | ||
| 354 | return yield* new EnvironmentConnectNotAuthorized({ | ||
| 355 | environmentId: input.link.environmentId, | ||
| 356 | operation: input.operation, | ||
| 357 | reason: "managed_endpoint_hostname_invalid", | ||
| 358 | }); | ||
| 359 | } | ||
| 360 | const endpoint = ManagedEndpointAllocations.resolveReadyManagedEndpoint({ | ||
| 361 | allocation, | ||
| 362 | baseDomain: settings.managedEndpointBaseDomain, | ||
| 363 | }); | ||
| 277 | if ( | 364 | if ( |
| 278 | endpoint === null || | 365 | endpoint === null || |
| 279 | endpoint.httpBaseUrl !== input.link.endpoint.httpBaseUrl || | 366 | endpoint.httpBaseUrl !== input.link.endpoint.httpBaseUrl || |
| 280 | endpoint.wsBaseUrl !== input.link.endpoint.wsBaseUrl | 367 | endpoint.wsBaseUrl !== input.link.endpoint.wsBaseUrl |
| 281 | ) { | 368 | ) { |
| 369 | yield* Effect.annotateCurrentSpan({ | ||
| 370 | ...allocationAttributes, | ||
| 371 | "relay.authorization.linked_http_base_url": input.link.endpoint.httpBaseUrl, | ||
| 372 | "relay.authorization.linked_ws_base_url": input.link.endpoint.wsBaseUrl, | ||
| 373 | ...(endpoint | ||
| 374 | ? { | ||
| 375 | "relay.authorization.resolved_http_base_url": endpoint.httpBaseUrl, | ||
| 376 | "relay.authorization.resolved_ws_base_url": endpoint.wsBaseUrl, | ||
| 377 | } | ||
| 378 | : {}), | ||
| 379 | }); | ||
| 282 | return yield* new EnvironmentConnectNotAuthorized({ | 380 | return yield* new EnvironmentConnectNotAuthorized({ |
| 283 | environmentId: input.link.environmentId, | 381 | environmentId: input.link.environmentId, |
| 382 | operation: input.operation, | ||
| 383 | reason: "managed_endpoint_mismatch", | ||
| 284 | }); | 384 | }); |
| 285 | } | 385 | } |
| 286 | return endpoint; | 386 | return endpoint; |
| ... | @@ -295,9 +395,17 @@ const make = Effect.gen(function* () { | ... | @@ -295,9 +395,17 @@ const make = Effect.gen(function* () { |
| 295 | }); | 395 | }); |
| 296 | const link = yield* links.getForUser(input); | 396 | const link = yield* links.getForUser(input); |
| 297 | if (!link) { | 397 | if (!link) { |
| 298 | return yield* new EnvironmentConnectNotAuthorized({ environmentId: input.environmentId }); | 398 | return yield* new EnvironmentConnectNotAuthorized({ |
| 399 | environmentId: input.environmentId, | ||
| 400 | operation: "status", | ||
| 401 | reason: "environment_link_not_found", | ||
| 402 | }); | ||
| 299 | } | 403 | } |
| 300 | const endpoint = yield* resolveManagedEndpoint({ userId: input.userId, link }); | 404 | const endpoint = yield* resolveManagedEndpoint({ |
| 405 | userId: input.userId, | ||
| 406 | operation: "status", | ||
| 407 | link, | ||
| 408 | }); | ||
| 301 | const now = yield* DateTime.now; | 409 | const now = yield* DateTime.now; |
| 302 | const expiresAt = DateTime.add(now, { minutes: 2 }); | 410 | const expiresAt = DateTime.add(now, { minutes: 2 }); |
| 303 | const nonce = yield* crypto.randomUUIDv4.pipe( | 411 | const nonce = yield* crypto.randomUUIDv4.pipe( |
| ... | @@ -404,13 +512,25 @@ const make = Effect.gen(function* () { | ... | @@ -404,13 +512,25 @@ const make = Effect.gen(function* () { |
| 404 | ...(input.deviceId ? { "relay.mobile.device_id": input.deviceId } : {}), | 512 | ...(input.deviceId ? { "relay.mobile.device_id": input.deviceId } : {}), |
| 405 | }); | 513 | }); |
| 406 | if (input.clientProofKeyThumbprint.trim().length === 0) { | 514 | if (input.clientProofKeyThumbprint.trim().length === 0) { |
| 407 | return yield* new EnvironmentConnectNotAuthorized({ environmentId: input.environmentId }); | 515 | return yield* new EnvironmentConnectNotAuthorized({ |
| 516 | environmentId: input.environmentId, | ||
| 517 | operation: "connect", | ||
| 518 | reason: "client_proof_key_thumbprint_missing", | ||
| 519 | }); | ||
| 408 | } | 520 | } |
| 409 | const link = yield* links.getForUser(input); | 521 | const link = yield* links.getForUser(input); |
| 410 | if (!link) { | 522 | if (!link) { |
| 411 | return yield* new EnvironmentConnectNotAuthorized({ environmentId: input.environmentId }); | 523 | return yield* new EnvironmentConnectNotAuthorized({ |
| 524 | environmentId: input.environmentId, | ||
| 525 | operation: "connect", | ||
| 526 | reason: "environment_link_not_found", | ||
| 527 | }); | ||
| 412 | } | 528 | } |
| 413 | const endpoint = yield* resolveManagedEndpoint({ userId: input.userId, link }); | 529 | const endpoint = yield* resolveManagedEndpoint({ |
| 530 | userId: input.userId, | ||
| 531 | operation: "connect", | ||
| 532 | link, | ||
| 533 | }); | ||
| 414 | const now = yield* DateTime.now; | 534 | const now = yield* DateTime.now; |
| 415 | const expiresAt = DateTime.add(now, { minutes: 2 }); | 535 | const expiresAt = DateTime.add(now, { minutes: 2 }); |
| 416 | const nonce = yield* crypto.randomUUIDv4.pipe( | 536 | const nonce = yield* crypto.randomUUIDv4.pipe( |
infra/relay/src/http/Api.ts+4-4| ... | @@ -830,7 +830,7 @@ const currentTraceId = Effect.currentParentSpan.pipe( | ... | @@ -830,7 +830,7 @@ const currentTraceId = Effect.currentParentSpan.pipe( |
| 830 | Effect.orElseSucceed(() => "unavailable"), | 830 | Effect.orElseSucceed(() => "unavailable"), |
| 831 | ); | 831 | ); |
| 832 | 832 | ||
| 833 | const COMMON_AUTH_INVALID_REASONS = [ | 833 | const RelayCommonPersistenceError = Schema.Union([ |
| 834 | Devices.DeviceRegistrationPersistenceError, | 834 | Devices.DeviceRegistrationPersistenceError, |
| 835 | Devices.DeviceUnregistrationPersistenceError, | 835 | Devices.DeviceUnregistrationPersistenceError, |
| 836 | Devices.DeviceListPersistenceError, | 836 | Devices.DeviceListPersistenceError, |
| ... | @@ -850,9 +850,9 @@ const COMMON_AUTH_INVALID_REASONS = [ | ... | @@ -850,9 +850,9 @@ const COMMON_AUTH_INVALID_REASONS = [ |
| 850 | AgentActivityRows.AgentActivityRowListPersistenceError, | 850 | AgentActivityRows.AgentActivityRowListPersistenceError, |
| 851 | LiveActivities.LiveActivityDeliveryMarkPersistenceError, | 851 | LiveActivities.LiveActivityDeliveryMarkPersistenceError, |
| 852 | DeliveryAttempts.DeliveryAttemptRecordPersistenceError, | 852 | DeliveryAttempts.DeliveryAttemptRecordPersistenceError, |
| 853 | ] as const; | 853 | ]); |
| 854 | type RelayCommonPersistenceError = InstanceType<(typeof COMMON_AUTH_INVALID_REASONS)[number]>; | 854 | type RelayCommonPersistenceError = typeof RelayCommonPersistenceError.Type; |
| 855 | const isRelayCommonPersistenceError = Schema.is(Schema.Union(COMMON_AUTH_INVALID_REASONS)); | 855 | const isRelayCommonPersistenceError = Schema.is(RelayCommonPersistenceError); |
| 856 | 856 | ||
| 857 | type MapRelayCommonApiError<E> = | 857 | type MapRelayCommonApiError<E> = |
| 858 | | Exclude<E, HttpApiError.Unauthorized | RelayCommonPersistenceError> | 858 | | Exclude<E, HttpApiError.Unauthorized | RelayCommonPersistenceError> |
infra/relay/src/observability.test.ts+5-6| ... | @@ -9,7 +9,7 @@ import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; | ... | @@ -9,7 +9,7 @@ import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 9 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; | 9 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; |
| 10 | import type { OtlpTracer } from "effect/unstable/observability"; | 10 | import type { OtlpTracer } from "effect/unstable/observability"; |
| 11 | 11 | ||
| 12 | import { EnvironmentMintRequestFailed } from "./environments/EnvironmentConnector.ts"; | 12 | import { EnvironmentConnectNotAuthorized } from "./environments/EnvironmentConnector.ts"; |
| 13 | import { makeRelayTraceLayer } from "./observability.ts"; | 13 | import { makeRelayTraceLayer } from "./observability.ts"; |
| 14 | 14 | ||
| 15 | interface ExportedRequest { | 15 | interface ExportedRequest { |
| ... | @@ -43,10 +43,10 @@ it.effect("exports schema error fields as span attributes", () => | ... | @@ -43,10 +43,10 @@ it.effect("exports schema error fields as span attributes", () => |
| 43 | ); | 43 | ); |
| 44 | 44 | ||
| 45 | yield* Effect.fail( | 45 | yield* Effect.fail( |
| 46 | new EnvironmentMintRequestFailed({ | 46 | new EnvironmentConnectNotAuthorized({ |
| 47 | environmentId: "environment-1", | 47 | environmentId: "environment-1", |
| 48 | operation: "connect", | 48 | operation: "connect", |
| 49 | cause: new Error("upstream unavailable"), | 49 | reason: "managed_endpoint_allocation_not_ready", |
| 50 | }), | 50 | }), |
| 51 | ).pipe( | 51 | ).pipe( |
| 52 | Effect.withSpan("relay.test.schema_error"), | 52 | Effect.withSpan("relay.test.schema_error"), |
| ... | @@ -76,11 +76,10 @@ it.effect("exports schema error fields as span attributes", () => | ... | @@ -76,11 +76,10 @@ it.effect("exports schema error fields as span attributes", () => |
| 76 | expect(request.authorization).toBe("Bearer test-token"); | 76 | expect(request.authorization).toBe("Bearer test-token"); |
| 77 | expect(request.dataset).toBe("relay-test-traces"); | 77 | expect(request.dataset).toBe("relay-test-traces"); |
| 78 | expect(attributes).toMatchObject({ | 78 | expect(attributes).toMatchObject({ |
| 79 | "error.type": "EnvironmentMintRequestFailed", | 79 | "error.type": "EnvironmentConnectNotAuthorized", |
| 80 | "error.environmentId": "environment-1", | 80 | "error.environmentId": "environment-1", |
| 81 | "error.operation": "connect", | 81 | "error.operation": "connect", |
| 82 | "error.cause.name": "Error", | 82 | "error.reason": "managed_endpoint_allocation_not_ready", |
| 83 | "error.cause.message": "upstream unavailable", | ||
| 84 | }); | 83 | }); |
| 85 | }).pipe(Effect.provide(NodeHttpServer.layerTest), Effect.scoped), | 84 | }).pipe(Effect.provide(NodeHttpServer.layerTest), Effect.scoped), |
| 86 | ); | 85 | ); |