import * as Exit from "effect/Exit"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import { OAuthStartError } from "@executor-js/sdk/shared"; // --------------------------------------------------------------------------- // Blocked by administrator — reading an enterprise policy denial as STRUCTURE. // // When an enterprise identity provider refuses to mint an ID-JAG, the failure // is an administrator's decision, not a credential problem. `OAuthStartError` // says so in a typed field (`blockedByAdmin`) precisely so a console never has // to read the sentence: matching on message text would silently start passing // or failing whenever the wording changed, and the consequence of getting it // wrong is offering the interactive per-server flow — which would route the // user straight around the control the enterprise just exercised. // // So: this module decodes the field, and NOTHING here inspects `message` // except to display it. // --------------------------------------------------------------------------- /** An enterprise identity provider declined this connect under administrator * policy. Terminal by construction: there is no retry and no alternative * route to offer, only the decision and a code support can trace. */ export interface OAuthAdminBlock { /** The failure's own message, shown verbatim. */ readonly message: string; /** The identity provider's RFC 6749 §5.2 code (`unauthorized_client`, * `invalid_target`, …), when it returned one. Null otherwise. */ readonly oauthErrorCode: string | null; } const decodeStartError = Schema.decodeUnknownOption(OAuthStartError); /** One level of wrapping: the popup flow wraps a start failure in its own * tagged error before it reaches a renderer, and the verdict must survive * that hop. Exactly one level — a `cause` chain is not a search space. */ const decodeWrapped = Schema.decodeUnknownOption(Schema.Struct({ cause: Schema.Unknown })); const directAdminBlock = (error: unknown): OAuthAdminBlock | null => Option.match(decodeStartError(error), { onNone: () => null, onSome: (start) => start.blockedByAdmin === true ? { message: start.message, oauthErrorCode: start.oauthErrorCode ?? null } : null, }); /** The administrator verdict carried by a failed `oauth.start`, or null when * the failure is anything else — including every other OAuth start failure, * which leaves the interactive route open. */ export const adminBlockFrom = (error: unknown): OAuthAdminBlock | null => directAdminBlock(error) ?? Option.match(decodeWrapped(error), { onNone: () => null, onSome: (wrapper) => directAdminBlock(wrapper.cause), }); /** `adminBlockFrom` over an `Exit`, for the mutation call sites that hold one. */ export const adminBlockFromExit = (exit: Exit.Exit): OAuthAdminBlock | null => Option.match(Exit.findErrorOption(exit), { onNone: () => null, onSome: adminBlockFrom, }); /** What the user is told. Sentence case per the design system's voice rules: * what happened, then what to do next — and the next step is a person, not a * button, because no action in this console can change the verdict. */ export const ADMIN_BLOCK_TITLE = "Blocked by your organization"; export const ADMIN_BLOCK_NEXT_STEP = "Ask an administrator to allow this server at your identity provider."; /** The support-traceable reference line, or null when the provider returned no * code. Mono metadata, per the design system. */ export const adminBlockReference = (block: OAuthAdminBlock): string | null => block.oauthErrorCode === null ? null : `Reference: ${block.oauthErrorCode}`;