TS54 linhas2.067 bytes
apps/worker/src/services/assinatura.ts
SHA-256
be1f0828aa2bbe5c33cc2ff5aa55222bff889d1c36c797e1d298c720cbe5b581
Somente leiturafonte-0531042d9f3e
/**
* HMAC-SHA256 com o ADMIN_SECRET — links de ação para o admin (veto da
* newsletter) e pseudonimização de IP/e-mail no rate limit.
*/
const encoder = new TextEncoder()
async function hmacHex(secret: string, msg: string): Promise<string> {
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
)
const sig = await crypto.subtle.sign('HMAC', key, encoder.encode(msg))
return Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, '0')).join('')
}
/** Comparação em tempo constante (para strings de mesmo alfabeto). */
export function iguaisTempoConstante(a: string, b: string): boolean {
if (a.length !== b.length) return false
let diff = 0
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)
return diff === 0
}
export function assinarVeto(secret: string, edicao: string): Promise<string> {
return hmacHex(secret, `newsletter-veto:${edicao}`)
}
export async function verificarVeto(secret: string, edicao: string, sig: string): Promise<boolean> {
return iguaisTempoConstante(await assinarVeto(secret, edicao), sig)
}
/** Identificador estável e não reversível (sem o segredo) para buckets de rate limit. */
export async function pseudonimo(secret: string, valor: string): Promise<string> {
return (await hmacHex(secret, `rate-limit:${valor}`)).slice(0, 32)
}
/**
* Selo de integridade do certificado: amarra o código ao conteúdo impresso.
* Quem altera o registro no banco sem o segredo não consegue refazer o selo.
*/
export function assinarCertificado(secret: string, codigo: string, dados: string): Promise<string> {
return hmacHex(secret, `certificado:${codigo}:${dados}`)
}
export async function sha256Hex(dados: string | Uint8Array): Promise<string> {
const bytes = typeof dados === 'string' ? encoder.encode(dados) : dados
const hash = await crypto.subtle.digest('SHA-256', bytes)
return Array.from(new Uint8Array(hash), (b) => b.toString(16).padStart(2, '0')).join('')
}