Skip to content

Commit

Permalink
Add storage utils to persist cancelled RP IDs (#2784)
Browse files Browse the repository at this point in the history
* Add storage utils to persist cancelled RP IDs

* Rename var
  • Loading branch information
lmuntaner authored Jan 17, 2025
1 parent 736a396 commit 3ee7ece
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 0 deletions.
76 changes: 76 additions & 0 deletions src/frontend/src/storage/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import { expect } from "vitest";
import {
MAX_SAVED_ANCHORS,
MAX_SAVED_PRINCIPALS,
addAnchorCancelledRpId,
cleanUpRpIdMapper,
getAnchorByPrincipal,
getAnchorIfLastUsed,
getAnchors,
getCancelledRpIds,
setAnchorUsed,
setKnownPrincipal,
} from ".";
Expand Down Expand Up @@ -414,6 +417,79 @@ test(
})
);

test(
"should create an anchor after adding a cancelled RP ID",
withStorage(async () => {
const origin = "https://example.com";
const userNumber = BigInt(10000);
const cancelledRpId = "https://identity.ic0.app";

expect(await getAnchors()).toEqual([]);
await addAnchorCancelledRpId({ userNumber, origin, cancelledRpId });
expect(await getAnchors()).toEqual([userNumber]);
})
);

test(
"should reset the cancelled RP IDs by user number",
withStorage(async () => {
const origin = "https://example.com";
const userNumber = BigInt(10000);
const anotherUserNumber = BigInt(10001);
const cancelledRpId = "https://identity.ic0.app";

expect(await getCancelledRpIds({ userNumber, origin })).toEqual(
new Set([])
);
await addAnchorCancelledRpId({ userNumber, origin, cancelledRpId });
await addAnchorCancelledRpId({
userNumber: anotherUserNumber,
origin,
cancelledRpId,
});
expect(await getCancelledRpIds({ userNumber, origin })).toEqual(
new Set([cancelledRpId])
);
expect(
await getCancelledRpIds({ userNumber: anotherUserNumber, origin })
).toEqual(new Set([cancelledRpId]));
await cleanUpRpIdMapper(userNumber);
expect(await getCancelledRpIds({ userNumber, origin })).toEqual(
new Set([])
);
expect(
await getCancelledRpIds({ userNumber: anotherUserNumber, origin })
).toEqual(new Set([cancelledRpId]));
})
);

test(
"should add cancelled RP IDs per origin",
withStorage(async () => {
const origin = "https://example.com";
const undefinedOrigin = undefined;
const anotherOrigin = "https://another.com";
const userNumber = BigInt(10000);
const cancelledRpId = "https://identity.ic0.app";

expect(await getCancelledRpIds({ userNumber, origin })).toEqual(
new Set([])
);
await addAnchorCancelledRpId({ userNumber, origin, cancelledRpId });
await addAnchorCancelledRpId({
userNumber,
origin,
cancelledRpId: undefinedOrigin,
});
expect(await getCancelledRpIds({ userNumber, origin })).toEqual(
new Set([cancelledRpId, undefined])
);
expect(
await getCancelledRpIds({ userNumber, origin: anotherOrigin })
).toEqual(new Set([]));
})
);

/** Test storage usage. Storage is cleared after the callback has returned.
* If `before` is specified, storage is populated with its content before the test is run.
* If `after` is specified, the content of storage are checked against `after` after the
Expand Down
98 changes: 98 additions & 0 deletions src/frontend/src/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,94 @@ export const setKnownPrincipal = async ({
});
};

/**
* Sets the `cancelledRpIdsMapper` field to an empty object.
*
* This is needed when a user removes a device from their account.
* @param userNumber {bigint} The anchor number.
*/
export const cleanUpRpIdMapper = async (userNumber: bigint) => {
await withStorage((storage) => {
const anchorIndex = userNumber.toString();
const anchors = storage.anchors;
const oldAnchor = anchors[anchorIndex];

if (isNullish(oldAnchor)) {
return storage;
}

storage.anchors[anchorIndex] = {
...oldAnchor,
cancelledRpIdsMapper: {},
};

return storage;
});
};

/**
* Adds a RP ID as cancelled RP ID into the set of cancelled RP IDs for that anchor and origin.
*
* @param userNumber
* @param origin
* @param cancelledRpId
*/
export const addAnchorCancelledRpId = async ({
userNumber,
origin,
cancelledRpId,
}: {
userNumber: bigint;
origin: string;
cancelledRpId: string | undefined;
}) => {
await withStorage((storage) => {
const anchorIndex = userNumber.toString();
const anchors = storage.anchors;
const defaultAnchor: Omit<Anchor, "lastUsedTimestamp"> = {
knownPrincipals: [],
};
const oldAnchor = anchors[anchorIndex] ?? defaultAnchor;

const cancelledRpIdsMapper = oldAnchor?.cancelledRpIdsMapper ?? {};
const originCancelledRpIds = cancelledRpIdsMapper[origin] ?? [];
originCancelledRpIds.push(cancelledRpId);

storage.anchors[anchorIndex] = {
...oldAnchor,
lastUsedTimestamp: nowMillis(),
cancelledRpIdsMapper: {
...cancelledRpIdsMapper,
[origin]: originCancelledRpIds,
},
};

return storage;
});
};

/**
* Returns the last RP ID successfully used for the specific anchor in the specific ii origin.
*
* @param params
* @param params.userNumber The anchor number.
* @param params.origin The origin of the ii.
* @returns {Set<string | undefined>} The set of cancelled RP IDs for the anchor and origin. `undefined` is a valid cancelled RP ID.
*/
export const getCancelledRpIds = async ({
userNumber,
origin,
}: {
userNumber: bigint;
origin: string;
}): Promise<Set<string | undefined>> => {
const storage = await readStorage();
const anchors = storage.anchors;

const anchorData = anchors[userNumber.toString()];
return new Set(anchorData?.cancelledRpIdsMapper?.[origin] ?? []);
};

/** Accessing functions */

// Simply read the storage without updating it
Expand Down Expand Up @@ -653,9 +741,19 @@ const PrincipalDataV4 = z.object({
lastUsedTimestamp: z.number(),
});

/**
* Mapper of which RP ID didn't work for the user
*
* Record<ii_origin, Set<rp_id>>
*/
const cancelledRpIdsMapper = z.record(
z.array(z.union([z.string(), z.undefined()]))
);

const AnchorV4 = z.object({
/** Timestamp (mills since epoch) of when anchor was last used */
lastUsedTimestamp: z.number(),
cancelledRpIdsMapper: cancelledRpIdsMapper.optional(),

knownPrincipals: z.array(PrincipalDataV4),
});
Expand Down

0 comments on commit 3ee7ece

Please sign in to comment.