diff --git a/package-lock.json b/package-lock.json index 6271f1e..0a01c2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blockin", - "version": "1.2.79", + "version": "1.2.83", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "blockin", - "version": "1.2.79", + "version": "1.2.83", "license": "ISC", "devDependencies": { "@ant-design/icons": "^5.2.5", diff --git a/package.json b/package.json index b3ded65..5176452 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockin", - "version": "1.2.79", + "version": "1.2.83", "description": "", "files": [ "dist" @@ -45,6 +45,7 @@ }, "prepublishOnly": "npm run build", "devDependencies": { + "@ant-design/icons": "^5.2.5", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", @@ -53,6 +54,7 @@ "@types/react": "^18.0.8", "@types/react-blockies": "^1.4.1", "@types/react-dom": "^16.9.14", + "antd": "^5.8.4", "babel-loader": "^8.2.2", "node-sass": "^8.0.0", "postcss": "^8.4.31", @@ -63,8 +65,7 @@ "rollup-plugin-typescript2": "^0.31.2", "sass-loader": "^10.4.1", "typedoc": "^0.25.4", - "typescript": "^4.5.5", - "antd": "^5.8.4", - "@ant-design/icons": "^5.2.5" - } -} + "typescript": "^4.5.5" + }, + "dependencies": {} +} \ No newline at end of file diff --git a/src/ui/SignInModal/SignInModal.tsx b/src/ui/SignInModal/SignInModal.tsx index 58ae89f..84e9e42 100644 --- a/src/ui/SignInModal/SignInModal.tsx +++ b/src/ui/SignInModal/SignInModal.tsx @@ -62,16 +62,20 @@ const BlockinUIDisplay: React.FC> = ({ }) => { const [selectedUris, setSelectedUris] = useState(getDefaultSelectedResources(displayedResources, displayedAssets).selectedUris); const [selectedAssets, setSelectedAssets] = useState(getDefaultSelectedResources(displayedResources, displayedAssets).selectedAssets); + const [displayMessage, setDisplayMessage] = useState(''); const [chain, setChain] = useState(getChain(selectedChainName, selectedChainInfo)); - const [assetId, setAssetId] = useState(''); - const [uri, setUri] = useState(''); const [loading, setLoading] = useState(''); const [advancedIsVisible, setAdvancedIsVisible] = useState(false); const [hours, setHours] = useState(24); + useEffect(() => { + setSelectedUris(getDefaultSelectedResources(displayedResources, displayedAssets).selectedUris); + setSelectedAssets(getDefaultSelectedResources(displayedResources, displayedAssets).selectedAssets); + }, [displayedAssets, displayedResources]) + /** * This will be true when 1) there are no selectable resources passed in by provider and 2) user can not add custom * resources. @@ -125,6 +129,11 @@ const BlockinUIDisplay: React.FC> = ({ setDisplayMessage(''); const assetsToVerify: PresetAsset[] = selectedAssets.map(asset => displayedAssets.find(elem => `${elem.name}` === asset) as PresetAsset); + if (assetsToVerify.some(x => !x)) { + setDisplayMessage('Error: Asset not found.'); + throw 'Error: Asset not found.' + } + /** * Generate the challenge object by and input the selectedUris */ diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..c8a04d6 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,45 @@ +export function compareObjects(obj1: any, obj2: any): boolean { + if (obj1 === obj2) { + return true; + } + + if (typeof obj1 !== typeof obj2 || typeof obj1 !== 'object' || obj1 === null || obj2 === null) { + return false; + } + + if (Array.isArray(obj1) !== Array.isArray(obj2)) { + return false; + } + + if (Array.isArray(obj1)) { + if (obj1.length !== obj2.length) { + return false; + } + + for (let i = 0; i < obj1.length; i++) { + if (!compareObjects(obj1[i], obj2[i])) { + return false; + } + } + } else { + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) { + return false; + } + + for (let key of keys1) { + if (!obj2.hasOwnProperty(key) || !compareObjects(obj1[key], obj2[key])) { + return false; + } + } + } + + if (typeof obj1 === 'bigint' || typeof obj2 === 'bigint') { + return BigInt(obj1) === BigInt(obj2); + } + + + return true; +} \ No newline at end of file diff --git a/src/verify.ts b/src/verify.ts index ca5a2a2..2aa3ca4 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -1,5 +1,6 @@ import { IChainDriver, IChainDriverWithHelpers } from './index.js'; import { Asset, ChallengeParams, CreateChallengeOptions, NumberType, UintRange, VerifyChallengeOptions } from './types/verify.types.js'; +import { compareObjects } from './utils.js'; const URI_REGEX: RegExp = /\w+:(\/?\/?)[^\s]+/; const ISO8601_DATE_REGEX: RegExp = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/ @@ -8,6 +9,22 @@ const BigIntify = (item: NumberType) => { return BigInt(item); } +function getChainForAddress(address: string) { + let addr: string = address; + if (addr.startsWith('0x')) { + return 'Ethereum'; + } else if (addr.startsWith('cosmos')) { + return 'Cosmos'; + } else if (address.length == 44) { + return 'Solana'; + } else if (address.startsWith('bc')) { + return 'Bitcoin'; + } + + return 'Web3' +} + + /** * Creates a challenge that is well-formed according to EIP-4361 - Sign in With Ethereum. Some * slight modifications to EIP-4361 for our library include 1) any blockchain's native address, signature, @@ -15,12 +32,11 @@ const BigIntify = (item: NumberType) => { * to specify micro-authorizations or role-based access using an on-chain asset. * * @param challengeParams - JSON object with the challenge details such as domain, uri, statement, address, etc. - * @param chainName - Name of the blockchain to include in the statement - "Sign in with your ____ account" * @param options - JSON object speicfying any additional options when creating the challenge * @returns Well-formed challenge string to be signed by the user, if successsful. Error string is returned * upon failure. */ -export function createChallenge(challengeParams: ChallengeParams, chainName?: string, options?: CreateChallengeOptions): string { +export function createChallenge(challengeParams: ChallengeParams, options?: CreateChallengeOptions): string { /** * This function should remain completely ChainDriver free. ChainDriver dependencies tend to mess up the * React component generation in the browser. @@ -59,7 +75,7 @@ export function createChallenge(challengeParams: Challenge validateChallengeObjectIsWellFormed(challenge); // will throw error if invalid - return constructChallengeStringFromChallengeObject(challenge, chainName); + return constructChallengeStringFromChallengeObject(challenge); } catch (error: unknown) { throw `Error: ${error} - ${challengeParams.toString()}`; } @@ -109,11 +125,17 @@ export async function verifyChallenge( await verifyChallengeSignature(chainDriver, message, signature) if (options?.expectedChallengeParams) { + + for (const key of Object.keys(options?.expectedChallengeParams ?? {})) { - if (challenge[key as keyof ChallengeParams] !== options?.expectedChallengeParams[key as keyof VerifyChallengeOptions['expectedChallengeParams']]) { - throw `Error: unexpected value for ${key}: ${challenge[key as keyof ChallengeParams]}. Expected ${options?.expectedChallengeParams[key as keyof VerifyChallengeOptions['expectedChallengeParams']]}` + const expected = JSON.parse(JSON.stringify(options?.expectedChallengeParams[key as keyof ChallengeParams])); + const actual = JSON.parse(JSON.stringify(challenge[key as keyof ChallengeParams])); + if (compareObjects(expected, actual) !== true) { + throw `Error: unexpected value for ${key}: ${JSON.stringify(challenge[key as keyof ChallengeParams])} !== ${JSON.stringify(options?.expectedChallengeParams[key as keyof ChallengeParams] ?? 'undefined')}`; } + } + } const toSkipAssetVerification = options?.skipAssetVerification ?? false; if (challenge.resources || challenge.assets) { @@ -354,9 +376,11 @@ export function validateChallengeObjectIsWellFormed(challe * @param chainName - Name of the blockchain to include in the statement - "Sign in with your ____ account" * @returns - Well-formatted EIP-4361 challenge string to be signed. */ -export function constructChallengeStringFromChallengeObject(challenge: ChallengeParams, chainName?: string): string { +export function constructChallengeStringFromChallengeObject(challenge: ChallengeParams): string { + const chain = getChainForAddress(challenge.address); + let message = ""; - message += `${challenge.domain} wants you to sign in with your ${chainName ? chainName : 'Web3'} account:\n` + message += `${challenge.domain} wants you to sign in with your ${chain} account:\n` message += `${challenge.address}\n\n`; if (challenge.statement) { message += `${challenge.statement}\n`;