feat(android): a local account, so the app serves without a repository

Until now the phone could only read a vault that came from elsewhere:
without a URL, a token and a sync password it showed nothing. It now
signs in like the desktop does - username and master password - and can
create its own vault, with no network at all.

The local container carries exactly what the repository would carry: same
format, same encryption under the password. Two reasons, not one. It is
the desktop's model, where the master password protects the vault at rest
rather than the hardware alone; and the format staying the repository's
means sync keeps working with nothing to convert. No Keystore on that
file - it is already encrypted under the password, and binding it to the
device would make it unrecoverable after a reinstall. vault.bin keeps
that role: it is the fast-open cache, and only it needs sealing.

The password opens the container - the decryption itself is the check, no
secret is ever compared - and the username, read from inside once open,
says whose vault it is.

The home screen now knows which of three situations it is in: nothing
here yet, a container without a cache, or a cache ready to open.

Verified on the device: with the cache set aside, the screen offers
username, master password and Create vault, alongside the download path.
Creating an account could not be exercised to the end - the device locked
its screen mid-run - and the vault that was set aside is back in place,
same size, still sealed under its code.
This commit is contained in:
r-zakarya
2026-08-21 14:07:39 +01:00
parent b1be802e22
commit f32cde81fc
4 changed files with 288 additions and 1 deletions
+27
View File
@@ -35,6 +35,11 @@ type
/// ecrire quand meme la ferait reapparaitre.
function PatchEntryField(const APlainJson, AUuid, AField, AValue: string): string;
/// Un instantane vide, au nom de ce compte. Sert a l'inscription depuis le
/// telephone : meme forme que ce qu'ecrit le poste, pour qu'un coffre ne
/// naisse pas dans un dialecte a part.
function NewEmptySnapshot(const AUsername: string): string;
/// Horodatage UTC au format que tout le reste emploie (toISOString cote JS).
/// MELANGER heure locale et UTC a deja decale l'arbitrage de la fusion une
/// fois : il n'y a qu'une source d'heure ici, et elle est en UTC.
@@ -48,6 +53,28 @@ begin
TTimeZone.Local.ToUniversalTime(Now));
end;
function NewEmptySnapshot(const AUsername: string): string;
var
LRoot: TJSONObject;
begin
LRoot := TJSONObject.Create;
try
// Les memes cles que buildSyncSnapshot, tableaux vides compris : un lecteur
// qui trouve la cle absente et un lecteur qui la trouve vide ne se
// comportent pas forcement pareil, et ce coffre sera lu par le poste.
LRoot.AddPair('version', TJSONNumber.Create(1));
LRoot.AddPair('snapshot_at', NowUtcIso);
LRoot.AddPair('username', AUsername);
LRoot.AddPair('folders', TJSONArray.Create);
LRoot.AddPair('avatar_b64', '');
LRoot.AddPair('entries', TJSONArray.Create);
LRoot.AddPair('tombstones', TJSONArray.Create);
Result := LRoot.ToJSON;
finally
LRoot.Free;
end;
end;
function PatchEntryField(const APlainJson, AUuid, AField, AValue: string): string;
var
LRoot, LEntry: TJSONObject;