import { createHmac, timingSafeEqual as cryptoTimingSafeEqual } from 'crypto';
import { NOTION_KEY_PATTERNS, SECURITY } from '../constants.js';
/**
* Security utilities
*/
/**
* Secure timing-safe string comparison to prevent timing attacks
* Uses HMAC with a constant-time comparison
*/
export function timingSafeEqual(a: string, b: string): boolean {
// Empty strings should not match
if (a.length === 0 && b.length === 0) {
return false;
}
if (a.length !== b.length) {
return false;
}
// Use HMAC for timing-safe comparison
const hmac = createHmac('sha256', 'timing-safe-comparison-key');
hmac.update(a);
const aHash = hmac.digest();
const hmac2 = createHmac('sha256', 'timing-safe-comparison-key');
hmac2.update(b);
const bHash = hmac2.digest();
// Constant-time comparison
let result = 0;
for (let i = 0; i < aHash.length; i++) {
result |= aHash[i] ^ bHash[i];
}
return result === 0;
}
/**
* Validate Notion API key format
*/
export function validateNotionApiKey(apiKey: string): { valid: boolean; error?: string } {
if (!apiKey || typeof apiKey !== 'string') {
return { valid: false, error: 'API key is required and must be a string' };
}
// Check against known patterns
const isValidFormat = NOTION_KEY_PATTERNS.LEGACY.test(apiKey) ||
NOTION_KEY_PATTERNS.NEW.test(apiKey);
if (isValidFormat) {
return { valid: true };
}
// Basic validation: must start with known prefix and have reasonable length
const hasValidPrefix = apiKey.startsWith('secret_') || apiKey.startsWith('ntn_');
const hasValidLength = apiKey.length >= SECURITY.MIN_NOTION_KEY_LENGTH;
if (!hasValidPrefix || !hasValidLength) {
return {
valid: false,
error: `Invalid Notion API key format. Key must start with "secret_" or "ntn_" and be at least ${SECURITY.MIN_NOTION_KEY_LENGTH} characters long`
};
}
// Additional validation for non-standard formats
// Must have some content after the prefix
const prefixLength = apiKey.startsWith('secret_') ? 7 : 4; // "secret_" = 7, "ntn_" = 4
const contentAfterPrefix = apiKey.slice(prefixLength);
if (contentAfterPrefix.length === 0) {
return {
valid: false,
error: 'API key must have content after the prefix'
};
}
return { valid: true };
}