From d1b9dcf84cdb202f458593d557c2782f3e7d6c36 Mon Sep 17 00:00:00 2001 From: Burnt Val Date: Tue, 15 Oct 2024 14:48:55 -0400 Subject: [PATCH 1/3] implement a grant validity check on the authenticate call --- .../abstraxion-core/src/AbstraxionAuth.ts | 91 +++++++++++++++++-- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/packages/abstraxion-core/src/AbstraxionAuth.ts b/packages/abstraxion-core/src/AbstraxionAuth.ts index c7d61f8..2b131df 100644 --- a/packages/abstraxion-core/src/AbstraxionAuth.ts +++ b/packages/abstraxion-core/src/AbstraxionAuth.ts @@ -1,6 +1,10 @@ import { GasPrice } from "@cosmjs/stargate"; import { fetchConfig } from "@burnt-labs/constants"; -import type { ContractGrantDescription, SpendLimit } from "@/types"; +import type { + ContractGrantDescription, + GrantsResponse, + SpendLimit, +} from "@/types"; import { GranteeSignerClient } from "./GranteeSignerClient"; import { SignArbSecp256k1HdWallet } from "./SignArbSecp256k1HdWallet"; @@ -339,18 +343,89 @@ export class AbstraxionAuth { this.triggerAuthStateChange(false); } + /** + * Checks if a grant is valid by verifying its expiration. + * + * @param {string} grantee - The address of the grantee. + * @param {string} granter - The address of the granter. + * @returns {Promise} - Returns true if the grant is valid, otherwise false. + */ + async checkGrantValidity(grantee: string, granter: string): Promise { + if (!this.rpcUrl) { + throw new Error("AbstraxionAuth needs to be configured."); + } + if (!grantee) { + throw new Error("No keypair address"); + } + if (!granter) { + throw new Error("No granter address"); + } + + const pollBaseUrl = + this.restUrl || (await fetchConfig(this.rpcUrl)).restUrl; + + try { + const url = new URL(`${pollBaseUrl}/cosmos/authz/v1beta1/grants`); + url.search = new URLSearchParams({ grantee, granter }).toString(); + + const res = await fetch(url, { cache: "no-store" }); + const data: GrantsResponse = await res.json(); + + if (data.grants.length === 0) { + console.warn("No grants found."); + return false; + } + + // Check expiration for each grant + const currentTime = new Date().toISOString(); + const validGrant = data.grants.some((grant) => { + const { expiration } = grant; + return !expiration || expiration > currentTime; + }); + + return validGrant; + } catch (error) { + console.error("Error fetching grants:", error); + return false; + } + } + /** * Authenticates the user based on the presence of a local keypair and a granter address. - * If a local keypair and granter address are found, sets the abstract account and triggers authentication state change. - * This method is typically called to authenticate the user and persist state between renders. + * Also checks if the grant is still valid by verifying the expiration. + * If valid, sets the abstract account and triggers authentication state change. + * If expired, clears local state and prompts reauthorization. + * + * @returns {Promise} - Resolves if authentication is successful or logs out the user otherwise. */ async authenticate(): Promise { - const keypair = await this.getLocalKeypair(); - const granter = this.getGranter(); + try { + const keypair = await this.getLocalKeypair(); + const granter = this.getGranter(); - if (keypair && granter) { - this.abstractAccount = keypair; - this.triggerAuthStateChange(true); + if (!keypair || !granter) { + console.warn("Missing keypair or granter, cannot authenticate."); + return; + } + + const accounts = await keypair.getAccounts(); + const keypairAddress = accounts[0].address; + + // Check for existing grants with an expiration check + const isGrantValid = await this.checkGrantValidity( + keypairAddress, + granter, + ); + + if (isGrantValid) { + this.abstractAccount = keypair; + this.triggerAuthStateChange(true); + } else { + throw new Error("Grant expired or not found. Logging out."); + } + } catch (error) { + console.error("Error during authentication:", error); + this.logout(); } } From e8325919d6be8af525c179d25d2fa0f5d558751c Mon Sep 17 00:00:00 2001 From: Burnt Val Date: Tue, 15 Oct 2024 14:55:23 -0400 Subject: [PATCH 2/3] changeset --- .changeset/sharp-carpets-confess.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sharp-carpets-confess.md diff --git a/.changeset/sharp-carpets-confess.md b/.changeset/sharp-carpets-confess.md new file mode 100644 index 0000000..fdb1cb5 --- /dev/null +++ b/.changeset/sharp-carpets-confess.md @@ -0,0 +1,5 @@ +--- +"@burnt-labs/abstraxion-core": minor +--- + +Added a grant validity check to verify expiration From 8a0a133bdec2350de27a501863cd6417672802d5 Mon Sep 17 00:00:00 2001 From: Burnt Val Date: Tue, 15 Oct 2024 15:51:19 -0400 Subject: [PATCH 3/3] consolidate redundant grant fetch logic --- .../abstraxion-core/src/AbstraxionAuth.ts | 70 ++++--------------- 1 file changed, 14 insertions(+), 56 deletions(-) diff --git a/packages/abstraxion-core/src/AbstraxionAuth.ts b/packages/abstraxion-core/src/AbstraxionAuth.ts index 2b131df..c4c95b1 100644 --- a/packages/abstraxion-core/src/AbstraxionAuth.ts +++ b/packages/abstraxion-core/src/AbstraxionAuth.ts @@ -316,12 +316,20 @@ export class AbstraxionAuth { const res = await fetch(url, { cache: "no-store", }); - const data = await res.json(); - if (data.grants.length > 0) { - return true; + const data: GrantsResponse = await res.json(); + if (data.grants.length === 0) { + console.warn("No grants found."); + return false; } - // Retry if no grants found - throw new Error("No grants found."); + + // Check expiration for each grant + const currentTime = new Date().toISOString(); + const validGrant = data.grants.some((grant) => { + const { expiration } = grant; + return !expiration || expiration > currentTime; + }); + + return validGrant; } catch (error) { console.warn("Error fetching grants: ", error); const delay = Math.pow(2, retries) * 1000; @@ -343,53 +351,6 @@ export class AbstraxionAuth { this.triggerAuthStateChange(false); } - /** - * Checks if a grant is valid by verifying its expiration. - * - * @param {string} grantee - The address of the grantee. - * @param {string} granter - The address of the granter. - * @returns {Promise} - Returns true if the grant is valid, otherwise false. - */ - async checkGrantValidity(grantee: string, granter: string): Promise { - if (!this.rpcUrl) { - throw new Error("AbstraxionAuth needs to be configured."); - } - if (!grantee) { - throw new Error("No keypair address"); - } - if (!granter) { - throw new Error("No granter address"); - } - - const pollBaseUrl = - this.restUrl || (await fetchConfig(this.rpcUrl)).restUrl; - - try { - const url = new URL(`${pollBaseUrl}/cosmos/authz/v1beta1/grants`); - url.search = new URLSearchParams({ grantee, granter }).toString(); - - const res = await fetch(url, { cache: "no-store" }); - const data: GrantsResponse = await res.json(); - - if (data.grants.length === 0) { - console.warn("No grants found."); - return false; - } - - // Check expiration for each grant - const currentTime = new Date().toISOString(); - const validGrant = data.grants.some((grant) => { - const { expiration } = grant; - return !expiration || expiration > currentTime; - }); - - return validGrant; - } catch (error) { - console.error("Error fetching grants:", error); - return false; - } - } - /** * Authenticates the user based on the presence of a local keypair and a granter address. * Also checks if the grant is still valid by verifying the expiration. @@ -412,10 +373,7 @@ export class AbstraxionAuth { const keypairAddress = accounts[0].address; // Check for existing grants with an expiration check - const isGrantValid = await this.checkGrantValidity( - keypairAddress, - granter, - ); + const isGrantValid = await this.pollForGrants(keypairAddress, granter); if (isGrantValid) { this.abstractAccount = keypair;