feat(auth): wallet partagé = seul mode + purge identifiant-wallet↔username

AccessGateScreen 3-branches (erreur config si pas de wallet partagé) ; renommage username→identifier de l'identité du wallet (registration, ngSession, hooks, steps auth) sans toucher UserProfile.username ; .env.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
This commit is contained in:
Sylvain Duchesne
2026-07-20 13:16:04 +02:00
parent 46ed894621
commit e326bebd42
8 changed files with 99 additions and 23 deletions
+3 -3
View File
@@ -49,9 +49,9 @@ import { setCurrentUser } from '@ng-eventually/client/polyfill';
// Festipod localStorage key for the current identifier (same-partition
// prefill/convenience only — never the cross-frontier carrier; that's the URL
// param). Changed from the historical 'festipod.account.username' → any
// pre-existing stored "logins" under the old key are dropped (acceptable: this
// is a stopgap test env; the URL param carries identity anyway).
// param). Renamed to `.identifier` from a historical key that mislabeled this
// account id → any pre-existing stored logins under the old key are dropped
// (acceptable: this is a stopgap test env; the URL param carries identity anyway).
const STORAGE_KEY = 'festipod.account.identifier';
/** Name of the URL param that carries the identifier across the broker frontier. */
+3 -3
View File
@@ -343,17 +343,17 @@ export async function readRegistrationNotifications(
* just-written participation, so a second join checking only the reactive set would
* write a duplicate. Querying the broker sees the real state regardless of read lag.
*
* Scoped to the CURRENT account (`username`) via `listMyEntityDocs` — a user's own
* Scoped to the CURRENT account (`identifier`) via `listMyEntityDocs` — a user's own
* participations live in their own account, so there is NO need to fan out over all
* accounts (which would open/sync other accounts' unsynced docs → the ~75s hang).
*/
export async function countUserParticipations(
username: string,
identifier: string,
eventId: string,
userId: string,
): Promise<number> {
const sid = (await sessionPromise).session_id;
const docs_ = await listMyEntityDocs(username, 'protected');
const docs_ = await listMyEntityDocs(identifier, 'protected');
let total = 0;
for (const g of docs_) {
total += await countParticipations(sid, g, eventId, userId).catch(() => 0);
+7 -7
View File
@@ -22,7 +22,7 @@ setDefaultTimeout(90000);
// account (whose shim key uses a sentinel prefix `normalizeIdentifier` can't emit).
const RUN_NONCE = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
let scenarioSeq = 0;
function freshScenarioUsername(): string {
function freshScenarioIdentifier(): string {
scenarioSeq += 1;
return `test-${RUN_NONCE}-${scenarioSeq}`;
}
@@ -580,7 +580,7 @@ Before({ timeout: 60000 }, async function (this: FestipodWorld, scenario) {
// the run self-heals instead of cascading failures across the rest.
this.page = await newWalletPageResilient();
// FRESH VIRTUAL WALLET per scenario (see freshScenarioUsername above). Set a
// FRESH VIRTUAL WALLET per scenario (see freshScenarioIdentifier above). Set a
// UNIQUE app-level identifier into localStorage['festipod.account.identifier']
// on EVERY origin (the init script runs in each frame before its scripts do —
// including the harness iframe on 127.0.0.1). At mount the harness's
@@ -588,11 +588,11 @@ Before({ timeout: 60000 }, async function (this: FestipodWorld, scenario) {
// login(DEFAULT_HARNESS_USER)` is skipped and the scenario runs on a fresh,
// empty virtual wallet. Overwrites any value persisted in the Chromium profile
// (init scripts run on each navigation), so no accumulated wallet leaks in.
const freshUser = freshScenarioUsername();
(this as any).freshUser = freshUser;
const freshIdentifier = freshScenarioIdentifier();
(this as any).freshIdentifier = freshIdentifier;
await this.page.addInitScript((u: string) => {
try { window.localStorage.setItem('festipod.account.identifier', u); } catch { /* opaque origin */ }
}, freshUser);
}, freshIdentifier);
// Capture console for debugging AND collect into the World so smoke
// scenarios can assert no runtime error was emitted during the connected
@@ -622,8 +622,8 @@ Before({ timeout: 60000 }, async function (this: FestipodWorld, scenario) {
);
// NO per-scenario registry/wallet reset needed anymore (was T03.j
// resetDataState). Each @data scenario now runs under a UNIQUE username
// (freshScenarioUsername, set into localStorage above), so the shim hands it
// resetDataState). Each @data scenario now runs under a UNIQUE identifier
// (freshScenarioIdentifier, set into localStorage above), so the shim hands it
// a FRESH, EMPTY virtual wallet whose account registry starts empty by
// construction — nothing to purge. This also drops the ≤10s reset cost that
// shared the Before hook's budget with the (slow) broker login.
+1 -1
View File
@@ -82,7 +82,7 @@ export async function login() {
* REAL NextGraph logout — stops the session of the SHARED wallet.
*
* STOPGAP: must stay HIDDEN (Settings/debug only). The everyday "Déconnexion"
* is the FAUX one (AccountContext.logout, clears the username only). Calling
* is the FAUX one (AccountContext.logout, clears the identifier only). Calling
* this forces a new broker redirect on the next access — see
* decision_2026-06-15_shared-wallet-login-flow.
*/