/* ============================================================ EATME PORTÁL – SECURITY HELPERS ============================================================ */ /* ============================================================ NÁHODNÝ AKTIVAČNÍ KÓD ============================================================ */ function randomCode_() { return String( Math.floor( 100000 + Math.random() * 900000 ) ); } /* ============================================================ SESSION TOKEN ============================================================ */ function randomToken_() { return Utilities .base64EncodeWebSafe( Utilities.computeDigest( Utilities.DigestAlgorithm.SHA_256, Utilities.getUuid() + ':' + new Date().getTime() + ':' + Math.random() ) ) .replace( /=+$/, '' ); } /* ============================================================ SHA-256 ============================================================ */ function hashText_(text) { const bytes = Utilities.computeDigest( Utilities.DigestAlgorithm.SHA_256, String( text ) ); return bytes .map( function(byte) { return ( '0' + ( ( byte < 0 ? byte + 256 : byte ) .toString( 16 ) ) ).slice( -2 ); } ) .join( '' ); } /* ============================================================ PASSWORD HASH POZOR: Toto je původní algoritmus. Neměníme jej, aby fungovala současná hesla. ============================================================ */ function passwordHash_( password, salt ) { const pepper = PropertiesService .getScriptProperties() .getProperty( 'PASSWORD_PEPPER' ) || ''; let value = String( password ) + '|' + salt + '|' + pepper; for ( let i = 0; i < CFG.PASSWORD_ROUNDS; i++ ) { value = hashText_( value + '|' + i ); } return value; } /* ============================================================ VALIDACE HESLA ============================================================ */ function validatePassword_(password) { const value = String( password || '' ); if ( value.length < 10 ) { throw new Error( 'Heslo musí mít alespoň 10 znaků.' ); } if ( !/[A-Za-z]/.test( value ) || !/[0-9]/.test( value ) ) { throw new Error( 'Heslo musí obsahovat písmeno a číslo.' ); } } /* ============================================================ OVĚŘENÍ UŽIVATELE Optimalizace: už při každém requestu NEZAPISUJEME last_seen_at. ============================================================ */ function requireUser_( token, roles ) { const tokenHash = hashText_( String( token || '' ) ); const session = findOne_( CFG.SHEETS.SESSIONS, function(row) { return ( String( row.token_hash ) === tokenHash && new Date( row.expires_at ).getTime() > Date.now() ); } ); if ( !session ) { throw new Error( 'Přihlášení vypršelo. Přihlas se znovu.' ); } const user = findOne_( CFG.SHEETS.USERS, function(row) { return ( String( row.user_id ) === String( session.user_id ) ); } ); if ( !user || !( user.active === true || String( user.active ).toUpperCase() === 'TRUE' ) ) { throw new Error( 'Účet není aktivní.' ); } if ( roles && roles.length && !roles.includes( String( user.role ) ) ) { throw new Error( 'Nemáš oprávnění.' ); } /* * PŮVODNĚ ZDE BYLO: * * updateBy_( * CFG.SHEETS.SESSIONS, * 'session_id', * session.session_id, * {last_seen_at:now_()} * ); * * To způsobovalo zápis do Google Sheets při téměř * každém kliknutí v aplikaci. */ return user; } /* ============================================================ AUDIT ============================================================ */ function audit_( userId, action, entityType, entityId, oldValue, newValue ) { append_( CFG.SHEETS.AUDIT_LOG, { audit_id: uuid_( 'AUD' ), user_id: userId || 'SYSTEM', action: action, entity_type: entityType || '', entity_id: entityId || '', old_value: json_( oldValue ), new_value: json_( newValue ), created_at: now_() } ); }