Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
43 changes: 41 additions & 2 deletions e2e/account-secrets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ async function saveSecret(
name: string
description: string
value: string
allowedHosts?: Array<string>
allowedCapabilities?: Array<string>
},
) {
const response = await page.request.post('/account/secrets.json', {
Expand All @@ -16,8 +18,8 @@ async function saveSecret(
appId: null,
description: input.description,
value: input.value,
allowedHosts: [],
allowedCapabilities: [],
allowedHosts: input.allowedHosts ?? [],
allowedCapabilities: input.allowedCapabilities ?? [],
},
headers: { 'Content-Type': 'application/json' },
})
Expand Down Expand Up @@ -81,3 +83,40 @@ test('switching secrets updates detail view without a full reload', async ({
),
).resolves.toBe('still-here')
})

test('landing on an approval link shows already added when the host is present', async ({
page,
login,
}) => {
await login()

const nonce = Date.now().toString(36)
const secret = {
name: `cloudflare-token-${nonce}`,
description: `Cloudflare token ${nonce}`,
value: `token-${nonce}`,
allowedHosts: ['api.cloudflare.com'],
}

await saveSecret(page, secret)

await page.goto(
`/account/secrets/user/${secret.name}?allowed-host=api.cloudflare.com&request=stale-token`,
)

await expect(
page.getByRole('heading', { level: 2, name: secret.name }),
).toBeVisible()
await expect(
page.getByRole('heading', { level: 2, name: 'Already added' }),
).toBeVisible()
await expect(
page.getByRole('status').getByText('This request is already complete for this secret.'),
).toBeVisible()
await expect(
page.getByRole('status').getByText('Host', { exact: false }),
).toContainText('api.cloudflare.com')
await expect(
page.getByRole('heading', { level: 2, name: 'Approve host access' }),
).toHaveCount(0)
})
127 changes: 119 additions & 8 deletions packages/worker/client/routes/account-secrets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,62 @@ function readCapabilityPrefill(href: string) {
return value?.trim() ? value.trim() : null
}

function readRequestedHost(href: string) {
const url = new URL(href, 'http://localhost')
const value = url.searchParams.get('allowed-host')
return value?.trim() ? value.trim() : null
}

function normalizeSingleAllowedHost(host: string | null) {
if (!host) return null
return clientNormalizeAllowedHosts([host])[0] ?? null
}

function normalizeSingleAllowedCapability(capability: string | null) {
if (!capability) return null
return clientNormalizeAllowedCapabilities([capability])[0] ?? null
}

function getAlreadyAddedNotice(input: {
href: string
selectedSecret: SecretDetail | null
approval: ApprovalView | null
}) {
const requestedHost = normalizeSingleAllowedHost(readRequestedHost(input.href))
const requestedCapability = normalizeSingleAllowedCapability(
readCapabilityPrefill(input.href),
)
const allowedHosts = input.selectedSecret
? clientNormalizeAllowedHosts(coerceStringRows(input.selectedSecret.allowedHosts))
: input.approval
? clientNormalizeAllowedHosts(input.approval.currentAllowedHosts)
: []
const allowedCapabilities = input.selectedSecret
? clientNormalizeAllowedCapabilities(
coerceStringRows(input.selectedSecret.allowedCapabilities),
)
: []
const items: Array<string> = []
const hostAlreadyAdded =
requestedHost != null && allowedHosts.includes(requestedHost)
if (hostAlreadyAdded) {
items.push(`Host ${requestedHost} is already in allowed hosts.`)
}
if (
requestedCapability != null &&
allowedCapabilities.includes(requestedCapability)
) {
items.push(
`Capability ${requestedCapability} is already in allowed capabilities.`,
)
}
if (items.length === 0) return null
return {
items,
hostAlreadyAdded,
}
}

function applyCapabilityPrefill(state: EditorState, capability: string | null) {
if (!capability) return state
if (state.allowedCapabilities.some((entry) => entry.trim() === capability)) {
Expand Down Expand Up @@ -767,6 +823,17 @@ export function AccountSecretsRoute(handle: Handle) {
const isMutating = saveState !== 'idle' || submittingApprovalAction != null
const canCreateAppSecrets = apps.length > 0
const showEditor = selection.isCreating || selectedSecret != null
const alreadyAddedNotice = getAlreadyAddedNotice({
href: currentHref,
selectedSecret,
approval,
})
const approvalCard =
approval &&
!isRefreshingForLocationChange &&
!alreadyAddedNotice?.hostAlreadyAdded
? approval
: null

return (
<section
Expand Down Expand Up @@ -811,7 +878,7 @@ export function AccountSecretsRoute(handle: Handle) {
</button>
</header>

{approval && !isRefreshingForLocationChange ? (
{approvalCard ? (
<section
css={{
display: 'grid',
Expand All @@ -834,20 +901,20 @@ export function AccountSecretsRoute(handle: Handle) {
Approve host access
</h2>
<p css={{ margin: 0, color: colors.textMuted }}>
Allow <code>{approval.requestedHost}</code> to receive secret{' '}
<code>{approval.name}</code> from the{' '}
{getScopeLabel(approval.scope)} scope.
Allow <code>{approvalCard.requestedHost}</code> to receive
secret <code>{approvalCard.name}</code> from the{' '}
{getScopeLabel(approvalCard.scope)} scope.
</p>
{approval.requestedCapability ? (
{approvalCard.requestedCapability ? (
<p css={{ margin: 0, color: colors.textMuted }}>
Requested capability:{' '}
<code>{approval.requestedCapability}</code>
<code>{approvalCard.requestedCapability}</code>
</p>
) : null}
<p css={{ margin: 0, color: colors.textMuted }}>
Current allowed hosts:{' '}
{approval.currentAllowedHosts.length > 0
? approval.currentAllowedHosts.join(', ')
{approvalCard.currentAllowedHosts.length > 0
? approvalCard.currentAllowedHosts.join(', ')
: 'none'}
</p>
</div>
Expand All @@ -871,6 +938,50 @@ export function AccountSecretsRoute(handle: Handle) {
</div>
</section>
) : null}
{alreadyAddedNotice ? (
<section
css={{
display: 'grid',
gap: spacing.sm,
padding: spacing.lg,
borderRadius: radius.lg,
border: `1px solid ${colors.primary}`,
backgroundColor: colors.primarySoftest,
}}
role="status"
>
<div css={{ display: 'grid', gap: spacing.xs }}>
<h2
css={{
margin: 0,
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.semibold,
color: colors.text,
}}
>
Already added
</h2>
<p css={{ margin: 0, color: colors.textMuted }}>
This request is already complete for this secret.
</p>
</div>
<ul
css={{
margin: 0,
paddingLeft: spacing.lg,
color: colors.textMuted,
display: 'grid',
gap: spacing.xs,
}}
>
{alreadyAddedNotice.items.map((item) => (
<li key={item}>
{item}
</li>
))}
</ul>
</section>
) : null}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale data notice shown during location change refresh

Low Severity

The alreadyAddedNotice section renders without checking isRefreshingForLocationChange, unlike approvalCard which is explicitly gated by it. During a navigation-triggered data refresh, selectedSecret and approval hold stale data from the previous page while currentHref already reflects the new URL. This means getAlreadyAddedNotice can match the new URL's allowed-host param against the old secret's allowed hosts, briefly flashing a misleading "Already added" notice for the wrong secret.

Additional Locations (1)
Fix in Cursor Fix in Web


{status === 'loading' ? (
<p css={{ color: colors.textMuted, margin: 0 }}>Loading secrets…</p>
Expand Down
Loading