import { RelayEnvironmentLinkProofPayload, RelayEnvironmentLinkProofInvalidReason, type RelayEnvironmentLinkRequest,} from "@t3tools/contracts/relay";import { decodeRelayJwt, normalizeRelayIssuer, RELAY_LINK_PROOF_TYP, verifyRelayJwt,} from "@t3tools/shared/relayJwt";import * as Context from "effect/Context";import * as DateTime from "effect/DateTime";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Schema from "effect/Schema"; import * as DpopProofs from "../auth/DpopProofs.ts";import * as RelayTokens from "../auth/RelayTokens.ts";import * as EnvironmentCredentials from "./EnvironmentCredentials.ts";import * as EnvironmentLinks from "./EnvironmentLinks.ts";import * as ManagedEndpointProvider from "./ManagedEndpointProvider.ts";import * as RelayConfiguration from "../Config.ts"; export class EnvironmentLinkProofExpired extends Schema.TaggedErrorClass<EnvironmentLinkProofExpired>()( "EnvironmentLinkProofExpired", { userId: Schema.String, environmentId: Schema.String, expiresAt: Schema.String, },) { override get message(): string { return `Environment '${this.environmentId}' link proof expired at ${this.expiresAt}`; }} export class EnvironmentLinkProofInvalid extends Schema.TaggedErrorClass<EnvironmentLinkProofInvalid>()( "EnvironmentLinkProofInvalid", { userId: Schema.String, environmentId: Schema.String, reason: RelayEnvironmentLinkProofInvalidReason, stage: Schema.Literals([ "decode_token", "decode_payload", "verify_proof", "authorize_capabilities", "validate_descriptor", "verify_challenge", "validate_expiration", "consume_proof_nonce", "consume_challenge_nonce", "validate_origin", "validate_endpoint", ]), cause: Schema.optional(Schema.Defect()), },) { override get message(): string { return `Environment '${this.environmentId}' link proof is invalid during ${this.stage}: ${this.reason}`; }} export type EnvironmentLinkError = | EnvironmentLinkProofExpired | EnvironmentLinkProofInvalid | DpopProofs.DpopProofReplayPersistenceError | EnvironmentLinks.EnvironmentLinkUpsertPersistenceError | EnvironmentCredentials.EnvironmentCredentialCreatePersistenceError | ManagedEndpointProvider.ManagedEndpointProviderError; export class EnvironmentLinker extends Context.Service< EnvironmentLinker, { readonly link: (input: { readonly userId: string; readonly request: RelayEnvironmentLinkRequest; }) => Effect.Effect< { readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"]; readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"]; readonly endpointRuntime: | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"] | null; readonly environmentCredential: string; }, EnvironmentLinkError >; }>()("t3code-relay/environments/EnvironmentLinker") {} const decodeProof = Schema.decodeUnknownEffect(RelayEnvironmentLinkProofPayload); function proofAuthorizesRequestedCapabilities( proof: RelayEnvironmentLinkProofPayload, request: RelayEnvironmentLinkRequest,): boolean { const scopes = new Set(proof.scopes); if (request.managedTunnelsEnabled && !scopes.has("managed_tunnels")) { return false; } return !( (request.notificationsEnabled || request.liveActivitiesEnabled) && !scopes.has("agent_activity_notifications") );} function isSecureManagedEndpoint(endpoint: RelayEnvironmentLinkProofPayload["endpoint"]): boolean { try { const httpUrl = new URL(endpoint.httpBaseUrl); const wsUrl = new URL(endpoint.wsBaseUrl); return httpUrl.protocol === "https:" && wsUrl.protocol === "wss:"; } catch { return false; }} function normalizeHostname(hostname: string): string { return hostname .trim() .toLowerCase() .replace(/^\[(.*)\]$/u, "$1");} function isLoopbackManagedTunnelOrigin( origin: RelayEnvironmentLinkProofPayload["origin"],): boolean { const hostname = normalizeHostname(origin.localHttpHost); return ( (hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost") && Number.isInteger(origin.localHttpPort) && origin.localHttpPort > 0 && origin.localHttpPort <= 65_535 );} const make = Effect.gen(function* () { const links = yield* EnvironmentLinks.EnvironmentLinks; const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; const managedEndpointProvider = yield* ManagedEndpointProvider.ManagedEndpointProvider; const proofReplay = yield* DpopProofs.DpopProofReplay; const relayTokens = yield* RelayTokens.RelayTokens; const config = yield* RelayConfiguration.RelayConfiguration; return EnvironmentLinker.of({ link: Effect.fn("relay.environment_linker.link")(function* (input) { const now = yield* DateTime.now; const nowSeconds = Math.floor(now.epochMilliseconds / 1_000); const unverified = yield* Effect.try({ try: () => decodeRelayJwt(input.request.proof), catch: (cause) => new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: "unknown", reason: "invalid_signature_or_scope", stage: "decode_token", cause, }), }); const candidate = yield* decodeProof(unverified).pipe( Effect.mapError( (cause) => new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: "unknown", reason: "invalid_signature_or_scope", stage: "decode_payload", cause, }), ), ); yield* Effect.annotateCurrentSpan({ "relay.environment_id": candidate.environmentId, "relay.link.notifications_enabled": input.request.notificationsEnabled, "relay.link.live_activities_enabled": input.request.liveActivitiesEnabled, "relay.link.managed_tunnels_enabled": input.request.managedTunnelsEnabled, }); if (candidate.exp <= nowSeconds) { return yield* new EnvironmentLinkProofExpired({ userId: input.userId, environmentId: candidate.environmentId, expiresAt: DateTime.formatIso(DateTime.makeUnsafe(candidate.exp * 1_000)), }); } const issuer = `t3-env:${candidate.environmentId}`; const relayIssuer = normalizeRelayIssuer(config.relayIssuer); const verified = yield* verifyRelayJwt({ publicKey: candidate.environmentPublicKey, token: input.request.proof, typ: RELAY_LINK_PROOF_TYP, issuer, audience: relayIssuer, nowEpochSeconds: nowSeconds, }).pipe( Effect.flatMap(decodeProof), Effect.mapError( (cause) => new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: candidate.environmentId, reason: "invalid_signature_or_scope", stage: "verify_proof", cause, }), ), ); if ( verified.sub !== verified.environmentId || !proofAuthorizesRequestedCapabilities(verified, input.request) ) { return yield* new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: candidate.environmentId, reason: "invalid_signature_or_scope", stage: "authorize_capabilities", }); } if (verified.descriptor.environmentId !== verified.environmentId) { return yield* new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: verified.environmentId, reason: "descriptor_mismatch", stage: "validate_descriptor", }); } const challenge = yield* relayTokens.verifyLinkChallenge({ token: verified.challenge, userId: input.userId, request: { notificationsEnabled: input.request.notificationsEnabled, liveActivitiesEnabled: input.request.liveActivitiesEnabled, managedTunnelsEnabled: input.request.managedTunnelsEnabled, }, nowEpochSeconds: nowSeconds, }); if (challenge === null) { return yield* new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: verified.environmentId, reason: "challenge_invalid", stage: "verify_challenge", }); } const expiresAt = DateTime.make(verified.exp * 1_000); if (expiresAt._tag === "None") { return yield* new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: verified.environmentId, reason: "invalid_signature_or_scope", stage: "validate_expiration", }); } const consumedNonce = yield* proofReplay.consume({ thumbprint: verified.environmentPublicKey, jti: verified.jti, iat: verified.iat, expiresAt: expiresAt.value, }); if (!consumedNonce) { return yield* new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: verified.environmentId, reason: "replayed_nonce", stage: "consume_proof_nonce", }); } const consumedChallenge = yield* proofReplay.consume({ thumbprint: "relay-environment-link-challenge", jti: challenge.jti, iat: challenge.iat, expiresAt: expiresAt.value, }); if (!consumedChallenge) { return yield* new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: verified.environmentId, reason: "challenge_invalid", stage: "consume_challenge_nonce", }); } if (input.request.managedTunnelsEnabled && !isLoopbackManagedTunnelOrigin(verified.origin)) { return yield* new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: verified.environmentId, reason: "origin_not_allowed", stage: "validate_origin", }); } const provisioned = input.request.managedTunnelsEnabled ? yield* managedEndpointProvider.provision({ userId: input.userId, environmentId: verified.environmentId, origin: verified.origin, }) : null; const endpoint = provisioned?.endpoint ?? verified.endpoint; if (!isSecureManagedEndpoint(endpoint)) { return yield* new EnvironmentLinkProofInvalid({ userId: input.userId, environmentId: verified.environmentId, reason: "endpoint_not_secure", stage: "validate_endpoint", }); } yield* links.upsert({ ...input, proof: verified, endpoint }); const environmentCredential = yield* credentials.create({ environmentId: verified.environmentId, environmentPublicKey: verified.environmentPublicKey, }); return { environmentId: verified.environmentId, endpoint, endpointRuntime: provisioned?.runtime ?? null, environmentCredential, }; }), });}); export const layer = Layer.effect(EnvironmentLinker, make);
import {
RelayEnvironmentLinkProofPayload,
RelayEnvironmentLinkProofInvalidReason,
type RelayEnvironmentLinkRequest,
} from "@t3tools/contracts/relay";
import {
decodeRelayJwt,
normalizeRelayIssuer,
RELAY_LINK_PROOF_TYP,
verifyRelayJwt,
} from "@t3tools/shared/relayJwt";
import * as Context from "effect/Context";
import * as DateTime from "effect/DateTime";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import * as DpopProofs from "../auth/DpopProofs.ts";
import * as RelayTokens from "../auth/RelayTokens.ts";
import * as EnvironmentCredentials from "./EnvironmentCredentials.ts";
import * as EnvironmentLinks from "./EnvironmentLinks.ts";
import * as ManagedEndpointProvider from "./ManagedEndpointProvider.ts";
import * as RelayConfiguration from "../Config.ts";
export class EnvironmentLinkProofExpired extends Schema.TaggedErrorClass<EnvironmentLinkProofExpired>()(
"EnvironmentLinkProofExpired",
{
userId: Schema.String,
environmentId: Schema.String,
expiresAt: Schema.String,
},
) {
override get message(): string {
return `Environment '${this.environmentId}' link proof expired at ${this.expiresAt}`;
}
}
export class EnvironmentLinkProofInvalid extends Schema.TaggedErrorClass<EnvironmentLinkProofInvalid>()(
"EnvironmentLinkProofInvalid",
{
userId: Schema.String,
environmentId: Schema.String,
reason: RelayEnvironmentLinkProofInvalidReason,
stage: Schema.Literals([
"decode_token",
"decode_payload",
"verify_proof",
"authorize_capabilities",
"validate_descriptor",
"verify_challenge",
"validate_expiration",
"consume_proof_nonce",
"consume_challenge_nonce",
"validate_origin",
"validate_endpoint",
]),
cause: Schema.optional(Schema.Defect()),
},
) {
override get message(): string {
return `Environment '${this.environmentId}' link proof is invalid during ${this.stage}: ${this.reason}`;
}
}
export type EnvironmentLinkError =
| EnvironmentLinkProofExpired
| EnvironmentLinkProofInvalid
| DpopProofs.DpopProofReplayPersistenceError
| EnvironmentLinks.EnvironmentLinkUpsertPersistenceError
| EnvironmentCredentials.EnvironmentCredentialCreatePersistenceError
| ManagedEndpointProvider.ManagedEndpointProviderError;
export class EnvironmentLinker extends Context.Service<
EnvironmentLinker,
{
readonly link: (input: {
readonly userId: string;
readonly request: RelayEnvironmentLinkRequest;
}) => Effect.Effect<
{
readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"];
readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"];
readonly endpointRuntime:
| ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"]
| null;
readonly environmentCredential: string;
},
EnvironmentLinkError
>;
}
>()("t3code-relay/environments/EnvironmentLinker") {}
const decodeProof = Schema.decodeUnknownEffect(RelayEnvironmentLinkProofPayload);
function proofAuthorizesRequestedCapabilities(
proof: RelayEnvironmentLinkProofPayload,
request: RelayEnvironmentLinkRequest,
): boolean {
const scopes = new Set(proof.scopes);
if (request.managedTunnelsEnabled && !scopes.has("managed_tunnels")) {
return false;
}
return !(
(request.notificationsEnabled || request.liveActivitiesEnabled) &&
!scopes.has("agent_activity_notifications")
);
}
function isSecureManagedEndpoint(endpoint: RelayEnvironmentLinkProofPayload["endpoint"]): boolean {
try {
const httpUrl = new URL(endpoint.httpBaseUrl);
const wsUrl = new URL(endpoint.wsBaseUrl);
return httpUrl.protocol === "https:" && wsUrl.protocol === "wss:";
} catch {
return false;
}
}
function normalizeHostname(hostname: string): string {
return hostname
.trim()
.toLowerCase()
.replace(/^\[(.*)\]$/u, "$1");
}
function isLoopbackManagedTunnelOrigin(
origin: RelayEnvironmentLinkProofPayload["origin"],
): boolean {
const hostname = normalizeHostname(origin.localHttpHost);
return (
(hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost") &&
Number.isInteger(origin.localHttpPort) &&
origin.localHttpPort > 0 &&
origin.localHttpPort <= 65_535
);
}
const make = Effect.gen(function* () {
const links = yield* EnvironmentLinks.EnvironmentLinks;
const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
const managedEndpointProvider = yield* ManagedEndpointProvider.ManagedEndpointProvider;
const proofReplay = yield* DpopProofs.DpopProofReplay;
const relayTokens = yield* RelayTokens.RelayTokens;
const config = yield* RelayConfiguration.RelayConfiguration;
return EnvironmentLinker.of({
link: Effect.fn("relay.environment_linker.link")(function* (input) {
const now = yield* DateTime.now;
const nowSeconds = Math.floor(now.epochMilliseconds / 1_000);
const unverified = yield* Effect.try({
try: () => decodeRelayJwt(input.request.proof),
catch: (cause) =>
new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: "unknown",
reason: "invalid_signature_or_scope",
stage: "decode_token",
cause,
}),
});
const candidate = yield* decodeProof(unverified).pipe(
Effect.mapError(
(cause) =>
new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: "unknown",
reason: "invalid_signature_or_scope",
stage: "decode_payload",
cause,
}),
),
);
yield* Effect.annotateCurrentSpan({
"relay.environment_id": candidate.environmentId,
"relay.link.notifications_enabled": input.request.notificationsEnabled,
"relay.link.live_activities_enabled": input.request.liveActivitiesEnabled,
"relay.link.managed_tunnels_enabled": input.request.managedTunnelsEnabled,
});
if (candidate.exp <= nowSeconds) {
return yield* new EnvironmentLinkProofExpired({
userId: input.userId,
environmentId: candidate.environmentId,
expiresAt: DateTime.formatIso(DateTime.makeUnsafe(candidate.exp * 1_000)),
});
}
const issuer = `t3-env:${candidate.environmentId}`;
const relayIssuer = normalizeRelayIssuer(config.relayIssuer);
const verified = yield* verifyRelayJwt({
publicKey: candidate.environmentPublicKey,
token: input.request.proof,
typ: RELAY_LINK_PROOF_TYP,
issuer,
audience: relayIssuer,
nowEpochSeconds: nowSeconds,
}).pipe(
Effect.flatMap(decodeProof),
Effect.mapError(
(cause) =>
new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: candidate.environmentId,
reason: "invalid_signature_or_scope",
stage: "verify_proof",
cause,
}),
),
);
if (
verified.sub !== verified.environmentId ||
!proofAuthorizesRequestedCapabilities(verified, input.request)
) {
return yield* new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: candidate.environmentId,
reason: "invalid_signature_or_scope",
stage: "authorize_capabilities",
});
}
if (verified.descriptor.environmentId !== verified.environmentId) {
return yield* new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: verified.environmentId,
reason: "descriptor_mismatch",
stage: "validate_descriptor",
});
}
const challenge = yield* relayTokens.verifyLinkChallenge({
token: verified.challenge,
userId: input.userId,
request: {
notificationsEnabled: input.request.notificationsEnabled,
liveActivitiesEnabled: input.request.liveActivitiesEnabled,
managedTunnelsEnabled: input.request.managedTunnelsEnabled,
},
nowEpochSeconds: nowSeconds,
});
if (challenge === null) {
return yield* new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: verified.environmentId,
reason: "challenge_invalid",
stage: "verify_challenge",
});
}
const expiresAt = DateTime.make(verified.exp * 1_000);
if (expiresAt._tag === "None") {
return yield* new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: verified.environmentId,
reason: "invalid_signature_or_scope",
stage: "validate_expiration",
});
}
const consumedNonce = yield* proofReplay.consume({
thumbprint: verified.environmentPublicKey,
jti: verified.jti,
iat: verified.iat,
expiresAt: expiresAt.value,
});
if (!consumedNonce) {
return yield* new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: verified.environmentId,
reason: "replayed_nonce",
stage: "consume_proof_nonce",
});
}
const consumedChallenge = yield* proofReplay.consume({
thumbprint: "relay-environment-link-challenge",
jti: challenge.jti,
iat: challenge.iat,
expiresAt: expiresAt.value,
});
if (!consumedChallenge) {
return yield* new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: verified.environmentId,
reason: "challenge_invalid",
stage: "consume_challenge_nonce",
});
}
if (input.request.managedTunnelsEnabled && !isLoopbackManagedTunnelOrigin(verified.origin)) {
return yield* new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: verified.environmentId,
reason: "origin_not_allowed",
stage: "validate_origin",
});
}
const provisioned = input.request.managedTunnelsEnabled
? yield* managedEndpointProvider.provision({
userId: input.userId,
environmentId: verified.environmentId,
origin: verified.origin,
})
: null;
const endpoint = provisioned?.endpoint ?? verified.endpoint;
if (!isSecureManagedEndpoint(endpoint)) {
return yield* new EnvironmentLinkProofInvalid({
userId: input.userId,
environmentId: verified.environmentId,
reason: "endpoint_not_secure",
stage: "validate_endpoint",
});
}
yield* links.upsert({ ...input, proof: verified, endpoint });
const environmentCredential = yield* credentials.create({
environmentId: verified.environmentId,
environmentPublicKey: verified.environmentPublicKey,
});
return {
environmentId: verified.environmentId,
endpoint,
endpointRuntime: provisioned?.runtime ?? null,
environmentCredential,
};
}),
});
});
export const layer = Layer.effect(EnvironmentLinker, make);