everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
1 lines • 17.1 kB
Source Map (JSON)
{"version":3,"file":"keys.cjs","names":["ResolutionSessionService","DeviceLoginService","SessionStore","getNetworkIdForAccount","Effect","colors","openInBrowser","process","loginOutlayerCli","SiteClient","mintPublishKey","parseKey"],"sources":["../../src/handlers/keys.ts"],"sourcesContent":["import process from \"node:process\";\nimport { Effect } from \"effect\";\nimport { parseKey } from \"near-kit\";\nimport {\n DEFAULT_DEPLOY_KEY_EXPIRES_IN_SECONDS,\n DEFAULT_LOGIN_EXPIRES_IN_SECONDS,\n type LoginResult,\n type LogoutResult,\n} from \"../contract\";\nimport { DeviceLoginService, openInBrowser } from \"../services/auth/device-login\";\nimport { loginOutlayerCli } from \"../services/auth/outlayer\";\nimport { mintPublishKey } from \"../services/auth/publish-key\";\nimport type { SessionCredential } from \"../services/auth/session\";\nimport { SessionStore } from \"../services/auth/session-store\";\nimport { SiteClient, type SiteClientShape } from \"../services/auth/site-client\";\nimport type { ResolutionSession } from \"../services/resolution/session\";\nimport { ResolutionSessionService } from \"../services/resolution/session\";\nimport { getNetworkIdForAccount } from \"../utils/network\";\nimport { colors } from \"../utils/theme\";\nimport type { BosBuilder } from \"./shared\";\n\nexport function registerKeys(builder: BosBuilder) {\n return {\n login: builder.login.effect(function* (_opts, input) {\n const session = yield* ResolutionSessionService;\n const deviceLogin = yield* DeviceLoginService;\n const sessions = yield* SessionStore;\n const staging = input.env === \"staging\";\n const config = session?.config;\n const account = staging\n ? (config?.staging?.account ?? config?.account ?? \"\")\n : (config?.account ?? \"\");\n const gateway = staging\n ? (config?.staging?.domain ?? config?.domain ?? \"\")\n : (config?.domain ?? \"\");\n const expiresIn =\n input.expiresIn ??\n (input.key ? DEFAULT_DEPLOY_KEY_EXPIRES_IN_SECONDS : DEFAULT_LOGIN_EXPIRES_IN_SECONDS);\n\n const siteUrl = resolveLoginSiteUrl(input.site, session, staging);\n let deployKey: { account: string; gateway: string } | undefined;\n if (input.key) {\n if (!account || !gateway) {\n return {\n status: \"error\" as const,\n siteUrl: siteUrl,\n error:\n \"Deploy keys scope to the authored config's account and domain — set both in bos.config.json (or drop --key for a plain login)\",\n } satisfies LoginResult;\n }\n deployKey = { account, gateway };\n }\n\n const login = yield* deviceLogin\n .start({\n siteUrl,\n device: input.device,\n account: account || undefined,\n custody: input.custody ? { network: getNetworkIdForAccount(account) } : undefined,\n expiresIn,\n deployKey,\n })\n .pipe(\n Effect.catchIf(\n () => true,\n (error: Error) => Effect.succeed({ startFailed: error.message } as const),\n ),\n );\n if (\"startFailed\" in login) {\n return {\n status: \"error\" as const,\n siteUrl: input.site ?? \"\",\n error: login.startFailed,\n } satisfies LoginResult;\n }\n\n console.log();\n console.log(` One-time code: ${colors.cyan(login.userCode)}`);\n yield* Effect.catchIf(\n Effect.tryPromise(() => openInBrowser(login.verificationUrl)),\n () => true,\n (error) => Effect.sync(() => console.log(colors.yellow(` ⚠ ${(error as Error).message}`))),\n );\n console.log();\n console.log(` Waiting for approval at ${colors.dim(login.verificationUrl)}…`);\n\n return yield* Effect.tryPromise({\n try: async () => {\n const approval = await login.waitForApproval();\n\n if (!approval.apiKey) {\n return {\n status: \"error\" as const,\n siteUrl,\n loginUrl: login.verificationUrl,\n error: \"Login approved but no CLI credential was created\",\n } satisfies LoginResult;\n }\n\n const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n const configDir = session?.root ?? process.cwd();\n const existingSession = sessions.read(configDir);\n sessions.write(configDir, {\n version: 1,\n credential: {\n kind: \"session\",\n apiKey: approval.apiKey.key,\n apiKeyId: approval.apiKey.id,\n accountId: approval.accountId,\n label: input.device ?? siteUrl,\n siteUrl,\n createdAt: new Date().toISOString(),\n expiresAt,\n },\n publishKey: null,\n delegateKey: null,\n custodyWallet: approval.custodyWallet ?? existingSession?.custodyWallet ?? null,\n });\n\n let warning: string | null = null;\n if (input.custody && !approval.custodyWallet) {\n warning =\n \"Custody wallet credential could not be fetched after approval. Re-run `bos login --custody` — the wallet is kept server-side.\";\n }\n if (account && approval.accountId && approval.accountId !== account) {\n warning = warning\n ? `${warning} Logged in as ${approval.accountId}, but the authored config account is ${account}. Publishes will use the configured account.`\n : `Logged in as ${approval.accountId}, but the authored config account is ${account}. Publishes will use the configured account.`;\n }\n\n let publishKey: LoginResult[\"publishKey\"] = null;\n if (input.key) {\n try {\n publishKey = await exportPublishKey(sessions, account, input.registry);\n } catch (error) {\n warning = `Session stored, but publish-key export failed: ${\n error instanceof Error ? error.message : \"unknown error\"\n }`;\n }\n }\n\n let outlayerCli: LoginResult[\"outlayerCli\"] = null;\n const custodyWallet = approval.custodyWallet;\n if (input.custody && custodyWallet) {\n outlayerCli = await loginOutlayerCli(custodyWallet);\n if (outlayerCli.status === \"error\") {\n const failure = `OutLayer CLI login failed: ${outlayerCli.detail}`;\n warning = warning ? `${warning} ${failure}` : failure;\n }\n }\n\n return {\n status: \"logged-in\" as const,\n siteUrl,\n accountId: approval.accountId,\n expiresAt,\n loginUrl: login.verificationUrl,\n publishKey,\n deployKey:\n deployKey && approval.apiKey\n ? {\n key: approval.apiKey.key,\n account: deployKey.account,\n gateway: deployKey.gateway,\n }\n : null,\n custodyWallet: approval.custodyWallet\n ? {\n walletId: approval.custodyWallet.walletId,\n nearAccountId: approval.custodyWallet.nearAccountId,\n network: approval.custodyWallet.network,\n handoffUrl: approval.custodyWallet.handoffUrl,\n }\n : null,\n outlayerCli,\n warning,\n } satisfies LoginResult;\n },\n catch: (error) =>\n ({\n status: \"error\" as const,\n siteUrl: input.site ?? \"\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n }) satisfies LoginResult,\n });\n }),\n\n logout: builder.logout.effect(function* (_opts, input) {\n const session = yield* ResolutionSessionService;\n const siteClient = yield* SiteClient;\n const sessions = yield* SessionStore;\n const configDir = input.configDir ?? session?.root ?? process.cwd();\n\n return yield* Effect.tryPromise({\n try: () => logoutBody({ siteClient, sessions, session }, configDir),\n catch: (error) =>\n ({\n status: \"error\" as const,\n revokedApiKey: false,\n removedPublishKey: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n }) satisfies LogoutResult,\n });\n }),\n };\n}\n\nasync function logoutBody(\n deps: {\n siteClient: SiteClientShape;\n sessions: SessionStore[\"Service\"];\n session: ResolutionSession | null;\n },\n configDir: string,\n): Promise<LogoutResult> {\n const { siteClient, sessions, session } = deps;\n const stored = sessions.read(configDir);\n let revokedApiKey = false;\n let removedPublishKey = false;\n let warning: string | null = null;\n\n const credential = stored?.credential;\n if (credential) {\n revokedApiKey = await revokeApiKey(siteClient, credential).catch(() => false);\n if (!revokedApiKey) {\n warning =\n \"Could not revoke the API key remotely — revoke it manually under Settings → API keys.\";\n }\n }\n\n if (stored?.publishKey) {\n const account = stored.credential?.accountId ?? session?.config?.account ?? \"\";\n sessions.removePublishedKeyFile(\n stored.publishKey.network,\n account,\n stored.publishKey.publicKey,\n );\n removedPublishKey = true;\n warning = warning\n ? `${warning} Re-run ${colors.cyan(\"bos login --key\")} to re-export a publish key.`\n : \"Removed the exported publish key. Re-run bos login --key to re-export one.\";\n }\n\n sessions.delete(configDir);\n return {\n status: \"logged-out\" as const,\n revokedApiKey,\n removedPublishKey,\n warning,\n } satisfies LogoutResult;\n}\n\nfunction resolveLoginSiteUrl(\n siteInput: string | undefined,\n session: ResolutionSession | null,\n staging: boolean,\n): string {\n if (siteInput) {\n return siteInput.replace(/\\/+$/, \"\");\n }\n if (staging) {\n const stagingDomain = session?.config?.staging?.domain ?? session?.config?.domain;\n return stagingDomain ? `https://${stagingDomain}` : (session?.runtime?.ui?.url ?? \"\");\n }\n const devUiUrl = session?.runtime?.ui?.url;\n if (devUiUrl && /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)/.test(devUiUrl)) {\n return devUiUrl.replace(/\\/+$/, \"\");\n }\n const domain = session?.config?.domain;\n return domain ? `https://${domain}` : \"\";\n}\n\n/** `login --key`'s export: the shared mint, then the near-cli-rs keystore\n * handoff (~/.near-credentials) with a 0600 permission fix-up. */\nasync function exportPublishKey(\n sessions: { nearCredentialsPath: (network: \"mainnet\" | \"testnet\", account: string) => string },\n account: string,\n registry: string | undefined,\n): Promise<NonNullable<LoginResult[\"publishKey\"]>> {\n const minted = await mintPublishKey({ account, registry });\n\n const { FileKeyStore } = await import(\"near-kit/keys/file\");\n const keyStore = new FileKeyStore(\"~/.near-credentials\", minted.network);\n await keyStore.add(account, parseNearPrivateKey(minted.keyPair.privateKey));\n try {\n const { statSync, chmodSync } = await import(\"node:fs\");\n const credentialPath = sessions.nearCredentialsPath(minted.network, account);\n if (statSync(credentialPath).mode & 0o077) {\n chmodSync(credentialPath, 0o600);\n }\n } catch {}\n\n return {\n publicKey: minted.keyPair.publicKey,\n network: minted.network,\n contract: minted.contract,\n exportedTo: sessions.nearCredentialsPath(minted.network, account),\n };\n}\n\nfunction parseNearPrivateKey(privateKey: string): ReturnType<typeof parseKey> {\n return parseKey(privateKey);\n}\n\nasync function revokeApiKey(\n siteClient: SiteClientShape,\n credential: SessionCredential,\n): Promise<boolean> {\n if (!credential.apiKey || !credential.siteUrl) return false;\n try {\n const response = await siteClient.authPost(\n credential.siteUrl,\n \"/api-key/delete\",\n { keyId: credential.apiKeyId },\n undefined,\n { \"x-api-key\": credential.apiKey },\n AbortSignal.timeout(10_000),\n );\n return response.status >= 200 && response.status < 300;\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}