Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
a89eac2
feat(api-keys): add API key authentication via FAB SecurityManager
aminghadersohi Feb 14, 2026
e5448fc
fix: also pin FAB in development.txt to feature branch
aminghadersohi Feb 14, 2026
fa5f121
fix: correct frontend imports and add ApiKeyApi to CSRF exempt test
aminghadersohi Feb 14, 2026
77308c1
fix: address review feedback from automated code review
aminghadersohi Feb 14, 2026
5f4ae3c
style: prettier formatting for ApiKeyList imports
aminghadersohi Feb 14, 2026
41d1cec
fix: wire ApiKeyList component into UserInfo page
aminghadersohi Feb 17, 2026
7e9f03a
fix(auth): reload API key user with eager relationships
aminghadersohi Feb 17, 2026
af5d584
fix: pin FAB dependency to commit hash for reproducible builds
aminghadersohi Feb 18, 2026
cebd84d
fix: update FAB pin to latest commit with O(1) lookup and review fixes
aminghadersohi Feb 19, 2026
43827d1
fix: update FAB pin to include black formatting fix
aminghadersohi Feb 19, 2026
2cb3b40
fix: update FAB pin to include HMAC lookup hash fix
aminghadersohi Feb 19, 2026
a66803c
fix: update FAB pin to include lint and CodeQL fixes
aminghadersohi Feb 19, 2026
7407e53
fix: update FAB pin to BLAKE2b lookup hash commit
aminghadersohi Feb 19, 2026
8ea1686
fix: update FAB pin to scrypt lookup hash commit
aminghadersohi Feb 20, 2026
d729fd3
feat: update FAB dependency to 5.2.0 release
aminghadersohi Mar 2, 2026
69ada07
fix: wrap 'API Keys' string with t() for translation
aminghadersohi Mar 2, 2026
2a6e07f
chore: trigger CI re-run for flaky jest shard
aminghadersohi Mar 4, 2026
149d05a
ci: retrigger CI for flaky jest shard 7
aminghadersohi Mar 4, 2026
d7270d6
chore: trigger CI re-run for flaky jest shard
aminghadersohi Mar 4, 2026
91485d4
fix: address review feedback on API key PR
aminghadersohi Mar 5, 2026
c7e80ce
ci: retrigger CI after cancelled runs
aminghadersohi Mar 5, 2026
94d1944
refactor: rename prefix variable for clarity in auth error message
aminghadersohi Mar 5, 2026
c8511a1
ci: retrigger CI for flaky jest timeout tests
aminghadersohi Mar 5, 2026
7c78f55
ci: retrigger for flaky FileHandler test timeout
aminghadersohi Mar 5, 2026
9e4f29c
chore: retrigger CI
aminghadersohi Mar 6, 2026
ad94587
fix(api-keys): update imports to use new @apache-superset/core subpat…
aminghadersohi Mar 6, 2026
5ee00dd
fix: gate API keys behind feature flag and address review feedback
aminghadersohi Mar 9, 2026
2c5fa82
chore: update auto-generated feature-flags.json
aminghadersohi Mar 9, 2026
8788940
fix: restore standalone FAB_API_KEY_ENABLED config and fix CI failures
aminghadersohi Mar 9, 2026
4f9f96f
fix: resolve migration down_revision conflict and move has_request_co…
aminghadersohi Mar 9, 2026
a4bd8de
fix(api-keys): simplify useCallback/useEffect pattern in ApiKeyList
aminghadersohi Mar 9, 2026
d654e39
chore: re-trigger CI after flaky jest timeouts
aminghadersohi Mar 10, 2026
e4bf843
fix(api-keys): add downgrade guard and remove duplicate fetch
aminghadersohi Mar 10, 2026
0be74aa
fix(api-keys): address review feedback on migration, auth, and tests
aminghadersohi Mar 10, 2026
85b6f6d
fix(api-keys): address bot review comments on feature flag, race cond…
aminghadersohi Mar 13, 2026
9837e57
fix(api-keys): fix error swallowing, post-create modal, status badge,…
kgabryje Mar 13, 2026
5527485
test(api-keys): add unit tests for API key auth path in get_user_from…
kgabryje Mar 13, 2026
ea8e4ee
fix(api-keys): fix ruff PT001 and formatting in test_auth_api_key
aminghadersohi Mar 13, 2026
e7bc8e6
fix(api-keys): fix test fixtures for CI compatibility
aminghadersohi Mar 13, 2026
5a1f1ea
ci: retrigger CI (flaky FileHandler jest timeout)
aminghadersohi Mar 13, 2026
0c90d7c
fix(api-keys): fix setTimeout cleanup and migration index creation
aminghadersohi Mar 16, 2026
153916e
ci: retrigger CI (flaky FileHandler jest timeout)
aminghadersohi Mar 16, 2026
6b1f9f6
fix(api-keys): prevent accidental dismissal of one-time secret modal
aminghadersohi Mar 17, 2026
e2785a2
chore: re-trigger CI after flaky FiltersConfigModal jest timeout
aminghadersohi Mar 17, 2026
05c90aa
chore: trigger CI re-run for flaky jest shard
aminghadersohi Mar 4, 2026
328acfb
ci: retrigger CI for flaky jest shard 7
aminghadersohi Mar 4, 2026
979c25e
chore: trigger CI re-run for flaky jest shard
aminghadersohi Mar 4, 2026
6d9b516
ci: retrigger CI after cancelled runs
aminghadersohi Mar 5, 2026
dc8eaf9
ci: retrigger CI for flaky jest timeout tests
aminghadersohi Mar 5, 2026
dc4cb85
ci: retrigger for flaky FileHandler test timeout
aminghadersohi Mar 5, 2026
1614d80
chore: retrigger CI
aminghadersohi Mar 6, 2026
33d4ce0
chore: re-trigger CI after flaky jest timeouts
aminghadersohi Mar 10, 2026
a38f323
ci: retrigger CI (flaky FileHandler jest timeout)
aminghadersohi Mar 13, 2026
b51f7fb
ci: retrigger CI (flaky FileHandler jest timeout)
aminghadersohi Mar 16, 2026
89859b8
chore: re-trigger CI after flaky FiltersConfigModal jest timeout
aminghadersohi Mar 17, 2026
0359dad
fix(mcp): address review feedback for API key auth
aminghadersohi Mar 24, 2026
b04a415
fix(migration): add SIP-59 compliance notes and explicit FK name
aminghadersohi Mar 24, 2026
9f1b5d1
Revert "fix(migration): add SIP-59 compliance notes and explicit FK n…
aminghadersohi Mar 24, 2026
8b6074b
fix(migration): remove redundant ab_api_key Alembic migration
aminghadersohi Mar 24, 2026
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
6 changes: 6 additions & 0 deletions docs/static/feature-flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
"lifecycle": "development",
"description": "Enable Superset extensions for custom functionality without modifying core"
},
{
"name": "FAB_API_KEY_ENABLED",
"default": false,
"lifecycle": "development",
"description": "Enable API key authentication via FAB SecurityManager When enabled, users can create/manage API keys in the User Info page"
},
{
"name": "GRANULAR_EXPORT_CONTROLS",
"default": false,
Expand Down
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ flask==2.3.3
# flask-session
# flask-sqlalchemy
# flask-wtf
flask-appbuilder==5.1.0
flask-appbuilder==5.2.0
# via
# apache-superset (pyproject.toml)
# apache-superset-core
Expand Down
2 changes: 1 addition & 1 deletion requirements/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ flask==2.3.3
# flask-sqlalchemy
# flask-testing
# flask-wtf
flask-appbuilder==5.1.0
flask-appbuilder==5.2.0
# via
# -c requirements/base-constraint.txt
# apache-superset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export enum FeatureFlag {
EnableTemplateProcessing = 'ENABLE_TEMPLATE_PROCESSING',
EscapeMarkdownHtml = 'ESCAPE_MARKDOWN_HTML',
EstimateQueryCost = 'ESTIMATE_QUERY_COST',
FabApiKeyEnabled = 'FAB_API_KEY_ENABLED',
FilterBarClosedByDefault = 'FILTERBAR_CLOSED_BY_DEFAULT',
GlobalAsyncQueries = 'GLOBAL_ASYNC_QUERIES',
GlobalTaskFramework = 'GLOBAL_TASK_FRAMEWORK',
Expand Down
175 changes: 175 additions & 0 deletions superset-frontend/src/features/apiKeys/ApiKeyCreateModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useEffect, useRef, useState } from 'react';
import { SupersetClient } from '@superset-ui/core';
import { t } from '@apache-superset/core/translation';
import { css, useTheme } from '@apache-superset/core/theme';
import { Alert } from '@apache-superset/core/components';
import {
FormModal,
FormItem,
Input,
Button,
Modal,
} from '@superset-ui/core/components';
import { useToasts } from 'src/components/MessageToasts/withToasts';

interface ApiKeyCreateModalProps {
show: boolean;
onHide: () => void;
onSuccess: () => void;
}

interface FormValues {
name: string;
}

export function ApiKeyCreateModal({
show,
onHide,
onSuccess,
}: ApiKeyCreateModalProps) {
const theme = useTheme();
const { addDangerToast, addSuccessToast } = useToasts();
const [createdKey, setCreatedKey] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const copyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

useEffect(
() => () => {
if (copyTimerRef.current) {
clearTimeout(copyTimerRef.current);
}
},
[],
);

const handleFormSubmit = async (values: FormValues) => {
try {
const response = await SupersetClient.post({
endpoint: '/api/v1/security/api_keys/',
jsonPayload: values,
});
const key = response.json?.result?.key;
if (!key) {
throw new Error('API response did not include a key');
}
setCreatedKey(key);
addSuccessToast(t('API key created successfully'));
} catch (error) {
addDangerToast(t('Failed to create API key'));
throw error;
}
};

const handleCopyKey = async () => {
if (!createdKey) {
return;
}
try {
await navigator.clipboard.writeText(createdKey);
setCopied(true);
if (copyTimerRef.current) {
clearTimeout(copyTimerRef.current);
}
copyTimerRef.current = setTimeout(() => setCopied(false), 2000);
} catch {
addDangerToast(t('Failed to copy API key to clipboard'));
}
};

const handleClose = () => {
if (createdKey) {
onSuccess();
}
setCreatedKey(null);
setCopied(false);
onHide();
};

if (createdKey) {
return (
<Modal
show={show}
onHide={handleClose}
title={t('API Key Created')}
maskClosable={false}
closable={false}
footer={
<Button type="primary" onClick={handleClose}>
{t('Done')}
</Button>
}
>
Comment on lines +108 to +119
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.

Suggestion: The one-time secret modal can be dismissed by clicking outside or using the close icon, which can cause users to permanently lose the API key before copying it. Since the key is intentionally shown only once, make this confirmation step non-dismissible except through the explicit Done action. [logic error]

Severity Level: Major ⚠️
- ⚠️ Newly created key can be irretrievably lost by misclick.
- ⚠️ Users must revoke and recreate keys, disrupting automation setup.
Suggested change
<Modal
show={show}
onHide={handleClose}
title={t('API Key Created')}
footer={
<Button type="primary" onClick={handleClose}>
{t('Done')}
</Button>
}
>
<Modal
show={show}
onHide={handleClose}
title={t('API Key Created')}
closable={false}
maskClosable={false}
footer={
<Button type="primary" onClick={handleClose}>
{t('Done')}
</Button>
}
>
Steps of Reproduction ✅
1. Open the User Info page and expand the API Keys panel
(`src/pages/UserInfo/index.tsx:43-49, 223-228`), which renders `ApiKeyList`.

2. Click **Create API Key** (`src/features/apiKeys/ApiKeyList.tsx:212-214`), setting
`showCreateModal` and mounting `ApiKeyCreateModal` (`ApiKeyList.tsx:223-232`).

3. Submit the form; `handleFormSubmit` posts to `/api/v1/security/api_keys/` and stores
`createdKey` (`ApiKeyCreateModal.tsx:63-74`), switching UI to the one-time key modal
branch (`ApiKeyCreateModal.tsx:106-149`).

4. Dismiss that modal via close icon or backdrop click; `Modal` routes cancel actions to
`onHide` (`packages/superset-ui-core/src/components/Modal/Modal.tsx:61, 237-240`), which
calls `handleClose` (`ApiKeyCreateModal.tsx:97-104`) and clears `createdKey` (`line 101`).
Reopening shows the create form only, so the key value is gone; this aligns with the
one-time warning text (`ApiKeyList.tsx:209`, `ApiKeyCreateModal.tsx:122`).
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-frontend/src/features/apiKeys/ApiKeyCreateModal.tsx
**Line:** 108:117
**Comment:**
	*Logic Error: The one-time secret modal can be dismissed by clicking outside or using the close icon, which can cause users to permanently lose the API key before copying it. Since the key is intentionally shown only once, make this confirmation step non-dismissible except through the explicit `Done` action.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 61c8685 — added maskClosable={false} and closable={false} to the one-time secret modal so it can only be dismissed via the Done button.

<Alert
type="warning"
message={t('Save this API key securely')}
description={t(
'This is the only time you will see this key. Store it securely.',
)}
showIcon
css={css`
margin-bottom: ${theme.sizeUnit * 4}px;
`}
/>
<div
css={css`
display: flex;
gap: ${theme.sizeUnit * 2}px;
align-items: center;
`}
>
<Input
value={createdKey}
readOnly
css={css`
flex: 1;
font-family: monospace;
`}
/>
<Button onClick={handleCopyKey}>
{copied ? t('Copied!') : t('Copy')}
</Button>
</div>
</Modal>
);
}

return (
<FormModal
show={show}
onHide={handleClose}
title={t('Create API Key')}
onSave={() => {}}
formSubmitHandler={handleFormSubmit}
requiredFields={['name']}
>
<FormItem
name="name"
label={t('Name')}
rules={[{ required: true, message: t('API key name is required') }]}
>
<Input
name="name"
placeholder={t('e.g., CI/CD Pipeline, Analytics Script')}
/>
</FormItem>
</FormModal>
);
}
Loading
Loading