feat(user): enhance email handling with validation and normalization functions
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s

- Introduced `looksLikePlausibleEmail` to validate email format, ensuring only plausible addresses are processed.
- Added `normalizeEmailCandidate` to standardize email input, returning null for invalid formats.
- Updated `decodeEncryptedBlob` to utilize normalization functions for both UTF-8 and hex formats, improving email decryption reliability.
- Adjusted `SettingsService` to ensure email is set after merging adult access state, maintaining data integrity.
This commit is contained in:
Torsten Schulz (local)
2026-04-02 08:07:25 +02:00
parent 3fb4fb92c6
commit 13534498fa
2 changed files with 30 additions and 7 deletions

View File

@@ -8,6 +8,26 @@ function encodeEncryptedValueToBlob(value) {
return Buffer.from(encrypted, 'utf8'); return Buffer.from(encrypted, 'utf8');
} }
/** Nur echte Adressen zurückgeben — verhindert Anzeige von Base64-/Key-artigem Müll bei fehlender Entschlüsselung. */
function looksLikePlausibleEmail(s) {
if (typeof s !== 'string') {
return false;
}
const t = s.trim();
if (!t || t.length > 254) {
return false;
}
return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i.test(t);
}
function normalizeEmailCandidate(s) {
if (!s || typeof s !== 'string') {
return null;
}
const t = s.trim();
return looksLikePlausibleEmail(t) ? t : null;
}
function decodeEncryptedBlob(value) { function decodeEncryptedBlob(value) {
if (!value) { if (!value) {
return null; return null;
@@ -16,8 +36,9 @@ function decodeEncryptedBlob(value) {
try { try {
const encryptedUtf8 = value.toString('utf8'); const encryptedUtf8 = value.toString('utf8');
const decryptedUtf8 = decrypt(encryptedUtf8); const decryptedUtf8 = decrypt(encryptedUtf8);
if (decryptedUtf8) { const fromUtf8 = normalizeEmailCandidate(decryptedUtf8);
return decryptedUtf8; if (fromUtf8) {
return fromUtf8;
} }
} catch (error) { } catch (error) {
console.warn('Email utf8 decryption failed, trying legacy hex format:', error.message); console.warn('Email utf8 decryption failed, trying legacy hex format:', error.message);
@@ -26,15 +47,16 @@ function decodeEncryptedBlob(value) {
try { try {
const encryptedHex = value.toString('hex'); const encryptedHex = value.toString('hex');
const decryptedHex = decrypt(encryptedHex); const decryptedHex = decrypt(encryptedHex);
if (decryptedHex) { const fromHex = normalizeEmailCandidate(decryptedHex);
return decryptedHex; if (fromHex) {
return fromHex;
} }
} catch (error) { } catch (error) {
console.warn('Email legacy hex decryption failed:', error.message); console.warn('Email legacy hex decryption failed:', error.message);
} }
try { try {
return value.toString('utf8'); return normalizeEmailCandidate(value.toString('utf8'));
} catch (error) { } catch (error) {
console.warn('Email could not be read as plain text:', error.message); console.warn('Email could not be read as plain text:', error.message);
return null; return null;

View File

@@ -426,11 +426,12 @@ class SettingsService extends BaseService{
const adultAccess = await this.getAdultAccessStateByUserId(user.id); const adultAccess = await this.getAdultAccessStateByUserId(user.id);
/* adultAccess zuletzt mergen, email danach setzen — nie von anderen Payload-Feldern überschreiben */
return { return {
username: user.username, username: user.username,
email: email,
showinsearch: user.searchable, showinsearch: user.searchable,
...adultAccess ...adultAccess,
email,
}; };
} catch (error) { } catch (error) {
console.error('Error getting account settings:', error); console.error('Error getting account settings:', error);