mirror of
https://github.com/3lLobo/zkAuth.git
synced 2026-01-06 19:13:51 -05:00
The Merge 🌊
This commit is contained in:
@@ -3,4 +3,4 @@
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# Zero-Knowledge 2-Factor Authentication 🗝️ (zk-2FA)
|
||||
|
||||
|
||||
The goal of this project is to provide 2FA for EVM compatible blockchains.
|
||||
We follow a parallel approach for a twofold Authentication solution. The first implements the popular and broadly adopted TOTP 2FA with a trusted validator. The second solution implements a password-generator based zk proof, which is validated onChain providing a zero-trust security level.
|
||||
We follow a parallel approach for a twofold Authentication solution. The first implements the popular and broadly adopted TOTP 2FA with a trusted validator. The second solution implements a password-generator based zk proof, which is validated onChain providing a zero-trust security level.
|
||||
|
||||
Further we provide a dapp to facilitate user-interaction with our smrt-contracts. All dapp interactions can likewise be performed manually per console.
|
||||
Further we provide a dapp to facilitate user-interaction with our smrt-contracts. All dapp interactions can likewise be performed manually per console.
|
||||
|
||||
## TOTP 2FA
|
||||
|
||||
@@ -15,7 +14,6 @@ A picturesque flow-chart of our TOTP 2FA solution:
|
||||
|
||||
**Artworq in the making**
|
||||
|
||||
|
||||
## Contribute
|
||||
|
||||
Feedback and contributions are always welcome 🤗
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "hardhat-project",
|
||||
"scripts": {
|
||||
"prettier": "prettier --write . --config ../.prettierrc"
|
||||
"prettier": "prettier --write ../ --config ../.prettierrc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^1.0.3",
|
||||
|
||||
69
dapp/components/CardChoice.tsx
Normal file
69
dapp/components/CardChoice.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { useTheme } from 'next-themes'
|
||||
import Image from 'next/image'
|
||||
|
||||
interface CardChoiceProps {
|
||||
authType: string
|
||||
setAuthType: (arg: string) => void
|
||||
}
|
||||
|
||||
const CardChoice = (props: CardChoiceProps) => {
|
||||
const { theme } = useTheme()
|
||||
return (
|
||||
{
|
||||
totp: (
|
||||
<button
|
||||
className="w-full max-w-sm bg-white rounded-lg border border-gray-200 shadow-md p-6 lg:p-8 dark:bg-gray-800 dark:border-gray-700 hover:scale-110 transition ease-in-out duration-500 grid grid-cols-12 gap-4"
|
||||
onClick={() => props.setAuthType('totp')}
|
||||
>
|
||||
<div className="flex h-full col-span-3 lg:col-span-12 items-center justify-center">
|
||||
<Image
|
||||
src="/TOTP.svg"
|
||||
alt="TOTP"
|
||||
width={150}
|
||||
height={150}
|
||||
className={`filter-${theme}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-9 lg:col-span-12">
|
||||
<div className="text-xl lg:text-2xl font-medium lg:mt-4 text-start lg:text-center text-gray-900 dark:text-white">
|
||||
Set up TOTP 2FA
|
||||
</div>
|
||||
<div className="text-sm lg:text-base text-start lg:text-center mt-2 text-gray-500 dark:text-gray-400">
|
||||
This option will generate a set of Time dependent
|
||||
One-Time-Passwords that you can verify by using apps like Google
|
||||
Authenticator.
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
),
|
||||
zk: (
|
||||
<button
|
||||
className="w-full max-w-sm bg-white rounded-lg border border-gray-200 shadow-md p-6 lg:p-8 dark:bg-gray-800 dark:border-gray-700 hover:scale-110 transition ease-in-out duration-500 grid grid-cols-12 gap-4"
|
||||
onClick={() => props.setAuthType('zk')}
|
||||
>
|
||||
<div className="flex h-full col-span-3 lg:col-span-12 items-center justify-center">
|
||||
<Image
|
||||
src="/ZK.svg"
|
||||
alt="ZK"
|
||||
width={150}
|
||||
height={150}
|
||||
className={`filter-${theme}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-9 lg:col-span-12">
|
||||
<div className="text-xl lg:text-2xl font-medium lg:mt-4 text-start lg:text-center text-gray-900 dark:text-white">
|
||||
Set up zk-Password 2FA
|
||||
</div>
|
||||
<div className="text-sm lg:text-base text-start lg:text-center mt-2 text-gray-500 dark:text-gray-400">
|
||||
Set up your own password, that will be encripted and stored
|
||||
on-chain. To verify it a zk-Proof will be generated in the
|
||||
frontend and verified on-chain.
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
),
|
||||
}[props.authType] || <></>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardChoice
|
||||
96
dapp/components/DropdownAccount.tsx
Normal file
96
dapp/components/DropdownAccount.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
/* This example requires Tailwind CSS v2.0+ */
|
||||
import { Fragment } from 'react'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
ArrowRightOnRectangleIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { shortenAddress, useEthers, useLookupAddress } from '@usedapp/core'
|
||||
|
||||
function classNames(...classes: any) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
interface DropdownProps {
|
||||
account: string
|
||||
}
|
||||
|
||||
const DropdownAccount = (props: DropdownProps) => {
|
||||
const { deactivate } = useEthers()
|
||||
const { ens } = useLookupAddress(props.account)
|
||||
|
||||
return (
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<div>
|
||||
<Menu.Button
|
||||
className="font-medium rounded-lg text-sm px-3 py-1.5
|
||||
text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-700"
|
||||
>
|
||||
{ens ?? shortenAddress(props.account)}
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className="absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md
|
||||
bg-white dark:bg-gray-800
|
||||
text-gray-900 dark:text-white
|
||||
shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href="#"
|
||||
className={classNames(
|
||||
active
|
||||
? 'bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-100'
|
||||
: 'text-gray-700 dark:text-gray-300',
|
||||
'group flex items-center px-4 py-2 text-sm'
|
||||
)}
|
||||
>
|
||||
<ArrowTopRightOnSquareIcon
|
||||
className="mr-3 h-5 w-5 text-gray-400
|
||||
dark:text-gray-300 group-hover:text-gray-500 dark:group-hover:text-gray-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
See on Etherscan
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
{/* <Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
className={classNames(
|
||||
active
|
||||
? "bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-100"
|
||||
: "text-gray-700 dark:text-gray-300",
|
||||
"group flex items-center px-4 py-2 text-sm"
|
||||
)}
|
||||
onClick={() => deactivate()}
|
||||
>
|
||||
<ArrowRightOnRectangleIcon
|
||||
className="mr-3 h-5 w-5 text-gray-400
|
||||
dark:text-gray-300 group-hover:text-gray-500 dark:group-hover:text-gray-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Logout
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item> */}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropdownAccount
|
||||
16
dapp/components/Layout.tsx
Normal file
16
dapp/components/Layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Navbar } from './'
|
||||
import Head from 'next/head'
|
||||
|
||||
export default function Layout(props: any) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>zkAuth</title>
|
||||
<meta name="description" content="zero-knowledge Authentification" />
|
||||
<link rel="icon" href="" />
|
||||
</Head>
|
||||
<Navbar />
|
||||
<main>{props.children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
CheckIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
|
||||
interface ModalVerifyProps {
|
||||
interface ModalVerifyTotpProps {
|
||||
verified: boolean
|
||||
verifyCode: any
|
||||
}
|
||||
|
||||
const ModalVerify = (props: ModalVerifyProps) => {
|
||||
const ModalVerifyTotp = (props: ModalVerifyTotpProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const onSubmit = (e: React.UIEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
@@ -135,4 +135,4 @@ const ModalVerify = (props: ModalVerifyProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalVerify
|
||||
export default ModalVerifyTotp
|
||||
123
dapp/components/TotpSetup.tsx
Normal file
123
dapp/components/TotpSetup.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import { ArrowUturnLeftIcon } from '@heroicons/react/24/outline'
|
||||
import { useEthers } from '@usedapp/core'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { usePinInput, PinInputActions } from 'react-pin-input-hook'
|
||||
import { ModalVerifyTotp, QrCodeAuth } from './'
|
||||
|
||||
var jsotp = require('jsotp')
|
||||
var base32 = require('thirty-two')
|
||||
|
||||
interface TotpSetupProps {
|
||||
setAuthType: (arg: string) => void
|
||||
}
|
||||
|
||||
const TotpSetup = (props: TotpSetupProps) => {
|
||||
const { account, library: provider } = useEthers()
|
||||
|
||||
// Secret State Management
|
||||
const [secret, setSecret] = useState('')
|
||||
const [blur, setBlur] = useState('opacity-50 blur-sm')
|
||||
const loadSecret = async (e: React.FormEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
if (provider != undefined) {
|
||||
const signer = provider.getSigner()
|
||||
const signature = await signer.signMessage('zkAuth') // TODO: Random Input? ZK?
|
||||
const secretEncoded = base32
|
||||
.encode(signature)
|
||||
.toString()
|
||||
.replace(/=/g, '')
|
||||
setSecret(secretEncoded)
|
||||
setBlur('')
|
||||
}
|
||||
}
|
||||
|
||||
// PIN state management
|
||||
const [verified, setVerified] = useState(false)
|
||||
const [pin, setPin] = useState(['', '', '', '', '', ''])
|
||||
const [error, setError] = useState(false)
|
||||
const actionRef = useRef<PinInputActions>(null)
|
||||
const { fields } = usePinInput({
|
||||
values: pin,
|
||||
onChange: setPin,
|
||||
error,
|
||||
actionRef,
|
||||
placeholder: '•',
|
||||
})
|
||||
|
||||
function verifyCode(e: React.FormEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
// Check if there is at least one empty field. If there is, the input is considered empty.
|
||||
if (pin.includes('')) {
|
||||
// Setting the error.
|
||||
setError(true)
|
||||
// We set the focus on the first empty field if `error: true` was passed as a parameter in `options`.
|
||||
actionRef.current?.focus()
|
||||
}
|
||||
const verifier = jsotp.TOTP(secret)
|
||||
if (verifier.verify(pin.join(''))) {
|
||||
setVerified(true)
|
||||
} else {
|
||||
setVerified(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (!account) return null
|
||||
return (
|
||||
<div className="relative w-full max-w-sm bg-white rounded-lg border border-gray-200 shadow-md p-6 md:p-8 dark:bg-gray-800 dark:border-gray-700">
|
||||
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
onClick={() => props.setAuthType('')}
|
||||
>
|
||||
<span className="sr-only">Back</span>
|
||||
<ArrowUturnLeftIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<form className="space-y-6" action="#">
|
||||
<div className="text-2xl font-medium flex justify-center text-gray-900 dark:text-white">
|
||||
Set up your TOTP
|
||||
</div>
|
||||
<div className="relative flex justify-center items-center">
|
||||
<div className={blur}>
|
||||
<QrCodeAuth account={account} secret={secret} />
|
||||
</div>
|
||||
{secret == '' ? (
|
||||
<button
|
||||
className="absolute w-1/2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
onClick={(e) => loadSecret(e)}
|
||||
>
|
||||
Set up 2FA
|
||||
</button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
>
|
||||
Authenticator Code
|
||||
</label>
|
||||
|
||||
<div className="pin-input flex flex-row justify-center">
|
||||
{fields.map((propsField, index) => (
|
||||
<input
|
||||
key={index}
|
||||
className="pin-input__field w-10 p-2.5 mx-1 text-2xl rounded-lg text-center font-mono
|
||||
bg-gray-50 border border-gray-300 text-gray-900 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
||||
{...propsField}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalVerifyTotp verified={verified} verifyCode={verifyCode} />
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TotpSetup
|
||||
112
dapp/components/ZkPasswordSetup.tsx
Normal file
112
dapp/components/ZkPasswordSetup.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
ArrowUturnLeftIcon,
|
||||
CheckIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import { useEthers } from '@usedapp/core'
|
||||
import { useState } from 'react'
|
||||
|
||||
import dynamic from 'next/dynamic'
|
||||
const PasswordChecklist = dynamic(import('react-password-checklist'), {
|
||||
ssr: false,
|
||||
})
|
||||
interface ZkSetupProps {
|
||||
setAuthType: (arg: string) => void
|
||||
}
|
||||
const ZkPasswordSetup = (props: ZkSetupProps) => {
|
||||
const { account, library: provider } = useEthers()
|
||||
|
||||
// PIN state management
|
||||
const [password, setPassword] = useState('')
|
||||
const [passwordAgain, setPasswordAgain] = useState('')
|
||||
const [isValid, setIsValid] = useState(false)
|
||||
|
||||
// Set password to blockchain
|
||||
const onSubmit = (e: React.FormEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
if (!account) return null
|
||||
return (
|
||||
<div className="relative w-full max-w-sm bg-white rounded-lg border border-gray-200 shadow-md p-6 md:p-8 dark:bg-gray-800 dark:border-gray-700">
|
||||
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
onClick={() => props.setAuthType('')}
|
||||
>
|
||||
<span className="sr-only">Back</span>
|
||||
<ArrowUturnLeftIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<form className="space-y-6" action="#">
|
||||
<h5 className="text-2xl font-medium flex justify-center text-gray-900 dark:text-white">
|
||||
Set up zk-Password
|
||||
</h5>
|
||||
<div className="relative flex justify-center items-center"></div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
className="w-full p-2.5 rounded-lg
|
||||
bg-gray-50 border border-gray-300 text-gray-900 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
||||
placeholder="••••••••"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
>
|
||||
Repeat Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
className="w-full p-2.5 rounded-lg
|
||||
bg-gray-50 border border-gray-300 text-gray-900 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
||||
placeholder="••••••••"
|
||||
onChange={(e) => setPasswordAgain(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<PasswordChecklist
|
||||
rules={['minLength', 'specialChar', 'number', 'capital', 'match']}
|
||||
minLength={8}
|
||||
value={password}
|
||||
valueAgain={passwordAgain}
|
||||
iconComponents={{
|
||||
ValidIcon: <CheckIcon className="h-5 w-5 mr-1 text-green-600" />,
|
||||
InvalidIcon: <XMarkIcon className="h-5 w-5 mr-1 text-red-600" />,
|
||||
}}
|
||||
className="flex flex-col text-sm align-middle"
|
||||
onChange={(isValid) => {
|
||||
setIsValid(isValid)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
|
||||
disabled:opacity-25"
|
||||
onClick={(e) => onSubmit(e)}
|
||||
disabled={!isValid}
|
||||
>
|
||||
Set password
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ZkPasswordSetup
|
||||
@@ -3,6 +3,7 @@ import { useEthers } from '@usedapp/core'
|
||||
import Web3Modal from 'web3modal'
|
||||
import WalletConnectProvider from '@walletconnect/web3-provider'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { ethers } from 'ethers'
|
||||
|
||||
const ConnectWalletButton = () => {
|
||||
const { theme } = useTheme()
|
||||
@@ -28,6 +29,7 @@ const ConnectWalletButton = () => {
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
useEffect(() => setLoaded(true), [])
|
||||
|
||||
// Set up Web3modal
|
||||
const [web3Modal, setWeb3Modal] = useState<Web3Modal | undefined>(undefined)
|
||||
useEffect(() => {
|
||||
const providerOptions = {
|
||||
@@ -47,22 +49,16 @@ const ConnectWalletButton = () => {
|
||||
},
|
||||
}
|
||||
|
||||
const newWeb3Modal = new Web3Modal({
|
||||
providerOptions,
|
||||
cacheProvider: false,
|
||||
})
|
||||
setWeb3Modal(newWeb3Modal)
|
||||
}, [])
|
||||
if (loaded) {
|
||||
const newWeb3Modal = new Web3Modal({
|
||||
providerOptions,
|
||||
cacheProvider: false,
|
||||
theme: theme,
|
||||
})
|
||||
|
||||
// const connect = async () => {
|
||||
// try {
|
||||
// const provider = await web3Modal?.connect()
|
||||
// await activate(provider)
|
||||
// setActivateError("")
|
||||
// } catch (error: any) {
|
||||
// setActivateError(error.message)
|
||||
// }
|
||||
// }
|
||||
setWeb3Modal(newWeb3Modal)
|
||||
}
|
||||
}, [loaded, theme])
|
||||
|
||||
const connect = useCallback(async () => {
|
||||
try {
|
||||
@@ -74,12 +70,22 @@ const ConnectWalletButton = () => {
|
||||
}
|
||||
}, [web3Modal, activate])
|
||||
|
||||
// useEffect(() => {
|
||||
// if (web3Modal && web3Modal.cachedProvider) {
|
||||
// console.log(web3Modal.cachedProvider)
|
||||
// connect()
|
||||
// }
|
||||
// }, [connect, web3Modal])
|
||||
// Set up provider if already connected
|
||||
useEffect(() => {
|
||||
const { ethereum } = window
|
||||
const checkMetaMaskConnected = async () => {
|
||||
var provider = new ethers.providers.Web3Provider(ethereum)
|
||||
const accounts = await provider.listAccounts()
|
||||
const connected = accounts.length > 0
|
||||
console.log('CONNECTED', connected)
|
||||
if (connected) {
|
||||
activate(provider)
|
||||
}
|
||||
}
|
||||
if (ethereum) {
|
||||
checkMetaMaskConnected()
|
||||
}
|
||||
})
|
||||
|
||||
if (!loaded) return null
|
||||
|
||||
|
||||
11
dapp/components/index.ts
Normal file
11
dapp/components/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { default as ConnectWalletButton } from './ConnectWalletButton'
|
||||
export { default as DropdownAccount } from './DropdownAccount'
|
||||
export { default as LogInBox } from './LogInBox'
|
||||
export { default as ModalVerifyTotp } from './ModalVerifyTotp'
|
||||
export { default as Navbar } from './Navbar'
|
||||
export { default as QrCodeAuth } from './QrCodeAuth'
|
||||
export { default as ToggleColorMode } from './ToggleColorMode'
|
||||
export { default as TotpSetup } from './TotpSetup'
|
||||
export { default as ZkPasswordSetup } from './ZkPasswordSetup'
|
||||
export { default as CardChoice } from './CardChoice'
|
||||
export { default as Layout } from './Layout'
|
||||
@@ -1,123 +1,72 @@
|
||||
import { useEthers, shortenAddress, useLookupAddress } from '@usedapp/core'
|
||||
import { ethers } from 'ethers'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { usePinInput, PinInputActions } from 'react-pin-input-hook'
|
||||
import ConnectWalletButton from './connectWalletButton'
|
||||
import ModalVerify from './modalVerify'
|
||||
import QrCodeAuth from './qrCodeAuth'
|
||||
import { useEthers } from '@usedapp/core'
|
||||
import { useState } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
var jsotp = require('jsotp')
|
||||
var base32 = require('thirty-two')
|
||||
import { ConnectWalletButton, TotpSetup, ZkPasswordSetup, CardChoice } from './'
|
||||
|
||||
const LogInBox = () => {
|
||||
const { account, library: provider } = useEthers()
|
||||
const { ens } = useLookupAddress(account)
|
||||
|
||||
// Secret State Management
|
||||
const [secret, setSecret] = useState('')
|
||||
const [blur, setBlur] = useState('opacity-50 blur-sm')
|
||||
const loadSecret = async (e: React.FormEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault()
|
||||
if (provider != undefined) {
|
||||
const signer = provider.getSigner()
|
||||
const signature = await signer.signMessage('zkAuth') // TODO: Random Input? ZK?
|
||||
const secretEncoded = base32
|
||||
.encode(signature)
|
||||
.toString()
|
||||
.replace(/=/g, '')
|
||||
setSecret(secretEncoded)
|
||||
setBlur('')
|
||||
}
|
||||
}
|
||||
|
||||
// PIN state management
|
||||
const [verified, setVerified] = useState(false)
|
||||
const [pin, setPin] = useState(['', '', '', '', '', ''])
|
||||
const [error, setError] = useState(false)
|
||||
const actionRef = useRef<PinInputActions>(null)
|
||||
const { fields } = usePinInput({
|
||||
values: pin,
|
||||
onChange: setPin,
|
||||
error,
|
||||
actionRef,
|
||||
placeholder: '•',
|
||||
})
|
||||
|
||||
function verifyCode(e: React.FormEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
// Check if there is at least one empty field. If there is, the input is considered empty.
|
||||
if (pin.includes('')) {
|
||||
// Setting the error.
|
||||
setError(true)
|
||||
// We set the focus on the first empty field if `error: true` was passed as a parameter in `options`.
|
||||
actionRef.current?.focus()
|
||||
}
|
||||
const verifier = jsotp.TOTP(secret)
|
||||
if (verifier.verify(pin.join(''))) {
|
||||
setVerified(true)
|
||||
} else {
|
||||
setVerified(false)
|
||||
}
|
||||
}
|
||||
const [authType, setAuthType] = useState('')
|
||||
|
||||
return (
|
||||
<>
|
||||
{!account ? (
|
||||
<div className="flex justify-center">
|
||||
<motion.div
|
||||
key="connect"
|
||||
className="flex justify-center"
|
||||
initial={{ y: 10, opacity: 0, scale: 0.9 }}
|
||||
animate={{ y: 0, opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 1, ease: 'easeOut' }}
|
||||
>
|
||||
<ConnectWalletButton />
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
<div className="w-full max-w-sm bg-white rounded-lg border border-gray-200 shadow-md sm:p-6 md:p-8 dark:bg-gray-800 dark:border-gray-700">
|
||||
<form className="space-y-6" action="#">
|
||||
<h5 className="text-xl font-medium text-gray-900 dark:text-white">
|
||||
Sign in to our platform
|
||||
</h5>
|
||||
<label
|
||||
htmlFor="account"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
{
|
||||
totp: (
|
||||
<motion.div
|
||||
key="settotp"
|
||||
initial={{ y: 10, opacity: 0, scale: 0.9 }}
|
||||
animate={{ y: 0, opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.6, ease: 'easeOut' }}
|
||||
>
|
||||
Your account
|
||||
</label>
|
||||
<span>{ens ?? shortenAddress(account)}</span>
|
||||
<div className="relative flex justify-center items-center">
|
||||
<div className={blur}>
|
||||
<QrCodeAuth account={account} secret={secret} />
|
||||
</div>
|
||||
{secret == '' ? (
|
||||
<button
|
||||
className="absolute w-1/2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
onClick={(e) => loadSecret(e)}
|
||||
>
|
||||
Set up 2FA
|
||||
</button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
>
|
||||
Authenticator Code
|
||||
</label>
|
||||
|
||||
<div className="pin-input flex flex-row justify-center">
|
||||
{fields.map((propsField, index) => (
|
||||
<input
|
||||
key={index}
|
||||
className="pin-input__field w-10 p-2.5 mx-1 text-2xl rounded-lg text-center font-mono
|
||||
bg-gray-50 border border-gray-300 text-gray-900 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
|
||||
{...propsField}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalVerify verified={verified} verifyCode={verifyCode} />
|
||||
</form>
|
||||
</div>
|
||||
<TotpSetup setAuthType={setAuthType} />
|
||||
</motion.div>
|
||||
),
|
||||
zk: (
|
||||
<motion.div
|
||||
key="setzk"
|
||||
initial={{ y: 10, opacity: 0, scale: 0.9 }}
|
||||
animate={{ y: 0, opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.6, ease: 'easeOut' }}
|
||||
>
|
||||
<ZkPasswordSetup setAuthType={setAuthType} />
|
||||
</motion.div>
|
||||
),
|
||||
}[authType] || (
|
||||
<div className="flex flex-col space-y-10 lg:space-y-0 lg:flex-row lg:space-x-20">
|
||||
<motion.div
|
||||
key="totp"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ y: 0, opacity: 1, scale: 1 }}
|
||||
exit={{ y: -100, opacity: 0, transition: { duration: 1 } }}
|
||||
transition={{ duration: 0.6, ease: 'easeOut' }}
|
||||
>
|
||||
<CardChoice authType="totp" setAuthType={setAuthType} />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
key="zk"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ y: 0, opacity: 1, scale: 1 }}
|
||||
exit={{ y: -100, opacity: 0, transition: { duration: 1 } }}
|
||||
transition={{ duration: 0.6, delay: 0.3, ease: 'easeOut' }}
|
||||
>
|
||||
<CardChoice authType="zk" setAuthType={setAuthType} />
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import ToggleColorMode from './toggleColorMode'
|
||||
import { useEthers } from '@usedapp/core'
|
||||
import { DropdownAccount, ToggleColorMode } from './'
|
||||
|
||||
const Navbar = () => {
|
||||
const { account } = useEthers()
|
||||
|
||||
return (
|
||||
<nav
|
||||
className="
|
||||
dark:bg-gray-900 bg-white
|
||||
border-gray-200
|
||||
px-2 sm:px-4 py-2.5 rounded"
|
||||
>
|
||||
<nav className="px-4 py-4">
|
||||
<div className="container flex flex-wrap justify-between items-center mx-auto max-w-6xl">
|
||||
<span className="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">
|
||||
zkAuth
|
||||
</span>
|
||||
<ToggleColorMode />
|
||||
<div className="flex flex-row space-x-4 align-middle">
|
||||
{account ? <DropdownAccount account={account} /> : <></>}
|
||||
|
||||
<ToggleColorMode />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
|
||||
@@ -15,8 +15,9 @@ const ToggleColorMode = () => {
|
||||
<div className="flex flex-0 color-snow hover:scale-110 transition ease-in-out duration-500">
|
||||
<button
|
||||
className="
|
||||
dark:bg-gray-600 bg-gray-300
|
||||
dark:border-gray-300 border-gray-600
|
||||
dark:bg-gray-800 bg-white
|
||||
dark:hover:bg-gray-700 hover:bg-gray-100
|
||||
dark:border-gray-600 border-gray-300
|
||||
rounded-full border p-2"
|
||||
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
|
||||
>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@usedapp/core": "^1.1.5",
|
||||
"@walletconnect/web3-provider": "^1.8.0",
|
||||
"ethers": "^5.7.0",
|
||||
"framer-motion": "^7.3.5",
|
||||
"fs": "^0.0.1-security",
|
||||
"jsotp": "^1.0.4",
|
||||
"next": "12.3.0",
|
||||
@@ -22,6 +23,7 @@
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-password-checklist": "^1.4.1",
|
||||
"react-pin-input-hook": "^1.0.8",
|
||||
"thirty-two": "^1.0.2",
|
||||
"web3modal": "^1.9.9"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../styles/globals.css'
|
||||
import type { AppProps } from 'next/app'
|
||||
import { Layout } from '../components'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
import { DAppProvider } from '@usedapp/core'
|
||||
|
||||
@@ -9,9 +10,11 @@ const config = {
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<ThemeProvider attribute="class">
|
||||
<ThemeProvider attribute="class" enableSystem={false} defaultTheme="dark">
|
||||
<DAppProvider config={config}>
|
||||
<Component {...pageProps} />
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</DAppProvider>
|
||||
</ThemeProvider>
|
||||
)
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import type { NextPage } from 'next'
|
||||
import Head from 'next/head'
|
||||
import LogInBox from '../components/logInBox'
|
||||
import Navbar from '../components/navbar'
|
||||
import { LogInBox } from '../components'
|
||||
|
||||
const Home: NextPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>zkAuth</title>
|
||||
<meta name="description" content="zero-knowledge Authentification" />
|
||||
<link rel="icon" href="" />
|
||||
</Head>
|
||||
|
||||
<Navbar />
|
||||
|
||||
<div className="w-screen h-screen flex flex-col justify-center items-center">
|
||||
<div className="h-[calc(100vh-100px)] flex justify-center items-center">
|
||||
<LogInBox />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
50
dapp/public/TOTP.svg
Normal file
50
dapp/public/TOTP.svg
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg"
|
||||
width="338.99203"
|
||||
height="386.78464"
|
||||
viewBox="0 0 338.99203 386.78464"
|
||||
version="1.1"
|
||||
sodipodi:docname="TOTP.svg"
|
||||
inkscape:version="1.2 (dc2aeda, 2022-05-15)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2291" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2289"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.45617322"
|
||||
inkscape:cx="-288.26769"
|
||||
inkscape:cy="116.18393"
|
||||
inkscape:window-width="1355"
|
||||
inkscape:window-height="702"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svgg" />
|
||||
<g
|
||||
id="svgg"
|
||||
transform="translate(-34.415837,-10.279271)">
|
||||
<path
|
||||
id="path0"
|
||||
d="M 171.942,93.076 C 65.669,103.963 3.204,216.677 50.355,312.47 c 42.874,87.102 156.101,111.948 232.051,50.92 47.068,-37.821 67.03067,-102.62298 51.02067,-161.19098 -26.88888,0.40353 -0.54179,0.32655 -27.39866,0.27033 2.808,8.211 5.66399,20.03265 6.89799,30.50465 l 0.296,2.518 h -13.03 c -14.578,0 -15.999,0.219 -18.657,2.877 -3.851,3.851 -3.714,10.25 0.299,13.999 2.341,2.186 3.277,2.308 17.708,2.308 h 13.629 l -0.273,2.278 c -7.47,62.362 -52.349,106.975 -114.697,114.018 l -2.518,0.284 -0.011,-11.887 c -0.006,-6.538 -0.202,-12.707 -0.435,-13.71 -1.963,-8.458 -13.841,-10.192 -17.889,-2.613 -1.042,1.949 -1.095,2.596 -1.238,14.868 l -0.148,12.829 h -1.713 c -2.45,0 -10.608,-1.329 -16.253,-2.648 -47.149,-11.018 -84.057,-48.259 -94.881,-95.738 -0.671,-2.942 -2.675,-15.712 -2.681,-17.081 -0.002,-0.465 2.862,-0.6 12.727,-0.6 11.762,0 12.871,-0.072 14.6,-0.955 5.759,-2.938 6.841,-10.933 2.09,-15.446 -2.741,-2.604 -3.849,-2.783 -17.174,-2.783 -10.373,0 -12.245,-0.107 -12.245,-0.695 0,-27.627 20.036,-66.319 45.138,-87.163 19.043,-15.815 50.794,-29.027 70.089,-29.166 0.198,-0.002 0.361,5.663 0.363,12.587 0.003,13.725 0.225,15.321 2.493,17.903 3.811,4.341 9.978,4.54 14.064,0.455 2.821,-2.822 3.104,-4.559 3.104,-19.047 v -12.033 l 3.238,0.318 c 9.195,0.903 19.873,3.332 29.732,6.761 3.423,1.191 6.363,2.166 6.532,2.166 0.168,0 0.307,-6.123 0.307,-13.607 v -13.607 l -5.156,-1.503 c -17.992,-5.247 -41.202,-7.546 -58.394,-5.785 m -35.425,90.339 c -3.546,1.305 -6.061,4.746 -6.061,8.29 0,2.423 -0.145,2.209 17.631,26.196 l 14.865,20.058 -0.465,2.004 c -0.623,2.678 -0.178,10.135 0.756,12.685 l 0.75,2.048 -20.605,30.964 c -20.236,30.408 -20.606,31.007 -20.606,33.347 0,5.157 4.168,8.901 9.423,8.465 3.293,-0.273 0.834,2.284 28.837,-29.99 l 24.241,-27.938 2.202,-0.007 c 15.394,-0.049 27.246,-15.724 23.312,-30.83 -2.95,-11.324 -12.376,-18.541 -24.239,-18.557 l -4.089,-0.006 -18.693,-17.291 c -23.822,-22.036 -22.59,-21.158 -27.259,-19.438 m 57.387,46.568 c 15.586,7.671 10.24,31.141 -7.093,31.141 -16.525,0 -22.626,-21.781 -8.54,-30.486 4.463,-2.759 10.819,-3.025 15.633,-0.655 m -10.606,5.095 c -4.01,1.155 -7.3,6.682 -6.596,11.083 1.979,12.379 20.067,11.242 20.126,-1.266 0.035,-7.296 -6.307,-11.898 -13.53,-9.817"
|
||||
stroke="none"
|
||||
fill="#000000"
|
||||
fill-rule="evenodd"
|
||||
sodipodi:nodetypes="cccccccsscscccccccccsccssccsscccccscscssccccsccccccscccsccccccscccccc" />
|
||||
<path
|
||||
id="path2418"
|
||||
style="fill:#000000;fill-rule:evenodd;stroke:none;stroke-width:0.960696"
|
||||
d="m 310.51758,10.279297 c -3.51692,0.0038 -7.07688,0.433269 -10.63867,1.332031 -25.07513,6.326389 -43.45118,30.697975 -43.45118,60.390625 v 4.708985 h 9.60743 9.60742 l 0.2539,-5.427735 c 1.47975,-31.625626 31.26686,-49.981063 53.32813,-32.863281 10.07247,7.816127 15.63666,19.002742 16.21875,32.609375 l 0.24219,5.681641 h 9.60937 9.60742 v -3.257813 c 0,-34.902772 -25.74704,-63.205044 -54.38476,-63.173828 z m 0.13672,76.876953 c -30.60523,0.04317 -61.19445,0.446787 -61.79492,1.167969 -0.96956,1.164418 -1.36177,97.868021 -0.40626,100.013671 0.68465,1.53712 123.73919,1.53712 124.42383,0 0.89592,-2.00983 0.57246,-99.203114 -0.33398,-100.187499 -0.66142,-0.719077 -31.28345,-1.037307 -61.88867,-0.994141 z m 0.4414,20.23047 c 8.41415,7.7e-4 15.2347,6.82218 15.23438,15.23633 -0.0747,5.5366 -3.14733,10.59685 -8.02539,13.21679 l 3.98047,14.65039 c 2.5203,9.27534 4.68988,17.38454 4.82226,18.02149 l 0.24219,1.15625 h -16.44922 c -12.98816,0 -16.40954,-0.13289 -16.25,-0.63086 0.11046,-0.34743 2.30835,-8.44058 4.88477,-17.98438 l 4.13672,-15.32226 c -4.76209,-2.6563 -7.74153,-7.65505 -7.8125,-13.10742 -3.2e-4,-8.41491 6.82141,-15.23665 15.23632,-15.23633 z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
45
dapp/public/ZK.svg
Normal file
45
dapp/public/ZK.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.2 KiB |
@@ -1,3 +1,18 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-gradient-to-b from-white to-gray-200 dark:from-gray-900 dark:via-black dark:to-black h-screen;
|
||||
}
|
||||
/* ... */
|
||||
}
|
||||
|
||||
.filter-dark {
|
||||
filter: invert(80%);
|
||||
}
|
||||
|
||||
.filter-light {
|
||||
filter: invert(20%);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user