mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-08-28 16:31:13 +08:00
feat(apiKey): add secure API key generation utility and tests
This commit is contained in:
@@ -5,6 +5,7 @@ import { Modal } from '@/components/ui/Modal';
|
||||
import { useNotificationStore } from '@/stores';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
import { makeClientId } from '@/types/visualConfig';
|
||||
import { generateSecureApiKey } from '@/utils/apiKey';
|
||||
import { maskApiKey } from '@/utils/format';
|
||||
import { isValidApiKeyCharset } from '@/utils/validation';
|
||||
import styles from './Blocks.module.scss';
|
||||
@@ -46,13 +47,6 @@ export const ApiKeysCardEditor = memo(function ApiKeysCardEditor({
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [formError, setFormError] = useState('');
|
||||
|
||||
function generateSecureApiKey(): string {
|
||||
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const array = new Uint8Array(17);
|
||||
crypto.getRandomValues(array);
|
||||
return 'sk-' + Array.from(array, (b) => charset[b % charset.length]).join('');
|
||||
}
|
||||
|
||||
const openAddModal = () => {
|
||||
setEditingApiKeyId(null);
|
||||
setInputValue('');
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
const API_KEY_PREFIX = 'sk-';
|
||||
const API_KEY_RANDOM_LENGTH = 48;
|
||||
const API_KEY_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const MAX_UNBIASED_BYTE = Math.floor(256 / API_KEY_CHARSET.length) * API_KEY_CHARSET.length;
|
||||
|
||||
/**
|
||||
* Generates a cryptographically secure, uniformly distributed API key.
|
||||
* The resulting key is 51 characters long: `sk-` plus 48 random characters.
|
||||
*/
|
||||
export function generateSecureApiKey(): string {
|
||||
const characters: string[] = [];
|
||||
|
||||
while (characters.length < API_KEY_RANDOM_LENGTH) {
|
||||
const remaining = API_KEY_RANDOM_LENGTH - characters.length;
|
||||
const randomBytes = new Uint8Array(Math.ceil(remaining * 1.1));
|
||||
globalThis.crypto.getRandomValues(randomBytes);
|
||||
|
||||
for (const byte of randomBytes) {
|
||||
if (byte >= MAX_UNBIASED_BYTE) continue;
|
||||
|
||||
characters.push(API_KEY_CHARSET[byte % API_KEY_CHARSET.length]);
|
||||
if (characters.length === API_KEY_RANDOM_LENGTH) break;
|
||||
}
|
||||
}
|
||||
|
||||
return `${API_KEY_PREFIX}${characters.join('')}`;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { generateSecureApiKey } from '../src/utils/apiKey';
|
||||
|
||||
describe('API key generation', () => {
|
||||
test('generates a 51-character key with the expected prefix and charset', () => {
|
||||
const apiKey = generateSecureApiKey();
|
||||
|
||||
expect(apiKey).toHaveLength(51);
|
||||
expect(apiKey).toMatch(/^sk-[A-Za-z0-9]{48}$/);
|
||||
});
|
||||
|
||||
test('generates distinct keys', () => {
|
||||
const apiKeys = Array.from({ length: 100 }, () => generateSecureApiKey());
|
||||
|
||||
expect(new Set(apiKeys).size).toBe(apiKeys.length);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user