Skip to content

Commit

Permalink
Connect provider parse session policies and origin (#340)
Browse files Browse the repository at this point in the history
* Connect provider parse session policies and origin

* Use connection provider instead of context

* Login mode
  • Loading branch information
broody authored Jun 6, 2024
1 parent 357f1d5 commit b694c88
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 63 deletions.
2 changes: 1 addition & 1 deletion packages/keychain/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -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"
24 changes: 10 additions & 14 deletions packages/keychain/src/components/connect/CreateController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,40 @@ 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<string>();
const ctx = context as ConnectCtx;

if (error) {
return <>{error.message}</>;
}

return showSignup ? (
<Signup
origin={ctx?.origin}
policies={ctx?.policies}
chainId={chainId}
rpcUrl={rpcUrl}
prefilledName={prefilledUsername}
onLogin={(username) => {
setPrefilledUsername(username);
setShowSignup(false);
}}
onSuccess={setController}
isSlot={isSlot}
/>
) : (
<Login
origin={ctx?.origin}
policies={ctx?.policies}
chainId={chainId}
rpcUrl={rpcUrl}
prefilledName={prefilledUsername}
onSignup={(username) => {
setPrefilledUsername(username);
setShowSignup(true);
}}
onSuccess={setController}
mode={loginMode}
isSlot={isSlot}
/>
);
Expand Down
32 changes: 21 additions & 11 deletions packages/keychain/src/components/connect/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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 (
Expand Down
22 changes: 8 additions & 14 deletions packages/keychain/src/components/connect/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -78,15 +75,11 @@ export function Signup({
validateOnBlur={false}
>
<Form
chainId={chainId}
rpcUrl={rpcUrl}
onLogin={onLogin}
onSuccess={onSuccess}
isRegistering={isRegistering}
isLoading={isLoading}
onSuccess={onSuccess}
setIsRegistering={setIsRegistering}
origin={origin}
policies={policies}
isSlot={isSlot}
error={error}
/>
Expand All @@ -97,10 +90,6 @@ export function Signup({
}

function Form({
origin,
policies,
chainId,
rpcUrl,
isRegistering,
isLoading,
isSlot,
Expand All @@ -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<FormValues>();

Expand Down Expand Up @@ -160,7 +150,11 @@ function Form({
controller.store();
await controller.account.sync();

onSuccess(controller);
setController(controller);

if (onSuccess) {
onSuccess();
}
},
},
);
Expand Down
17 changes: 7 additions & 10 deletions packages/keychain/src/components/connect/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import Controller from "utils/controller";
import { Policy } from "@cartridge/controller";

export type FormValues = {
username: string;
};
Expand All @@ -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;
};
28 changes: 22 additions & 6 deletions packages/keychain/src/hooks/connection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConnectionContextValue>(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<ParentMethods>();
const [context, setContext] = useState<ConnectionCtx>();
const [origin, setOrigin] = useState<string>();
const [rpcUrl, setRpcUrl] = useState<string>();
const [chainId, setChainId] = useState<string>();
const [policies, setPolicies] = useState<Policy[]>([]);
const [controller, setController] = useState(Controller.fromStore);
const [error, setError] = useState<Error>();

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,
});
Expand Down Expand Up @@ -80,12 +94,14 @@ export function ConnectionProvider({ children }: PropsWithChildren) {
return (
<ConnectionContext.Provider
value={{
context,
controller,
setController,
origin,
rpcUrl,
chainId,
policies,
error,
context,
setController,
setContext,
close,
}}
Expand Down
3 changes: 2 additions & 1 deletion packages/keychain/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from "utils/connection";
import { diff } from "utils/controller";
import { logout } from "utils/connection/logout";
import { LoginMode } from "components/connect/types";

function Home() {
const { context, controller, chainId, setContext, error } = useConnection();
Expand All @@ -37,7 +38,7 @@ function Home() {

// No controller, send to login
if (!controller) {
return <CreateController />;
return <CreateController loginMode={LoginMode.Controller} />;
}

const onLogout = (context: ConnectionCtx) => {
Expand Down
2 changes: 0 additions & 2 deletions packages/keychain/src/pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ const Login: NextPage = () => {

return (
<LoginComponent
chainId={chainId}
rpcUrl={rpcUrl}
onSignup={() => router.push({ pathname: "/signup", query: router.query })}
onSuccess={async () => {
router.replace(`${process.env.NEXT_PUBLIC_ADMIN_URL}/profile`);
Expand Down
2 changes: 0 additions & 2 deletions packages/keychain/src/pages/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ export default function Signup() {

return (
<SignupComponent
chainId={chainId}
rpcUrl={rpcUrl}
onLogin={() => router.push({ pathname: "/login", query: router.query })}
onSuccess={() => {
router.replace(`${process.env.NEXT_PUBLIC_ADMIN_URL}/profile`);
Expand Down
6 changes: 6 additions & 0 deletions packages/keychain/src/utils/connection/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConnectReply> => {
setOrigin(origin);
setRpcUrl(rpcUrl);
setPolicies(policies);

return new Promise((resolve, reject) => {
setContext({
Expand Down
10 changes: 8 additions & 2 deletions packages/keychain/src/utils/connection/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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)),
Expand Down

0 comments on commit b694c88

Please sign in to comment.