mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-26 17:43:00 +00:00
fix(console): validate auth redirects (#45027)
This commit is contained in:
parent
1cc53890dc
commit
ba4d0ea8bf
6 changed files with 52 additions and 1 deletions
1
bun.lock
1
bun.lock
|
|
@ -235,6 +235,7 @@
|
|||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"openai": "5.11.0",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "catalog:",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"openai": "5.11.0",
|
||||
"typescript": "catalog:",
|
||||
|
|
|
|||
18
packages/console/function/src/auth-redirect.test.ts
Normal file
18
packages/console/function/src/auth-redirect.test.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { isAllowedAuthorizationRedirect } from "./auth-redirect"
|
||||
|
||||
describe("authorization redirect validation", () => {
|
||||
test("allows registered OpenCode callbacks", () => {
|
||||
expect(isAllowedAuthorizationRedirect("app", "https://opencode.ai/auth/callback")).toBe(true)
|
||||
expect(isAllowedAuthorizationRedirect("app", "https://dev.opencode.ai/auth/callback")).toBe(true)
|
||||
expect(isAllowedAuthorizationRedirect("app", "http://localhost:3000/auth/callback")).toBe(true)
|
||||
expect(isAllowedAuthorizationRedirect("app", "http://127.0.0.1:3000/auth/callback")).toBe(true)
|
||||
})
|
||||
|
||||
test("rejects unregistered clients and external redirects", () => {
|
||||
expect(isAllowedAuthorizationRedirect("other", "https://opencode.ai/auth/callback")).toBe(false)
|
||||
expect(isAllowedAuthorizationRedirect("app", "https://evil.example/callback")).toBe(false)
|
||||
expect(isAllowedAuthorizationRedirect("app", "https://opencode.ai.evil.example/callback")).toBe(false)
|
||||
expect(isAllowedAuthorizationRedirect("app", "javascript:alert(1)")).toBe(false)
|
||||
})
|
||||
})
|
||||
18
packages/console/function/src/auth-redirect.ts
Normal file
18
packages/console/function/src/auth-redirect.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export const isAllowedAuthorizationRedirect = (clientID: string, redirectURI: string) => {
|
||||
if (clientID !== "app") return false
|
||||
const redirect = (() => {
|
||||
try {
|
||||
return new URL(redirectURI)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
})()
|
||||
if (redirect === undefined) return false
|
||||
if (redirect.hostname === "localhost" || redirect.hostname === "127.0.0.1") {
|
||||
return redirect.protocol === "http:" || redirect.protocol === "https:"
|
||||
}
|
||||
return (
|
||||
redirect.protocol === "https:" &&
|
||||
(redirect.hostname === "opencode.ai" || redirect.hostname.endsWith(".opencode.ai"))
|
||||
)
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.j
|
|||
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
|
||||
import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
|
||||
import { Identifier } from "@opencode-ai/console-core/identifier.js"
|
||||
import { isAllowedAuthorizationRedirect } from "./auth-redirect.js"
|
||||
|
||||
type Env = {
|
||||
AuthStorage: KVNamespace
|
||||
|
|
@ -41,6 +42,17 @@ const MY_THEME: Theme = {
|
|||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
||||
const requestURL = new URL(request.url)
|
||||
if (requestURL.pathname === "/authorize") {
|
||||
const redirectURI = requestURL.searchParams.get("redirect_uri")
|
||||
if (
|
||||
redirectURI !== null &&
|
||||
!isAllowedAuthorizationRedirect(requestURL.searchParams.get("client_id") ?? "", redirectURI)
|
||||
) {
|
||||
return new Response("Unauthorized client", { status: 400 })
|
||||
}
|
||||
}
|
||||
|
||||
const result = await issuer({
|
||||
theme: MY_THEME,
|
||||
providers: {
|
||||
|
|
@ -102,6 +114,7 @@ export default {
|
|||
namespace: env.AuthStorage,
|
||||
}),
|
||||
subjects,
|
||||
allow: ({ clientID, redirectURI }) => Promise.resolve(isAllowedAuthorizationRedirect(clientID, redirectURI)),
|
||||
async success(ctx, response) {
|
||||
console.log(response)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@
|
|||
"moduleResolution": "bundler",
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "react",
|
||||
"types": ["@cloudflare/workers-types", "node"]
|
||||
"types": ["@cloudflare/workers-types", "bun", "node"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue