diff --git a/README.md b/README.md index e34be49..f5e8c2f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Regélő – Android alkalmazás (v1.3.1) +# Regélő – Android alkalmazás (v1.3.2) Magyar írói és olvasói platform (Wattpad / Inkitt jellegű), Kotlin + Jetpack Compose. @@ -23,7 +23,7 @@ Google Playre aláírt Android App Bundle-t (AAB) tölts fel, ne fejlesztői deb 4. Készítsd el az AAB-t, ellenőrizd a Play Console belső tesztcsatornáján, majd csak sikeres ellenőrzés után indítsd a szélesebb körű kiadást. 5. Minden új feltöltés előtt növeld az `app/build.gradle.kts` fájlban a `versionCode` értékét. Jelenlegi értéke - `131`; ez azonos vagy kisebb értékkel nem tölthető fel frissítésként. + `132`; ez azonos vagy kisebb értékkel nem tölthető fel frissítésként. ### Parancssoros/CI aláírás Biztonságos build/CI titokként add meg mind a négy környezeti változót: @@ -48,6 +48,9 @@ a szerveroldali hitelesítés és RLS-szabályok adják. Service-role kulcsot ti - Az alkalmazás API 37-et céloz. A `versionCode` frissítése és az aláírt bundle ellenőrzése még szükséges a kiadáshoz. - A completion RPC-t használó kliens kiadása előtt a cél Supabase-környezeten legyen alkalmazva a `supabase/2026-10-26_app_v1_4.sql` migráció. +- A fióktörlés biztonságos útvonalához alkalmazd a + `supabase/2026-10-26_app_v1_4_1.sql` migrációt, és telepítsd a `delete-account` Edge Functiont. + A mobilkliens nem használ adatbázis-fallbacket, így sikertelen fájltakarítás esetén nem törli a fiókot. - A helyi Room-adatbázis SQLCipherrel titkosított. Meglévő telepítésen az indulási migráció WAL-ellenőrzőpontot készít, külön titkosított példányon konvertál, ellenőrzi, majd atomi fájlcserével vált; hiba esetén az eredeti adatbázis marad érintetlen. A véletlen adatvesztés elkerülésére ne töröld az alkalmazás adatait vagy Keystore-kulcsát. @@ -73,6 +76,10 @@ Hivatalos e-mail: regeloapp@gmail.com · Hivatalos profil az appban: @regelohiva Az olvasási előrehaladás sikertelen feltöltése WorkManager-újrapróbálást eredményez; a fájlimport a 40 MB-os határt olvasás közben érvényesíti, nem a teljes fájl memóriába töltése után. Regressziós tesztek fedik le e változtatások kritikus útvonalait. +- **1.3.2 – Biztonságos fióktörlés** – Az app csak a fájlokat is takarító `delete-account` Edge Functionön + keresztül töröl; hiba esetén jelzi, hogy a törlés eredménye nem igazolható, és nem indít fájlokat hátrahagyó + adatbázis-fallbacket. A Supabase RPC-jogosultság visszavonva, a Storage-mappák mélységhatárának elérése pedig + hibával leállítja a törlést a hiányos sikerjelzés helyett. - **v1.4 backend-kiegészítés** – A könyv befejezésének mentése szerveroldali, idempotens művelet, amely a budapesti naptári évet és az adatbázisban szereplő publikált/befejezett állapotot használja. A rate limit párhuzamos kéréseknél is atomikusan érvényesül. A kliens nem ment tokent titkosítatlanul, @@ -80,6 +87,10 @@ Hivatalos e-mail: regeloapp@gmail.com · Hivatalos profil az appban: @regelohiva az ismeretlen sémájú helyi adatokat. A helyi Room-adatbázis SQLCipherrel titkosított; a kulcs Keystore-ban védett AES-GCM burkolással tárolódik, sérült vagy hiányzó kulcs esetén az alkalmazás az adatok törlése helyett hibával leáll. A korábbi sima SQLite-fájlt ellenőrzött, atomi migráció alakítja át. +- **v1.4.1 backend-kiegészítés** – A fióktörlési RPC végrehajtása anon és bejelentkezett kliensek elől visszavonva, + így a Storage-fájlok törlését kihagyó közvetlen adatbázis-törlési útvonal nem használható. A kliens Edge Function + hibájánál megőrzi a fiókot, és hibát jelez; a Storage-listázás mélységkorlátja túllépésekor a törlés megáll, + nem jelenti sikeresnek a hiányos fájltakarítást. - **Profilon kezelt írások** – A saját művek és piszkozatok, import, sorozatok, export, statisztika és törlés a Profil fülön érhető el; az Írás fül a napi célra és írói kihívásokra összpontosít. - **1.3.0** – Hivatalos kiadványok, adatvédelem és biztonság: diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bc32383..005c110 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,8 +26,8 @@ android { applicationId = "hu.regelo.app" minSdk = 26 targetSdk = 37 - versionCode = 131 - versionName = "1.3.1" + versionCode = 132 + versionName = "1.3.2" buildConfigField("String", "SUPABASE_URL", "\"https://pqmfezkvaslqedalwwnn.supabase.co\"") // Nyilvános (publishable) kulcs – a sorszintű jogosultságok (RLS) védik az adatokat. diff --git a/app/src/main/java/hu/regelo/app/data/AuthRepository.kt b/app/src/main/java/hu/regelo/app/data/AuthRepository.kt index 0db177e..e069e67 100644 --- a/app/src/main/java/hu/regelo/app/data/AuthRepository.kt +++ b/app/src/main/java/hu/regelo/app/data/AuthRepository.kt @@ -188,20 +188,20 @@ class AuthRepository { /** * App Store / Google Play követelmény: a fiók és minden adata végleges törlése. * Elsőként a delete-account Edge Function fut (a feltöltött képeket is törli a tárolókból); - * ha az nem érhető el, az adatbázis-függvény törli a fiókot és minden sorát. + * hiba esetén nem használunk adatbázis-fallbacket, mert az árván hagyhatja a feltöltött fájlokat. */ suspend fun deleteAccount() { - val deletedWithFiles = try { + try { AppGraph.supabase.functions.invoke("delete-account") - true } catch (e: CancellationException) { throw e } catch (e: Exception) { hu.regelo.app.util.ErrorReporter.report(e, "delete-account-function") - false + throw IllegalStateException( + "A fióktörlés eredményét nem sikerült megerősíteni. Ellenőrizd a fiók állapotát, majd próbáld újra.", + e, + ) } - // Ha a függvény már törölte a fiókot, ez nem talál sort (a token még érvényes) – nem hiba. - if (!deletedWithFiles) AppGraph.supabase.postgrest.rpc("delete_own_account") withContext(NonCancellable) { AppGraph.messages.stop() runCatching { auth.signOut() } diff --git a/supabase/2026-10-26_app_v1_4_1.sql b/supabase/2026-10-26_app_v1_4_1.sql new file mode 100644 index 0000000..286b63f --- /dev/null +++ b/supabase/2026-10-26_app_v1_4_1.sql @@ -0,0 +1,3 @@ +-- Regélő app v1.4.1 – a fióktörlés csak a fájlokat is kezelő Edge Functionön keresztül történhet. +-- Az alkalmazás nem hívja ezt az RPC-t; közvetlen hozzáférés megkerülné a Storage-takarítást. +revoke execute on function public.delete_own_account() from public, anon, authenticated; diff --git a/supabase/functions/delete-account/index.ts b/supabase/functions/delete-account/index.ts index 419ad6d..20cef2f 100644 --- a/supabase/functions/delete-account/index.ts +++ b/supabase/functions/delete-account/index.ts @@ -20,7 +20,7 @@ const admin: SupabaseClient = createClient( /** Egy mappa összes fájlja (rekurzívan), a tároló gyökeréhez viszonyított útvonallal. */ async function listAll(bucket: string, prefix: string, depth = 0): Promise { - if (depth > 6) return []; + if (depth > 6) throw new Error(`${bucket}: storage folder nesting limit exceeded`); const paths: string[] = []; for (let offset = 0; ; offset += PAGE) { const { data, error } = await admin.storage.from(bucket).list(prefix, { limit: PAGE, offset }); diff --git a/supabase/tests/rls_battery_v1_3.sql b/supabase/tests/rls_battery_v1_3.sql index 188bfd8..04c268d 100644 --- a/supabase/tests/rls_battery_v1_3.sql +++ b/supabase/tests/rls_battery_v1_3.sql @@ -22,6 +22,13 @@ begin and c.is_draft = false order by c.order_index desc limit 1), false); end $$; +insert into t_log(ok, msg) +values ( + not has_function_privilege('authenticated', 'public.delete_own_account()', 'EXECUTE') + and not has_function_privilege('anon', 'public.delete_own_account()', 'EXECUTE'), + 'A fiók törlése csak a Storage-t is takarító Edge Functionön keresztül érhető el' +); + -- ================= A (hétköznapi felhasználó) ================= select set_config('request.jwt.claims', json_build_object('sub', current_setting('t.a'), 'role', 'authenticated')::text, true); set local role authenticated;