-
Notifications
You must be signed in to change notification settings - Fork 356
feat(web): add service accounts for non-human API access #1583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| -- CreateEnum | ||
| CREATE TYPE "UserType" AS ENUM ('HUMAN', 'SERVICE'); | ||
|
|
||
| -- AlterTable | ||
| ALTER TABLE "User" ADD COLUMN "createdById" TEXT, | ||
| ADD COLUMN "description" TEXT, | ||
| ADD COLUMN "type" "UserType" NOT NULL DEFAULT 'HUMAN'; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "User" ADD CONSTRAINT "User_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| 'use server'; | ||
|
|
||
| import { createAudit } from "@/ee/features/audit/audit"; | ||
| import { auditActorForUser } from "@/ee/features/audit/utils"; | ||
| import { ErrorCode } from "@/lib/errorCodes"; | ||
| import { notFound, ServiceError } from "@/lib/serviceError"; | ||
| import { sew } from "@/middleware/sew"; | ||
|
|
@@ -54,10 +55,7 @@ export const createApiKey = async (name: string): Promise<{ key: string } | Serv | |
| if (existingApiKey) { | ||
| await createAudit({ | ||
| action: "api_key.creation_failed", | ||
| actor: { | ||
| id: user.id, | ||
| type: "user" | ||
| }, | ||
| actor: auditActorForUser(user), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Block service accounts from the generic API-key actions.
📍 Affects 1 file
🤖 Prompt for AI Agents |
||
| target: { | ||
| id: org.id.toString(), | ||
| type: "org" | ||
|
|
@@ -87,10 +85,7 @@ export const createApiKey = async (name: string): Promise<{ key: string } | Serv | |
|
|
||
| await createAudit({ | ||
| action: "api_key.created", | ||
| actor: { | ||
| id: user.id, | ||
| type: "user" | ||
| }, | ||
| actor: auditActorForUser(user), | ||
| target: { | ||
| id: apiKey.hash, | ||
| type: "api_key" | ||
|
|
@@ -115,10 +110,7 @@ export const deleteApiKey = async (name: string): Promise<{ success: boolean } | | |
| if (!apiKey) { | ||
| await createAudit({ | ||
| action: "api_key.deletion_failed", | ||
| actor: { | ||
| id: user.id, | ||
| type: "user" | ||
| }, | ||
| actor: auditActorForUser(user), | ||
| target: { | ||
| id: org.id.toString(), | ||
| type: "org" | ||
|
|
@@ -144,10 +136,7 @@ export const deleteApiKey = async (name: string): Promise<{ success: boolean } | | |
|
|
||
| await createAudit({ | ||
| action: "api_key.deleted", | ||
| actor: { | ||
| id: user.id, | ||
| type: "user" | ||
| }, | ||
| actor: auditActorForUser(user), | ||
| target: { | ||
| id: apiKey.hash, | ||
| type: "api_key" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { authenticatedPage } from "@/middleware/authenticatedPage"; | ||
| import { getServiceAccountApiKeysAction, listServiceAccounts } from "@/features/serviceAccounts/actions"; | ||
| import { isServiceError } from "@/lib/utils"; | ||
| import { ServiceErrorException } from "@/lib/serviceError"; | ||
| import { OrgRole } from "@sourcebot/db"; | ||
| import { notFound } from "next/navigation"; | ||
| import Link from "next/link"; | ||
| import { ArrowLeft } from "lucide-react"; | ||
| import { ServiceAccountApiKeysPage } from "./serviceAccountApiKeysPage"; | ||
|
|
||
| export default authenticatedPage<{ params: Promise<{ id: string }> }>(async (_auth, { params }) => { | ||
| const { id } = await params; | ||
|
|
||
| const [serviceAccounts, apiKeys] = await Promise.all([ | ||
| listServiceAccounts(), | ||
| getServiceAccountApiKeysAction(id), | ||
| ]); | ||
|
|
||
| if (isServiceError(serviceAccounts)) { | ||
| throw new ServiceErrorException(serviceAccounts); | ||
| } | ||
|
|
||
| const serviceAccount = serviceAccounts.find((sa) => sa.id === id); | ||
| if (!serviceAccount) { | ||
| return notFound(); | ||
| } | ||
|
|
||
| if (isServiceError(apiKeys)) { | ||
| throw new ServiceErrorException(apiKeys); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="flex flex-1 min-h-0 flex-col gap-6"> | ||
| <div> | ||
| <Link | ||
| href="/settings/serviceAccounts" | ||
| className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-2" | ||
| > | ||
| <ArrowLeft className="h-3.5 w-3.5" /> | ||
| Service Accounts | ||
| </Link> | ||
| <h3 className="text-lg font-medium">{serviceAccount.name}</h3> | ||
| <p className="text-sm text-muted-foreground"> | ||
| Create and manage API keys for this service account. | ||
| </p> | ||
| </div> | ||
| <ServiceAccountApiKeysPage serviceAccountId={id} apiKeys={apiKeys} /> | ||
| </div> | ||
| ); | ||
| }, { | ||
| minRole: OrgRole.OWNER, | ||
| redirectTo: '/settings', | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Avoid a write-blocking foreign-key validation during deployment.
Line 10 adds a validated foreign key to the existing
"User"table. PostgreSQL scans the table and takes locks that block writes while it adds this constraint. Add the constraint asNOT VALID, then validate it in a later migration and separate transaction.🧰 Tools
🪛 Squawk (2.61.0)
[warning] 10-10: By default new constraints require a table scan and block writes to the table while that scan occurs. Use
NOT VALIDwith a laterVALIDATE CONSTRAINTcall.(constraint-missing-not-valid)
[warning] 10-10: Adding a foreign key constraint requires a table scan and a
SHARE ROW EXCLUSIVElock on both tables, which blocks writes to each table. AddNOT VALIDto the constraint in one transaction and then VALIDATE the constraint in a separate transaction.(adding-foreign-key-constraint)
🤖 Prompt for AI Agents
Source: Linters/SAST tools