Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: using internet identity service #90

Merged
merged 12 commits into from
May 10, 2021
319 changes: 53 additions & 266 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
"dependencies": {
"@babel/core": "7.9.0",
"@babel/preset-react": "7.9.0",
"@dfinity/agent": "0.8.3",
"@dfinity/authentication": "0.8.3",
"@dfinity/agent": "0.8.7",
"@dfinity/auth-client": "0.8.7",
"@dfinity/authentication": "0.8.7",
"@emotion/css": "^11.1.3",
"@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2",
Expand Down
59 changes: 29 additions & 30 deletions wallet_ui/canister/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@
* It is also useful because that puts all the code in one place, including the
* authentication logic. We do not use `window.ic` anywhere in this.
*/
import { HttpAgent, Actor, Principal, ActorSubclass } from "@dfinity/agent";
import { AuthenticationClient } from "../utils/authClient";
import {
HttpAgent,
Actor,
Principal,
ActorSubclass,
AnonymousIdentity,
} from "@dfinity/agent";
import _SERVICE from "./wallet/wallet";
import factory, { Event } from "./wallet";
import { authClient } from "../utils/authClient";
export * from "./wallet";

function convertIdlEventMap(idlEvent: any): Event {
return {
Expand All @@ -25,18 +32,9 @@ function convertIdlEventMap(idlEvent: any): Event {
kind: idlEvent.kind,
};
}

export * from "./wallet";

// Need to export the enumeration from wallet.did
export { Principal } from "@dfinity/agent";

const authClient = new AuthenticationClient();

export async function getAgentPrincipal(): Promise<Principal> {
return authClient.getIdentity().getPrincipal();
}

function getCanisterId(): Principal {
// Check the query params.
const maybeCanisterId = new URLSearchParams(window.location.search).get(
Expand All @@ -59,21 +57,14 @@ function getCanisterId(): Principal {

throw new Error("Could not find the canister ID.");
}
let walletCanisterCache: ActorSubclass<_SERVICE> | null = null;

let walletCanisterCache: ActorSubclass<_SERVICE>;

export async function login() {
const redirectUri = `${location.origin}/${location.search}`;
await authClient.loginWithRedirect({
redirectUri,
scope: [getWalletId()],
});
}

export async function handleAuthRedirect() {
// Check if we need to parse the authentication.
if (authClient.shouldParseResult(location)) {
await authClient.handleRedirectCallback(location);
export async function getAgentPrincipal(): Promise<Principal> {
const identity = await authClient.getIdentity();
if (identity) {
return await identity.getPrincipal();
} else {
return Promise.reject("Could not find identity");
}
}

Expand All @@ -82,19 +73,23 @@ async function getWalletCanister(): Promise<ActorSubclass<_SERVICE>> {
return walletCanisterCache;
}

await handleAuthRedirect();

let walletId: Principal | null = null;
walletId = getWalletId(walletId);

const agent = new HttpAgent({ identity: authClient.getIdentity() });
if (!authClient.ready) {
return Promise.reject("not yet ready");
}

const identity = (await authClient.getIdentity()) ?? new AnonymousIdentity();
const agent = new HttpAgent({
identity,
});
if (!walletId) {
throw new Error("Need to have a wallet ID.");
} else {
walletCanisterCache = (Actor as any).createActor(factory as any, {
agent,
canisterId: walletId,
canisterId: (await getWalletId()) || "",
// Override the defaults for polling.
maxAttempts: 201,
throttleDurationInMSecs: 1500,
Expand Down Expand Up @@ -142,7 +137,11 @@ export const Wallet = {
await this.balance();
},
async balance(): Promise<number> {
return Number((await (await getWalletCanister()).wallet_balance()).amount);
const walletCanister = await getWalletCanister();
return Number((await walletCanister.wallet_balance()).amount);
},
clearWalletCache() {
walletCanisterCache = null;
},
async events(from?: number, to?: number): Promise<Event[]> {
return (
Expand Down
45 changes: 27 additions & 18 deletions wallet_ui/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,25 @@ import CssBaseline from "@material-ui/core/CssBaseline";
import { WalletAppBar } from "./WalletAppBar";
import Typography from "@material-ui/core/Typography";
import Link from "@material-ui/core/Link";
import {
orange,
lightBlue,
deepPurple,
deepOrange,
} from "@material-ui/core/colors";
import {
BrowserRouter as Router,
Switch as RouterSwitch,
Route,
Redirect,
} from "react-router-dom";

// For Switch Theming
import ThemeProvider from "@material-ui/styles/ThemeProvider";

// For document title setting
import { handleAuthRedirect, Wallet } from "../canister";
import { Wallet } from "../canister";

// Routes
import { Authorize } from "./routes/Authorize";
import { Dashboard } from "./routes/Dashboard";
import { useLocalStorage } from "../utils/hooks";
import generateTheme from "../utils/materialTheme";
import { authClient } from "../utils/authClient";

export function Copyright() {
return (
Expand Down Expand Up @@ -132,25 +128,29 @@ function useDarkState(): [boolean, (newState?: boolean) => void] {

export default function App() {
const [ready, setReady] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState<null | boolean>(null);
const [open, setOpen] = useLocalStorage("app-menu-open", false);
const [darkState, setDarkState] = useDarkState();
const classes = useStyles();
const theme = generateTheme(darkState);

useEffect(() => {
Wallet.name().then((name) => {
document.title = name;
});
}, []);

const theme = generateTheme(darkState);

const classes = useStyles();

// Check if we need to parse the hash.
handleAuthRedirect().then(() => setReady(true));
useEffect(() => {
if (!authClient.ready) {
return;
}
setReady(true);
authClient.isAuthenticated().then((value) => {
setIsAuthenticated(value ?? false);
});
}, [authClient.ready]);

if (!ready) {
return <></>;
}
if (!ready) return null;

return (
<ThemeProvider theme={theme}>
Expand All @@ -176,11 +176,20 @@ export default function App() {

<RouterSwitch>
<Route path="/authorize">
<Authorize />
<Authorize setIsAuthenticated={setIsAuthenticated} />
</Route>

<Route path="/">
<Dashboard open={open} onOpenToggle={() => setOpen(!open)} />
{authClient.ready && isAuthenticated === false ? (
<Redirect
to={{
pathname: `/authorize`,
search: location.search,
}}
/>
) : (
<Dashboard open={open} onOpenToggle={() => setOpen(!open)} />
)}
</Route>
</RouterSwitch>
</div>
Expand Down
1 change: 0 additions & 1 deletion wallet_ui/components/CycleSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ function CycleSlider(props: Props) {
const sdrUsdRate = 0.69977;

function handleSlide(e: any) {
console.log("slide", e.target.value);
if (balance && e.target?.value) {
const newValue = Math.floor((balance * e.target.value) / 1000);
setCycles(newValue);
Expand Down
9 changes: 0 additions & 9 deletions wallet_ui/components/panels/Untitled-1.ts

This file was deleted.

77 changes: 46 additions & 31 deletions wallet_ui/components/routes/Authorize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Wallet,
Principal,
getAgentPrincipal,
login,
getWalletId,
} from "../../canister";
import { useHistory } from "react-router";
Expand All @@ -21,6 +20,7 @@ import { Copyright } from "../App";
import Button from "@material-ui/core/Button";
import { PrimaryButton } from "../Buttons";
import { css } from "@emotion/css";
import { authClient } from "../../utils/authClient";

const CHECK_ACCESS_FREQUENCY_IN_SECONDS = 15;

Expand Down Expand Up @@ -87,7 +87,12 @@ const CopyButton = (props: {
);
};

export function Authorize() {
type AuthorizeProps = {
setIsAuthenticated: (x: boolean) => void;
};

export function Authorize(props: AuthorizeProps) {
const { setIsAuthenticated } = props;
const [agentPrincipal, setAgentPrincipal] = useState<Principal | null>(null);
const [copied, setCopied] = useState(false);
const history = useHistory();
Expand All @@ -114,8 +119,8 @@ export function Authorize() {
if (agentPrincipal && !agentPrincipal.isAnonymous()) {
const canisterId = getWalletId();
const isLocalhost = !!window.location.hostname.match(/^(.*\.)?localhost$/);
const canisterCallShCode = `dfx canister --no-wallet${
isLocalhost ? "" : " --network alpha"
const canisterCallShCode = `dfx canister ${
isLocalhost ? "" : "--no-wallet --network alpha"
} call "${
canisterId?.toText() || ""
}" authorize '(principal "${agentPrincipal.toText()}")'`;
Expand Down Expand Up @@ -196,33 +201,43 @@ export function Authorize() {
</Box>
</main>
);
} else if (agentPrincipal && agentPrincipal.isAnonymous()) {
return (
<main className={classes.content}>
<div className={classes.appBarSpacer} />
<Container maxWidth="lg" className={classes.container}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper className={classes.paper}>
<Typography component="h1" variant="h4">
Anonymous Device
}

return (
<main className={classes.content}>
<div className={classes.appBarSpacer} />
<Container maxWidth="lg" className={classes.container}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper className={classes.paper}>
<Typography component="h1" variant="h4">
Anonymous Device
</Typography>
<Box mb={4}>
<Typography variant="body1" color="textPrimary">
You are using an anonymous Principal. You need to
authenticate.
</Typography>
<Box mb={4}>
<Typography variant="body1" color="textPrimary">
You are using an anonymous Principal. You need to
authenticate.
</Typography>
</Box>
<PrimaryButton onClick={async () => await login()}>
Authenticate
</PrimaryButton>
</Paper>
</Grid>
</Box>
<PrimaryButton
onClick={async () => {
await authClient.login();
const identity = await authClient.getIdentity();
Wallet.clearWalletCache();
if (identity) {
setIsAuthenticated(true);
setAgentPrincipal(identity.getPrincipal());
} else {
console.error("could not get identity");
}
}}
>
Authenticate
</PrimaryButton>
</Paper>
</Grid>
</Container>
</main>
);
} else {
return <></>;
}
</Grid>
</Container>
</main>
);
}
Loading