Skip to content

Commit

Permalink
feat: using internet identity service (#90)
Browse files Browse the repository at this point in the history
* feat: using internet identity service
will resolve #79

* rebase against next

* working flow from fresh and returning

* previously authenticated re-log flow

* removes testing address

* removing duplicate interface declaration, cleaning up

* better devServer fallback

* Update wallet_ui/components/routes/Authorize.tsx

Co-authored-by: Prithvi Shahi <[email protected]>

* Update wallet_ui/components/routes/Authorize.tsx

Co-authored-by: Prithvi Shahi <[email protected]>

* updating copy

* removes @dfinity/authentication

Co-authored-by: Prithvi Shahi <[email protected]>
  • Loading branch information
krpeacock and p-shahi authored May 10, 2021
1 parent c3cbfc5 commit 06bb256
Show file tree
Hide file tree
Showing 9 changed files with 4,442 additions and 4,119 deletions.
8,193 changes: 4,309 additions & 3,884 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"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",
"@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.

75 changes: 45 additions & 30 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 Down Expand Up @@ -115,7 +120,7 @@ export function Authorize() {
const canisterId = getWalletId();
const isLocalhost = !!window.location.hostname.match(/^(.*\.)?localhost$/);
const canisterCallShCode = `dfx canister --no-wallet${
isLocalhost ? "" : " --network alpha"
isLocalhost ? "" : " --network ic"
} 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 currently using this service anonymously. Please
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

0 comments on commit 06bb256

Please sign in to comment.