feat(security): implement dynamic tactical license gate, instant key revocation, and clean hardcoded credentials
This commit is contained in:
@@ -328,9 +328,14 @@ const app = {
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right">
|
||||
<button class="p-2 text-slate-500 hover:text-white" onclick="app.fetchData()">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="p-2 text-slate-500 hover:text-white" title="Refresh" onclick="app.fetchData()">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="p-2 text-slate-500 hover:text-red-400 transition-colors" title="Revoke Key" onclick="app.deleteKey('${key.id}', '${key.name}')">
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
@@ -339,6 +344,29 @@ const app = {
|
||||
lucide.createIcons();
|
||||
},
|
||||
|
||||
deleteKey: async (keyId, keyName) => {
|
||||
if (!confirm(`Are you sure you want to revoke the API key "${keyName || 'Production Key'}"? This action cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/auth/management/keys/${keyId}`, {
|
||||
method: 'DELETE',
|
||||
headers: auth.getAuthHeader()
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
await app.fetchData();
|
||||
} else {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
alert(data.message || 'Failed to revoke API key');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Delete key failed:', e);
|
||||
alert('Failed to revoke API key');
|
||||
}
|
||||
},
|
||||
|
||||
toggleKeyVisibility: (id) => {
|
||||
app.state.showKeys[id] = !app.state.showKeys[id];
|
||||
app.renderKeysTable();
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Eye,
|
||||
EyeOff,
|
||||
RefreshCw,
|
||||
Trash2,
|
||||
ShieldCheck,
|
||||
Globe,
|
||||
Zap,
|
||||
@@ -53,7 +54,7 @@ const DashboardHome = ({ tenant, keys, loading, onRefresh }: DashboardHomeProps)
|
||||
const createKey = async (name: string, limit: number) => {
|
||||
if (!tenant) return;
|
||||
try {
|
||||
await axios.post(`/api/auth/management/keys/${tenant.id}`, {
|
||||
await axios.post(`/api/auth/management/keys`, {
|
||||
name,
|
||||
rateLimit: limit
|
||||
});
|
||||
@@ -65,6 +66,18 @@ const DashboardHome = ({ tenant, keys, loading, onRefresh }: DashboardHomeProps)
|
||||
}
|
||||
};
|
||||
|
||||
const deleteKey = async (keyId: string, keyName: string) => {
|
||||
if (!window.confirm(`Are you sure you want to revoke the API key "${keyName || 'Production Key'}"? This action cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await axios.delete(`/api/auth/management/keys/${keyId}`);
|
||||
onRefresh();
|
||||
} catch (e) {
|
||||
alert('Failed to revoke key');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||
{/* Hero Section */}
|
||||
@@ -264,9 +277,14 @@ const DashboardHome = ({ tenant, keys, loading, onRefresh }: DashboardHomeProps)
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-6 text-right">
|
||||
<button className="p-2 text-slate-500 hover:text-white" onClick={onRefresh}>
|
||||
<RefreshCw size={16} />
|
||||
</button>
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<button className="p-2 text-slate-500 hover:text-white" title="Refresh" onClick={onRefresh}>
|
||||
<RefreshCw size={16} />
|
||||
</button>
|
||||
<button className="p-2 text-slate-500 hover:text-red-400 transition-colors" title="Revoke Key" onClick={() => deleteKey(apiKey.id, apiKey.name)}>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user