summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorYukai Huang <[email protected]>2025-12-11 06:23:12 +0800
committerGitHub <[email protected]>2025-12-10 16:23:12 -0600
commite46080aa8c34ef3132d76b412cc6e750a2f16b32 (patch)
tree0ae872efb9dd88d67a65b3038a96bca02830e536
parent7d82f1769cbde45384467308106099610fb7810a (diff)
downloadopencode-e46080aa8c34ef3132d76b412cc6e750a2f16b32.tar.gz
opencode-e46080aa8c34ef3132d76b412cc6e750a2f16b32.zip
fix(auth): add plugin lookup for custom provider in 'Other' flow (#5324)
-rw-r--r--packages/opencode/src/cli/cmd/auth.ts294
1 files changed, 158 insertions, 136 deletions
diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts
index 61fe4e5bd..658329fb6 100644
--- a/packages/opencode/src/cli/cmd/auth.ts
+++ b/packages/opencode/src/cli/cmd/auth.ts
@@ -10,6 +10,154 @@ import { Config } from "../../config/config"
import { Global } from "../../global"
import { Plugin } from "../../plugin"
import { Instance } from "../../project/instance"
+import type { Hooks } from "@opencode-ai/plugin"
+
+type PluginAuth = NonNullable<Hooks["auth"]>
+
+/**
+ * Handle plugin-based authentication flow.
+ * Returns true if auth was handled, false if it should fall through to default handling.
+ */
+async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string): Promise<boolean> {
+ let index = 0
+ if (plugin.auth.methods.length > 1) {
+ const method = await prompts.select({
+ message: "Login method",
+ options: [
+ ...plugin.auth.methods.map((x, index) => ({
+ label: x.label,
+ value: index.toString(),
+ })),
+ ],
+ })
+ if (prompts.isCancel(method)) throw new UI.CancelledError()
+ index = parseInt(method)
+ }
+ const method = plugin.auth.methods[index]
+
+ // Handle prompts for all auth types
+ await new Promise((resolve) => setTimeout(resolve, 10))
+ const inputs: Record<string, string> = {}
+ if (method.prompts) {
+ for (const prompt of method.prompts) {
+ if (prompt.condition && !prompt.condition(inputs)) {
+ continue
+ }
+ if (prompt.type === "select") {
+ const value = await prompts.select({
+ message: prompt.message,
+ options: prompt.options,
+ })
+ if (prompts.isCancel(value)) throw new UI.CancelledError()
+ inputs[prompt.key] = value
+ } else {
+ const value = await prompts.text({
+ message: prompt.message,
+ placeholder: prompt.placeholder,
+ validate: prompt.validate ? (v) => prompt.validate!(v ?? "") : undefined,
+ })
+ if (prompts.isCancel(value)) throw new UI.CancelledError()
+ inputs[prompt.key] = value
+ }
+ }
+ }
+
+ if (method.type === "oauth") {
+ const authorize = await method.authorize(inputs)
+
+ if (authorize.url) {
+ prompts.log.info("Go to: " + authorize.url)
+ }
+
+ if (authorize.method === "auto") {
+ if (authorize.instructions) {
+ prompts.log.info(authorize.instructions)
+ }
+ const spinner = prompts.spinner()
+ spinner.start("Waiting for authorization...")
+ const result = await authorize.callback()
+ if (result.type === "failed") {
+ spinner.stop("Failed to authorize", 1)
+ }
+ if (result.type === "success") {
+ const saveProvider = result.provider ?? provider
+ if ("refresh" in result) {
+ const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
+ await Auth.set(saveProvider, {
+ type: "oauth",
+ refresh,
+ access,
+ expires,
+ ...extraFields,
+ })
+ }
+ if ("key" in result) {
+ await Auth.set(saveProvider, {
+ type: "api",
+ key: result.key,
+ })
+ }
+ spinner.stop("Login successful")
+ }
+ }
+
+ if (authorize.method === "code") {
+ const code = await prompts.text({
+ message: "Paste the authorization code here: ",
+ validate: (x) => (x && x.length > 0 ? undefined : "Required"),
+ })
+ if (prompts.isCancel(code)) throw new UI.CancelledError()
+ const result = await authorize.callback(code)
+ if (result.type === "failed") {
+ prompts.log.error("Failed to authorize")
+ }
+ if (result.type === "success") {
+ const saveProvider = result.provider ?? provider
+ if ("refresh" in result) {
+ const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
+ await Auth.set(saveProvider, {
+ type: "oauth",
+ refresh,
+ access,
+ expires,
+ ...extraFields,
+ })
+ }
+ if ("key" in result) {
+ await Auth.set(saveProvider, {
+ type: "api",
+ key: result.key,
+ })
+ }
+ prompts.log.success("Login successful")
+ }
+ }
+
+ prompts.outro("Done")
+ return true
+ }
+
+ if (method.type === "api") {
+ if (method.authorize) {
+ const result = await method.authorize(inputs)
+ if (result.type === "failed") {
+ prompts.log.error("Failed to authorize")
+ }
+ if (result.type === "success") {
+ const saveProvider = result.provider ?? provider
+ await Auth.set(saveProvider, {
+ type: "api",
+ key: result.key,
+ })
+ prompts.log.success("Login successful")
+ }
+ prompts.outro("Done")
+ return true
+ }
+ }
+
+ return false
+}
export const AuthCommand = cmd({
command: "auth",
@@ -160,142 +308,8 @@ export const AuthLoginCommand = cmd({
const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider))
if (plugin && plugin.auth) {
- let index = 0
- if (plugin.auth.methods.length > 1) {
- const method = await prompts.select({
- message: "Login method",
- options: [
- ...plugin.auth.methods.map((x, index) => ({
- label: x.label,
- value: index.toString(),
- })),
- ],
- })
- if (prompts.isCancel(method)) throw new UI.CancelledError()
- index = parseInt(method)
- }
- const method = plugin.auth.methods[index]
-
- // Handle prompts for all auth types
- await new Promise((resolve) => setTimeout(resolve, 10))
- const inputs: Record<string, string> = {}
- if (method.prompts) {
- for (const prompt of method.prompts) {
- if (prompt.condition && !prompt.condition(inputs)) {
- continue
- }
- if (prompt.type === "select") {
- const value = await prompts.select({
- message: prompt.message,
- options: prompt.options,
- })
- if (prompts.isCancel(value)) throw new UI.CancelledError()
- inputs[prompt.key] = value
- } else {
- const value = await prompts.text({
- message: prompt.message,
- placeholder: prompt.placeholder,
- validate: prompt.validate ? (v) => prompt.validate!(v ?? "") : undefined,
- })
- if (prompts.isCancel(value)) throw new UI.CancelledError()
- inputs[prompt.key] = value
- }
- }
- }
-
- if (method.type === "oauth") {
- const authorize = await method.authorize(inputs)
-
- if (authorize.url) {
- prompts.log.info("Go to: " + authorize.url)
- }
-
- if (authorize.method === "auto") {
- if (authorize.instructions) {
- prompts.log.info(authorize.instructions)
- }
- const spinner = prompts.spinner()
- spinner.start("Waiting for authorization...")
- const result = await authorize.callback()
- if (result.type === "failed") {
- spinner.stop("Failed to authorize", 1)
- }
- if (result.type === "success") {
- const saveProvider = result.provider ?? provider
- if ("refresh" in result) {
- const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
- await Auth.set(saveProvider, {
- type: "oauth",
- refresh,
- access,
- expires,
- ...extraFields,
- })
- }
- if ("key" in result) {
- await Auth.set(saveProvider, {
- type: "api",
- key: result.key,
- })
- }
- spinner.stop("Login successful")
- }
- }
-
- if (authorize.method === "code") {
- const code = await prompts.text({
- message: "Paste the authorization code here: ",
- validate: (x) => (x && x.length > 0 ? undefined : "Required"),
- })
- if (prompts.isCancel(code)) throw new UI.CancelledError()
- const result = await authorize.callback(code)
- if (result.type === "failed") {
- prompts.log.error("Failed to authorize")
- }
- if (result.type === "success") {
- const saveProvider = result.provider ?? provider
- if ("refresh" in result) {
- const { type: _, provider: __, refresh, access, expires, ...extraFields } = result
- await Auth.set(saveProvider, {
- type: "oauth",
- refresh,
- access,
- expires,
- ...extraFields,
- })
- }
- if ("key" in result) {
- await Auth.set(saveProvider, {
- type: "api",
- key: result.key,
- })
- }
- prompts.log.success("Login successful")
- }
- }
-
- prompts.outro("Done")
- return
- }
-
- if (method.type === "api") {
- if (method.authorize) {
- const result = await method.authorize(inputs)
- if (result.type === "failed") {
- prompts.log.error("Failed to authorize")
- }
- if (result.type === "success") {
- const saveProvider = result.provider ?? provider
- await Auth.set(saveProvider, {
- type: "api",
- key: result.key,
- })
- prompts.log.success("Login successful")
- }
- prompts.outro("Done")
- return
- }
- }
+ const handled = await handlePluginAuth({ auth: plugin.auth }, provider)
+ if (handled) return
}
if (provider === "other") {
@@ -306,6 +320,14 @@ export const AuthLoginCommand = cmd({
if (prompts.isCancel(provider)) throw new UI.CancelledError()
provider = provider.replace(/^@ai-sdk\//, "")
if (prompts.isCancel(provider)) throw new UI.CancelledError()
+
+ // Check if a plugin provides auth for this custom provider
+ const customPlugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider))
+ if (customPlugin && customPlugin.auth) {
+ const handled = await handlePluginAuth({ auth: customPlugin.auth }, provider)
+ if (handled) return
+ }
+
prompts.log.warn(
`This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
)