mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-28 13:33:35 +08:00
fix(console): restrict legacy access to Black (#50716)
This commit is contained in:
@@ -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<Actor.Info> => {
|
||||
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<Actor.Info> => {
|
||||
}
|
||||
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<Actor.Info> => {
|
||||
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<Actor.Info> => {
|
||||
})()
|
||||
return evt.locals.actor
|
||||
}
|
||||
|
||||
async function requireBlackAccount(account: { id: string; email: string }): Promise<Actor.Info> {
|
||||
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" } })
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))!
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user