Skip to main content
Glama
MisterSandFR

Supabase MCP Server - Self-Hosted Edition

by MisterSandFR
auth-security.ts7.43 kB
import * as crypto from 'node:crypto'; import { z } from 'zod'; /** * Secure authentication utilities for handling passwords and tokens */ // Password strength requirements const PasswordSchema = z.string() .min(8, 'Password must be at least 8 characters') .max(128, 'Password must not exceed 128 characters') .regex(/[A-Z]/, 'Password must contain at least one uppercase letter') .regex(/[a-z]/, 'Password must contain at least one lowercase letter') .regex(/[0-9]/, 'Password must contain at least one number') .regex(/[^A-Za-z0-9]/, 'Password must contain at least one special character'); // Email validation schema const EmailSchema = z.string().email('Invalid email format'); /** * Validates password strength */ export function validatePassword(password: string): { valid: boolean; errors?: string[] } { try { PasswordSchema.parse(password); return { valid: true }; } catch (error) { if (error instanceof z.ZodError) { return { valid: false, errors: error.errors.map(e => e.message) }; } return { valid: false, errors: ['Invalid password'] }; } } /** * Validates email format */ export function validateEmail(email: string): boolean { try { EmailSchema.parse(email); return true; } catch { return false; } } /** * Generates a secure random token */ export function generateSecureToken(length: number = 32): string { return crypto.randomBytes(length).toString('hex'); } /** * Hashes a password using PBKDF2 (for environments without bcrypt) * Note: In production, bcrypt is preferred and handled by Supabase/PostgreSQL */ export function hashPasswordPBKDF2(password: string, salt?: string): { hash: string; salt: string } { const actualSalt = salt || crypto.randomBytes(16).toString('hex'); const iterations = 100000; const keyLength = 64; const digest = 'sha256'; const hash = crypto.pbkdf2Sync(password, actualSalt, iterations, keyLength, digest).toString('hex'); return { hash: `pbkdf2:sha256:${iterations}$${actualSalt}$${hash}`, salt: actualSalt }; } /** * Verifies a password against a PBKDF2 hash */ export function verifyPasswordPBKDF2(password: string, hashedPassword: string): boolean { try { const parts = hashedPassword.split('$'); if (parts.length !== 3 || !parts[0].startsWith('pbkdf2:sha256:')) { return false; } const iterations = parseInt(parts[0].split(':')[2]); const salt = parts[1]; const hash = parts[2]; const verifyHash = crypto.pbkdf2Sync(password, salt, iterations, 64, 'sha256').toString('hex'); // Use timing-safe comparison return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(verifyHash)); } catch { return false; } } /** * Sanitizes user metadata to remove sensitive information */ export function sanitizeUserMetadata(metadata: Record<string, any>): Record<string, any> { const sensitiveKeys = [ 'password', 'encrypted_password', 'salt', 'secret', 'token', 'api_key', 'private_key', 'ssn', 'credit_card' ]; const sanitized = { ...metadata }; for (const key of Object.keys(sanitized)) { // Remove sensitive keys if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) { delete sanitized[key]; } // Recursively sanitize nested objects if (typeof sanitized[key] === 'object' && sanitized[key] !== null) { sanitized[key] = sanitizeUserMetadata(sanitized[key]); } } return sanitized; } /** * Generates a secure session token */ export function generateSessionToken(): string { return crypto.randomBytes(32).toString('base64url'); } /** * Creates a secure hash of sensitive data for logging/comparison * Never log actual sensitive data */ export function hashForLogging(data: string): string { return crypto.createHash('sha256').update(data).digest('hex').substring(0, 8) + '...'; } /** * Validates JWT structure (basic validation without verification) */ export function isValidJWTStructure(token: string): boolean { if (!token || typeof token !== 'string') { return false; } const parts = token.split('.'); if (parts.length !== 3) { return false; } try { // Check if each part is valid base64url parts.forEach(part => { const decoded = Buffer.from(part, 'base64url').toString(); if (!decoded) { throw new Error('Invalid base64url'); } }); // Try to parse header and payload as JSON const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString()); const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString()); // Basic JWT header validation if (!header.alg || !header.typ) { return false; } return true; } catch { return false; } } /** * Masks sensitive values in objects for safe logging */ export function maskSensitiveData(obj: any): any { if (typeof obj !== 'object' || obj === null) { return obj; } const sensitiveKeys = [ 'password', 'token', 'key', 'secret', 'authorization', 'cookie', 'session' ]; const masked = Array.isArray(obj) ? [...obj] : { ...obj }; for (const key in masked) { const lowerKey = key.toLowerCase(); if (sensitiveKeys.some(sensitive => lowerKey.includes(sensitive))) { if (typeof masked[key] === 'string' && masked[key].length > 0) { masked[key] = masked[key].substring(0, 4) + '****'; } else { masked[key] = '****'; } } else if (typeof masked[key] === 'object' && masked[key] !== null) { masked[key] = maskSensitiveData(masked[key]); } } return masked; } /** * Generates a cryptographically secure UUID v4 */ export function generateSecureUUID(): string { const bytes = crypto.randomBytes(16); // Set version (4) and variant bits bytes[6] = (bytes[6] & 0x0f) | 0x40; bytes[8] = (bytes[8] & 0x3f) | 0x80; const hex = bytes.toString('hex'); return [ hex.substring(0, 8), hex.substring(8, 12), hex.substring(12, 16), hex.substring(16, 20), hex.substring(20, 32) ].join('-'); } /** * Constant-time string comparison to prevent timing attacks */ export function secureCompare(a: string, b: string): boolean { if (a.length !== b.length) { return false; } return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); } /** * Creates a secure HMAC signature */ export function createHMAC(data: string, secret: string): string { return crypto.createHmac('sha256', secret).update(data).digest('hex'); } /** * Verifies an HMAC signature */ export function verifyHMAC(data: string, signature: string, secret: string): boolean { const expectedSignature = createHMAC(data, secret); return secureCompare(signature, expectedSignature); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MisterSandFR/Supabase-MCP-SelfHosted'

If you have feedback or need assistance with the MCP directory API, please join our Discord server