233 lines
6.1 KiB
TypeScript
233 lines
6.1 KiB
TypeScript
/**
|
|
* Utilitários para autenticação e criptografia
|
|
* Usando Web Crypto API para criptografia segura
|
|
*/
|
|
|
|
/**
|
|
* Gera um hash seguro de senha usando PBKDF2
|
|
*/
|
|
export async function hashPassword(password: string): Promise<string> {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(password);
|
|
|
|
// Gerar salt aleatório
|
|
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
|
|
// Importar a senha como chave
|
|
const keyMaterial = await crypto.subtle.importKey('raw', data, 'PBKDF2', false, ['deriveBits']);
|
|
|
|
// Derivar a chave usando PBKDF2
|
|
const derivedBits = await crypto.subtle.deriveBits(
|
|
{
|
|
name: 'PBKDF2',
|
|
salt: salt,
|
|
iterations: 100000,
|
|
hash: 'SHA-256'
|
|
},
|
|
keyMaterial,
|
|
256
|
|
);
|
|
|
|
// Combinar salt + hash
|
|
const hashArray = new Uint8Array(derivedBits);
|
|
const combined = new Uint8Array(salt.length + hashArray.length);
|
|
combined.set(salt);
|
|
combined.set(hashArray, salt.length);
|
|
|
|
// Converter para base64
|
|
return btoa(String.fromCharCode(...combined));
|
|
}
|
|
|
|
/**
|
|
* Verifica se uma senha corresponde ao hash
|
|
*/
|
|
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
try {
|
|
// Decodificar o hash de base64
|
|
const combined = Uint8Array.from(atob(hash), (c) => c.charCodeAt(0));
|
|
|
|
// Extrair salt e hash
|
|
const salt = combined.slice(0, 16);
|
|
const storedHash = combined.slice(16);
|
|
|
|
// Gerar hash da senha fornecida
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(password);
|
|
|
|
const keyMaterial = await crypto.subtle.importKey('raw', data, 'PBKDF2', false, ['deriveBits']);
|
|
|
|
const derivedBits = await crypto.subtle.deriveBits(
|
|
{
|
|
name: 'PBKDF2',
|
|
salt: salt,
|
|
iterations: 100000,
|
|
hash: 'SHA-256'
|
|
},
|
|
keyMaterial,
|
|
256
|
|
);
|
|
|
|
const newHash = new Uint8Array(derivedBits);
|
|
|
|
// Comparar os hashes
|
|
if (newHash.length !== storedHash.length) {
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < newHash.length; i++) {
|
|
if (newHash[i] !== storedHash[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Erro ao verificar senha:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gera um token aleatório seguro
|
|
*/
|
|
export function generateToken(): string {
|
|
const array = new Uint8Array(32);
|
|
crypto.getRandomValues(array);
|
|
return btoa(String.fromCharCode(...array))
|
|
.replace(/\+/g, '-')
|
|
.replace(/\//g, '_')
|
|
.replace(/=/g, '');
|
|
}
|
|
|
|
/**
|
|
* Valida formato de matrícula (apenas números)
|
|
*/
|
|
export function validarMatricula(matricula: string): boolean {
|
|
return /^\d+$/.test(matricula) && matricula.length >= 3;
|
|
}
|
|
|
|
/**
|
|
* Valida formato de senha (alfanuméricos e símbolos)
|
|
*/
|
|
export function validarSenha(senha: string): boolean {
|
|
// Mínimo 8 caracteres, pelo menos uma letra, um número e um símbolo
|
|
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
|
|
return regex.test(senha);
|
|
}
|
|
|
|
/**
|
|
* Criptografia reversível para senhas SMTP usando AES-GCM
|
|
* NOTA: Esta função é usada apenas para senhas SMTP que precisam ser descriptografadas.
|
|
* Para senhas de usuários, use hashPassword() que é unidirecional.
|
|
*/
|
|
|
|
// Chave de criptografia derivada (em produção, deve vir de variável de ambiente)
|
|
// Para desenvolvimento, usando uma chave fixa. Em produção, deve ser configurada via env var.
|
|
const getEncryptionKey = async (): Promise<CryptoKey> => {
|
|
// Chave base - em produção, isso deve vir de process.env.ENCRYPTION_KEY
|
|
// Por enquanto, usando uma chave derivada de um valor fixo
|
|
const keyMaterial = new TextEncoder().encode('SGSE-EMAIL-ENCRYPTION-KEY-2024');
|
|
|
|
// Deriva uma chave de 256 bits usando PBKDF2
|
|
const key = await crypto.subtle.importKey('raw', keyMaterial, { name: 'PBKDF2' }, false, [
|
|
'deriveBits',
|
|
'deriveKey'
|
|
]);
|
|
|
|
return await crypto.subtle.deriveKey(
|
|
{
|
|
name: 'PBKDF2',
|
|
salt: new TextEncoder().encode('SGSE-SALT'),
|
|
iterations: 100000,
|
|
hash: 'SHA-256'
|
|
},
|
|
key,
|
|
{ name: 'AES-GCM', length: 256 },
|
|
false,
|
|
['encrypt', 'decrypt']
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Criptografa uma senha SMTP usando AES-GCM
|
|
*/
|
|
export async function encryptSMTPPassword(password: string): Promise<string> {
|
|
try {
|
|
const key = await getEncryptionKey();
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(password);
|
|
|
|
// Gerar IV (Initialization Vector) aleatório
|
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
|
|
// Criptografar
|
|
const encrypted = await crypto.subtle.encrypt(
|
|
{
|
|
name: 'AES-GCM',
|
|
iv: iv
|
|
},
|
|
key,
|
|
data
|
|
);
|
|
|
|
// Combinar IV + dados criptografados e converter para base64
|
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
combined.set(iv);
|
|
combined.set(new Uint8Array(encrypted), iv.length);
|
|
|
|
return btoa(String.fromCharCode(...combined));
|
|
} catch (error) {
|
|
console.error('Erro ao criptografar senha SMTP:', error);
|
|
throw new Error('Falha ao criptografar senha SMTP');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Descriptografa uma senha SMTP usando AES-GCM
|
|
*/
|
|
export async function decryptSMTPPassword(encryptedPassword: string): Promise<string> {
|
|
try {
|
|
const key = await getEncryptionKey();
|
|
|
|
// Decodificar base64
|
|
const combined = Uint8Array.from(atob(encryptedPassword), (c) => c.charCodeAt(0));
|
|
|
|
// Extrair IV e dados criptografados
|
|
const iv = combined.slice(0, 12);
|
|
const encrypted = combined.slice(12);
|
|
|
|
// Descriptografar
|
|
const decrypted = await crypto.subtle.decrypt(
|
|
{
|
|
name: 'AES-GCM',
|
|
iv: iv
|
|
},
|
|
key,
|
|
encrypted
|
|
);
|
|
|
|
// Converter para string
|
|
const decoder = new TextDecoder();
|
|
return decoder.decode(decrypted);
|
|
} catch (error) {
|
|
console.error('Erro ao descriptografar senha SMTP:', error);
|
|
throw new Error('Falha ao descriptografar senha SMTP');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Criptografa um JWT secret usando AES-GCM (mesma lógica de SMTP password)
|
|
*/
|
|
export async function encryptJWTSecret(secret: string): Promise<string> {
|
|
// Reutilizar a mesma função de criptografia de SMTP password
|
|
return encryptSMTPPassword(secret);
|
|
}
|
|
|
|
/**
|
|
* Descriptografa um JWT secret usando AES-GCM (mesma lógica de SMTP password)
|
|
*/
|
|
export async function decryptJWTSecret(encryptedSecret: string): Promise<string> {
|
|
// Reutilizar a mesma função de descriptografia de SMTP password
|
|
return decryptSMTPPassword(encryptedSecret);
|
|
}
|