From fe51b0b19ae44184df92db24394cdcdf8c7aef14 Mon Sep 17 00:00:00 2001 From: vprdev Date: Tue, 22 Sep 2026 12:23:33 -0700 Subject: [PATCH 1/2] fix(console): restrict legacy access to Black (#50716) --- packages/console/app/src/context/auth.ts | 71 ++++++++--- packages/console/app/src/routes/auth/index.ts | 3 +- packages/console/app/src/routes/go/index.tsx | 2 +- .../src/routes/workspace/[id]/go/index.tsx | 3 +- packages/console/core/src/billing.ts | 1 + packages/console/function/src/auth.ts | 114 +++++++----------- 6 files changed, 104 insertions(+), 90 deletions(-) diff --git a/packages/console/app/src/context/auth.ts b/packages/console/app/src/context/auth.ts index 90669d764703..25a28321f32c 100644 --- a/packages/console/app/src/context/auth.ts +++ b/packages/console/app/src/context/auth.ts @@ -1,7 +1,8 @@ import { getRequestEvent } from "solid-js/web" -import { and, Database, eq, inArray, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js" +import { and, Database, eq, inArray, isNotNull, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js" import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" +import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js" import { redirect } from "@solidjs/router" import { Actor } from "@opencode-ai/console-core/actor.js" @@ -48,28 +49,14 @@ export const getActor = async (workspace?: string): Promise => { if (!workspace) { const account = auth.data.account ?? {} const current = account[auth.data.current ?? ""] - if (current) { - return { - type: "account", - properties: { - email: current.email, - accountID: current.id, - }, - } - } + if (current) return requireBlackAccount(current) if (Object.keys(account).length > 0) { const current = Object.values(account)[0] await auth.update((val) => ({ ...val, current: current.id, })) - return { - type: "account", - properties: { - email: current.email, - accountID: current.id, - }, - } + return requireBlackAccount(current) } return { type: "public", @@ -78,6 +65,22 @@ export const getActor = async (workspace?: string): Promise => { } const accounts = Object.keys(auth.data.account ?? {}) if (accounts.length) { + const blackAccounts = await Database.use((tx) => + tx + .selectDistinct({ accountID: UserTable.accountID }) + .from(UserTable) + .innerJoin(BillingTable, eq(BillingTable.workspaceID, UserTable.workspaceID)) + .where( + and( + inArray(UserTable.accountID, accounts), + isNull(UserTable.timeDeleted), + isNotNull(BillingTable.subscriptionID), + ), + ) + .then((rows) => rows.map((row) => row.accountID).filter((accountID): accountID is string => !!accountID)), + ) + if (!blackAccounts.length) throw redirectToNewConsole() + const user = await Database.use((tx) => tx .select({ @@ -93,7 +96,7 @@ export const getActor = async (workspace?: string): Promise => { and( eq(UserTable.workspaceID, workspace), isNull(UserTable.timeDeleted), - inArray(UserTable.accountID, accounts), + inArray(UserTable.accountID, blackAccounts), ), ) .limit(1) @@ -131,3 +134,35 @@ export const getActor = async (workspace?: string): Promise => { })() return evt.locals.actor } + +async function requireBlackAccount(account: { id: string; email: string }): Promise { + const black = await Database.use((tx) => + tx + .select({ id: UserTable.id }) + .from(UserTable) + .innerJoin(BillingTable, eq(BillingTable.workspaceID, UserTable.workspaceID)) + .where( + and( + eq(UserTable.accountID, account.id), + isNull(UserTable.timeDeleted), + isNotNull(BillingTable.subscriptionID), + ), + ) + .limit(1) + .then((rows) => rows[0]), + ) + if (!black) throw redirectToNewConsole() + return { + type: "account", + properties: { + email: account.email, + accountID: account.id, + }, + } +} + +function redirectToNewConsole() { + const destination = Resource.ConsoleMigration.consoleUrl + if (!destination) throw new Error("New Console URL is not configured") + return redirect(`${destination}/login`, { status: 302, headers: { "Cache-Control": "no-store" } }) +} diff --git a/packages/console/app/src/routes/auth/index.ts b/packages/console/app/src/routes/auth/index.ts index 3840d653e13b..6fc52ad8e5f4 100644 --- a/packages/console/app/src/routes/auth/index.ts +++ b/packages/console/app/src/routes/auth/index.ts @@ -14,7 +14,8 @@ export async function GET(input: APIEvent) { return redirect(`${destination}/login`, { headers: { "Cache-Control": "no-store" } }) } return redirect(route(locale, `/workspace/${workspaceID}`)) - } catch { + } catch (error) { + if (error instanceof Response) throw error return redirect("/auth/authorize") } } diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index 1fba8a147bab..dc64d6835d60 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -59,7 +59,7 @@ const models = [ export default function Home() { const workspaceID = createAsync(() => checkLoggedIn()) - const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/go` : "/auth")) + const subscribeUrl = createMemo(() => "/console/go") const i18n = useI18n() const language = useLanguage() return ( diff --git a/packages/console/app/src/routes/workspace/[id]/go/index.tsx b/packages/console/app/src/routes/workspace/[id]/go/index.tsx index c73aae7daf8d..6be49815d879 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/index.tsx @@ -1,4 +1,4 @@ -import { createAsync, useParams } from "@solidjs/router" +import { createAsync, redirect, useParams } from "@solidjs/router" import { Show } from "solid-js" import { IconGo } from "~/component/icon" import { GoReferralSection, queryGoReferral } from "~/component/go-referral" @@ -7,6 +7,7 @@ import { useLanguage } from "~/context/language" import { LiteSection, queryLiteSubscription } from "./lite-section" export default function () { + throw redirect("/console/go") const params = useParams() const i18n = useI18n() const language = useLanguage() diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index adeabd9c73c8..19427044a944 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -304,6 +304,7 @@ export namespace Billing { }), async (input) => { const user = Actor.assert("user") + throw new Error("Go subscriptions have moved to the new Console") const { successUrl, cancelUrl, method } = input const email = (await User.getAuthEmail(user.properties.userID))! diff --git a/packages/console/function/src/auth.ts b/packages/console/function/src/auth.ts index 457ccc571d52..cdd5b7e438b9 100644 --- a/packages/console/function/src/auth.ts +++ b/packages/console/function/src/auth.ts @@ -7,15 +7,14 @@ import { THEME_OPENAUTH } from "@openauthjs/openauth/ui/theme" import { GithubProvider } from "@openauthjs/openauth/provider/github" import { GoogleOidcProvider } from "@openauthjs/openauth/provider/google" import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare" -import { Account } from "@opencode-ai/console-core/account.js" -import { Workspace } from "@opencode-ai/console-core/workspace.js" import { Actor } from "@opencode-ai/console-core/actor.js" import { Resource } from "@opencode-ai/console-resource" import { User } from "@opencode-ai/console-core/user.js" -import { and, Database, eq, isNull, or } from "@opencode-ai/console-core/drizzle/index.js" +import { and, Database, eq, isNotNull, isNull, or } from "@opencode-ai/console-core/drizzle/index.js" import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js" +import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js" import { Identifier } from "@opencode-ai/console-core/identifier.js" import { isAllowedAuthorizationRedirect } from "./auth-redirect.js" @@ -115,7 +114,7 @@ export default { }), subjects, allow: ({ clientID, redirectURI }) => Promise.resolve(isAllowedAuthorizationRedirect(clientID, redirectURI)), - async success(ctx, response) { + async success(ctx, response, request) { console.log(response) let subject: string | undefined @@ -155,85 +154,62 @@ export default { throw new Error("Invalid email") } - // Get account - let newAccount = false - const accountID = await (async () => { - const matches = await Database.use(async (tx) => - tx - .select({ - provider: AuthTable.provider, - accountID: AuthTable.accountID, - }) - .from(AuthTable) - .where( - or( - and(eq(AuthTable.provider, response.provider), eq(AuthTable.subject, subject)), - and(eq(AuthTable.provider, "email"), eq(AuthTable.subject, email)), - ), + const matches = await Database.use((tx) => + tx + .select({ provider: AuthTable.provider, accountID: AuthTable.accountID }) + .from(AuthTable) + .where( + or( + and(eq(AuthTable.provider, response.provider), eq(AuthTable.subject, subject)), + and(eq(AuthTable.provider, "email"), eq(AuthTable.subject, email)), ), - ) - const idByProvider = matches.find((x) => x.provider === response.provider)?.accountID - const idByEmail = matches.find((x) => x.provider === "email")?.accountID - if (idByProvider && idByEmail) return idByProvider + ), + ) + const accountID = + matches.find((match) => match.provider === response.provider)?.accountID ?? + matches.find((match) => match.provider === "email")?.accountID + if (!accountID) return redirectToNewConsole(request) - // create account if not found - let accountID = idByProvider ?? idByEmail - if (!accountID) { - console.log("creating account for", email) - accountID = await Account.create({}) - newAccount = true - } - - await Database.use(async (tx) => - tx - .insert(AuthTable) - .values([ - { - id: Identifier.create("auth"), - accountID, - provider: response.provider, - subject, - }, - { - id: Identifier.create("auth"), - accountID, - provider: "email", - subject: email, - }, - ]) - .onDuplicateKeyUpdate({ - set: { - timeDeleted: null, - }, - }), - ) - - return accountID - })() - - // Get workspace - await Actor.provide("account", { accountID, email }, async () => { + const black = await Actor.provide("account", { accountID, email }, async () => { await User.joinInvitedWorkspaces() - const workspaces = await Database.use((tx) => + return Database.use((tx) => tx - .select({ id: WorkspaceTable.id }) - .from(WorkspaceTable) - .innerJoin(UserTable, eq(UserTable.workspaceID, WorkspaceTable.id)) + .select({ id: UserTable.id }) + .from(UserTable) + .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID)) + .innerJoin(BillingTable, eq(BillingTable.workspaceID, UserTable.workspaceID)) .where( and( eq(UserTable.accountID, accountID), isNull(UserTable.timeDeleted), isNull(WorkspaceTable.timeDeleted), + isNotNull(BillingTable.subscriptionID), ), - ), + ) + .limit(1) + .then((rows) => rows[0]), ) - if (workspaces.length === 0) { - await Workspace.create({ name: "Default" }) - } }) - return ctx.subject("account", accountID, { accountID, email, newAccount }) + if (!black) return redirectToNewConsole(request) + + await Database.use((tx) => + tx + .insert(AuthTable) + .values([ + { id: Identifier.create("auth"), accountID, provider: response.provider, subject }, + { id: Identifier.create("auth"), accountID, provider: "email", subject: email }, + ]) + .onDuplicateKeyUpdate({ set: { timeDeleted: null } }), + ) + return ctx.subject("account", accountID, { accountID, email, newAccount: false }) }, }).fetch(request, env, ctx) return result }, } + +function redirectToNewConsole(request: Request) { + const destination = new URL("/console", request.url) + destination.hostname = destination.hostname.replace(/^auth\./, "") + return Response.redirect(destination.toString(), 302) +} From 2406400f0aeb07b36d0495af4e05aaca49159832 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 22 Sep 2026 19:25:10 +0000 Subject: [PATCH 2/2] chore: generate --- packages/console/app/src/context/auth.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/console/app/src/context/auth.ts b/packages/console/app/src/context/auth.ts index 25a28321f32c..864b488f5177 100644 --- a/packages/console/app/src/context/auth.ts +++ b/packages/console/app/src/context/auth.ts @@ -142,11 +142,7 @@ async function requireBlackAccount(account: { id: string; email: string }): Prom .from(UserTable) .innerJoin(BillingTable, eq(BillingTable.workspaceID, UserTable.workspaceID)) .where( - and( - eq(UserTable.accountID, account.id), - isNull(UserTable.timeDeleted), - isNotNull(BillingTable.subscriptionID), - ), + and(eq(UserTable.accountID, account.id), isNull(UserTable.timeDeleted), isNotNull(BillingTable.subscriptionID)), ) .limit(1) .then((rows) => rows[0]),