Skip to content

Commit

Permalink
chore: comments, clean-up and loose ends
Browse files Browse the repository at this point in the history
  • Loading branch information
Keyrxng committed Apr 22, 2024
1 parent c0d1f4a commit 5f3d382
Show file tree
Hide file tree
Showing 12 changed files with 581 additions and 563 deletions.
64 changes: 43 additions & 21 deletions app/components/claims-portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,73 @@ import { app } from "../scripts/rewards/app-state";
import { readClaimDataFromUrl } from "../scripts/rewards/render-transaction/read-claim-data-from-url";
import { WebAuthnHandler } from "../scripts/rewards/account-abstraction/webauthn";
import { githubLoginHandler } from "../scripts/rewards/account-abstraction/github-login-button";
import { toaster } from "../scripts/rewards/toaster";
import { getButtonController, toaster } from "../scripts/rewards/toaster";
import { User } from "@supabase/supabase-js";
import { GitHubUser } from "../scripts/rewards/account-abstraction/supabase-server-client";
import { SupabaseBrowserClient } from "../scripts/rewards/account-abstraction/supabase-browser-client";
import { renderTransaction } from "../scripts/rewards/render-transaction/render-transaction";

async function readClaimData() {
await readClaimDataFromUrl(app);
}

export default function ClaimsPortal({ permits, githubUser, supabaseUser }: { permits?: string; githubUser?: GitHubUser; supabaseUser?: User }) {
export default function ClaimsPortal({ permits, supabaseUser }: { permits?: string; supabaseUser?: User | null }) {
const webAuthnHandler = new WebAuthnHandler();
const [isMounted, setMounted] = React.useState(false);
const isLoggedIn = React.useMemo(() => !!supabaseUser, [supabaseUser]);

useEffect(() => {
async function load() {
if (!isLoggedIn && permits) {
await SupabaseBrowserClient.getInstance().loginWithGitHub(permits);
return;
}
await readClaimData();

if (app.claims.length === 0 || !permits) {
return;
}

if (!githubUser || !supabaseUser) {
console.log("No user data found");
return;
}
if (supabaseUser && !isMounted) {
// use this to create or authenticate with webauthn
const dataForWebAuthnCredential = {
id: new TextEncoder().encode(supabaseUser.email),
name: supabaseUser.user_metadata.preferred_username,
displayName: supabaseUser.user_metadata.preferred_username,
};

// use this to create or authenticate with webauthn
const dataForWebAuthnCredential = {
id: new TextEncoder().encode(supabaseUser.email),
name: supabaseUser.user_metadata.preferred_username,
displayName: supabaseUser.user_metadata.preferred_username,
};
// we'll create an EOA for the user and then attach it to the SMA
if (!window.ethereum) {
app.signer = await webAuthnHandler.handleUserAuthentication(supabaseUser, dataForWebAuthnCredential, app);

// we'll create an EOA for the user and then attach it to the SMA
if (!window.ethereum) {
app.signer = await webAuthnHandler.handleUserAuthentication(supabaseUser, githubUser, dataForWebAuthnCredential, app);
} else {
app.signer = await webAuthnHandler.registerEOA(supabaseUser, githubUser, dataForWebAuthnCredential, app);
if (app.signer.account?.address) {
toaster.create("success", `Successfully authenticated with WebAuthn. Welcome back ${supabaseUser.user_metadata.preferred_username}!`);
} else {
toaster.create("warning", "Failed to authenticate with WebAuthn. Please try again.");
}
} else {
// just saves their EOA to supabase for future use
// webauthn doesn't make sense here unless using it to either
// - create them an EOA like above then we can reproduce the private key to sign txs with
// - embed their current EOA private key in the webauthn credential (not recommended)

console.log("app.signer", app.signer);
app.signer = await webAuthnHandler.registerEOA(supabaseUser, dataForWebAuthnCredential, app);
}
}

if (app.signer?.account) {
toaster.create("success", `Successfully authenticated with WebAuthn. Welcome back ${supabaseUser.user_metadata.preferred_username}!`);
const toasterEle = document.getElementsByClassName("toast .fa-circle-check success");

await app.signer.getPermissions();

const [address] = (await app.signer.getAddresses()) || [];

if (!toasterEle.length && address) {
toaster.create("success", `Connected to ${address}!`);
await renderTransaction();
getButtonController().showMakeClaim();
}
}
load().catch(console.error);
setMounted(true);
}, []);

return (
Expand Down
22 changes: 13 additions & 9 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import ClaimsPortal from "./components/claims-portal";
import { SupabaseServerClient } from "./scripts/rewards/account-abstraction/supabase-server-client";

async function loadUserData() {
async function loadUserData(permits: string) {
const supabase = SupabaseServerClient.getInstance();
const {
data: { user },
} = await supabase.getSupabaseUser();

const githubUser = await supabase.getGitHubUser();
if (!user) {
try {
await supabase.loginWithGitHub(permits);
} catch (error) {
console.error("Failed to login", error);
}
}

return { user, githubUser };
return { user };
}

export default async function Page(params: { searchParams: { claim: string } }) {
const permitData = params.searchParams.claim;

const { user, githubUser } = await loadUserData();
const { user } = await loadUserData(permitData);

if (permitData && user && githubUser) {
return <ClaimsPortal permits={permitData} githubUser={githubUser} supabaseUser={user} />;
}
return <ClaimsPortal permits={permitData} supabaseUser={user} />;

/**
* good idea to have section for account setup, options, etc. here if we don't have a permit
*/

// return <ClaimsPortal permits={permitData} githubUser={githubUser} supabaseUser={user} />;
*
*/
}
20 changes: 10 additions & 10 deletions app/scripts/rewards/account-abstraction/sodium.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { LocalAccountSigner } from "@alchemy/aa-core";
import { createHash } from "crypto";
import { wordlists } from "ethers";
import { BytesLike, entropyToMnemonic, isValidMnemonic } from "ethers/lib/utils";
import { crypto_generichash, crypto_generichash_BYTES } from "libsodium-wrappers";
import { Hex } from "viem";
import { Hex, keccak256 } from "viem";

export async function generateSAPrivateKey(
publicKey: ArrayBuffer | { valueOf(): ArrayBuffer | SharedArrayBuffer } | undefined,
Expand All @@ -13,7 +14,7 @@ export async function generateSAPrivateKey(
/**
* Public key is the public key of the credential, good source of entropy
*
* credentials ID is unique to the credential and authenticator, good source of entropy
* binaryID is unique to the credential and authenticator, good source of entropy
*
* userHandle is the email of the user, bad source of entropy
*
Expand All @@ -32,19 +33,18 @@ export async function generateSAPrivateKey(
if (!publicKey) throw new Error("No public key created for private key generation");

const salt = process.env.SALT || "ubiquity-rewards";
const concData = Buffer.concat([
Buffer.from(publicKey),
Buffer.from(binaryID),
Buffer.from(userHandle),
Buffer.from(supabaseAuthID),
Buffer.from(salt, "hex"),
]);
const concData = keccak256(
Buffer.concat([Buffer.from(publicKey), Buffer.from(binaryID), Buffer.from(userHandle), Buffer.from(supabaseAuthID), Buffer.from(salt, "hex")])
);

const hash = crypto_generichash(crypto_generichash_BYTES, concData);
const privateKey = ("0x" + createHash("sha256").update(hash).digest().toString("hex")) as Hex;

const mnemonic = generateMnemonic(privateKey);
const publicKeyHex = Buffer.from(publicKey).toString("hex");

const accSigner = LocalAccountSigner.mnemonicToAccountSigner(mnemonic);

const publicKeyHex = await accSigner.getAddress();

return { mnemonic, publicKey: publicKeyHex, privateKey };
}
Expand Down
29 changes: 6 additions & 23 deletions app/scripts/rewards/account-abstraction/supabase-browser-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,36 +51,19 @@ export class SupabaseBrowserClient {
return await this.supabaseBrowserClient().auth.getUser();
}

async getGitHubUser(permits: string): Promise<GitHubUser | null> {
const {
data: { user },
} = await this.getSupabaseUser();

if (!user) {
await this.loginWithGitHub(permits);
}

let activeSessionToken = await this.getSessionToken();

if (!activeSessionToken) {
await this.loginWithGitHub(permits);
}

activeSessionToken = await this.getSessionToken();

return await this.getNewGitHubUser(activeSessionToken);
}

async getSessionToken() {
/**
* Using supabase.auth.getSession() is potentially insecure as it loads data directly from
* the storage medium (typically cookies) which may not be authentic.
* Prefer using supabase.auth.getUser() instead.
* To suppress this warning call supabase.auth.getUser() before you call supabase.auth.getSession()
*/
await this.getSupabaseUser();
const cachedSessionToken = this.supabaseBrowserClient().auth.getSession();
return (await cachedSessionToken).data.session?.provider_token ?? null;
const user = this.getSupabaseUser();
if (user) {
return (await this.supabaseBrowserClient().auth.getSession()).data.session?.provider_token;
}

return null;
}

async getNewGitHubUser(providerToken: string | null): Promise<GitHubUser | null> {
Expand Down
Loading

0 comments on commit 5f3d382

Please sign in to comment.