From b694c8813898e04942636e00e26f8b79d7df518f Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 6 Jun 2024 08:13:54 -1000 Subject: [PATCH] Connect provider parse session policies and origin (#340) * Connect provider parse session policies and origin * Use connection provider instead of context * Login mode --- packages/keychain/.env.development | 2 +- .../components/connect/CreateController.tsx | 24 ++++++-------- .../keychain/src/components/connect/Login.tsx | 32 ++++++++++++------- .../src/components/connect/Signup.tsx | 22 +++++-------- .../keychain/src/components/connect/types.ts | 17 ++++------ packages/keychain/src/hooks/connection.tsx | 28 ++++++++++++---- packages/keychain/src/pages/index.tsx | 3 +- packages/keychain/src/pages/login.tsx | 2 -- packages/keychain/src/pages/signup.tsx | 2 -- .../keychain/src/utils/connection/connect.ts | 6 ++++ .../keychain/src/utils/connection/index.ts | 10 ++++-- 11 files changed, 85 insertions(+), 63 deletions(-) diff --git a/packages/keychain/.env.development b/packages/keychain/.env.development index e583bf8be..a7296ca2a 100644 --- a/packages/keychain/.env.development +++ b/packages/keychain/.env.development @@ -11,7 +11,7 @@ NEXT_PUBLIC_ETH_RPC_SEPOLIA="https://eth-sepolia.g.alchemy.com/v2/mURnclB5pn5elD NEXT_PUBLIC_ORIGIN="http://localhost:3001" NEXT_PUBLIC_RP_ID="localhost" NEXT_PUBLIC_ADMIN_URL="http://localhost:3000" -NEXT_PUBLIC_API_BASE_URL="http://localhost:3001" +NEXT_PUBLIC_API_BASE_URL="http://localhost:8000" NEXT_PUBLIC_API_URL="http://localhost:8000/query" NEXT_PUBLIC_RPC_MAINNET="http://localhost:8000/x/starknet/mainnet" NEXT_PUBLIC_RPC_SEPOLIA="http://localhost:8000/x/starknet/sepolia" diff --git a/packages/keychain/src/components/connect/CreateController.tsx b/packages/keychain/src/components/connect/CreateController.tsx index 9441bb968..fdc50d0c1 100644 --- a/packages/keychain/src/components/connect/CreateController.tsx +++ b/packages/keychain/src/components/connect/CreateController.tsx @@ -2,13 +2,18 @@ import { useState } from "react"; import { Signup } from "./Signup"; import { Login } from "./Login"; import { useConnection } from "hooks/connection"; -import { ConnectCtx } from "utils/connection"; +import { LoginMode } from "./types"; -export function CreateController({ isSlot }: { isSlot?: boolean }) { - const { chainId, rpcUrl, context, setController, error } = useConnection(); +export function CreateController({ + isSlot, + loginMode, +}: { + isSlot?: boolean; + loginMode?: LoginMode; +}) { + const { error } = useConnection(); const [showSignup, setShowSignup] = useState(false); const [prefilledUsername, setPrefilledUsername] = useState(); - const ctx = context as ConnectCtx; if (error) { return <>{error.message}; @@ -16,30 +21,21 @@ export function CreateController({ isSlot }: { isSlot?: boolean }) { return showSignup ? ( { setPrefilledUsername(username); setShowSignup(false); }} - onSuccess={setController} isSlot={isSlot} /> ) : ( { setPrefilledUsername(username); setShowSignup(true); }} - onSuccess={setController} + mode={loginMode} isSlot={isSlot} /> ); diff --git a/packages/keychain/src/components/connect/Login.tsx b/packages/keychain/src/components/connect/Login.tsx index 74d8e8210..f107bb5c5 100644 --- a/packages/keychain/src/components/connect/Login.tsx +++ b/packages/keychain/src/components/connect/Login.tsx @@ -9,24 +9,23 @@ import { } from "components"; import { useCallback, useState } from "react"; import Controller from "utils/controller"; -import { FormValues, LoginProps } from "./types"; +import { FormValues, LoginMode, LoginProps } from "./types"; import { useAnalytics } from "hooks/analytics"; import { fetchAccount, validateUsernameFor } from "./utils"; import { RegistrationLink } from "./RegistrationLink"; import { useControllerTheme } from "hooks/theme"; import { doLogin } from "hooks/account"; import { Error as ErrorComp } from "components/Error"; +import { useConnection } from "hooks/connection"; export function Login({ - chainId, - rpcUrl, - origin, - policies, prefilledName = "", isSlot, + mode = LoginMode.Webauthn, onSuccess, onSignup, }: LoginProps) { + const { origin, policies, chainId, rpcUrl, setController } = useConnection(); const { event: log } = useAnalytics(); const theme = useControllerTheme(); const [isLoading, setIsLoading] = useState(false); @@ -56,14 +55,25 @@ export function Login({ }); try { - if (isSlot || !origin || !policies) { - await doLogin(values.username, credentialId, publicKey); - } else { - await controller.approve(origin, expiresAt, policies); + switch (mode) { + case LoginMode.Webauthn: + await doLogin(values.username, credentialId, publicKey); + break; + case LoginMode.Controller: + if (policies.length === 0) { + throw new Error("Policies required for controller "); + } + + await controller.approve(origin, expiresAt, policies); + break; } controller.store(); - onSuccess(controller); + setController(controller); + + if (onSuccess) { + onSuccess(); + } log({ type: "webauthn_login", address }); } catch (e) { @@ -80,7 +90,7 @@ export function Login({ setIsLoading(false); }, - [chainId, rpcUrl, origin, policies, expiresAt, isSlot, log, onSuccess], + [chainId, rpcUrl, origin, policies, expiresAt, isSlot, log], ); return ( diff --git a/packages/keychain/src/components/connect/Signup.tsx b/packages/keychain/src/components/connect/Signup.tsx index 1cd6d01c7..44f43fa23 100644 --- a/packages/keychain/src/components/connect/Signup.tsx +++ b/packages/keychain/src/components/connect/Signup.tsx @@ -25,14 +25,11 @@ import { doSignup } from "hooks/account"; import { useControllerTheme } from "hooks/theme"; import { Error as ErrorComp } from "components/Error"; import { shortString } from "starknet"; +import { useConnection } from "hooks/connection"; export function Signup({ prefilledName = "", - origin, - policies, isSlot, - chainId, - rpcUrl, onSuccess, onLogin, }: SignupProps) { @@ -78,15 +75,11 @@ export function Signup({ validateOnBlur={false} >
@@ -97,10 +90,6 @@ export function Signup({ } function Form({ - origin, - policies, - chainId, - rpcUrl, isRegistering, isLoading, isSlot, @@ -114,6 +103,7 @@ function Form({ setIsRegistering: (val: boolean) => void; error: Error; }) { + const { origin, policies, chainId, rpcUrl, setController } = useConnection(); const theme = useControllerTheme(); const { values, isValidating } = useFormikContext(); @@ -160,7 +150,11 @@ function Form({ controller.store(); await controller.account.sync(); - onSuccess(controller); + setController(controller); + + if (onSuccess) { + onSuccess(); + } }, }, ); diff --git a/packages/keychain/src/components/connect/types.ts b/packages/keychain/src/components/connect/types.ts index bed90e9ce..6f6d37f2f 100644 --- a/packages/keychain/src/components/connect/types.ts +++ b/packages/keychain/src/components/connect/types.ts @@ -1,6 +1,3 @@ -import Controller from "utils/controller"; -import { Policy } from "@cartridge/controller"; - export type FormValues = { username: string; }; @@ -9,20 +6,20 @@ export type AuthProps = SignupProps | LoginProps; type AuthBaseProps = { prefilledName?: string; - origin?: string; - policies?: Policy[]; isSlot?: boolean; - chainId: string; - rpcUrl: string; - onSuccess: (controller: Controller) => void; + onSuccess?: () => void; }; export type SignupProps = AuthBaseProps & { onLogin: (username: string) => void; }; +export enum LoginMode { + Webauthn, // client server login flow + Controller, // client side only create session flow +} + export type LoginProps = AuthBaseProps & { - chainId: string; - rpcUrl: string; + mode?: LoginMode; onSignup: (username: string) => void; }; diff --git a/packages/keychain/src/hooks/connection.tsx b/packages/keychain/src/hooks/connection.tsx index 41bab5408..741c58e78 100644 --- a/packages/keychain/src/hooks/connection.tsx +++ b/packages/keychain/src/hooks/connection.tsx @@ -11,39 +11,53 @@ import Controller from "utils/controller"; import { connectToController, ConnectionCtx } from "utils/connection"; import { isIframe } from "components/connect/utils"; import { RpcProvider } from "starknet"; +import { Policy } from "@cartridge/controller"; const ConnectionContext = createContext(undefined); type ConnectionContextValue = { - controller: Controller; - setController: (controller: Controller) => void; context: ConnectionCtx; - setContext: (context: ConnectionCtx) => void; + controller: Controller; + origin: string; rpcUrl: string; - error: Error; chainId: string; + policies: Policy[]; + error: Error; + setContext: (context: ConnectionCtx) => void; + setController: (controller: Controller) => void; close: () => void; }; export function ConnectionProvider({ children }: PropsWithChildren) { const [parent, setParent] = useState(); const [context, setContext] = useState(); + const [origin, setOrigin] = useState(); const [rpcUrl, setRpcUrl] = useState(); const [chainId, setChainId] = useState(); + const [policies, setPolicies] = useState([]); const [controller, setController] = useState(Controller.fromStore); const [error, setError] = useState(); + const parsePolicies = (policiesStr: string | null): Policy[] => { + if (!policiesStr) return []; + return JSON.parse(policiesStr); + }; + useEffect(() => { if (!isIframe()) { const urlParams = new URLSearchParams(window.location.search); + setOrigin(urlParams.get("origin") || process.env.NEXT_PUBLIC_ORIGIN); setRpcUrl( urlParams.get("rpc_url") || process.env.NEXT_PUBLIC_RPC_SEPOLIA, ); + setPolicies(parsePolicies(urlParams.get("policies"))); return; } const connection = connectToController({ + setOrigin, setRpcUrl, + setPolicies, setContext, setController, }); @@ -80,12 +94,14 @@ export function ConnectionProvider({ children }: PropsWithChildren) { return ( ; + return ; } const onLogout = (context: ConnectionCtx) => { diff --git a/packages/keychain/src/pages/login.tsx b/packages/keychain/src/pages/login.tsx index 0d6d43df0..61e4e91cd 100644 --- a/packages/keychain/src/pages/login.tsx +++ b/packages/keychain/src/pages/login.tsx @@ -21,8 +21,6 @@ const Login: NextPage = () => { return ( router.push({ pathname: "/signup", query: router.query })} onSuccess={async () => { router.replace(`${process.env.NEXT_PUBLIC_ADMIN_URL}/profile`); diff --git a/packages/keychain/src/pages/signup.tsx b/packages/keychain/src/pages/signup.tsx index fc79b2a9e..83bf33e22 100644 --- a/packages/keychain/src/pages/signup.tsx +++ b/packages/keychain/src/pages/signup.tsx @@ -20,8 +20,6 @@ export default function Signup() { return ( router.push({ pathname: "/login", query: router.query })} onSuccess={() => { router.replace(`${process.env.NEXT_PUBLIC_ADMIN_URL}/profile`); diff --git a/packages/keychain/src/utils/connection/connect.ts b/packages/keychain/src/utils/connection/connect.ts index c64632b24..0b0afb6b7 100644 --- a/packages/keychain/src/utils/connection/connect.ts +++ b/packages/keychain/src/utils/connection/connect.ts @@ -3,15 +3,21 @@ import { ConnectCtx, ConnectionCtx } from "./types"; import Controller from "utils/controller"; export function connectFactory({ + setOrigin, setRpcUrl, + setPolicies, setContext, }: { + setOrigin: (origin: string) => void; setRpcUrl: (url: string) => void; + setPolicies: (policies: Policy[]) => void; setContext: (context: ConnectionCtx) => void; }) { return (origin: string) => (policies: Policy[], rpcUrl: string): Promise => { + setOrigin(origin); setRpcUrl(rpcUrl); + setPolicies(policies); return new Promise((resolve, reject) => { setContext({ diff --git a/packages/keychain/src/utils/connection/index.ts b/packages/keychain/src/utils/connection/index.ts index 251a3788c..36b5f15c7 100644 --- a/packages/keychain/src/utils/connection/index.ts +++ b/packages/keychain/src/utils/connection/index.ts @@ -1,6 +1,6 @@ export * from "./types"; -import { ConnectError, ResponseCodes } from "@cartridge/controller"; +import { ConnectError, Policy, ResponseCodes } from "@cartridge/controller"; import { connectToParent } from "@cartridge/penpal"; import { normalize as normalizeOrigin } from "utils/url"; import Controller from "utils/controller"; @@ -15,17 +15,23 @@ import { username } from "./username"; import { ConnectionCtx } from "./types"; export function connectToController({ + setOrigin, setRpcUrl, + setPolicies, setContext, setController, }: { + setOrigin: (origin: string) => void; setRpcUrl: (url: string) => void; + setPolicies: (policies: Policy[]) => void; setContext: (ctx: ConnectionCtx) => void; setController: (controller: Controller) => void; }) { return connectToParent({ methods: { - connect: normalize(connectFactory({ setRpcUrl, setContext })), + connect: normalize( + connectFactory({ setOrigin, setRpcUrl, setPolicies, setContext }), + ), disconnect: normalize(validate(disconnectFactory(setController))), execute: normalize(validate(executeFactory({ setContext }))), estimateDeclareFee: normalize(validate(estimateDeclareFee)),