From 2ea195475e5662ea503819cbbda32828fdc8c080 Mon Sep 17 00:00:00 2001 From: Sarahtonin Date: Fri, 4 Sep 2026 18:02:15 +0200 Subject: [PATCH] Added deletion of users and export of data. GDPR.. --- Pages/Guide.php | 34 ++++++ Pages/Profile.php | 42 +++++++ Scripts/Garden/Account.js | 245 ++++++++++++++++++++++++++++++++++++++ Styles/Style.css | 14 +++ 4 files changed, 335 insertions(+) diff --git a/Pages/Guide.php b/Pages/Guide.php index 7b653bc..dee5190 100644 --- a/Pages/Guide.php +++ b/Pages/Guide.php @@ -231,6 +231,40 @@ $PageSection = "Guide"; has it can load the same Garden account and mess stuff up.

+ +
+

Privacy and your data

+ +

+ The Garden API stores the data needed to keep your + account synchronized, including your Garden save, + username, profile colour, Dew totals, and the time + the server save was last updated. The server identifies + the account using a one-way hash derived from your + Account Key rather than storing the Account Key itself. +

+ +

+ This browser stores your Account Key and a local copy + of your Garden save so the same account can keep working + between visits. If you set a username, some profile + information and statistics can also appear on the + public leaderboard. +

+ +

+ The Profile + page lets you export a JSON copy of the account data + stored on the server, or permanently delete the account. + Deleting removes the server account and clears the + Account Key and local save from the browser where you + perform the deletion. Other devices have their own local + browser storage and cannot be cleared remotely. +

+
diff --git a/Pages/Profile.php b/Pages/Profile.php index 82d7772..0dbe0fe 100644 --- a/Pages/Profile.php +++ b/Pages/Profile.php @@ -320,6 +320,48 @@ $PageTitle = "Profile"; >

+ + +

Account data

+ +

+ Download a JSON copy of the Garden account + data currently stored on the server. The + Account Key is not included in this export. +

+ +
+ +
+ + +

Delete account

+ +

+ Permanently delete this Garden account from + the server and clear its Account Key and save + from this browser. This cannot be undone. +

+ +
+ +
+ +

diff --git a/Scripts/Garden/Account.js b/Scripts/Garden/Account.js index 610c071..7d33eac 100644 --- a/Scripts/Garden/Account.js +++ b/Scripts/Garden/Account.js @@ -14,6 +14,16 @@ function StartAccount() { "ExportAccountKeyButton" ); + const ExportDataButton = + document.getElementById( + "ExportAccountDataButton" + ); + + const DeleteAccountButton = + document.getElementById( + "DeleteAccountButton" + ); + const ImportForm = document.getElementById( "ImportAccountForm" @@ -45,6 +55,16 @@ function StartAccount() { ExportAccountKey ); + ExportDataButton.addEventListener( + "click", + ExportAccountData + ); + + DeleteAccountButton.addEventListener( + "click", + DeleteAccount + ); + ImportForm.addEventListener( "submit", SubmitAccountImport @@ -272,6 +292,231 @@ function ExportAccountKey() { } +async function ExportAccountData() { + const Message = + document.getElementById( + "AccountDataMessage" + ); + + const Button = + document.getElementById( + "ExportAccountDataButton" + ); + + + Message.textContent = + "Preparing account data..."; + + Button.disabled = true; + + + try { + const Result = + await RequestAccountData( + "Export" + ); + + if ( + Result.Exists !== true || + Result.Data === undefined + ) { + Message.textContent = + "No server account data exists to export."; + + return; + } + + + const File = + new Blob( + [ + JSON.stringify( + Result.Data, + null, + 4 + ) + "\n" + ], + { + type: + "application/json;charset=utf-8" + } + ); + + const FileUrl = + URL.createObjectURL( + File + ); + + const Link = + document.createElement( + "a" + ); + + Link.href = FileUrl; + Link.download = + "SarahtoninGardenAccountData.json"; + + + document.body.appendChild( + Link + ); + + Link.click(); + Link.remove(); + + + URL.revokeObjectURL( + FileUrl + ); + + + Message.textContent = + "Account data exported."; + } catch (Error) { + console.error( + "Couldn't export account data:", + Error + ); + + Message.textContent = + "Couldn't export account data right now."; + } finally { + Button.disabled = false; + } +} + + +async function DeleteAccount() { + const Message = + document.getElementById( + "AccountDataMessage" + ); + + const Button = + document.getElementById( + "DeleteAccountButton" + ); + + + const Confirmation = + window.prompt( + "Permanently delete this Garden account?\n\n" + + "The server save and this browser's local " + + "Account Key/save will be removed. This cannot " + + "be undone.\n\n" + + "Type DELETE to continue." + ); + + + if (Confirmation !== "DELETE") { + Message.textContent = + Confirmation === null + ? "Account deletion cancelled." + : "Account deletion cancelled: type DELETE exactly."; + + return; + } + + + Message.textContent = + "Deleting account..."; + + Button.disabled = true; + + + try { + await RequestAccountData( + "Delete", + { + Confirm: "DELETE" + } + ); + + + localStorage.removeItem( + LocalSaveName + ); + + localStorage.removeItem( + SaveKeyName + ); + + + Message.textContent = + "Account deleted. Returning home..."; + + + window.location.replace( + "/" + ); + } catch (Error) { + console.error( + "Couldn't delete account:", + Error + ); + + Message.textContent = + "Couldn't delete the account. Nothing was cleared locally."; + + Button.disabled = false; + } +} + + +async function RequestAccountData( + Action, + ExtraData = {} +) { + const Response = + await fetch( + ApiUrl + "/AccountData.php", + { + method: "POST", + + headers: { + "Content-Type": + "application/json" + }, + + body: JSON.stringify({ + SaveKey: GetSaveKey(), + Action: Action, + ...ExtraData + }) + } + ); + + + let Result; + + try { + Result = + await Response.json(); + } catch (Error) { + throw new Error( + "Account data API returned an invalid response." + ); + } + + + if ( + !Response.ok || + Result.Success !== true + ) { + throw new Error( + Result.Error ?? + ( + "Account data API returned HTTP " + + Response.status + ) + ); + } + + + return Result; +} + + async function SubmitAccountImport( Event ) { diff --git a/Styles/Style.css b/Styles/Style.css index 8424134..23526a9 100644 --- a/Styles/Style.css +++ b/Styles/Style.css @@ -973,6 +973,20 @@ input, button, textarea, select { color: var(--Subtext1); } +.ProfileDangerText { + color: var(--Red); +} + +.ProfileSection .ProfileDangerButton { + color: var(--Red); + border-color: var(--Red); +} + +.ProfileSection .ProfileDangerButton:hover { + color: var(--Red); + border-color: var(--Red); +} + .ProfileStatistics { display: flex; flex-direction: column;