feat(api-tokens): add scoped API tokens for CI/CD automation (Team Pro) (#220)

Add long-lived API tokens with three permission scopes (read-only,
deploy-only, full-admin) for CI/CD pipelines, scripts, and automation.

- Database: api_tokens table with SHA-256 hashed storage
- Auth: extend middleware to authenticate Bearer API tokens
- Scope enforcement: middleware restricts actions per token scope
- API: CRUD endpoints gated behind Team Pro + admin
- UI: ApiTokensSection in Settings Hub with create/revoke/copy flows
- Docs: new api-tokens.mdx with usage examples and screenshots
This commit is contained in:
Anso
2026-03-28 15:37:54 -04:00
committed by GitHub
parent e2da1bf43d
commit 8d8118c963
9 changed files with 573 additions and 3 deletions
+10 -2
View File
@@ -28,6 +28,7 @@ import { useLicense } from '@/context/LicenseContext';
import { TierBadge } from './TierBadge';
import { ProGate } from './ProGate';
import { SSOSection } from './SSOSection';
import { ApiTokensSection } from './ApiTokensSection';
interface Agent {
type: 'discord' | 'slack' | 'webhook';
@@ -49,7 +50,7 @@ interface PatchableSettings {
log_retention_days?: string;
}
type SectionId = 'account' | 'license' | 'users' | 'sso' | 'system' | 'notifications' | 'webhooks' | 'developer' | 'nodes' | 'appstore' | 'support' | 'about';
type SectionId = 'account' | 'license' | 'users' | 'sso' | 'api-tokens' | 'system' | 'notifications' | 'webhooks' | 'developer' | 'nodes' | 'appstore' | 'support' | 'about';
interface WebhookItem {
id: number;
@@ -657,7 +658,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
// When switching to a remote node, reset to a node-scoped section if on a global-only one
useEffect(() => {
if (isRemote && (activeSection === 'account' || activeSection === 'license' || activeSection === 'users' || activeSection === 'sso' || activeSection === 'notifications' || activeSection === 'webhooks' || activeSection === 'nodes' || activeSection === 'appstore')) {
if (isRemote && (activeSection === 'account' || activeSection === 'license' || activeSection === 'users' || activeSection === 'sso' || activeSection === 'api-tokens' || activeSection === 'notifications' || activeSection === 'webhooks' || activeSection === 'nodes' || activeSection === 'appstore')) {
setActiveSection('system');
}
}, [isRemote]); // eslint-disable-line react-hooks/exhaustive-deps
@@ -1001,6 +1002,9 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
{!isRemote && isAdmin && isPro && license?.variant === 'team' && (
<NavButton section="sso" icon={<Shield className="w-4 h-4 mr-2" />} label="SSO" />
)}
{!isRemote && isAdmin && isPro && license?.variant === 'team' && (
<NavButton section="api-tokens" icon={<Zap className="w-4 h-4 mr-2" />} label="API Tokens" />
)}
<NavButton
section="system"
icon={<Activity className="w-4 h-4 mr-2" />}
@@ -1460,6 +1464,10 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
<SSOSection />
)}
{activeSection === 'api-tokens' && (
<ApiTokensSection />
)}
{activeSection === 'developer' && (
<div className="space-y-6">
<div className="flex items-start justify-between pr-8">