Skip to content

Commit

Permalink
Merge pull request #288 from idos-network/fix/blocked-popup
Browse files Browse the repository at this point in the history
fix: prevent backup popup of being blocked
  • Loading branch information
Mohammed-Mamoun98 authored Oct 1, 2024
2 parents 02b13b3 + 01edaed commit 28e13f1
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 97 deletions.
17 changes: 16 additions & 1 deletion apps/idos-enclave/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,18 @@
background-color: grey;
}

button#confirm:disabled::before {
button#backup:disabled::before {
content: "Waiting...";
}
button#backup::before {
content: "💼 Back up idOS key";
}

button#backup:disabled {
background-color: grey;
}

button#backup:disabled::before {
content: "Waiting...";
}
</style>
Expand All @@ -65,6 +76,10 @@
id="confirm"
class="h-full w-full rounded-lg bg-[#00ffb9] px-[5%] py-[2%] font-medium text-[41cqmin] text-zinc-950 transition-colors hover:bg-green-300"
></button>
<button
id="backup"
class="h-full w-full whitespace-pre rounded-lg bg-[#00ffb9] px-[5%] py-[2%] font-medium text-[41cqmin] text-zinc-950 transition-colors hover:bg-green-300"
></button>
</div>

<script type="module" src="/src/main.ts"></script>
Expand Down
44 changes: 24 additions & 20 deletions apps/idos-enclave/src/lib/enclave.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class Enclave {

this.unlockButton = document.querySelector("button#unlock");
this.confirmButton = document.querySelector("button#confirm");
this.backupButton = document.querySelector("button#backup");

const storeWithCodec = this.store.pipeCodec(Base64Codec);
const secretKey = storeWithCodec.get("encryption-private-key");
Expand Down Expand Up @@ -47,29 +48,13 @@ export class Enclave {
});
}

storage(
humanId,
signerAddress,
signerPublicKey,
expectedUserEncryptionPublicKey,
litAttrs,
userWallets,
) {
storage(humanId, signerAddress, signerPublicKey, expectedUserEncryptionPublicKey) {
humanId && this.store.set("human-id", humanId);
signerAddress && this.store.set("signer-address", signerAddress);
signerPublicKey && this.store.set("signer-public-key", signerPublicKey);
const safeLitAttrs = Array.isArray(litAttrs)
? litAttrs
: typeof litAttrs === "string"
? JSON.parse(litAttrs)
: [];

const litAttrsWithWallets = [
...safeLitAttrs,
{ attribute_key: "new-user-wallets", value: userWallets },
];

this.handlstoreableAttributes(litAttrsWithWallets);
const litAttrs = this.store.get("litAttrs");
this.handlstoreableAttributes(litAttrs);

const storeWithCodec = this.store.pipeCodec(Base64Codec);
this.expectedUserEncryptionPublicKey = expectedUserEncryptionPublicKey;
Expand Down Expand Up @@ -321,7 +306,19 @@ export class Enclave {
}

async backupPasswordOrSecret() {
return this.#openDialog("backupPasswordOrSecret");
this.backupButton.style.display = "block";
this.backupButton.disabled = false;
return new Promise((resolve, reject) => {
this.backupButton.addEventListener("click", async () => {
try {
this.backupButton.disabled = true;
await this.#openDialog("backupPasswordOrSecret");
resolve();
} catch (error) {
reject(error);
}
});
});
}

#listenToRequests() {
Expand All @@ -346,6 +343,8 @@ export class Enclave {
expectedUserEncryptionPublicKey,
litAttrs,
userWallets,
key,
value,
} = requestData;

const paramBuilder = {
Expand Down Expand Up @@ -384,6 +383,10 @@ export class Enclave {
});
}

updateStore(key, value) {
this.store.set(key, value);
}

async handleidOSStore(payload) {
return new Promise((resolve, reject) => {
const { port1, port2 } = new MessageChannel();
Expand Down Expand Up @@ -430,6 +433,7 @@ export class Enclave {
if (error) {
this.unlockButton.disabled = false;
this.confirmButton.disabled = false;
this.backupButton.disabled = false;
return reject(error);
}

Expand Down
12 changes: 7 additions & 5 deletions apps/idos-enclave/src/pages/recovery/PasswordOrKeyBackup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ export function PasswordOrKeyBackup({
}

const enableGoogleRecovery = false;
const resultMsgSrc: Record<string, string> = {
failure: "An error ocurred while updating your attributes. Please try again.",
success: `Your ${passwordOrSecretKey} has been encrypted and safely stored in your idOS.`,
};

const resultMsg = resultMsgSrc?.[backupStatus] || "";

return (
<div class="flex flex-col gap-5">
Expand All @@ -372,11 +378,7 @@ export function PasswordOrKeyBackup({
)}
</Button>
)}
{backupStatus === "success" ? (
<Paragraph>
Your {passwordOrSecretKey} has been encrypted and safely stored in your idOS.
</Paragraph>
) : null}
{resultMsg ? <Paragraph>{resultMsg}</Paragraph> : null}
</div>
);
}
20 changes: 12 additions & 8 deletions packages/idos-sdk-js/src/lib/enclave-providers/iframe-enclave.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BackupPasswordInfo, UserWallet, idOSCredential, idOSHumanAttribute } from "../types";
import type { BackupPasswordInfo, idOSCredential } from "../types";
import type { EnclaveOptions, EnclaveProvider, StoredData } from "./types";

export class IframeEnclave implements EnclaveProvider {
Expand Down Expand Up @@ -29,17 +29,13 @@ export class IframeEnclave implements EnclaveProvider {
signerAddress?: string,
signerPublicKey?: string,
expectedUserEncryptionPublicKey?: string,
litAttrs?: idOSHumanAttribute[],
userWallets?: UserWallet[],
): Promise<Uint8Array> {
let { encryptionPublicKey } = (await this.#requestToEnclave({
storage: {
humanId,
signerAddress,
signerPublicKey,
expectedUserEncryptionPublicKey,
litAttrs,
userWallets,
},
})) as StoredData;

Expand Down Expand Up @@ -177,6 +173,7 @@ export class IframeEnclave implements EnclaveProvider {
backupFn: (data: BackupPasswordInfo) => Promise<void>,
): Promise<void> {
const abortController = new AbortController();
this.#showEnclave();

window.addEventListener(
"message",
Expand All @@ -188,8 +185,10 @@ export class IframeEnclave implements EnclaveProvider {
try {
status = "success";
await backupFn(event);
this.#hideEnclave();
} catch (error) {
status = "failure";
this.#hideEnclave();
}

event.ports[0].postMessage({
Expand All @@ -204,8 +203,13 @@ export class IframeEnclave implements EnclaveProvider {
{ signal: abortController.signal },
);

await this.#requestToEnclave({
backupPasswordOrSecret: {},
});
try {
await this.#requestToEnclave({
backupPasswordOrSecret: {},
});
this.#hideEnclave();
} catch (error) {
console.error(error);
}
}
}
4 changes: 1 addition & 3 deletions packages/idos-sdk-js/src/lib/enclave-providers/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BackupPasswordInfo, UserWallet, idOSCredential, idOSHumanAttribute } from "../types";
import type { BackupPasswordInfo, idOSCredential } from "../types";

export interface StoredData {
encryptionPublicKey?: Uint8Array;
Expand All @@ -23,8 +23,6 @@ export interface EnclaveProvider {
signerAddress?: string,
signerPublicKey?: string,
currentUserPublicKey?: string,
litAttrs?: idOSHumanAttribute[],
userWallets?: UserWallet[],
): Promise<Uint8Array>;
store(key: string, value: string): Promise<string>;
reset(): Promise<void>;
Expand Down
6 changes: 3 additions & 3 deletions packages/idos-sdk-js/src/lib/enclave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export class Enclave {
const litAttrs = await this.auth.kwilWrapper.getLitAttrs();
const userWallets = await this.auth.kwilWrapper.getEvmUserWallets();

await this.provider.store("litAttrs", JSON.stringify(litAttrs));
await this.provider.store("userWallets", JSON.stringify(userWallets));
await this.provider.updateStore("litAttrs", litAttrs);
await this.provider.updateStore("new-user-wallets", userWallets);

if (this.encryptionPublicKey) return this.encryptionPublicKey;

Expand Down Expand Up @@ -90,7 +90,7 @@ export class Enclave {
}

async backupPasswordOrSecret(callbackFn: (response: BackupPasswordInfo) => Promise<void>) {
if (!this.encryptionPublicKey) await this.ready();
await this.ready();

return this.provider.backupPasswordOrSecret(callbackFn);
}
Expand Down
107 changes: 64 additions & 43 deletions packages/idos-sdk-js/src/lib/idos.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Wallet } from "@near-wallet-selector/core";
import type { Signer } from "ethers";
import { differenceWith, isEqual } from "lodash-es";
import { isEqual } from "lodash-es";
import { Store } from "../../../idos-store";
import { Auth, type AuthUser } from "./auth";
import { Data } from "./data";
Expand Down Expand Up @@ -78,8 +78,6 @@ export class idOS {
});
await idos.enclave.load();

(window as any).sdk = idos;

return idos;
}

Expand Down Expand Up @@ -135,49 +133,73 @@ export class idOS {
litSavableAttributes: storableAttributes.filter(hasLitKey),
};
}

async updateAttributesIfNeeded(
filteredUserAttributes: idOSHumanAttribute[],
litSavableAttributes: StorableAttribute[],
) {
const userAttrMap = new Map(filteredUserAttributes.map((attr) => [attr.attribute_key, attr]));
const attributeToCreate: Omit<idOSHumanAttribute, "id" | "human_id">[] = [];

const prepareValueSetter = (value: unknown): string =>
Array.isArray(value) ? JSON.stringify(value) : typeof value === "string" ? value : "";

const prepareValueGetter = (value: string): unknown => {
filteredUserAttributes: idOSHumanAttribute[], // Arrays here are not safe (it's a string)
litSavableAttributes: StorableAttribute[], // Arrays here are safe (it's a real array)
): Promise<void> {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: <explanation>
return new Promise(async (res, rej) => {
try {
if (JSON.parse(value)) return JSON.parse(value);
const userAttrMap = new Map(
filteredUserAttributes.map((attr) => [attr.attribute_key, attr]),
);
const attributesToCreate: Omit<idOSHumanAttribute, "id" | "human_id">[] = [];

// Helper function to safely parse JSON strings
const safeParse = (text: string) => {
try {
return JSON.parse(text);
} catch {
return text;
}
};

// for a safe cooldown for consequent kwill update calls
const wait = (
ms = 1000, // TODO: find another way to handle sequential updating
) =>
new Promise((res) =>
setTimeout(() => {
res(null);
}, ms),
);

// Helper function to prepare a value for storage
const prepareValueSetter = (value: unknown): string =>
Array.isArray(value) ? JSON.stringify(value) : typeof value === "string" ? value : "";

// Loop through savable attributes and handle updates/creation sequentially
for (const storableAttribute of litSavableAttributes) {
const userAttr = userAttrMap.get(storableAttribute.key);
const userAttributeValue = userAttr ? safeParse(userAttr.value as string) : undefined;

const needsUpdate =
userAttributeValue && !isEqual(userAttributeValue, storableAttribute.value);

if (userAttr) {
if (needsUpdate) {
const updatedValue = prepareValueSetter(storableAttribute.value);
await this.data.update("attributes", { ...userAttr, value: updatedValue });
await wait();
}
} else {
// Prepare attributes to create if not found
attributesToCreate.push({
attribute_key: storableAttribute.key,
value: prepareValueSetter(storableAttribute.value),
});
}
}

// Create new attributes if any are missing
if (attributesToCreate.length)
await this.data.createMultiple("attributes", attributesToCreate);

res();
} catch (error) {
return value;
}
};

for (const storableAttribute of litSavableAttributes) {
const userAttr = userAttrMap.get(storableAttribute.key);
const userAttributeValue = userAttr && prepareValueGetter(userAttr.value);

if (userAttributeValue && userAttributeValue !== storableAttribute.value) {
if (
Array.isArray(userAttributeValue) &&
!differenceWith(userAttributeValue, storableAttribute.value, isEqual).length
)
return;
await this.data.update("attributes", { ...userAttr, value: storableAttribute.value });
rej(error);
}

if (!userAttr) {
attributeToCreate.push({
attribute_key: storableAttribute.key,
value: prepareValueSetter(storableAttribute.value),
});
}
}

if (attributeToCreate.length) {
await this.data.createMultiple("attributes", attributeToCreate, true);
}
});
}

formStorableAttributes(
Expand Down Expand Up @@ -215,7 +237,6 @@ export class idOS {
userAttrs,
storableAttributes,
);

await this.updateAttributesIfNeeded(filteredUserAttributes, litSavableAttributes);
});
}
Expand Down
Loading

0 comments on commit 28e13f1

Please sign in to comment.