diff --git a/app/__test-fixtures__/databaseStorageFixtures.ts b/app/__test-fixtures__/databaseStorageFixtures.ts
index 77f90e5486..ed0e0b9c29 100644
--- a/app/__test-fixtures__/databaseStorageFixtures.ts
+++ b/app/__test-fixtures__/databaseStorageFixtures.ts
@@ -51,6 +51,11 @@ export const facebookStampFixture: Stamp = {
credential,
};
+export const brightidStampFixture: Stamp = {
+ provider: "Brightid",
+ credential,
+};
+
export const passportFixture: Passport = {
issuanceDate: new Date("2022-01-01"),
expiryDate: new Date("2022-01-02"),
diff --git a/app/__test-fixtures__/verifiableCredentialResults.ts b/app/__test-fixtures__/verifiableCredentialResults.ts
index 4b3c5ed3b5..c1bbf485b8 100644
--- a/app/__test-fixtures__/verifiableCredentialResults.ts
+++ b/app/__test-fixtures__/verifiableCredentialResults.ts
@@ -63,3 +63,17 @@ export const SUCCESFUL_POH_RESULT: VerifiableCredentialRecord = {
challenge: credential,
credential: credential,
};
+
+export const SUCCESFUL_BRIGHTID_RESULT: VerifiableCredentialRecord = {
+ record: {
+ type: "Brightid",
+ address: "0xcF323CE817E25b4F784bC1e14c9A99A525fDC50f",
+ version: "0.0.0",
+ contextId: "somedata",
+ meets: "true",
+ },
+ signature:
+ "0xbdbac10fdb0921e73df7575e47cbda484be550c36570bc146bed90c5dcb7435e64178cb263864f48af1ad6eeee1ee94c9a0794a3812ae861f8898a973233abea1c",
+ challenge: credential,
+ credential: credential,
+};
diff --git a/app/__tests__/components/ProviderCards/BrightidCard.test.tsx b/app/__tests__/components/ProviderCards/BrightidCard.test.tsx
new file mode 100644
index 0000000000..3138e20e3b
--- /dev/null
+++ b/app/__tests__/components/ProviderCards/BrightidCard.test.tsx
@@ -0,0 +1,214 @@
+import React from "react";
+import { render, screen, fireEvent, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
+import { BrightidCard } from "../../../components/ProviderCards";
+
+import { UserContext, UserContextState } from "../../../context/userContext";
+import { mockAddress, mockWallet } from "../../../__test-fixtures__/onboardHookValues";
+import { STAMP_PROVIDERS } from "../../../config/providers";
+import { brightidStampFixture } from "../../../__test-fixtures__/databaseStorageFixtures";
+import { SUCCESFUL_BRIGHTID_RESULT } from "../../../__test-fixtures__/verifiableCredentialResults";
+import { fetchVerifiableCredential } from "@dpopp/identity";
+
+jest.mock("@dpopp/identity", () => ({
+ fetchVerifiableCredential: jest.fn(),
+}));
+jest.mock("../../../utils/onboard.ts");
+
+const mockHandleConnection = jest.fn();
+const mockCreatePassport = jest.fn();
+const handleAddStamp = jest.fn();
+const mockUserContext: UserContextState = {
+ userDid: "mockUserDid",
+ loggedIn: true,
+ passport: undefined,
+ isLoadingPassport: false,
+ allProvidersState: {
+ Brightid: {
+ providerSpec: STAMP_PROVIDERS.Brightid,
+ stamp: undefined,
+ },
+ },
+ handleAddStamp: handleAddStamp,
+ handleCreatePassport: mockCreatePassport,
+ handleConnection: mockHandleConnection,
+ address: mockAddress,
+ wallet: mockWallet,
+ signer: undefined,
+ walletLabel: mockWallet.label,
+};
+
+function setupFetchStub(valid: any) {
+ return function fetchStub(_url: any) {
+ return new Promise((resolve) => {
+ resolve({
+ json: () =>
+ Promise.resolve({
+ valid,
+ }),
+ });
+ });
+ };
+}
+
+describe("when user has not verfied with BrightId Provider", () => {
+ it("should display a verify button", () => {
+ render(
+
+
+
+ );
+
+ const initialVerifyButton = screen.queryByTestId("button-verify-brightid");
+
+ expect(initialVerifyButton).toBeInTheDocument();
+ });
+});
+
+describe("when user has verified with BrightId Provider", () => {
+ it("should display that a verified credential was returned", () => {
+ render(
+
+
+
+ );
+
+ const brightidVerified = screen.queryByText(/Verified/);
+
+ expect(brightidVerified).toBeInTheDocument();
+ });
+});
+
+describe("when the verify button is clicked", () => {
+ let originalFetch: any;
+ beforeEach(() => {
+ originalFetch = global.fetch;
+ global.fetch = jest.fn().mockImplementation(setupFetchStub(true));
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ jest.clearAllMocks();
+ });
+
+ describe("and when a successful BrightId result is returned", () => {
+ beforeEach(() => {
+ (fetchVerifiableCredential as jest.Mock).mockResolvedValue(SUCCESFUL_BRIGHTID_RESULT);
+ });
+
+ it("the modal displays the verify button", async () => {
+ render(
+
+
+
+ );
+
+ const initialVerifyButton = screen.queryByTestId("button-verify-brightid");
+
+ fireEvent.click(initialVerifyButton!);
+
+ const verifyModal = await screen.findByRole("dialog");
+ const verifyModalButton = screen.getByTestId("modal-verify");
+
+ expect(verifyModal).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(verifyModalButton).toBeInTheDocument();
+ });
+ });
+
+ it("clicking verify adds the stamp", async () => {
+ render(
+
+
+
+ );
+
+ const initialVerifyButton = screen.queryByTestId("button-verify-brightid");
+
+ // Click verify button on brightid card
+ fireEvent.click(initialVerifyButton!);
+
+ // Wait to see the verify button on the modal
+ await waitFor(() => {
+ const verifyModalButton = screen.getByTestId("modal-verify");
+ expect(verifyModalButton).toBeInTheDocument();
+ });
+
+ const finalVerifyButton = screen.queryByRole("button", {
+ name: /Verify/,
+ });
+
+ // Click the verify button on modal
+ fireEvent.click(finalVerifyButton!);
+
+ expect(handleAddStamp).toBeCalled();
+ });
+
+ it("clicking cancel closes the modal and a stamp should not be added", async () => {
+ (fetchVerifiableCredential as jest.Mock).mockResolvedValue(SUCCESFUL_BRIGHTID_RESULT);
+ render(
+
+
+
+ );
+
+ const initialVerifyButton = screen.queryByTestId("button-verify-brightid");
+
+ fireEvent.click(initialVerifyButton!);
+
+ // Wait to see the cancel button on the modal
+ let modalCancelButton: HTMLElement | null = null;
+ await waitFor(() => {
+ modalCancelButton = screen.queryByRole("button", {
+ name: /Cancel/,
+ });
+ expect(modalCancelButton).toBeInTheDocument();
+ });
+
+ fireEvent.click(modalCancelButton!);
+
+ expect(handleAddStamp).not.toBeCalled();
+
+ await waitForElementToBeRemoved(modalCancelButton);
+ expect(modalCancelButton).not.toBeInTheDocument();
+ });
+ });
+
+ describe("and when a failed Bright Id result is returned", () => {
+ it("modal displays steps to get sponsored", async () => {
+ global.fetch = jest.fn().mockImplementation(setupFetchStub(false));
+ (fetchVerifiableCredential as jest.Mock).mockRejectedValue("ERROR");
+ render(
+
+
+
+ );
+
+ const initialVerifyButton = screen.queryByTestId("button-verify-brightid");
+
+ fireEvent.click(initialVerifyButton!);
+
+ const verifyModal = await screen.findByRole("dialog");
+ const triggerSponsorButton = screen.queryByTestId("button-sponsor-brightid");
+ const verifyModalText = screen.getByText(
+ "A verifiable credential was not generated for your address. Follow the steps below to qualify:"
+ );
+
+ expect(verifyModal).toBeInTheDocument();
+ await waitFor(() => {
+ expect(verifyModalText).toBeInTheDocument();
+ });
+ expect(triggerSponsorButton).toBeInTheDocument();
+ });
+ });
+});
diff --git a/app/__tests__/components/ProviderCards/EnsCard.test.tsx b/app/__tests__/components/ProviderCards/EnsCard.test.tsx
index 28f04d3d9d..dc6ea190ce 100644
--- a/app/__tests__/components/ProviderCards/EnsCard.test.tsx
+++ b/app/__tests__/components/ProviderCards/EnsCard.test.tsx
@@ -18,6 +18,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: true,
passport: undefined,
isLoadingPassport: false,
diff --git a/app/__tests__/components/ProviderCards/FacebookCard.test.tsx b/app/__tests__/components/ProviderCards/FacebookCard.test.tsx
index f38c0cc897..8ca659be79 100644
--- a/app/__tests__/components/ProviderCards/FacebookCard.test.tsx
+++ b/app/__tests__/components/ProviderCards/FacebookCard.test.tsx
@@ -13,6 +13,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: true,
passport: undefined,
isLoadingPassport: false,
diff --git a/app/__tests__/components/ProviderCards/GoogleCard.test.tsx b/app/__tests__/components/ProviderCards/GoogleCard.test.tsx
index b57df4af29..74fa181976 100644
--- a/app/__tests__/components/ProviderCards/GoogleCard.test.tsx
+++ b/app/__tests__/components/ProviderCards/GoogleCard.test.tsx
@@ -13,6 +13,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: true,
passport: undefined,
isLoadingPassport: false,
diff --git a/app/__tests__/components/ProviderCards/PoapCard.test.tsx b/app/__tests__/components/ProviderCards/PoapCard.test.tsx
index 34b9e03aea..fcfec1604d 100644
--- a/app/__tests__/components/ProviderCards/PoapCard.test.tsx
+++ b/app/__tests__/components/ProviderCards/PoapCard.test.tsx
@@ -18,6 +18,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: true,
passport: undefined,
isLoadingPassport: false,
diff --git a/app/__tests__/components/ProviderCards/PohCard.test.tsx b/app/__tests__/components/ProviderCards/PohCard.test.tsx
index 7c2d77f569..aabc6edfca 100644
--- a/app/__tests__/components/ProviderCards/PohCard.test.tsx
+++ b/app/__tests__/components/ProviderCards/PohCard.test.tsx
@@ -18,6 +18,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: true,
passport: undefined,
isLoadingPassport: false,
@@ -114,7 +115,7 @@ describe("when the verify button is clicked", () => {
const initialVerifyButton = screen.queryByTestId("button-verify-poh");
- // Click verify button on ens card
+ // Click verify button on poh card
fireEvent.click(initialVerifyButton!);
// Wait to see the verify button on the modal
diff --git a/app/__tests__/components/ProviderCards/TwitterCard.test.tsx b/app/__tests__/components/ProviderCards/TwitterCard.test.tsx
index b1bca75364..bc662409d1 100644
--- a/app/__tests__/components/ProviderCards/TwitterCard.test.tsx
+++ b/app/__tests__/components/ProviderCards/TwitterCard.test.tsx
@@ -13,6 +13,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: true,
passport: undefined,
isLoadingPassport: false,
diff --git a/app/__tests__/pages/Dashboard.test.tsx b/app/__tests__/pages/Dashboard.test.tsx
index 7e8210d1f7..c706807d0a 100644
--- a/app/__tests__/pages/Dashboard.test.tsx
+++ b/app/__tests__/pages/Dashboard.test.tsx
@@ -12,6 +12,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: true,
passport: undefined,
isLoadingPassport: false,
@@ -40,6 +41,10 @@ const mockUserContext: UserContextState = {
providerSpec: STAMP_PROVIDERS.Facebook,
stamp: undefined,
},
+ Brightid: {
+ providerSpec: STAMP_PROVIDERS.Brightid,
+ stamp: undefined,
+ },
},
handleAddStamp: handleAddStamp,
handleCreatePassport: mockCreatePassport,
diff --git a/app/__tests__/pages/Home.test.tsx b/app/__tests__/pages/Home.test.tsx
index d283f09391..1b4081362d 100644
--- a/app/__tests__/pages/Home.test.tsx
+++ b/app/__tests__/pages/Home.test.tsx
@@ -12,6 +12,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: false,
passport: undefined,
isLoadingPassport: false,
@@ -40,6 +41,10 @@ const mockUserContext: UserContextState = {
providerSpec: STAMP_PROVIDERS.Facebook,
stamp: undefined,
},
+ Brightid: {
+ providerSpec: STAMP_PROVIDERS.Brightid,
+ stamp: undefined,
+ },
},
handleAddStamp: handleAddStamp,
handleCreatePassport: mockCreatePassport,
diff --git a/app/__tests__/pages/index.test.tsx b/app/__tests__/pages/index.test.tsx
index 4c8e735531..b2207faa5a 100644
--- a/app/__tests__/pages/index.test.tsx
+++ b/app/__tests__/pages/index.test.tsx
@@ -9,6 +9,7 @@ const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
+ userDid: undefined,
loggedIn: false,
passport: undefined,
isLoadingPassport: false,
@@ -37,6 +38,10 @@ const mockUserContext: UserContextState = {
providerSpec: STAMP_PROVIDERS.Facebook,
stamp: undefined,
},
+ Brightid: {
+ providerSpec: STAMP_PROVIDERS.Brightid,
+ stamp: undefined,
+ },
},
handleAddStamp: handleAddStamp,
handleCreatePassport: mockCreatePassport,
diff --git a/app/components/CardList.tsx b/app/components/CardList.tsx
index 66c5a1db82..61a90d3cc4 100644
--- a/app/components/CardList.tsx
+++ b/app/components/CardList.tsx
@@ -2,18 +2,19 @@
import React from "react";
// --- Identity Providers
-import { GoogleCard, EnsCard, PohCard, TwitterCard, PoapCard, FacebookCard } from "./ProviderCards";
+import { GoogleCard, EnsCard, PohCard, TwitterCard, PoapCard, FacebookCard, BrightidCard } from "./ProviderCards";
export const CardList = (): JSX.Element => {
return (
);
diff --git a/app/components/ProviderCards/BrightidCard.tsx b/app/components/ProviderCards/BrightidCard.tsx
new file mode 100644
index 0000000000..7cdb2e6655
--- /dev/null
+++ b/app/components/ProviderCards/BrightidCard.tsx
@@ -0,0 +1,238 @@
+// --- React Methods
+import React, { useContext, useState } from "react";
+
+// --- Identity tools
+import { fetchVerifiableCredential } from "@dpopp/identity";
+
+// --- pull context
+import { UserContext } from "../../context/userContext";
+
+// --- Verification step tools
+import QRCode from "react-qr-code";
+
+// --- import components
+import { Card } from "../Card";
+import { VerifyModal } from "../VerifyModal";
+import { useDisclosure, Button, useToast } from "@chakra-ui/react";
+
+// ---- Types
+import { PROVIDER_ID, Stamp, BrightIdProcedureResponse } from "@dpopp/types";
+import { ProviderSpec } from "../../config/providers";
+
+const iamUrl = process.env.NEXT_PUBLIC_DPOPP_IAM_URL || "";
+
+const providerId: PROVIDER_ID = "Brightid";
+
+type BrightIdProviderRecord = {
+ context?: string;
+ contextId?: string;
+ meets?: string;
+};
+
+export default function BrightIdCard(): JSX.Element {
+ const { address, signer, handleAddStamp, allProvidersState, userDid } = useContext(UserContext);
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ const [credentialResponse, SetCredentialResponse] = useState(undefined);
+ const [credentialResponseIsLoading, setCredentialResponseIsLoading] = useState(false);
+ const [brightIdVerification, SetBrightIdVerification] = useState(undefined);
+ const toast = useToast();
+
+ const handleFetchCredential = (): void => {
+ setCredentialResponseIsLoading(true);
+ fetchVerifiableCredential(
+ iamUrl,
+ {
+ type: "Brightid",
+ version: "0.0.0",
+ address: address || "",
+ proofs: {
+ did: userDid || "",
+ },
+ },
+ signer as { signMessage: (message: string) => Promise }
+ )
+ .then((verified: { record: any; credential: any }): void => {
+ SetBrightIdVerification(verified.record);
+ SetCredentialResponse({
+ provider: "Brightid",
+ credential: verified.credential,
+ });
+ })
+ .catch((e: any): void => {})
+ .finally((): void => {
+ setCredentialResponseIsLoading(false);
+ });
+ };
+
+ async function handleSponsorship(): Promise {
+ setCredentialResponseIsLoading(true);
+ const res = fetch(`${process.env.NEXT_PUBLIC_DPOPP_PROCEDURE_URL?.replace(/\/*?$/, "")}/brightid/sponsor`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ contextIdData: userDid,
+ }),
+ });
+ const data = await (await res).json();
+ if (data?.response?.result?.status === "success") {
+ toast({
+ title: "Success",
+ description: "Successfully triggered BrightID Sponsorship. Come back to Passport to Verify.",
+ status: "success",
+ duration: 9000,
+ isClosable: true,
+ });
+ } else {
+ toast({
+ title: "Failure",
+ description: "Failed to trigger BrightID Sponsorship",
+ status: "error",
+ duration: 9000,
+ isClosable: true,
+ });
+ }
+ setCredentialResponseIsLoading(false);
+ }
+
+ async function handleVerifyContextId(): Promise {
+ const res = await fetch(
+ `${process.env.NEXT_PUBLIC_DPOPP_PROCEDURE_URL?.replace(/\/*?$/, "")}/brightid/verifyContextId`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ contextIdData: userDid,
+ }),
+ }
+ );
+ const result: BrightIdProcedureResponse = await res.json();
+ return result.valid;
+ }
+
+ const handleUserVerify = (): void => {
+ if (credentialResponse) {
+ handleAddStamp(credentialResponse);
+ }
+ onClose();
+ };
+
+ // Widget displays steps to verify BrightID with Gitcoin
+ const brightIdSponsorshipWidget = (
+
+
BrightID
+ {userDid ? (
+
+
+
A verifiable credential was not generated for your address. Follow the steps below to qualify:
+
+
+
1) Download the BrightID App on your mobile device
+
+
+ 2) Connect BrightID to Gitcoin by scanning this QR code from the BrightID app, or clicking{" "}
+
+ here
+ {" "}
+ from your mobile device.
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
Refresh Browser and Try Again
+ )}
+
+ );
+
+ const issueCredentialWidget = (
+ <>
+
+
+ {brightIdVerification
+ ? `Your BrightId has been verified on ${brightIdVerification.context}`
+ : brightIdSponsorshipWidget}
+ >
+ }
+ isLoading={credentialResponseIsLoading}
+ />
+ >
+ );
+
+ return (
+
+ );
+}
diff --git a/app/components/ProviderCards/index.tsx b/app/components/ProviderCards/index.tsx
index 5a5fa11d52..78524d90c4 100644
--- a/app/components/ProviderCards/index.tsx
+++ b/app/components/ProviderCards/index.tsx
@@ -4,3 +4,4 @@ export { default as PohCard } from "./PohCard";
export { default as TwitterCard } from "./TwitterCard";
export { default as PoapCard } from "./PoapCard";
export { default as FacebookCard } from "./FacebookCard";
+export { default as BrightidCard } from "./BrightidCard";
diff --git a/app/config/providers.ts b/app/config/providers.ts
index 645e5f08ee..104a79dd23 100644
--- a/app/config/providers.ts
+++ b/app/config/providers.ts
@@ -1,7 +1,7 @@
import { PROVIDER_ID } from "@dpopp/types";
export type ProviderSpec = {
- icon: string | undefined;
+ icon?: string | undefined;
name: string;
description: string;
};
@@ -41,4 +41,9 @@ export const STAMP_PROVIDERS: Readonly = {
name: "Facebook",
description: "Facebook name",
},
+ Brightid: {
+ icon: "./assets/brightidStampIcon.svg",
+ name: "Bright ID",
+ description: "Bright ID name",
+ },
};
diff --git a/app/context/userContext.tsx b/app/context/userContext.tsx
index d37badcf40..e42a0394de 100644
--- a/app/context/userContext.tsx
+++ b/app/context/userContext.tsx
@@ -53,6 +53,10 @@ const startingAllProvidersState: AllProvidersState = {
providerSpec: STAMP_PROVIDERS.Facebook,
stamp: undefined,
},
+ Brightid: {
+ providerSpec: STAMP_PROVIDERS.Brightid,
+ stamp: undefined,
+ },
};
export interface UserContextState {
@@ -67,6 +71,7 @@ export interface UserContextState {
wallet: WalletState | null;
signer: JsonRpcSigner | undefined;
walletLabel: string | undefined;
+ userDid: string | undefined;
}
const startingState: UserContextState = {
loggedIn: false,
@@ -80,6 +85,7 @@ const startingState: UserContextState = {
wallet: null,
signer: undefined,
walletLabel: undefined,
+ userDid: undefined,
};
// create our app context
@@ -100,6 +106,7 @@ export const UserContextProvider = ({ children }: { children: any }) => {
const [walletLabel, setWalletLabel] = useState();
const [address, setAddress] = useState();
const [signer, setSigner] = useState();
+ const [userDid, setUserDid] = useState();
// Init onboard to enable hooks
useEffect((): void => {
@@ -168,6 +175,7 @@ export const UserContextProvider = ({ children }: { children: any }) => {
);
setCeramicDatabase(ceramicDatabaseInstance);
fetchPassport(ceramicDatabaseInstance);
+ setUserDid(viewerConnection.selfID.id);
break;
}
case "failed": {
@@ -301,6 +309,7 @@ export const UserContextProvider = ({ children }: { children: any }) => {
wallet,
signer,
walletLabel,
+ userDid,
}),
[loggedIn, address, passport, isLoadingPassport, signer, wallet, allProvidersState]
);
@@ -318,6 +327,7 @@ export const UserContextProvider = ({ children }: { children: any }) => {
wallet,
signer,
walletLabel,
+ userDid,
};
return {children};
diff --git a/app/package.json b/app/package.json
index fb1ae75569..66afb3aac3 100644
--- a/app/package.json
+++ b/app/package.json
@@ -30,6 +30,7 @@
"broadcast-channel": "^4.11.0",
"framer-motion": "^6.3.0",
"next": "latest",
+ "node-fetch": "^3.2.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-google-login": "^5.2.2",
@@ -51,6 +52,7 @@
"@typescript-eslint/parser": "^5.20.0",
"autoprefixer": "^10.4.0",
"babel-jest": "^28.0.0",
+ "base64-js": "^1.5.1",
"eslint": "^8.13.0",
"eslint-config-next": "^12.1.5",
"eslint-config-prettier": "^8.5.0",
@@ -64,6 +66,7 @@
"postcss": "^8.4.5",
"prettier": "^2.6.2",
"prettier-plugin-tailwindcss": "^0.1.1",
+ "react-qr-code": "^2.0.7",
"tailwindcss": "^3.0.7",
"ts-jest": "^27.1.4",
"ts-node": "^10.8.0",
diff --git a/app/public/assets/appAndroid.svg b/app/public/assets/appAndroid.svg
new file mode 100644
index 0000000000..0158ad4672
--- /dev/null
+++ b/app/public/assets/appAndroid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/public/assets/appApple.svg b/app/public/assets/appApple.svg
new file mode 100644
index 0000000000..cc4d7025fa
--- /dev/null
+++ b/app/public/assets/appApple.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/public/assets/brightidStampIcon.svg b/app/public/assets/brightidStampIcon.svg
index 04ede0143a..85dceb2390 100644
--- a/app/public/assets/brightidStampIcon.svg
+++ b/app/public/assets/brightidStampIcon.svg
@@ -1,5 +1,5 @@
diff --git a/iam/.env-example.env b/iam/.env-example.env
index a5d8333c67..63c44d11a2 100644
--- a/iam/.env-example.env
+++ b/iam/.env-example.env
@@ -5,6 +5,7 @@ TWITTER_CLIENT_ID=MY-APP-ID-TWITTER
TWITTER_CLIENT_SECRET=MY_APP_SECRET
FACEBOOK_APP_ID=MY_APP_ID
FACEBOOK_APP_SECRET=MY_APP_SECRET
+BRIGHTID_PRIVATE_KEY=BRIGHTID_PRIVATE_KEY
IAM_JWK='{"kty":"OKP","crv":"Ed25519","x":"yourIamKeyValues","d":"yourIamKeyValues"}'
RPC_URL=https://eth-mainnet.alchemyapi.io/v2/
\ No newline at end of file
diff --git a/iam/__tests__/brightid.test.ts b/iam/__tests__/brightid.test.ts
new file mode 100644
index 0000000000..c67158a6c9
--- /dev/null
+++ b/iam/__tests__/brightid.test.ts
@@ -0,0 +1,168 @@
+// --- Test subject
+import { BrightIdProvider } from "../src/providers/brightid";
+import { triggerBrightidSponsorship } from "../src/procedures/brightid";
+import { BrightIdVerificationResponse, BrightIdSponsorshipResponse } from "@dpopp/types";
+import { RequestPayload } from "@dpopp/types";
+import { verifyContextId, sponsor } from "brightid_sdk";
+
+jest.mock("brightid_sdk", () => ({
+ verifyContextId: jest.fn(),
+ sponsor: jest.fn(),
+}));
+
+describe("Attempt BrightId", () => {
+ const did = "did:3:kjzl0cwe1jw1475ckc0ib6k44mm4sqegorxc1x23ppqh9lt9quso6yp7ayh2fae";
+ const urlEncodedDid = "did%3A3%3Akjzl0cwe1jw1475ckc0ib6k44mm4sqegorxc1x23ppqh9lt9quso6yp7ayh2fae";
+ const nonUniqueResponse: BrightIdVerificationResponse = {
+ unique: false,
+ app: "Gitcoin",
+ context: "Gitcoin",
+ contextIds: ["sampleContextId"],
+ };
+
+ const validVerificationResponse: BrightIdVerificationResponse = {
+ unique: true,
+ app: "Gitcoin",
+ context: "Gitcoin",
+ contextIds: ["sampleContextId"],
+ };
+
+ let invalidVerificationResponse: BrightIdVerificationResponse = {
+ status: 400,
+ statusText: "Not Found",
+ data: {
+ error: true,
+ errorNum: 2,
+ errorMessage: "Not Found",
+ contextIds: ["sampleContextId"],
+ code: 400,
+ },
+ };
+
+ const validSponsorshipResponse: BrightIdSponsorshipResponse = {
+ status: "success",
+ statusReason: "successfulStatusReason",
+ };
+
+ let invalidSponsorshipResponse: BrightIdSponsorshipResponse = {
+ status: 404,
+ statusText: "Not Found",
+ data: {
+ error: true,
+ errorNum: 12,
+ errorMessage: "Passport app is not found.",
+ code: 404,
+ },
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("Handles Verification", () => {
+ it("valid BrightId did as contextId verification attempt, returns valid true, verifies if user has Meet status and verified contextId", async () => {
+ (verifyContextId as jest.Mock).mockResolvedValue(validVerificationResponse);
+
+ const result = await new BrightIdProvider().verify({
+ proofs: {
+ did,
+ },
+ } as unknown as RequestPayload);
+
+ expect(verifyContextId).toBeCalledTimes(1);
+ expect(verifyContextId).toBeCalledWith("Gitcoin", urlEncodedDid);
+ expect(result).toMatchObject({
+ valid: true,
+ record: {
+ contextId: "sampleContextId",
+ meets: "true",
+ },
+ });
+ });
+
+ it("invalid BrightId did as contextId verification attempt, returns valid false and record undefined", async () => {
+ (verifyContextId as jest.Mock).mockResolvedValue(invalidVerificationResponse);
+
+ const result = await new BrightIdProvider().verify({
+ proofs: {
+ did,
+ },
+ } as unknown as RequestPayload);
+
+ expect(verifyContextId).toBeCalledTimes(1);
+ expect(result).toMatchObject({
+ valid: false,
+ record: undefined,
+ });
+ });
+
+ it("thrown error from BrightId did as contextId verification attempt, returns valid false", async () => {
+ (verifyContextId as jest.Mock).mockRejectedValue("Thrown Error");
+
+ const result = await new BrightIdProvider().verify({
+ proofs: {
+ did,
+ },
+ } as unknown as RequestPayload);
+
+ expect(verifyContextId).toBeCalledTimes(1);
+ expect(result).toMatchObject({
+ valid: false,
+ });
+ });
+
+ it("user is sponsored but did not attend a connection party, returns valid false and record undefined", async () => {
+ (verifyContextId as jest.Mock).mockResolvedValue(nonUniqueResponse);
+
+ const result = await new BrightIdProvider().verify({
+ proofs: {
+ did,
+ },
+ } as unknown as RequestPayload);
+
+ expect(verifyContextId).toBeCalledTimes(1);
+ expect(result).toMatchObject({
+ valid: false,
+ record: undefined,
+ });
+ });
+ });
+
+ describe("Handles Sponsorship", () => {
+ it("successful attempt", async () => {
+ (sponsor as jest.Mock).mockResolvedValue(validSponsorshipResponse);
+ const result = await triggerBrightidSponsorship(did);
+
+ expect(sponsor).toBeCalledTimes(1);
+ expect(sponsor).toBeCalledWith(process.env.BRIGHTID_PRIVATE_KEY, "Gitcoin", urlEncodedDid);
+ expect(result).toMatchObject({
+ valid: true,
+ result: validSponsorshipResponse,
+ });
+ });
+
+ it("unsuccessful attempt", async () => {
+ (sponsor as jest.Mock).mockResolvedValue(invalidSponsorshipResponse);
+ const result = await triggerBrightidSponsorship(did);
+
+ expect(sponsor).toBeCalledTimes(1);
+ expect(sponsor).toBeCalledWith(process.env.BRIGHTID_PRIVATE_KEY, "Gitcoin", urlEncodedDid);
+ expect(result).toMatchObject({
+ valid: false,
+ result: invalidSponsorshipResponse,
+ });
+ });
+
+ it("error thrown from an unsuccessful attempt", async () => {
+ (sponsor as jest.Mock).mockRejectedValue("Thrown Error");
+ const result = await triggerBrightidSponsorship(did);
+
+ expect(sponsor).toBeCalledTimes(1);
+ expect(sponsor).toBeCalledWith(process.env.BRIGHTID_PRIVATE_KEY, "Gitcoin", urlEncodedDid);
+ expect(result).toMatchObject({
+ valid: false,
+ error: "Thrown Error",
+ });
+ });
+ });
+});
diff --git a/iam/package.json b/iam/package.json
index 8f0de44a05..f3d1c3f163 100644
--- a/iam/package.json
+++ b/iam/package.json
@@ -21,6 +21,7 @@
"@dpopp/types": "^0.0.1",
"@spruceid/didkit-wasm-node": "^0.2.1",
"axios": "^0.27.2",
+ "brightid_sdk": "^1.0.1",
"cors": "^2.8.5",
"dotenv": "^16.0.0",
"ethers": "^5.0.32",
@@ -29,7 +30,8 @@
"luxon": "^2.4.0",
"tslint": "^6.1.3",
"twitter-api-sdk": "1.0.6",
- "typescript": "~4.6.3"
+ "typescript": "~4.6.3",
+ "uuid": "^8.3.2"
},
"devDependencies": {
"@types/cors": "^2.8.12",
@@ -41,5 +43,8 @@
"@types/webpack-env": "^1.16.3",
"jest": "^27.5.1",
"supertest": "^6.2.2"
+ },
+ "resolutions": {
+ "leveldown": "6.1.1"
}
}
diff --git a/iam/src/index.ts b/iam/src/index.ts
index ae914fb842..f00de27929 100644
--- a/iam/src/index.ts
+++ b/iam/src/index.ts
@@ -37,6 +37,7 @@ import { EnsProvider } from "./providers/ens";
import { PohProvider } from "./providers/poh";
import { POAPProvider } from "./providers/poap";
import { FacebookProvider } from "./providers/facebook";
+import { BrightIdProvider } from "./providers/brightid";
// Initiate providers - new Providers should be registered in this array...
const providers = new Providers([
@@ -48,6 +49,7 @@ const providers = new Providers([
new PohProvider(),
new POAPProvider(),
new FacebookProvider(),
+ new BrightIdProvider(),
]);
// create the app and run on port
diff --git a/iam/src/procedures/brightid.ts b/iam/src/procedures/brightid.ts
new file mode 100644
index 0000000000..d094e6fe50
--- /dev/null
+++ b/iam/src/procedures/brightid.ts
@@ -0,0 +1,36 @@
+import { verifyContextId, sponsor } from "brightid_sdk";
+import { BrightIdProcedureResponse, BrightIdVerificationResponse, BrightIdSponsorshipResponse } from "@dpopp/types";
+
+// --- app name for Bright Id App
+const CONTEXT = "Gitcoin";
+
+export const verifyBrightidContextId = async (contextIdData: string): Promise => {
+ const contextId = encodeURIComponent(contextIdData);
+
+ try {
+ const verifyContextIdResult: BrightIdVerificationResponse = (await verifyContextId(
+ CONTEXT,
+ contextId
+ )) as BrightIdVerificationResponse;
+
+ return { valid: "contextIds" in verifyContextIdResult, result: verifyContextIdResult };
+ } catch (err: unknown) {
+ return { valid: false, error: err as string };
+ }
+};
+
+export const triggerBrightidSponsorship = async (contextIdData: string): Promise => {
+ const contextId = encodeURIComponent(contextIdData);
+
+ try {
+ const sponsorResult: BrightIdSponsorshipResponse = (await sponsor(
+ process.env.BRIGHTID_PRIVATE_KEY,
+ CONTEXT,
+ contextId
+ )) as BrightIdSponsorshipResponse;
+
+ return { valid: sponsorResult.status === "success", result: sponsorResult };
+ } catch (err: unknown) {
+ return { valid: false, error: err as string };
+ }
+};
diff --git a/iam/src/procedures/index.ts b/iam/src/procedures/index.ts
index e6a4597177..23b46a65d5 100644
--- a/iam/src/procedures/index.ts
+++ b/iam/src/procedures/index.ts
@@ -2,6 +2,7 @@
import { Request, Response, Router } from "express";
import { generateAuthURL, getSessionKey, initClient } from "./twitterOauth";
+import { triggerBrightidSponsorship, verifyBrightidContextId } from "./brightid";
export const router = Router();
@@ -9,6 +10,10 @@ export type GenerateTwitterAuthUrlRequestBody = {
callback: string;
};
+export type GenerateBrightidBody = {
+ contextIdData: string;
+};
+
router.post("/twitter/generateAuthUrl", (req: Request, res: Response): void => {
const { callback } = req.body as GenerateTwitterAuthUrlRequestBody;
if (callback) {
@@ -25,3 +30,25 @@ router.post("/twitter/generateAuthUrl", (req: Request, res: Response): void => {
res.status(400);
}
});
+
+router.post("/brightid/sponsor", (req: Request, res: Response): void => {
+ const { contextIdData } = req.body as GenerateBrightidBody;
+ if (contextIdData) {
+ return void triggerBrightidSponsorship(contextIdData).then((response) => {
+ return res.status(200).send({ response });
+ });
+ } else {
+ res.status(400);
+ }
+});
+
+router.post("/brightid/verifyContextId", (req: Request, res: Response): void => {
+ const { contextIdData } = req.body as GenerateBrightidBody;
+ if (contextIdData) {
+ return void verifyBrightidContextId(contextIdData).then((response) => {
+ return res.status(200).send({ response });
+ });
+ } else {
+ res.status(400);
+ }
+});
diff --git a/iam/src/providers/brightid.ts b/iam/src/providers/brightid.ts
new file mode 100644
index 0000000000..48bc1f1816
--- /dev/null
+++ b/iam/src/providers/brightid.ts
@@ -0,0 +1,47 @@
+// ----- Types
+import type { Provider, ProviderOptions } from "../types";
+import type { RequestPayload, VerifiedPayload, BrightIdVerificationResponse } from "@dpopp/types";
+
+// --- verifyMethod in providers
+import { verifyBrightidContextId } from "../procedures/brightid";
+
+// Request a verifiable credential from brightid
+export class BrightIdProvider implements Provider {
+ // Give the provider a type so that we can select it with a payload
+ type = "Brightid";
+ // Options can be set here and/or via the constructor
+ _options = {};
+
+ // construct the provider instance with supplied options
+ constructor(options: ProviderOptions = {}) {
+ this._options = { ...this._options, ...options };
+ }
+
+ async verify(payload: RequestPayload): Promise {
+ try {
+ const did = payload.proofs?.did;
+
+ const responseData = await verifyBrightidContextId(did);
+ const formattedData: BrightIdVerificationResponse = responseData?.result as BrightIdVerificationResponse;
+
+ // Unique is true if the user obtained "Meets" verification by attending a connection party
+ const isUnique = "unique" in formattedData && formattedData.unique === true;
+ const firstContextId =
+ "contextIds" in formattedData && formattedData.contextIds.length > 0 && formattedData.contextIds[0];
+ const valid: boolean = firstContextId && isUnique;
+
+ return {
+ valid,
+ record: valid
+ ? {
+ context: "context" in formattedData && formattedData.context,
+ contextId: firstContextId,
+ meets: JSON.stringify(isUnique),
+ }
+ : undefined,
+ };
+ } catch (e) {
+ return { valid: false };
+ }
+ }
+}
diff --git a/infra/production/index.ts b/infra/production/index.ts
index 2ca97fd3f2..739239bfbc 100644
--- a/infra/production/index.ts
+++ b/infra/production/index.ts
@@ -188,6 +188,10 @@ const service = new awsx.ecs.FargateService("dpopp-iam", {
name: "FACEBOOK_APP_SECRET",
valueFrom: `${IAM_SERVER_SSM_ARN}:FACEBOOK_APP_SECRET::`,
},
+ {
+ name: "BRIGHTID_PRIVATE_KEY",
+ valueFrom: `${IAM_SERVER_SSM_ARN}:BRIGHTID_PRIVATE_KEY::`,
+ },
],
},
},
diff --git a/infra/review/index.ts b/infra/review/index.ts
index 81f2e1a2ca..41b970e169 100644
--- a/infra/review/index.ts
+++ b/infra/review/index.ts
@@ -187,6 +187,10 @@ const service = new awsx.ecs.FargateService("dpopp-iam", {
name: "FACEBOOK_APP_SECRET",
valueFrom: `${IAM_SERVER_SSM_ARN}:FACEBOOK_APP_SECRET::`,
},
+ {
+ name: "BRIGHTID_PRIVATE_KEY",
+ valueFrom: `${IAM_SERVER_SSM_ARN}:BRIGHTID_PRIVATE_KEY::`,
+ },
],
},
},
diff --git a/infra/staging/index.ts b/infra/staging/index.ts
index 95b7acae87..d4d5c662ba 100644
--- a/infra/staging/index.ts
+++ b/infra/staging/index.ts
@@ -191,6 +191,10 @@ const service = new awsx.ecs.FargateService("dpopp-iam", {
name: "FACEBOOK_APP_SECRET",
valueFrom: `${IAM_SERVER_SSM_ARN}:FACEBOOK_APP_SECRET::`,
},
+ {
+ name: "BRIGHTID_PRIVATE_KEY",
+ valueFrom: `${IAM_SERVER_SSM_ARN}:BRIGHTID_PRIVATE_KEY::`,
+ },
],
},
},
diff --git a/types/src/brightid.d.ts b/types/src/brightid.d.ts
new file mode 100644
index 0000000000..c614743c83
--- /dev/null
+++ b/types/src/brightid.d.ts
@@ -0,0 +1,33 @@
+export type FailResponse = {
+ status?: number;
+ statusText?: string;
+ data?: {
+ error: boolean;
+ errorNum: number;
+ errorMessage: string;
+ contextIds?: string[];
+ code: number;
+ };
+};
+
+export type SponsorshipSuccessResponse = {
+ status: string;
+ statusReason: string;
+};
+
+export type VerificationSuccessResponse = {
+ unique?: boolean;
+ app?: string;
+ context?: string;
+ contextIds?: string[];
+};
+
+export type BrightIdVerificationResponse = FailResponse | VerificationSuccessResponse;
+
+export type BrightIdSponsorshipResponse = FailResponse | SponsorshipSuccessResponse;
+
+export type BrightIdProcedureResponse = {
+ valid: boolean;
+ result?: BrightIdSponsorshipResponse | BrightIdVerificationResponse;
+ error?: string;
+};
diff --git a/types/src/index.d.ts b/types/src/index.d.ts
index 8fb9a5a60f..10a4297d19 100644
--- a/types/src/index.d.ts
+++ b/types/src/index.d.ts
@@ -1,3 +1,6 @@
+// BrightId Shared Types
+export { BrightIdProcedureResponse, BrightIdVerificationResponse, BrightIdSponsorshipResponse } from "./brightid";
+
// Typing for required parts of DIDKit
export type DIDKitLib = {
verifyCredential: (vc: string, proofOptions: string) => Promise;
@@ -119,4 +122,4 @@ export type Passport = {
// Passport DID
export type DID = string;
-export type PROVIDER_ID = "Google" | "Ens" | "Poh" | "Twitter" | "POAP" | "Facebook";
+export type PROVIDER_ID = "Google" | "Ens" | "Poh" | "Twitter" | "POAP" | "Facebook" | "Brightid";
diff --git a/yarn.lock b/yarn.lock
index d2a8a2f1b5..866f7b7663 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5973,6 +5973,13 @@ axe-core@^4.3.5:
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz"
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
+axios@^0.21.0:
+ version "0.21.4"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
+ integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
+ dependencies:
+ follow-redirects "^1.14.0"
+
axios@^0.24.0:
version "0.24.0"
resolved "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz"
@@ -6149,7 +6156,7 @@ base32-encode@1.2.0:
dependencies:
to-data-view "^1.1.0"
-base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1:
+base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -6424,6 +6431,16 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
+brightid_sdk@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/brightid_sdk/-/brightid_sdk-1.0.1.tgz#e848865456f2db8ba49c2156483814e44a10ef9f"
+ integrity sha512-3tdprkXj/odL5BpQEHnWSLdwAxmMvs8AkRk7rF6UGnAoQTZjR4FEPNkxBAuPF2BbDoqKw2ebQ7P2LPhPGsahdA==
+ dependencies:
+ axios "^0.21.0"
+ base64-js "^1.5.1"
+ fast-json-stable-stringify "^2.1.0"
+ tweetnacl "^1.0.3"
+
broadcast-channel@^3.4.1:
version "3.7.0"
resolved "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz"
@@ -7535,6 +7552,11 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
+data-uri-to-buffer@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
+ integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
+
data-urls@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz"
@@ -9086,6 +9108,14 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
+fetch-blob@^3.1.2, fetch-blob@^3.1.4:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.5.tgz#0077bf5f3fcdbd9d75a0b5362f77dbb743489863"
+ integrity sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==
+ dependencies:
+ node-domexception "^1.0.0"
+ web-streams-polyfill "^3.0.3"
+
figures@^3.0.0:
version "3.2.0"
resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz"
@@ -9208,6 +9238,11 @@ focus-lock@^0.9.1:
dependencies:
tslib "^2.0.3"
+follow-redirects@^1.14.0:
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
+ integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
+
follow-redirects@^1.14.4, follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz"
@@ -9250,6 +9285,13 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
+formdata-polyfill@^4.0.10:
+ version "4.0.10"
+ resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
+ integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
+ dependencies:
+ fetch-blob "^3.1.2"
+
formidable@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz"
@@ -13373,6 +13415,11 @@ node-addon-api@^2.0.0:
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz"
integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==
+node-domexception@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
+ integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
+
node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7:
version "2.6.7"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz"
@@ -13380,6 +13427,15 @@ node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7:
dependencies:
whatwg-url "^5.0.0"
+node-fetch@^3.2.4:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.4.tgz#3fbca2d8838111048232de54cb532bd3cf134947"
+ integrity sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw==
+ dependencies:
+ data-uri-to-buffer "^4.0.0"
+ fetch-blob "^3.1.4"
+ formdata-polyfill "^4.0.10"
+
"node-fetch@https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz":
version "2.6.7"
resolved "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz#1b5d62978f2ed07b99444f64f0df39f960a6d34d"
@@ -14487,6 +14543,11 @@ q@^1.5.1:
resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
+qr.js@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
+ integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=
+
qrcode@1.4.4:
version "1.4.4"
resolved "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz"
@@ -14680,6 +14741,14 @@ react-native-fetch-api@^2.0.0:
dependencies:
p-defer "^3.0.0"
+react-qr-code@^2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/react-qr-code/-/react-qr-code-2.0.7.tgz#508304e031e82426a044f5e9490aca87d7c3de38"
+ integrity sha512-NpknL80p7dWbLdHfBSIxQIqLCu3+kBlyzYD692rO0UnVwfCSziIxc1xn/p3JhPEv1RV1lRD8j0w+hR3L7tawTQ==
+ dependencies:
+ prop-types "^15.7.2"
+ qr.js "0.0.0"
+
react-query@^3.35.0:
version "3.38.1"
resolved "https://registry.npmjs.org/react-query/-/react-query-3.38.1.tgz"
@@ -16645,6 +16714,11 @@ uuid@^3.3.2:
resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^8.3.2:
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
v8-compile-cache-lib@^3.0.0, v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
@@ -16764,6 +16838,11 @@ wcwidth@^1.0.0:
dependencies:
defaults "^1.0.3"
+web-streams-polyfill@^3.0.3:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
+ integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
+
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"