Skip to content
Open
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
15 changes: 14 additions & 1 deletion packages/app/src/components/dialog-connect-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,24 @@ export function DialogConnectProvider(props: { provider: string }) {
async function complete() {
await globalSDK.client.global.dispose()
dialog.close()

// Fetch identity to show which account was connected
let email: string | undefined
try {
const response = await fetch("/auth/identity")
if (response.ok) {
const identities = (await response.json()) as Record<string, string>
email = identities[props.provider]
}
} catch {}

showToast({
variant: "success",
icon: "circle-check",
title: language.t("provider.connect.toast.connected.title", { provider: provider().name }),
description: language.t("provider.connect.toast.connected.description", { provider: provider().name }),
description: email
? language.t("provider.connect.toast.connected.description", { provider: provider().name }) + ` (${email})`
: language.t("provider.connect.toast.connected.description", { provider: provider().name }),
})
}

Expand Down
18 changes: 17 additions & 1 deletion packages/app/src/components/settings-providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { Tag } from "@opencode-ai/ui/tag"
import { showToast } from "@opencode-ai/ui/toast"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { createMemo, type Component, For, Show } from "solid-js"
import { createMemo, createSignal, onMount, type Component, For, Show } from "solid-js"
import { useLanguage } from "@/context/language"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
Expand Down Expand Up @@ -34,6 +34,17 @@ export const SettingsProviders: Component = () => {
const globalSync = useGlobalSync()
const providers = useProviders()

const [identity, setIdentity] = createSignal<Record<string, string>>({})

onMount(async () => {
try {
const response = await fetch("/auth/identity")
if (response.ok) {
setIdentity(await response.json())
}
} catch {}
})

const connected = createMemo(() => {
return providers
.connected()
Expand Down Expand Up @@ -153,6 +164,11 @@ export const SettingsProviders: Component = () => {
<ProviderIcon id={item.id} class="size-5 shrink-0 icon-strong-base" />
<span class="text-14-medium text-text-strong truncate">{item.name}</span>
<Tag>{type(item)}</Tag>
<Show when={identity()[item.id]}>
{(email) => (
<span class="text-12-regular text-text-weak truncate">{email()}</span>
)}
</Show>
</div>
<Show
when={canDisconnect(item)}
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/auth/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class Oauth extends Schema.Class<Oauth>("OAuth")({
expires: Schema.Number,
accountId: Schema.optional(Schema.String),
enterpriseUrl: Schema.optional(Schema.String),
email: Schema.optional(Schema.String),
}) {}

export class Api extends Schema.Class<Api>("ApiAuth")({
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export namespace Auth {
expires: z.number(),
accountId: z.string().optional(),
enterpriseUrl: z.string().optional(),
email: z.string().optional(),
})
.meta({ ref: "OAuth" })

Expand Down
19 changes: 19 additions & 0 deletions packages/opencode/src/plugin/copilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
expires: number
provider?: string
enterpriseUrl?: string
email?: string
} = {
type: "success",
refresh: data.access_token,
Expand All @@ -264,6 +265,24 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
result.enterpriseUrl = domain
}

// Fetch GitHub user identity (best-effort)
try {
const apiBase = domain === "github.com"
? "https://api.github.com"
: `https://${domain}/api/v3`
const userResponse = await fetch(`${apiBase}/user`, {
headers: {
Authorization: `Bearer ${data.access_token}`,
Accept: "application/json",
"User-Agent": `opencode/${Installation.VERSION}`,
},
})
if (userResponse.ok) {
const user = (await userResponse.json()) as { login?: string; email?: string }
result.email = user.login ?? user.email
}
} catch {}

return result
}

Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/provider/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export namespace ProviderAuth {
refresh: result.refresh,
expires: result.expires,
...(result.accountId ? { accountId: result.accountId } : {}),
...(result.email ? { email: result.email } : {}),
})
}
})
Expand Down
28 changes: 28 additions & 0 deletions packages/opencode/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,34 @@ export namespace Server {
return c.json(true)
},
)
.get(
"/auth/identity",
describeRoute({
summary: "Get provider identities",
description: "Get the email or username associated with each authenticated provider.",
operationId: "auth.identity",
responses: {
200: {
description: "Map of provider IDs to identity strings",
content: {
"application/json": {
schema: resolver(z.record(z.string(), z.string())),
},
},
},
},
}),
async (c) => {
const all = await Auth.all()
const identity: Record<string, string> = {}
for (const [providerID, info] of Object.entries(all)) {
if (info.type === "oauth" && "email" in info && info.email) {
identity[providerID] = info.email
}
}
return c.json(identity)
},
)
.use(async (c, next) => {
if (c.req.path === "/log") return next()
const rawWorkspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace")
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export type AuthOuathResult = { url: string; instructions: string } & (
| ({
type: "success"
provider?: string
email?: string
} & (
| {
refresh: string
Expand All @@ -143,6 +144,7 @@ export type AuthOuathResult = { url: string; instructions: string } & (
| ({
type: "success"
provider?: string
email?: string
} & (
| {
refresh: string
Expand Down
Loading