@@ -40,15 +40,54 @@ import { FetchHttpClient, HttpClient } from "effect/unstable/http";
4040import * as EnvironmentLinks from "./EnvironmentLinks.ts" ;
4141import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts" ;
4242import * 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+ }
4380
4481export class EnvironmentConnectNotAuthorized extends Schema . TaggedErrorClass < EnvironmentConnectNotAuthorized > ( ) (
4582 "EnvironmentConnectNotAuthorized" ,
4683 {
4784 environmentId : Schema . String ,
85+ operation : Schema . Literals ( [ "connect" , "status" ] ) ,
86+ reason : EnvironmentConnectNotAuthorizedReason ,
4887 } ,
4988) {
5089 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 ) } ` ;
5291 }
5392}
5493
@@ -257,30 +296,91 @@ const make = Effect.gen(function* () {
257296 const resolveManagedEndpoint = Effect . fn ( "relay.environment_connector.resolve_managed_endpoint" ) (
258297 function * ( input : {
259298 readonly userId : string ;
299+ readonly operation : "connect" | "status" ;
260300 readonly link : EnvironmentLinks . RelayLinkedEnvironmentRecord ;
261301 } ) {
262302 if ( input . link . endpoint . providerKind !== "cloudflare_tunnel" ) {
303+ yield * Effect . annotateCurrentSpan ( {
304+ "relay.authorization.endpoint_provider_kind" : input . link . endpoint . providerKind ,
305+ } ) ;
263306 return yield * new EnvironmentConnectNotAuthorized ( {
264307 environmentId : input . link . environmentId ,
308+ operation : input . operation ,
309+ reason : "endpoint_provider_not_managed" ,
265310 } ) ;
266311 }
267312 const allocation = yield * allocations . get ( {
268313 userId : input . userId ,
269314 environmentId : input . link . environmentId ,
270315 } ) ;
271- const endpoint = allocation
272- ? ManagedEndpointAllocations . resolveReadyManagedEndpoint ( {
273- allocation,
274- baseDomain : settings . managedEndpointBaseDomain ,
275- } )
276- : null ;
316+ if ( ! allocation ) {
317+ return yield * new EnvironmentConnectNotAuthorized ( {
318+ environmentId : input . link . environmentId ,
319+ operation : input . operation ,
320+ reason : "managed_endpoint_allocation_not_found" ,
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+ } ) ;
277364 if (
278365 endpoint === null ||
279366 endpoint . httpBaseUrl !== input . link . endpoint . httpBaseUrl ||
280367 endpoint . wsBaseUrl !== input . link . endpoint . wsBaseUrl
281368 ) {
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+ } ) ;
282380 return yield * new EnvironmentConnectNotAuthorized ( {
283381 environmentId : input . link . environmentId ,
382+ operation : input . operation ,
383+ reason : "managed_endpoint_mismatch" ,
284384 } ) ;
285385 }
286386 return endpoint ;
@@ -295,9 +395,17 @@ const make = Effect.gen(function* () {
295395 } ) ;
296396 const link = yield * links . getForUser ( input ) ;
297397 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+ } ) ;
299403 }
300- const endpoint = yield * resolveManagedEndpoint ( { userId : input . userId , link } ) ;
404+ const endpoint = yield * resolveManagedEndpoint ( {
405+ userId : input . userId ,
406+ operation : "status" ,
407+ link,
408+ } ) ;
301409 const now = yield * DateTime . now ;
302410 const expiresAt = DateTime . add ( now , { minutes : 2 } ) ;
303411 const nonce = yield * crypto . randomUUIDv4 . pipe (
@@ -404,13 +512,25 @@ const make = Effect.gen(function* () {
404512 ...( input . deviceId ? { "relay.mobile.device_id" : input . deviceId } : { } ) ,
405513 } ) ;
406514 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+ } ) ;
408520 }
409521 const link = yield * links . getForUser ( input ) ;
410522 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+ } ) ;
412528 }
413- const endpoint = yield * resolveManagedEndpoint ( { userId : input . userId , link } ) ;
529+ const endpoint = yield * resolveManagedEndpoint ( {
530+ userId : input . userId ,
531+ operation : "connect" ,
532+ link,
533+ } ) ;
414534 const now = yield * DateTime . now ;
415535 const expiresAt = DateTime . add ( now , { minutes : 2 } ) ;
416536 const nonce = yield * crypto . randomUUIDv4 . pipe (
0 commit comments