File size: 5,144 Bytes
116a473 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | import { Resource } from "@opencode-ai/console-resource"
import { and, Database, eq, isNull } from "../src/drizzle/index.js"
import { Identifier } from "../src/identifier.js"
import { AccountTable } from "../src/schema/account.sql.js"
import { AuthTable } from "../src/schema/auth.sql.js"
import { BillingTable } from "../src/schema/billing.sql.js"
import { KeyTable } from "../src/schema/key.sql.js"
import { UserTable } from "../src/schema/user.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
import { centsToMicroCents } from "../src/util/price.js"
const args = parseArgs(process.argv.slice(2))
if (!args.email) {
console.error(
"Usage: bun script/create-api-key.ts --email <email> [--workspace-id <wrk_...>] [--workspace-name <name>] [--key-name <name>] [--balance-dollars <amount>] [--allow-production]",
)
process.exit(1)
}
if (Resource.App.stage === "production" && !args.allowProduction) {
throw new Error("Refusing to create a production API key without --allow-production")
}
const result = await Database.transaction(async (tx) => {
const auth = await tx
.select()
.from(AuthTable)
.where(and(eq(AuthTable.provider, "email"), eq(AuthTable.subject, args.email)))
.then((rows) => rows[0])
const accountID = auth?.accountID ?? Identifier.create("account")
if (!auth) {
await tx.insert(AccountTable).values({ id: accountID })
await tx.insert(AuthTable).values({
id: Identifier.create("auth"),
provider: "email",
subject: args.email,
accountID,
})
}
const workspace = args.workspaceID
? await tx
.select()
.from(WorkspaceTable)
.where(eq(WorkspaceTable.id, args.workspaceID))
.then((rows) => rows[0])
: await tx
.select({ workspace: WorkspaceTable })
.from(UserTable)
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
.where(and(eq(UserTable.accountID, accountID), isNull(UserTable.timeDeleted)))
.then((rows) => rows[0]?.workspace)
if (args.workspaceID && !workspace) throw new Error(`Workspace not found: ${args.workspaceID}`)
const workspaceID = workspace?.id ?? Identifier.create("workspace")
if (!workspace) {
await tx.insert(WorkspaceTable).values({
id: workspaceID,
slug: null,
name: args.workspaceName ?? `${args.email} manual`,
})
}
const user = await tx
.select()
.from(UserTable)
.where(
and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.accountID, accountID), isNull(UserTable.timeDeleted)),
)
.then((rows) => rows[0])
const userID = user?.id ?? Identifier.create("user")
if (!user) {
await tx.insert(UserTable).values({
id: userID,
workspaceID,
accountID,
email: args.email,
name: args.email,
role: "admin",
})
}
const balance = centsToMicroCents(args.balanceDollars * 100)
const billing = await tx
.select()
.from(BillingTable)
.where(eq(BillingTable.workspaceID, workspaceID))
.then((rows) => rows[0])
if (!billing) {
await tx.insert(BillingTable).values({
id: Identifier.create("billing"),
workspaceID,
balance,
})
} else if (billing.balance < balance) {
await tx.update(BillingTable).set({ balance }).where(eq(BillingTable.workspaceID, workspaceID))
}
const secretKey = createSecretKey()
const keyID = Identifier.create("key")
await tx.insert(KeyTable).values({
id: keyID,
workspaceID,
userID,
name: args.keyName ?? "Manual API Key",
key: secretKey,
timeUsed: null,
})
return { accountID, workspaceID, userID, keyID, secretKey }
})
console.log(JSON.stringify({ stage: Resource.App.stage, ...result }, null, 2))
function createSecretKey() {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
const values = new Uint32Array(64)
crypto.getRandomValues(values)
return `sk-${Array.from(values, (value) => chars[value % chars.length]).join("")}`
}
function parseArgs(argv: string[]) {
const parsed = {
email: "",
workspaceID: "",
workspaceName: "",
keyName: "",
balanceDollars: 100,
allowProduction: false,
}
for (let index = 0; index < argv.length; index++) {
const arg = argv[index]
if (arg === "--email") parsed.email = requiredValue(argv, ++index, arg)
if (arg === "--workspace-id") parsed.workspaceID = requiredValue(argv, ++index, arg)
if (arg === "--workspace-name") parsed.workspaceName = requiredValue(argv, ++index, arg)
if (arg === "--key-name") parsed.keyName = requiredValue(argv, ++index, arg)
if (arg === "--balance-dollars") parsed.balanceDollars = Number(requiredValue(argv, ++index, arg))
if (arg === "--allow-production") parsed.allowProduction = true
}
if (!Number.isFinite(parsed.balanceDollars) || parsed.balanceDollars < 0) throw new Error("Invalid --balance-dollars")
return parsed
}
function requiredValue(argv: string[], index: number, arg: string) {
const value = argv[index]
if (!value || value.startsWith("--")) throw new Error(`Missing value for ${arg}`)
return value
}
|