Skip to content

Commit

Permalink
docs: simplify the sample-javascript (dfinity#274)
Browse files Browse the repository at this point in the history
* docs: simplify the sample-javascript

Remove authClient and move everything into 3 functions; login, logout and handleCallback.
The goal is to move the login and handleCallback in the authentication package
and be the low level API. Even lower than that, login will most likely be
createRequestUrl() or something to that effect, and leave the navigation to the
application layer.

* .
  • Loading branch information
hansl committed Mar 3, 2021
1 parent 415b447 commit a520c6e
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 485 deletions.
138 changes: 0 additions & 138 deletions demos/sample-javascript/src/authClient.js

This file was deleted.

4 changes: 2 additions & 2 deletions demos/sample-javascript/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ <h2>Principal:</h2>
<div>
<h1>Contact the IC</h1>
<label for="hostUrl" style="display: inline-block; width: 120px">Replica URL: </label>
<input type="text" id="hostUrl" value="http://localhost:8000">
<input type="text" id="hostUrl" value="https://gw.dfinity.network/">
<br />
<label for="canisterId" style="display: inline-block; width: 120px">Canister ID: </label>
<input type="text" id="canisterId" value="rwlgt-iiaaa-aaaaa-aaaaa-cai">
<input type="text" id="canisterId" value="4k2wq-cqaaa-aaaab-qac7q-cai">
</div>
<div>
<button id="whoamiBtn">Who Am I?</button>
Expand Down
135 changes: 88 additions & 47 deletions demos/sample-javascript/src/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { HttpAgent, Principal } from '@dfinity/agent';
import { AuthenticationClient } from './authClient';
import {AnonymousIdentity, HttpAgent, Principal} from '@dfinity/agent';
import {
Authenticator,
DelegationChain,
DelegationIdentity,
Ed25519KeyIdentity
} from '@dfinity/authentication';

const signInBtn = document.getElementById("signinBtn");
const signOutBtn = document.getElementById("signoutBtn");
Expand All @@ -10,63 +15,95 @@ const canisterIdEl = document.getElementById("canisterId");
const principalEl = document.getElementById("principal");
const idpUrlEl = document.getElementById("idpUrl");

// This is a canister that exists in the main network.
const authClient = new AuthenticationClient({
identityProvider: new URL(idpUrlEl.value),
let identity = new AnonymousIdentity();

// This should not be needed if we want to use the default identity provider
// which is https://auth.ic0.app/.
const url = new URL(idpUrlEl.value);
const authenticator = new Authenticator({
identityProvider: { url: url.toString() },
});
// Remove the lines above to use the default authenticator.

function login() {
identity = Ed25519KeyIdentity.generate();
const session = { identity };
localStorage.setItem('ic-session', JSON.stringify(session));

signInBtn.addEventListener("click", async () => {
// Creates an Ed25519 identity, serializes it to JSON, saves it to
// storage, then window.location = CreateUrlFromOptions(options);
await new AuthenticationClient({
identityProvider: new URL(idpUrlEl.value),
}).loginWithRedirect({
authenticator.sendAuthenticationRequest({
redirectUri: window.location.origin,
scope: [{ principal: Principal.fromText(canisterIdEl.value) }],
session,
scope: [
{ principal: Principal.fromText(canisterIdEl.value) },
],
});
});
}

signOutBtn.addEventListener("click", async () => {
await new AuthenticationClient({
identityProvider: new URL(idpUrlEl.value),
}).logout({
returnTo: window.location.origin,
}); // This basically clear localStorage then window.location = options.returnTo
});
function logout() {
localStorage.removeItem('ic-session');
identity = new AnonymousIdentity();
updatePrincipal();
}

let identity = authClient.getIdentity();

window.onload = async () => {
const isAuthenticated = await authClient.isAuthenticated();
if (isAuthenticated) {
console.log("User already authenticated");
identity = await authClient.getIdentity();
} else {
if (authClient.shouldParseResult(location)) {
try {
// Gets search and hash and extracts all info,
// calls storage.set to store the authResponse object (DelegationChain),
// creates a DelegationIdentity with the Ed25519 from storage
// and the authentication response from this.
const result = await authClient.handleRedirectCallback(location);
// This is instanceof DelegationIdentity
identity = result.identity;
} catch (err) {
console.error("Error parsing redirect:", err);
}
function handleCallback(session) {
const searchParams = new URLSearchParams(location.search);
// Remove the `#` at the start.
const hashParams = new URLSearchParams(location.hash.substr(1));

const maybeAccessToken = searchParams.get('access_token') || hashParams.get('access_token');
if (maybeAccessToken) {
const chainJson = [...maybeAccessToken]
.reduce((acc, curr, i) => {
acc[Math.floor(i / 2)] = (acc[i / 2 | 0] || "") + curr;
return acc;
}, [])
.map(x => Number.parseInt(x, 16))
.map(x => String.fromCharCode(x))
.join('');

const key = Ed25519KeyIdentity.fromParsedJson(session.identity);
identity = DelegationIdentity.fromDelegation(key, DelegationChain.fromJSON(chainJson));

localStorage.setItem('ic-session', JSON.stringify({
...session,
authenticationResponse: chainJson,
}));
}
}

signInBtn.addEventListener("click", login);
signOutBtn.addEventListener("click", logout);

function updatePrincipal() {
principalEl.innerHTML = `<div>ID: ${identity.getPrincipal().toText()}</div>`;
}

function init() {
// Verify if an identity already exists.
const maybeSession = localStorage.getItem('ic-session');
if (maybeSession) {
let session = JSON.parse(maybeSession);
// TODO: move this into Authenticator.
if (session.authenticationResponse) {
const key = Ed25519KeyIdentity.fromParsedJson(session.identity);
const chain = DelegationChain.fromJSON(session.authenticationResponse);
identity = DelegationIdentity.fromDelegation(key, chain);
} else {
handleCallback(session);
}
}

principalEl.innerText = identity.getPrincipal().toText();
};
updatePrincipal();
}

window.onload = init;

whoamiBtn.addEventListener("click", async () => {
whoamiBtn.addEventListener("click", () => {
if (identity === null) {
console.error("No identity... Window not loaded?");
alert("No identity... Window not loaded?");
return;
}


// We either have an Agent with an anonymous identity (not authenticated),
// or already authenticated agent, or parsing the redirect from window.location.
const agent = new HttpAgent({
Expand All @@ -76,9 +113,13 @@ whoamiBtn.addEventListener("click", async () => {

const canisterId = Principal.fromText(canisterIdEl.value);
const actor = agent.makeActorFactory(({IDL}) => IDL.Service({
whoami: IDL.Func([], [IDL.Principal], ['query']),
whoami: IDL.Func([], [IDL.Principal], []),
}))({agent, canisterId});

whoAmIResponseEl.innerText = "Loading..."

// Similar to the sample project on dfx new:
whoAmIResponseEl.innerText = (await actor.whoami()).toText();
actor.whoami().then(principal => {
whoAmIResponseEl.innerText = principal.toText();
});
});

0 comments on commit a520c6e

Please sign in to comment.