Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/server/src/cloud/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
EnvironmentCloudEndpointUnavailableError,
EnvironmentCloudLinkStateResult,
EnvironmentCloudRelayConfigResult,
type EnvironmentConflictReason,
EnvironmentHttpApi,
EnvironmentHttpBadRequestError,
EnvironmentHttpConflictError,
Expand Down Expand Up @@ -240,6 +241,7 @@ function validateLinkedCloudUser(input: {
new EnvironmentHttpConflictError({
message:
"This environment is already linked to a different cloud account. Unlink it before switching accounts.",
reason: "linked_to_different_cloud_account" satisfies EnvironmentConflictReason,
}),
);
}),
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3236,11 +3236,13 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
const replacementBody = yield* responseJsonEffect<{
readonly _tag?: string;
readonly message?: string;
readonly reason?: string;
}>(replacementResponse);

assert.equal(firstResponse.status, 200);
assert.equal(replacementResponse.status, 409);
assert.equal(replacementBody._tag, "EnvironmentHttpConflictError");
assert.equal(replacementBody.reason, "linked_to_different_cloud_account");
assert.equal(
replacementBody.message,
"This environment is already linked to a different cloud account. Unlink it before switching accounts.",
Expand Down
29 changes: 29 additions & 0 deletions apps/web/src/cloud/forkRevokeRelayLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { EnvironmentId } from "@t3tools/contracts";
import { ManagedRelay } from "@t3tools/client-runtime/relay";
import { createRuntimeCommand } from "@t3tools/client-runtime/state/runtime";
import * as Effect from "effect/Effect";

import { connectionAtomRuntime } from "../connection/runtime";
import { type CloudLinkTarget, readPrimaryCloudLinkState } from "./linkEnvironment";
import { resolveCloudPublicConfig } from "./publicConfig";

/**
* Removes the token's own relay entry for an environment that was just
* unlinked locally. Run it after the local unlink, never before: the token may
* arrive late, so it first checks that the environment has not been linked
* again in the meantime and leaves the relay alone when it has.
*/
export const revokeRelayLinkAfterLocalUnlink = createRuntimeCommand(connectionAtomRuntime, {
label: "web:cloud:revoke-relay-link-after-local-unlink",
execute: (input: { readonly target: CloudLinkTarget; readonly clerkToken: string }) =>
Effect.gen(function* () {
if (!resolveCloudPublicConfig().relayUrl) return;
const linkState = yield* readPrimaryCloudLinkState({ target: input.target });
if (linkState?.linked) return;
const relayClient = yield* ManagedRelay.ManagedRelayClient;
yield* relayClient.unlinkEnvironment({
clerkToken: input.clerkToken,
environmentId: EnvironmentId.make(input.target.environmentId),
});
}),
});
Loading
Loading