-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🦝🧧 ↝ Adding auth fetcher, more graphql hooks to manage signing in #16
Currently there's a mutate issue in `lib/auth/useLogin.ts` (in `Server/frontend`). (not assignable to param type ~~...). This is causing the SignInButton (which is derived from @thirdweb-dev/react (sdk) to also have an error with the default return statement (which should provide an access token from Lens IF the user has succesfully connected their wallet, completed a challenge (this challenge will later be replaced by the Flask-based challenge in `Server/app.py`), and signed in with Lens. So I'll be fixing this in the next commit. More notes available on the Notion page linked in #23 / #22 / #21
- Loading branch information
1 parent
95feace
commit b84a27c
Showing
15 changed files
with
388 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { useAddress, useNetworkMismatch, useNetwork, ConnectWallet, ChainId } from '@thirdweb-dev/react'; | ||
import React from 'react'; | ||
import useLensUser from '../lib/auth/useLensUser'; | ||
import useLogin from '../lib/auth/useLogin'; | ||
|
||
type Props = {}; | ||
|
||
export default function SignInButton({}: Props) { | ||
const address = useAddress(); // Detect connected wallet | ||
const isOnWrongNetwork = useNetworkMismatch(); // Is different to `activeChainId` in `_app.tsx` | ||
const [, switchNetwork] = useNetwork(); // Switch network to `activeChainId` | ||
const { isSignedInQuery, profileQuery } = useLensUser(); | ||
const { mutate: requestLogin } = useLogin(); | ||
|
||
// Connect wallet | ||
if (!address) { | ||
return ( | ||
<ConnectWallet /> | ||
); | ||
} | ||
|
||
// Switch network to polygon | ||
if (!isOnWrongNetwork) { | ||
return ( | ||
<button | ||
onClick={() => switchNetwork?.(ChainId.Polygon)} | ||
>Switch Network</button> | ||
) | ||
} | ||
|
||
if (isSignedInQuery.isLoading) { // Loading signed in state | ||
return <div>Loading</div> | ||
} | ||
|
||
// Sign in with Lens | ||
if (!isSignedInQuery.data) { // Request a login to Lens | ||
return ( | ||
<button | ||
onClick={() => requestLogin()} | ||
>Sign in with Lens</button> | ||
) | ||
}; | ||
|
||
if (profileQuery.isLoading) { // Show user their Lens Profile | ||
return <div>Loading...</div>; | ||
}; | ||
|
||
if (!profileQuery.data?.defaultProfile) { // If there's no Lens profile for the connected wallet | ||
return <div>No Lens Profile</div>; | ||
}; | ||
|
||
if (profileQuery.data?.defaultProfile) { // If profile exists | ||
return <div>Hello {profileQuery.data?.defaultProfile?.handle} </div> | ||
}; | ||
|
||
return ( | ||
<div>Something went wrong</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
mutation authenticate($request: SignedAuthChallenge!) { | ||
authenticate(request: $request) { | ||
accessToken | ||
refreshToken | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
query Challenge($request: ChallengeRequest!) { | ||
challenge(request: $request) { | ||
text | ||
} | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
query defaultProfile($request: DefaultProfileRequest!) { | ||
defaultProfile(request: $request) { | ||
...ProfileFields | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
mutation Refresh($request: RefreshRequest!) { | ||
refresh(request: $request) { | ||
accessToken | ||
refreshToken | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { fetcher } from "../../graphql/auth-fetcher"; | ||
import { ChallengeDocument, ChallengeQuery, ChallengeQueryVariables } from "../../graphql/generated"; | ||
|
||
export default async function generateChallenge( address:string ) { | ||
return await fetcher<ChallengeQuery, ChallengeQueryVariables>(ChallengeDocument, { | ||
request: { | ||
address, | ||
}, | ||
})(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
const STORAGE_KEY = 'LH_STORAGE_KEY'; // lens hub storage key | ||
|
||
// Determine if exp date is expired | ||
export function isTokenExpired(exp: number) { | ||
if (!exp) return true; | ||
if (Date.now() >= exp * 1000) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
// Read access token from Lens (local storage) | ||
export function readAccessToken () { | ||
// Ensure user is on client environment | ||
if (typeof window === 'undefined') return null; | ||
const ls = localStorage || window.localStorage; | ||
if (!ls) { | ||
throw new Error("LocalStorage is not available"); | ||
} | ||
|
||
const data = ls.getItem(STORAGE_KEY); | ||
if (!data) return null; | ||
|
||
return JSON.parse(data) as { | ||
accessToken: string; | ||
refreshToken: string; | ||
exp: number; | ||
}; | ||
} | ||
|
||
// Set access token in storage | ||
export function setAccessToken ( | ||
accessToken: string, | ||
refreshToken: string, | ||
) { | ||
// Parse JWT token to get expiration date | ||
const { exp } = parseJwt(accessToken); | ||
|
||
// Set all three variables in local storage | ||
const ls = localStorage || window.localStorage; | ||
|
||
if (!ls) { | ||
throw new Error("LocalStorage is not available"); | ||
} | ||
|
||
ls.setItem(STORAGE_KEY, JSON.stringify({ | ||
accessToken, | ||
refreshToken, | ||
exp | ||
})); | ||
} | ||
|
||
// Parse JWT token and extract params | ||
export function parseJwt (token: string) { | ||
var base64Url = token.split(".")[1]; | ||
var base64 = base64Url.replace(/-/g, "+").replace(/_/g, '/'); | ||
var jsonPayload = decodeURIComponent( | ||
window | ||
.atob(base64) | ||
.split("") | ||
.map(function (c) { | ||
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); | ||
}) | ||
.join("") | ||
); | ||
|
||
return JSON.parse(jsonPayload); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { fetcher } from "../../graphql/auth-fetcher"; | ||
import { RefreshMutation, RefreshMutationVariables, RefreshDocument } from "../../graphql/generated"; | ||
import { readAccessToken, setAccessToken } from "./helpers"; | ||
|
||
export default async function refreshAccessToken () { // Take current refresh, access token to Lens to generate a new Access token | ||
// Read refresh token from local storage | ||
const currentRefreshToken = readAccessToken()?.refreshToken; | ||
if (!currentRefreshToken) return null; | ||
|
||
// Send refresh token to Lens | ||
const result = await fetcher<RefreshMutation, RefreshMutationVariables>(RefreshDocument, { | ||
request: { | ||
refreshToken: currentRefreshToken | ||
}, | ||
})(); | ||
|
||
// Set new refresh token | ||
const { | ||
accessToken, refreshToken: newRefreshToken | ||
} = result.refresh; | ||
setAccessToken(accessToken, newRefreshToken); | ||
|
||
return accessToken as string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { useAddress } from "@thirdweb-dev/react"; | ||
import { useDefaultProfileQuery } from "../../graphql/generated"; | ||
import { readAccessToken } from "./helpers"; | ||
|
||
export default function useLensUser() { | ||
// Make a react query for the local storage key | ||
const address = useAddress(); | ||
const localStorageQuery = useQuery( | ||
['lens-user', address], | ||
() => readAccessToken(), | ||
); | ||
|
||
// If wallet is connected, check for the default profile (on Lens) connected to that wallet | ||
const profileQuery = useDefaultProfileQuery({ | ||
request: { | ||
ethereumAddress: address, | ||
} | ||
}, | ||
{ | ||
enabled: !!address, | ||
}); | ||
|
||
return { | ||
isSignedInQuery: localStorageQuery, | ||
profileQuery: profileQuery, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { useMutation } from "@apollo/client"; | ||
import { useAddress, useSDK } from "@thirdweb-dev/react"; | ||
import { useAuthenticateMutation } from "../../graphql/generated"; | ||
import generateChallenge from "./generateChallenge"; | ||
import { setAccessToken } from "./helpers"; | ||
|
||
// Store access token inside local storage | ||
|
||
export default function useLogin() { | ||
const address = useAddress(); // Ensure user has connected wallet | ||
const sdk = useSDK(); | ||
const { | ||
mutateAsync: sendSignedMessage | ||
} = useAuthenticateMutation(); | ||
|
||
async function login () { | ||
if (!address) { | ||
console.error('No address found. Please try connecting your wallet to continue signing into Lens'); | ||
return null; | ||
} | ||
|
||
const { challenge } = await generateChallenge(address); // Generate challenge from the Lens API | ||
const signature = await sdk?.wallet.sign(challenge.text); // Sign the returned challenge with the user's wallet | ||
const { // Send the signed challenge to the Lens API | ||
authenticate | ||
} = await sendSignedMessage({ | ||
request: { | ||
address, | ||
signature, | ||
}, | ||
}); | ||
|
||
const { accessToken, refreshToken} = authenticate; | ||
|
||
setAccessToken(accessToken, refreshToken); | ||
} | ||
|
||
// Receive an access token from Lens API | ||
return useMutation(login); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,11 @@ | ||
import type { NextPage } from "next"; | ||
import useAuthenticate from '../hooks/useAuthenticate'; | ||
import { useAddress, useDisconnect, useUser, useLogin, useLogout, useMetamask } from "@thirdweb-dev/react"; | ||
import { useAddress, useDisconnect, useUser, useLogout, useMetamask, ConnectWallet } from "@thirdweb-dev/react"; | ||
import { useEffect, useState } from "react"; | ||
import { PublicationSortCriteria, useExplorePublicationsQuery } from "../graphql/generated"; | ||
import useLogin from "../lib/auth/useLogin"; | ||
import SignInButton from "../components/SignInButton"; | ||
|
||
export default function Home () { | ||
const { data, isLoading, error } = useExplorePublicationsQuery({ | ||
request: { | ||
sortCriteria: PublicationSortCriteria.TopCollected, | ||
}, | ||
}); | ||
|
||
return ( | ||
<div>Hello World</div> | ||
); | ||
return <SignInButton />; | ||
}; |