everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
1 lines • 19.2 kB
Source Map (JSON)
{"version":3,"file":"device-login.cjs","names":["realAuthPost","realRpcPost","realGetJson","BOS_CLI_CLIENT_ID","Context","Layer","Effect","SiteClient","parseSiteErrorDetail","platform"],"sources":["../../../src/services/auth/device-login.ts"],"sourcesContent":["import { platform } from \"node:os\";\nimport { BOS_CLI_CLIENT_ID } from \"better-near-auth\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { execa } from \"execa\";\nimport type { CustodyWalletRecord } from \"./session\";\nimport {\n parseSiteErrorDetail,\n authPost as realAuthPost,\n getJson as realGetJson,\n rpcPost as realRpcPost,\n SiteClient,\n type SiteClientShape,\n} from \"./site-client\";\n\nconst realSiteClient = {\n authPost: realAuthPost,\n rpcPost: realRpcPost,\n getJson: realGetJson,\n} as Pick<SiteClientShape, \"authPost\" | \"rpcPost\" | \"getJson\">;\n\nconst DEVICE_CODE_GRANT_TYPE = \"urn:ietf:params:oauth:grant-type:device_code\";\n\nexport const DEVICE_LINK_CLIENT_ID = BOS_CLI_CLIENT_ID;\nconst DEFAULT_CLIENT_ID = DEVICE_LINK_CLIENT_ID;\nconst DEFAULT_POLL_INTERVAL_SECONDS = 5;\nconst SLOW_DOWN_PENALTY_SECONDS = 5;\n\nexport interface DeviceLoginOptions {\n siteUrl: string;\n /** label for the approving device, shown on the approval page */\n device?: string;\n /** the NEAR account the CLI expects to authenticate as (approval-page copy only) */\n account?: string;\n /** delegate mode — the approval page adds this scoped function-call key to the user's account */\n delegate?: { pubKey: string; contract: string; network: string };\n /** custody mode — the approval page also registers an OutLayer custody wallet under the user's account */\n custody?: { network: \"mainnet\" | \"testnet\" };\n /** seconds the minted API key stays valid (login mode) */\n expiresIn?: number;\n /** deploy-key mode — the mint switches to the auth plugin's server-side route and bakes bundle-write permissions plus account/gateway scope into the key */\n deployKey?: { account: string; gateway: string };\n}\n\nexport interface DeviceApproval {\n sessionToken: string;\n /** the NEAR account signed in on the approving browser (login mode) */\n accountId: string | null;\n /** the minted CLI credential (login mode) */\n apiKey: { key: string; id: string } | null;\n /** the custody wallet registered during approval (custody mode) — null when it could not be fetched */\n custodyWallet: CustodyWalletRecord | null;\n}\n\nexport interface DeviceLoginHandle {\n userCode: string;\n verificationUrl: string;\n waitForApproval: (timeoutMs?: number) => Promise<DeviceApproval>;\n abort: (reason?: string) => void;\n}\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri?: string;\n verification_uri_complete?: string;\n expires_in?: number;\n interval?: number;\n}\n\ninterface DeviceTokenSuccess {\n access_token: string;\n token_type: \"Bearer\";\n}\n\ninterface DeviceTokenError {\n error:\n | \"authorization_pending\"\n | \"slow_down\"\n | \"expired_token\"\n | \"access_denied\"\n | \"invalid_grant\"\n | \"invalid_request\"\n | \"invalid_client\";\n error_description?: string;\n}\n\ninterface ApiKeyCreated {\n id: string;\n key: string;\n}\n\ninterface NearAccountsResponse {\n accounts: { accountId: string }[];\n activeAccount: { accountId: string } | null;\n}\n\n/**\n * CLI login via the OAuth 2.0 Device Flow (RFC 8628) — the same flow the\n * site's QR pairing uses. The CLI requests a device code, the user approves\n * at `/login/device` in any browser (same machine or not), and the CLI polls\n * the token endpoint until the grant completes. All HTTP goes through the\n * site client; the returned handle drives the poll loop from the handler.\n */\nexport async function startDeviceLogin(\n opts: DeviceLoginOptions,\n client: Pick<SiteClientShape, \"authPost\" | \"rpcPost\" | \"getJson\"> = realSiteClient,\n): Promise<DeviceLoginHandle> {\n const siteUrl = opts.siteUrl.replace(/\\/$/, \"\");\n const clientId = DEFAULT_CLIENT_ID;\n\n const codeResponse = await client.authPost(siteUrl, \"/device/code\", { client_id: clientId });\n if (codeResponse.status >= 400) {\n throw new Error(\n `Device login could not start (${codeResponse.status}) — is ${siteUrl} running the platform auth plugin?`,\n );\n }\n const code = codeResponse.json as DeviceCodeResponse;\n\n const verificationUrl = buildVerificationUrl(siteUrl, code, opts);\n\n let abortReason: string | null = null;\n let rejectPending!: (reason?: Error) => void;\n const pendingReject = new Promise<never>((_, reject) => {\n rejectPending = reject;\n });\n\n const waitForApproval = (timeoutMs = 10 * 60_000): Promise<DeviceApproval> => {\n const deadline = Date.now() + timeoutMs;\n let intervalSeconds = code.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;\n\n const poll = async (): Promise<DeviceApproval> => {\n if (abortReason) throw new Error(abortReason);\n if (Date.now() > deadline) {\n throw new Error(\"Login timed out — the device code expired before approval\");\n }\n\n const { status, json } = await client.authPost(siteUrl, \"/device/token\", {\n grant_type: DEVICE_CODE_GRANT_TYPE,\n device_code: code.device_code,\n client_id: clientId,\n });\n\n if (status === 200) {\n const { access_token: sessionToken } = json as DeviceTokenSuccess;\n return await completeLogin(siteUrl, sessionToken, opts, client);\n }\n\n const error = (json as DeviceTokenError).error;\n if (error === \"authorization_pending\") {\n // keep polling\n } else if (error === \"slow_down\") {\n intervalSeconds += SLOW_DOWN_PENALTY_SECONDS;\n } else if (error === \"expired_token\") {\n throw new Error(\"Login timed out — the device code expired before approval\");\n } else if (error === \"access_denied\") {\n throw new Error(\"Login request was denied on the approval page\");\n } else {\n throw new Error(\n (json as DeviceTokenError).error_description ?? `Login failed (${error ?? status})`,\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalSeconds * 1000));\n return poll();\n };\n\n return Promise.race([pendingReject, poll()]);\n };\n\n return {\n userCode: code.user_code,\n verificationUrl,\n waitForApproval,\n abort: (reason) => {\n abortReason = reason ?? \"Login aborted\";\n rejectPending(new Error(abortReason));\n },\n };\n}\n\n/** The device login as a Context.Service tag (ticket #423) — the login\n * handler yields it; tests provide fake site clients. */\nexport class DeviceLoginService extends Context.Service<\n DeviceLoginService,\n {\n start: (opts: DeviceLoginOptions) => Effect.Effect<DeviceLoginHandle, Error>;\n }\n>()(\"everything-dev/auth/device-login\") {\n static readonly Live = Layer.effect(\n this,\n Effect.gen(function* () {\n const client = yield* SiteClient;\n return DeviceLoginService.of({\n start: (opts) => Effect.tryPromise(() => startDeviceLogin(opts, client)),\n });\n }),\n );\n}\n\nfunction buildVerificationUrl(\n siteUrl: string,\n code: DeviceCodeResponse,\n opts: DeviceLoginOptions,\n): string {\n const base = code.verification_uri_complete ?? code.verification_uri ?? \"/login/device\";\n const url = new URL(base, siteUrl);\n const params = url.searchParams;\n params.set(\"user_code\", code.user_code);\n if (opts.account) params.set(\"account\", opts.account);\n if (opts.device) params.set(\"device\", opts.device);\n if (opts.delegate) {\n params.set(\"pubKey\", opts.delegate.pubKey);\n params.set(\"contract\", opts.delegate.contract);\n params.set(\"network\", opts.delegate.network);\n }\n if (opts.custody) {\n params.set(\"custody\", \"1\");\n params.set(\"network\", opts.custody.network);\n }\n return url.toString();\n}\n\nasync function completeLogin(\n siteUrl: string,\n sessionToken: string,\n opts: DeviceLoginOptions,\n client: Pick<SiteClientShape, \"authPost\" | \"rpcPost\" | \"getJson\">,\n): Promise<DeviceApproval> {\n // The token endpoint returns the raw session token; Better Auth session\n // cookies carry an HMAC signature, so exchange the token for a signed\n // session cookie via the device-link claim endpoint (the QR pairing path).\n const claim = await client.authPost(siteUrl, \"/device-link/claim\", {\n token: sessionToken,\n client_id: DEFAULT_CLIENT_ID,\n });\n if (claim.status !== 200) {\n const detail = parseSiteErrorDetail(claim.status, claim.json);\n throw new Error(\n `Approved session could not be claimed${detail ? `: ${detail}` : ` (${claim.status})`}`,\n );\n }\n const cookiePair = claim.setCookie?.split(\";\")[0];\n if (!cookiePair?.includes(\"=\")) {\n throw new Error(\"Session claim returned no session cookie\");\n }\n\n const apiKey = await (opts.deployKey\n ? mintDeployKey(siteUrl, cookiePair, opts, client)\n : mintApiKey(siteUrl, cookiePair, opts, client));\n const accountId = await resolveNearAccount(siteUrl, cookiePair, client);\n const custodyWallet = opts.custody\n ? await fetchCustodyCredential(siteUrl, cookiePair, opts.custody.network, client).catch(\n () => null,\n )\n : null;\n return { sessionToken, accountId, apiKey, custodyWallet };\n}\n\ninterface CustodyCredentialResponse {\n apiKey: string;\n walletId: string;\n nearAccountId: string;\n network: string;\n handoffUrl: string | null;\n registeredAt?: string;\n}\n\nasync function fetchCustodyCredential(\n siteUrl: string,\n cookiePair: string,\n network: \"mainnet\" | \"testnet\",\n client: Pick<SiteClientShape, \"authPost\">,\n): Promise<CustodyWalletRecord> {\n const { status, json } = await client.authPost(\n siteUrl,\n \"/custody/credential\",\n { network },\n cookiePair,\n );\n if (status !== 200) {\n const detail = parseSiteErrorDetail(status, json);\n throw new Error(\n `Custody wallet credential could not be fetched${detail ? `: ${detail}` : ` (${status})`}`,\n );\n }\n const data = json as CustodyCredentialResponse;\n if (!data.apiKey?.startsWith(\"wk_\")) {\n throw new Error(\"Custody wallet credential returned no API key\");\n }\n if (data.network !== \"mainnet\" && data.network !== \"testnet\") {\n throw new Error(`Custody wallet credential returned unknown network \"${data.network}\"`);\n }\n return {\n apiKey: data.apiKey,\n walletId: data.walletId,\n nearAccountId: data.nearAccountId,\n network: data.network,\n handoffUrl: data.handoffUrl ?? null,\n registeredAt: data.registeredAt ?? new Date().toISOString(),\n };\n}\n\nasync function mintApiKey(\n siteUrl: string,\n cookiePair: string,\n opts: DeviceLoginOptions,\n client: Pick<SiteClientShape, \"authPost\">,\n): Promise<DeviceApproval[\"apiKey\"]> {\n const { status, json } = await client.authPost(\n siteUrl,\n \"/api-key/create\",\n {\n configId: \"user-keys\",\n name: `bos login — ${opts.device ?? \"cli\"} — ${new Date().toISOString().slice(0, 16).replace(\"T\", \" \")}`,\n ...(opts.expiresIn ? { expiresIn: opts.expiresIn } : {}),\n },\n cookiePair,\n );\n if (status !== 200) {\n const detail = parseSiteErrorDetail(status, json);\n throw new Error(\n `CLI credential could not be created${detail ? `: ${detail}` : ` (${status})`}`,\n );\n }\n const created = json as ApiKeyCreated;\n if (!created.key) throw new Error(\"API key creation returned no key\");\n return { key: created.key, id: created.id };\n}\n\n/**\n * Deploy-key mint: only the auth plugin's server-side route may set\n * `permissions` (better-auth rejects client-set permissions with\n * SERVER_ONLY_PROPERTY), so the scoped bundle-write key is minted through the\n * plugin's oRPC route instead of better-auth's client endpoint. The oRPC\n * envelope error body (`{ json: { code, message, data } }`) is parsed by the\n * site client — the server's FORBIDDEN detail surfaces verbatim.\n */\nasync function mintDeployKey(\n siteUrl: string,\n cookiePair: string,\n opts: DeviceLoginOptions,\n client: Pick<SiteClientShape, \"rpcPost\">,\n): Promise<DeviceApproval[\"apiKey\"]> {\n const deployKey = opts.deployKey;\n if (!deployKey) throw new Error(\"Deploy-key mint requested without account scope\");\n const response = await client.rpcPost(\n siteUrl,\n \"/api/rpc/auth/createDeployKey\",\n {\n account: deployKey.account,\n gateway: deployKey.gateway,\n name: `bos deploy — ${opts.device ?? \"cli\"} — ${new Date().toISOString().slice(0, 16).replace(\"T\", \" \")}`,\n ...(opts.expiresIn ? { expiresIn: opts.expiresIn } : {}),\n },\n cookiePair,\n );\n if (response.status !== 200) {\n const detail = parseSiteErrorDetail(response.status, response.json);\n throw new Error(\n `Deploy key could not be created${detail ? `: ${detail}` : ` (${response.status})`}`,\n );\n }\n const created = (response.json as { json?: ApiKeyCreated }).json;\n if (!created?.key) throw new Error(\"Deploy key creation returned no key\");\n return { key: created.key, id: created.id };\n}\n\nasync function resolveNearAccount(\n siteUrl: string,\n cookiePair: string,\n client: Pick<SiteClientShape, \"getJson\">,\n): Promise<string | null> {\n try {\n const data = (await client.getJson(\n `${siteUrl.replace(/\\/$/, \"\")}/api/auth/near/list-accounts`,\n cookiePair,\n )) as NearAccountsResponse;\n return data.activeAccount?.accountId ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function openInBrowser(url: string): Promise<void> {\n const os = platform();\n try {\n if (os === \"darwin\") {\n await execa(\"open\", [url], { stdio: \"ignore\" });\n } else if (os === \"win32\") {\n await execa(\"cmd\", [\"/c\", \"start\", \"\", url], {\n stdio: \"ignore\",\n windowsVerbatimArguments: true,\n });\n } else {\n await execa(\"xdg-open\", [url], { stdio: \"ignore\" });\n }\n } catch {\n throw new Error(`Failed to open browser. Open this URL manually:\\n ${url}`);\n }\n}\n"],"mappings":"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"}