Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e/connect-secret.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ test('connect secret shows editable name and scope and saves the edited name', a
await expect(page.getByLabel('Description')).toHaveValue(description)
await expect(
page.getByPlaceholder('Enter the secret value').first(),
).toHaveValue('')
).toHaveValue(secretValue)
await expect(page.getByPlaceholder('saved package id')).toHaveValue(packageId)
})
7 changes: 5 additions & 2 deletions packages/worker/client/routes/account-secrets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ type SecretListItem = {
ttlMs: number | null
}

type SecretDetail = SecretListItem
type SecretDetail = SecretListItem & {
value: string
}

type AccountSecretsPayload = {
ok: true
Expand Down Expand Up @@ -176,7 +178,7 @@ function createEditorStateFromSecret(secret: SecretDetail): EditorState {
scope: secret.scope,
appId: secret.appId ?? '',
description: secret.description,
value: '',
value: secret.value,
allowedHosts: allowedHosts.length > 0 ? allowedHosts : [''],
allowedCapabilities:
allowedCapabilities.length > 0 ? allowedCapabilities : [''],
Expand Down Expand Up @@ -510,6 +512,7 @@ export function AccountSecretsRoute(handle: Handle) {
createEditorStateFromSecret(selectedSecret),
capabilityPrefill,
)
revealState = 'revealed'
return
}
editorState = applyCapabilityPrefill(
Expand Down
17 changes: 14 additions & 3 deletions packages/worker/src/app/handlers/account-secrets.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const mockModule = vi.hoisted(() => ({
listSavedPackagesByUserId: vi.fn(async () => []),
listSecrets: vi.fn(async () => []),
listAppSecretsByAppIds: vi.fn(async () => []),
resolveSecret: vi.fn(async () => null),
resolveSecret: vi.fn(async () => ({ found: false, value: null })),
deleteSecret: vi.fn(async () => false),
setSecretAllowedCapabilities: vi.fn(async () => undefined),
setSecretAllowedPackages: vi.fn(async () => undefined),
Expand Down Expand Up @@ -564,7 +564,7 @@ test('package approval approve deduplicates allowed package ids', async () => {
)
})

test('GET /account/secrets.json does not include value in selectedSecret', async () => {
test('GET /account/secrets.json includes decrypted value in selectedSecret', async () => {
mockModule.listSecrets.mockResolvedValueOnce([
{
name: 'myApiKey',
Expand All @@ -581,6 +581,10 @@ test('GET /account/secrets.json does not include value in selectedSecret', async
])
mockModule.listSavedPackagesByUserId.mockResolvedValueOnce([])
mockModule.listAppSecretsByAppIds.mockResolvedValueOnce(new Map())
mockModule.resolveSecret.mockResolvedValueOnce({
found: true,
value: 'the-actual-secret-value',
})

const handler = createAccountSecretsApiHandler(createEnv())
const response = await handler.handler({
Expand All @@ -596,7 +600,14 @@ test('GET /account/secrets.json does not include value in selectedSecret', async
expect(payload.ok).toBe(true)
expect(payload.selectedSecret).toBeDefined()
expect(payload.selectedSecret.name).toBe('myApiKey')
expect(payload.selectedSecret).not.toHaveProperty('value')
expect(payload.selectedSecret.value).toBe('the-actual-secret-value')
expect(mockModule.resolveSecret).toHaveBeenCalledWith(
expect.objectContaining({
name: 'myApiKey',
scope: 'user',
storageContext: { appId: null, sessionId: null },
}),
)
})

test('POST /account/secrets/reveal without password returns 401', async () => {
Expand Down
28 changes: 24 additions & 4 deletions packages/worker/src/app/handlers/account-secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ type AccountSecretListItem = {
ttlMs: number | null
}

type AccountSecretDetail = AccountSecretListItem
type AccountSecretDetail = AccountSecretListItem & {
value: string
}

type SecretApprovalView = {
name: string
Expand Down Expand Up @@ -735,7 +737,9 @@ async function buildAccountSecretsPayload(input: {
packageApps,
})
const selectedSecret = input.selectedSecretId
? resolveAccountSecretDetail({
? await resolveAccountSecretDetail({
env: input.env,
userId: input.user.mcpUser.userId,
secretId: input.selectedSecretId,
secrets,
})
Expand Down Expand Up @@ -896,15 +900,31 @@ async function resolveSecretApprovalView(input: {
} satisfies SecretApprovalView
}

function resolveAccountSecretDetail(input: {
async function resolveAccountSecretDetail(input: {
env: Env
userId: string
secretId: string
secrets: Array<AccountSecretListItem>
}) {
const parsed = parseAccountSecretId(input.secretId)
if (!parsed) return null

const selected = input.secrets.find((secret) => secret.id === input.secretId)
return selected ?? null
if (!selected) return null

const resolved = await resolveSecret({
env: input.env,
userId: input.userId,
name: parsed.name,
scope: parsed.scope,
storageContext: getSecretContextForAccountSecret(parsed),
})
if (!resolved?.found || resolved.value == null) return null
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved

return {
...selected,
value: resolved.value,
} satisfies AccountSecretDetail
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

function toAccountSecretListItem(
Expand Down
Loading