diff --git a/platforms/src/Civic/Providers/civic.ts b/platforms/src/Civic/Providers/civic.ts index dce0b09224..9e2d7eeac3 100644 --- a/platforms/src/Civic/Providers/civic.ts +++ b/platforms/src/Civic/Providers/civic.ts @@ -38,29 +38,29 @@ export class CivicPassProvider implements Provider { // Verify that address defined in the payload has a civic pass async verify(payload: RequestPayload): Promise { - // if a signer is provider we will use that address to verify against const address = payload.address.toString().toLowerCase(); const now = getNowAsBigNumberSeconds(); - let errors = undefined; - let record = undefined; + let errors, record, expiresInSeconds; let valid = false; const allPasses = await findAllPasses(address, this.includeTestnets, [this.passType]); + const activePasses = allPasses.filter(({ state }) => state === "ACTIVE"); + const validPasses = activePasses.filter(({ expiry }) => expiry.gt(now)); - if (allPasses.length > 0) { - const validPasses = allPasses.filter((pass) => pass.expiry.gt(now)); - if (validPasses.length > 0) { - record = { address }; - valid = true; - } else { - errors = [`Your ${CivicPassType[this.passType]} pass${allPasses.length > 1 ? "es are" : " is"} expired.`]; - } - } else { + if (allPasses.length === 0) { errors = [`You do not have a ${CivicPassType[this.passType]} pass.`]; + } else if (activePasses.length === 0) { + errors = [ + `Your ${CivicPassType[this.passType]} pass${allPasses.length > 1 ? "es are" : " is"} frozen or revoked.`, + ]; + } else if (validPasses.length === 0) { + errors = [`Your ${CivicPassType[this.passType]} pass${activePasses.length > 1 ? "es are" : " is"} expired.`]; + } else { + record = { address }; + valid = true; + expiresInSeconds = secondsFromNow(latestExpiry(validPasses)); } - const expiresInSeconds = valid ? secondsFromNow(latestExpiry(allPasses)) : undefined; - return { valid, errors, diff --git a/platforms/src/Civic/Providers/types.ts b/platforms/src/Civic/Providers/types.ts index 9b6775b34f..c7921206d2 100644 --- a/platforms/src/Civic/Providers/types.ts +++ b/platforms/src/Civic/Providers/types.ts @@ -28,6 +28,8 @@ export const supportedChains = [ export type SupportedChain = typeof supportedChains[number]; +type CivicPassState = "ACTIVE" | "FROZEN" | "REVOKED"; + export type CivicPassLookupPass = { type: { slotId: number; @@ -37,7 +39,7 @@ export type CivicPassLookupPass = { chain: string; identifier: string; expiry?: number; - state: "ACTIVE" | "FROZEN" | "REVOKED"; + state: CivicPassState; }; export type PassesForAddress = { passes: Record }; export type CivicPassLookupResponse = Record; @@ -45,6 +47,7 @@ export type CivicPassLookupResponse = Record; type PassDetails = { expiry?: BigNumber; identifier: string; + state: CivicPassState; }; export type Pass = PassDetails & { type: CivicPassType; diff --git a/platforms/src/Civic/Providers/util.ts b/platforms/src/Civic/Providers/util.ts index d1b5e0b830..7f2d965857 100644 --- a/platforms/src/Civic/Providers/util.ts +++ b/platforms/src/Civic/Providers/util.ts @@ -22,6 +22,7 @@ const passLookupResponseToPass = chain: pass.chain as SupportedChain, expiry: BigNumber.from(pass.expiry), identifier: pass.identifier, + state: pass.state, }); const passTypesToNames = (passTypes: CivicPassType[]): string[] => passTypes.map((id) => CivicPassType[id]); diff --git a/platforms/src/Civic/__tests__/civic.test.ts b/platforms/src/Civic/__tests__/civic.test.ts index 678f7a968c..b8b1623aa4 100644 --- a/platforms/src/Civic/__tests__/civic.test.ts +++ b/platforms/src/Civic/__tests__/civic.test.ts @@ -2,7 +2,6 @@ import { RequestPayload } from "@gitcoin/passport-types"; import { CivicPassProvider } from "../Providers/civic"; import { CivicPassLookupPass, CivicPassType, PassesForAddress } from "../Providers/types"; import axios from "axios"; -import { ProviderExternalVerificationError } from "../../types"; // Mock out all top level functions, such as get, put, delete and post: jest.mock("axios"); @@ -15,19 +14,21 @@ const stubCivic = (passes: PassesForAddress["passes"]): void => { }); }; -const stubCivicError = (error: string): void => { - (axios.get as jest.Mock).mockRejectedValue(new Error(error)); -}; - const now = Math.floor(Date.now() / 1000); const userAddress = "0x123"; const requestPayload = { address: userAddress } as RequestPayload; const expirySeconds = 1000; -const dummyPass = { +const dummyPass: CivicPassLookupPass = { chain: "ETHEREUM_MAINNET", expiry: now + expirySeconds, -} as CivicPassLookupPass; + state: "ACTIVE", + type: { + slotId: 0, + address: userAddress, + }, + identifier: "0x456", +}; describe("Civic Pass Provider", function () { beforeEach(() => { @@ -74,6 +75,28 @@ describe("Civic Pass Provider", function () { }); }); + it("should return detailed error for a revoked pass", async () => { + const revokedPass = { ...dummyPass }; + revokedPass.state = "REVOKED"; + + stubCivic({ + UNIQUENESS: [revokedPass], + }); + + const civic = new CivicPassProvider({ + type: "uniqueness", + passType: CivicPassType.UNIQUENESS, + }); + + const verifiedPayload = await civic.verify(requestPayload); + + expect(verifiedPayload).toMatchObject({ + valid: false, + record: undefined, + errors: ["Your UNIQUENESS pass is frozen or revoked."], + }); + }); + it("should return valid true if a pass is found", async () => { stubCivic({ UNIQUENESS: [dummyPass],