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

SIWW auth #724

Merged
merged 9 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dapp/.env
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ VITE_REFERRAL=0

# TODO: Set this env variable in CI.
VITE_TBTC_API_ENDPOINT=""
VITE_ACRE_API_ENDPOINT="http://localhost:8788/api/v1/"

# API KEYS
VITE_GELATO_RELAY_API_KEY="htaJCy_XHj8WsE3w53WBMurfySDtjLP_TrNPPa6IPIc_" # this key should not be used on production
Expand Down
27 changes: 13 additions & 14 deletions dapp/src/components/ConnectWalletModal/ConnectWalletButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CONNECTION_ERRORS, ONE_SEC_IN_MILLISECONDS } from "#/constants"
import {
useAppDispatch,
useModal,
useSignMessageAndCreateSession,
useWallet,
useWalletConnectionError,
} from "#/hooks"
Expand All @@ -20,7 +21,6 @@ import {
ImageProps,
VStack,
} from "@chakra-ui/react"
import { useSignMessage } from "wagmi"
import { IconArrowNarrowRight } from "@tabler/icons-react"
import { AnimatePresence, Variants, motion } from "framer-motion"
import ArrivingSoonTooltip from "../ArrivingSoonTooltip"
Expand Down Expand Up @@ -61,7 +61,8 @@ export default function ConnectWalletButton({
onDisconnect,
status: connectionStatus,
} = useWallet()
const { signMessageAsync, status: signMessageStatus } = useSignMessage()
const { signMessageStatus, signMessageAndCreateSession } =
useSignMessageAndCreateSession()
const { closeModal } = useModal()
const dispatch = useAppDispatch()

Expand All @@ -82,22 +83,18 @@ export default function ConnectWalletButton({
}
}, [closeModal, dispatch, onSuccess])

const handleSignMessage = useCallback(
const handleSignMessageAndCreateSession = useCallback(
async (connectedConnector: OrangeKitConnector, btcAddress: string) => {
const message = orangeKit.createSignInWithWalletMessage(btcAddress)
const signedMessage = await signMessageAsync({
message,
connector: orangeKit.typeConversionToConnector(connectedConnector),
})

try {
await orangeKit.verifySignInWithWalletMessage(message, signedMessage)
await signMessageAndCreateSession(connectedConnector, btcAddress)

onSuccessSignMessage()
} catch (error) {
console.error("Failed to sign siww message", error)
setConnectionError(CONNECTION_ERRORS.INVALID_SIWW_SIGNATURE)
}
},
[signMessageAsync, onSuccessSignMessage, setConnectionError],
[signMessageAndCreateSession, onSuccessSignMessage, setConnectionError],
)

const onSuccessConnection = useCallback(
Expand All @@ -106,9 +103,9 @@ export default function ConnectWalletButton({

if (!btcAddress) return

await handleSignMessage(connector, btcAddress)
await handleSignMessageAndCreateSession(connector, btcAddress)
},
[connector, handleSignMessage],
[connector, handleSignMessageAndCreateSession],
)

const handleConnection = useCallback(() => {
Expand Down Expand Up @@ -241,7 +238,9 @@ export default function ConnectWalletButton({
size="lg"
variant="outline"
onClick={() =>
logPromiseFailure(handleSignMessage(connector, address))
logPromiseFailure(
handleSignMessageAndCreateSession(connector, address),
)
}
>
Resume and try again
Expand Down
3 changes: 3 additions & 0 deletions dapp/src/constants/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const NETWORK_TYPE = USE_TESTNET ? "testnet" : "mainnet"

const LATEST_COMMIT_HASH = import.meta.env.VITE_LATEST_COMMIT_HASH

const ACRE_API_ENDPOINT = import.meta.env.VITE_ACRE_API_ENDPOINT

export default {
PROD,
USE_TESTNET,
Expand All @@ -35,4 +37,5 @@ export default {
MEZO_PORTAL_API_KEY,
NETWORK_TYPE,
LATEST_COMMIT_HASH,
ACRE_API_ENDPOINT,
}
4 changes: 4 additions & 0 deletions dapp/src/constants/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ export const REFETCH_INTERVAL_IN_MILLISECONDS =
ONE_SEC_IN_MILLISECONDS * ONE_MINUTE_IN_SECONDS * 5

export const DATE_FORMAT_LOCALE_TAG = "us-US"

// 7 days
export const ACRE_SESSION_EXPIRATION_TIME =
ONE_WEEK_IN_SECONDS * ONE_SEC_IN_MILLISECONDS
1 change: 1 addition & 0 deletions dapp/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export { default as useLocalStorage } from "./useLocalStorage"
export { default as useDetectReferral } from "./useDetectReferral"
export { default as useReferral } from "./useReferral"
export { default as useMats } from "./useMats"
export { default as useSignMessageAndCreateSession } from "./useSignMessageAndCreateSession"
57 changes: 57 additions & 0 deletions dapp/src/hooks/useSignMessageAndCreateSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { OrangeKitConnector } from "#/types"
import { acreApi, orangeKit } from "#/utils"
import { useCallback } from "react"
import { useSignMessage } from "wagmi"

function useSignMessageAndCreateSession() {
const { signMessageAsync, status: signMessageStatus } = useSignMessage()

const signMessageAndCreateSession = useCallback(
async (connectedConnector: OrangeKitConnector, btcAddress: string) => {
let session = await acreApi.getSession()
const hasSessionAddress = "address" in session

const isSessionAddressEqual = hasSessionAddress
? (session as { address: string }).address === btcAddress
: false

if (hasSessionAddress && isSessionAddressEqual) {
return
}

if (hasSessionAddress && !isSessionAddressEqual) {
// Delete session.
await acreApi.deleteSession()
// Ask for nonce to create new session.
session = await acreApi.getSession()
}

if (!("nonce" in session)) {
throw new Error("Session nonce not available")
}

const message = orangeKit.createSignInWithWalletMessage(
btcAddress,
session.nonce,
)

const signedMessage = await signMessageAsync({
message,
connector: orangeKit.typeConversionToConnector(connectedConnector),
})

const publicKey = await connectedConnector
.getBitcoinProvider()
.getPublicKey()

await acreApi.createSession(message, signedMessage, publicKey)
},
[signMessageAsync],
)

return {
signMessageAndCreateSession,
signMessageStatus,
}
}
export default useSignMessageAndCreateSession
41 changes: 41 additions & 0 deletions dapp/src/utils/acre-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { env } from "#/constants"
import axiosStatic from "axios"

const axios = axiosStatic.create({
baseURL: env.ACRE_API_ENDPOINT,
withCredentials: true,
})

async function getSession() {
const response = await axios.get<{ nonce: string } | { address: string }>(
"session",
)

return response.data
}

async function createSession(
message: string,
signature: string,
publicKey: string,
) {
const response = await axios.post<{ success: boolean }>("session", {
message,
signature,
publicKey,
})

if (!response.data.success) {
throw new Error("Failed to create Acre session")
}
}

async function deleteSession() {
await axios.delete("session")
}

export default {
createSession,
getSession,
deleteSession,
}
1 change: 1 addition & 0 deletions dapp/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export { default as userAgent } from "./userAgent"
export { default as referralProgram } from "./referralProgram"
export { default as mezoPortalAPI } from "./mezoPortalApi"
export { default as router } from "./router"
export { default as acreApi } from "./acre-api"
13 changes: 11 additions & 2 deletions dapp/src/utils/orangeKit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { CONNECTION_ERRORS, wallets } from "#/constants"
import {
ACRE_SESSION_EXPIRATION_TIME,
CONNECTION_ERRORS,
wallets,
} from "#/constants"
import {
ConnectionErrorData,
OrangeKitError,
Expand All @@ -10,6 +14,7 @@ import {
} from "@orangekit/react"
import { Connector } from "wagmi"
import { SignInWithWalletMessage } from "@orangekit/sign-in-with-wallet"
import { getExpirationDate } from "./time"

const getWalletInfo = (connector: OrangeKitConnector) => {
switch (connector.id) {
Expand Down Expand Up @@ -37,7 +42,7 @@ const isConnectedStatus = (status: string) => status === "connected"
const isOrangeKitConnector = (connector?: Connector) =>
connector?.type === "orangekit"

const createSignInWithWalletMessage = (address: string) => {
const createSignInWithWalletMessage = (address: string, nonce: string) => {
const { host: domain, origin: uri } = window.location

const message = new SignInWithWalletMessage({
Expand All @@ -47,6 +52,10 @@ const createSignInWithWalletMessage = (address: string) => {
issuedAt: new Date().toISOString(),
version: "1",
networkFamily: "bitcoin",
expirationTime: getExpirationDate(
ACRE_SESSION_EXPIRATION_TIME,
).toISOString(),
nonce,
})

return message.prepareMessage()
Expand Down
13 changes: 7 additions & 6 deletions dapp/src/utils/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,14 @@ export const displayBlockTimestamp = (blockTimestamp: number) => {
return getRelativeTime(blockTimestamp)
}

export const getExpirationDate = (duration: number, startDate?: Date) => {
const date = startDate ?? new Date()
return new Date(date.getTime() + duration)
}

/**
* Returns the expiration timestamp from the start date considering the specified duration.
* If the startDate is not passed, the function will take the current time as the start date.
*/
export const getExpirationTimestamp = (duration: number, startDate?: Date) => {
const date = startDate ?? new Date()
const expirationDate = new Date(date.getTime() + duration)

return dateToUnixTimestamp(expirationDate)
}
export const getExpirationTimestamp = (duration: number, startDate?: Date) =>
dateToUnixTimestamp(getExpirationDate(duration, startDate))
1 change: 1 addition & 0 deletions dapp/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface ImportMetaEnv {
readonly VITE_SUBGRAPH_API_KEY: string
readonly VITE_MEZO_PORTAL_API_KEY: string
readonly VITE_LATEST_COMMIT_HASH: string
readonly VITE_ACRE_API_ENDPOINT: string
}

interface ImportMeta {
Expand Down
Loading