From 2d5f4612fcc298b908c37e1d904bb115497ff1e2 Mon Sep 17 00:00:00 2001 From: Daniel Kesler Date: Wed, 22 Mar 2023 13:00:01 -0600 Subject: [PATCH 1/2] Adding GitHub authentication --- apps/firebase/database-rules.bolt | 8 +++ apps/firebase/package.json | 1 - apps/firebase/scripts/cli.ts | 12 +++- apps/firebase/src/config.ts | 6 +- apps/firebase/src/database-helper.ts | 26 ++++++-- apps/web/package.json | 1 + apps/web/pages/_app.tsx | 7 ++- apps/web/pages/api/auth/[...nextauth].ts | 44 +++++++++++++ apps/web/pages/api/faucet.ts | 27 ++++++-- apps/web/pages/index.tsx | 11 +++- apps/web/public/github_white.png | Bin 0 -> 13669 bytes apps/web/src/faucet-interfaces.ts | 13 +++- apps/web/src/firebase-serverside.ts | 14 +++-- apps/web/src/github-auth.tsx | 26 ++++++++ apps/web/src/request-form.tsx | 12 ++-- apps/web/src/setup-button.tsx | 3 +- apps/web/styles/Github.module.css | 19 ++++++ apps/web/styles/Home.module.css | 10 ++- yarn.lock | 76 ++++++++++++++++++++++- 19 files changed, 280 insertions(+), 36 deletions(-) create mode 100644 apps/web/pages/api/auth/[...nextauth].ts create mode 100644 apps/web/public/github_white.png create mode 100644 apps/web/src/github-auth.tsx create mode 100644 apps/web/styles/Github.module.css diff --git a/apps/firebase/database-rules.bolt b/apps/firebase/database-rules.bolt index d54803d35..461bc5cd1 100644 --- a/apps/firebase/database-rules.bolt +++ b/apps/firebase/database-rules.bolt @@ -10,6 +10,7 @@ type Request { status: RequestStatus, // Request Status enum type: RequestType, // Request Type enum tokens: RequestedTokenSet, + authLevel: AuthLevel | Null, } type Account { @@ -46,6 +47,13 @@ type RequestType extends String { } } +type AuthLevel extends String { + validate() { + this === 'none' || + this === 'authenticated' + } +} + /** * Node Paths */ diff --git a/apps/firebase/package.json b/apps/firebase/package.json index d09d1fb25..5c16a1655 100644 --- a/apps/firebase/package.json +++ b/apps/firebase/package.json @@ -16,7 +16,6 @@ "transfer-funds": "ts-node scripts/transfer-funds.ts", "cli": "ts-node scripts/cli.ts", "build:rules": "firebase-bolt database-rules.bolt", - "keys:decrypt": "bash scripts/key_placer.sh decrypt", "keys:encrypt": "bash scripts/key_placer.sh encrypt" }, "dependencies": { diff --git a/apps/firebase/scripts/cli.ts b/apps/firebase/scripts/cli.ts index 5c85afaa4..a6b0d481f 100644 --- a/apps/firebase/scripts/cli.ts +++ b/apps/firebase/scripts/cli.ts @@ -104,6 +104,12 @@ yargs .option('faucetStableAmount', { type: 'string', }) + .option('authenticatedGoldAmount', { + type: 'string', + }) + .option('authenticatedStableAmount', { + type: 'string', + }) .option('bigFaucetSafeAmount', { type: 'string', description: "Amount of CELO to be sent to *bigFaucetSafeAddress* each time the script runs" @@ -119,12 +125,14 @@ yargs .option('deploy', { type: 'boolean', - description: 'Wether to deploy functions after set config', + description: 'Whether to deploy functions after set config', }), (args) => { setConfig(args.net, { faucetGoldAmount: args.faucetGoldAmount, faucetStableAmount: args.faucetStableAmount, + authenticatedGoldAmount: args.authenticatedGoldAmount, + authenticatedStableAmount: args.authenticatedStableAmount, bigFaucetSafeAmount: args.bigFaucetSafeAmount, bigFaucetSafeStablesAmount: args.bigFaucetSafeStablesAmount, bigFaucetSafeAddress: args.bigFaucetSafeAddress, @@ -145,6 +153,8 @@ function setConfig(network: string, config: Partial) { setIfPresent('node_url', config.nodeUrl), setIfPresent('faucet_gold_amount', config.faucetGoldAmount), setIfPresent('faucet_stable_amount', config.faucetStableAmount), + setIfPresent('authenticated_gold_amount', config.authenticatedGoldAmount), + setIfPresent('authenticated_stable_amount', config.authenticatedStableAmount), setIfPresent('big_faucet_safe_address', config.bigFaucetSafeAddress), setIfPresent('big_faucet_safe_amount', config.bigFaucetSafeAmount), setIfPresent('big_faucet_safe_stables_amount', config.bigFaucetSafeStablesAmount), diff --git a/apps/firebase/src/config.ts b/apps/firebase/src/config.ts index 061900eac..fff71a31d 100644 --- a/apps/firebase/src/config.ts +++ b/apps/firebase/src/config.ts @@ -4,13 +4,13 @@ export interface NetworkConfig { nodeUrl: string faucetGoldAmount: string faucetStableAmount: string + authenticatedGoldAmount: string + authenticatedStableAmount: string bigFaucetSafeAddress: string bigFaucetSafeAmount: string bigFaucetSafeStablesAmount: string } - - export function getNetworkConfig(net: string): NetworkConfig { const allconfig = functions.config() const config = allconfig.faucet @@ -23,6 +23,8 @@ export function getNetworkConfig(net: string): NetworkConfig { nodeUrl: config[net].node_url, faucetGoldAmount: config[net].faucet_gold_amount, faucetStableAmount: config[net].faucet_stable_amount, + authenticatedGoldAmount: config[net].authenticated_gold_amount, + authenticatedStableAmount: config[net].authenticated_stable_amount, bigFaucetSafeAddress: config[net].big_faucet_safe_address, bigFaucetSafeAmount: config[net].big_faucet_safe_amount, bigFaucetSafeStablesAmount: config[net].big_faucet_safe_stables_amount, diff --git a/apps/firebase/src/database-helper.ts b/apps/firebase/src/database-helper.ts index 4d7bd0b90..7efdca3b4 100644 --- a/apps/firebase/src/database-helper.ts +++ b/apps/firebase/src/database-helper.ts @@ -16,6 +16,11 @@ export interface AccountRecord { locked: boolean } +export enum AuthLevel { + none = "none", + authenticated = "authenticated" +} + export enum RequestStatus { Pending = 'Pending', Working = 'Working', @@ -34,6 +39,7 @@ export interface RequestRecord { dollarTxHash?: string goldTxHash?: string tokens?: RequestedTokenSet + authLevel: AuthLevel } enum RequestedTokenSet { @@ -136,23 +142,35 @@ async function sendCelo(celo: CeloAdapter, to: string, amountInWei: string) { function buildHandleFaucet(request: RequestRecord, snap: DataSnapshot, config: NetworkConfig) { return async (account: AccountRecord) => { - const { nodeUrl, faucetGoldAmount, faucetStableAmount } = config + const { nodeUrl } = config + const { goldAmount, stableAmount } = getSendAmounts(request.authLevel, config) const celo = new CeloAdapter({ nodeUrl, pk: account.pk }) await celo.init() const ops: Array> = [] if (request.tokens === 'Celo' || request.tokens === 'All' || request.tokens === undefined) { - ops.push(retryAsync(sendGold, 3, [celo, request.beneficiary, faucetGoldAmount, snap], 500)) + ops.push(retryAsync(sendGold, 3, [celo, request.beneficiary, goldAmount, snap], 500)) } if (request.tokens === 'Stables' || request.tokens === 'All' || request.tokens === undefined) { - ops.push(sendStableTokens(celo, request.beneficiary, faucetStableAmount, false, snap)) + ops.push(sendStableTokens(celo, request.beneficiary, stableAmount, false, snap)) } await Promise.all(ops) } } +function getSendAmounts(authLevel: AuthLevel, config: NetworkConfig): { goldAmount: string, stableAmount: string } { + switch(authLevel) { + case undefined: + case AuthLevel.none: + return { goldAmount: config.faucetGoldAmount, stableAmount: config.faucetStableAmount } + case AuthLevel.authenticated: + return { goldAmount: config.authenticatedGoldAmount, stableAmount: config.authenticatedStableAmount } + } + +} + async function sendGold(celo: CeloAdapter, address: Address, amount: string, snap: DataSnapshot) { const token = await celo.kit.contracts.getGoldToken() @@ -254,7 +272,7 @@ enum ActionResult { export class AccountPool { constructor( private db: database.Database, - private network: string, + public network: string, private options: PoolOptions = { getAccountTimeoutMS: 10 * SECOND, retryWaitMS: 3000, diff --git a/apps/web/package.json b/apps/web/package.json index 54a71cd7e..4da8dfdde 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,6 +20,7 @@ "eslint-config-next": "13.1.1", "firebase": "~9.16.0", "next": "13.1.1", + "next-auth": "^4.20.1", "react": "18.2.0", "react-dom": "18.2.0", "react-google-recaptcha-v3": "^1.10.1", diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx index c2e374d97..43456c8a7 100644 --- a/apps/web/pages/_app.tsx +++ b/apps/web/pages/_app.tsx @@ -2,13 +2,16 @@ import type { AppProps } from 'next/app' import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3' import 'styles/globals.css' import { Analytics } from '@vercel/analytics/react'; +import { SessionProvider } from 'next-auth/react' -export default function App({ Component, pageProps }: AppProps) { +export default function App({ Component, pageProps: { session, ...pageProps } }: AppProps) { return ( <> - + + + diff --git a/apps/web/pages/api/auth/[...nextauth].ts b/apps/web/pages/api/auth/[...nextauth].ts new file mode 100644 index 000000000..4915c0b1a --- /dev/null +++ b/apps/web/pages/api/auth/[...nextauth].ts @@ -0,0 +1,44 @@ +import NextAuth, { AuthOptions, Profile, Session } from 'next-auth' +import GithubProvider from 'next-auth/providers/github' + +type ExtendedProfile = { + created_at?: string; +} & Profile + +interface ExtendedUser { + created_at?: string; + name?: string | null; + email?: string | null; + image?: string | null; +} + +type ExtendedSession = { + user?: ExtendedUser; +} & Session + +export const authOptions: AuthOptions = { + providers: [ + GithubProvider({ + clientId: process.env.GITHUB_ID || '', + clientSecret: process.env.GITHUB_SECRET || '', + }) + ], + callbacks: { + async jwt({ token, profile }) { + if (profile) { + const extProfile: ExtendedProfile = profile + token.user_created_at = extProfile.created_at + } + return token; + }, + async session({ session, token, user }) { + const extSession: ExtendedSession = session; + if (extSession.user) { + extSession.user.created_at = token.user_created_at as string + } + return session; + } + } +} + +export default NextAuth(authOptions) diff --git a/apps/web/pages/api/faucet.ts b/apps/web/pages/api/faucet.ts index faf1b2865..3db7fdf3e 100644 --- a/apps/web/pages/api/faucet.ts +++ b/apps/web/pages/api/faucet.ts @@ -1,18 +1,36 @@ import type { NextApiRequest, NextApiResponse } from 'next' +import { getServerSession } from 'next-auth/next' +import { authOptions } from "./auth/[...nextauth]" import captchaVerify from 'src/captcha-verify' -import { FaucetAPIResponse, RequestStatus } from 'src/faucet-interfaces' +import { FaucetAPIResponse, RequestStatus, AuthLevel, Network, networks } from 'src/faucet-interfaces' import { sendRequest } from 'src/firebase-serverside' - export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const { captchaToken, beneficiary, skipStables } = req.body + + let authLevel = AuthLevel.none; + try { + const session = await getServerSession(req, res, authOptions) + if (session) { + authLevel = AuthLevel.authenticated + } + } catch(e) { + console.error("Authentication check failed", e) + } + + const { captchaToken, beneficiary, skipStables, network } = req.body + + if (!networks.includes(network)) { + res.status(400).json({ status: RequestStatus.Failed, message: `Invalid network: ${network}` }) + return; + } + const captchaResponse = await captchaVerify(captchaToken) if (captchaResponse.success) { try { - const key = await sendRequest(beneficiary, skipStables) + const key = await sendRequest(beneficiary, skipStables, network, authLevel) res.status(200).json({ status: RequestStatus.Pending, key }) } catch (error) { console.error(error) @@ -22,5 +40,4 @@ export default async function handler( console.error("Faucet Failed due to Recaptcha", captchaResponse["error-codes"]) res.status(401).json({ status: RequestStatus.Failed, message: captchaResponse["error-codes"]?.toString() || 'unknown' }) } - } diff --git a/apps/web/pages/index.tsx b/apps/web/pages/index.tsx index 28bdc4139..a41bd4254 100644 --- a/apps/web/pages/index.tsx +++ b/apps/web/pages/index.tsx @@ -3,8 +3,10 @@ import Head from 'next/head' import { isBalanceBelowPar } from 'src/balance' import Logo from 'src/logo' import RequestForm from 'src/request-form' +import { GitHubAuth } from 'src/github-auth' import { SetupButton } from 'src/setup-button' import styles from 'styles/Home.module.css' + export const inter = Inter({ subsets: ['latin'] }) interface Props { @@ -26,8 +28,11 @@ export default function Home({isOutOfCELO}: Props) { {isOutOfCELO &&
The Faucet is out of CELO for now. It will be topped up within an hour
} -
- +
+
+ +
+
@@ -100,4 +105,4 @@ export async function getServerSideProps(): Promise<{props: Props}> { return { props: {isOutOfCELO} } -} \ No newline at end of file +} diff --git a/apps/web/public/github_white.png b/apps/web/public/github_white.png new file mode 100644 index 0000000000000000000000000000000000000000..6c3b3cd14aece03ce138e53c6e8ecc4954b17e8a GIT binary patch literal 13669 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 { export async function sendRequest( beneficiary: Address, - skipStables: boolean + skipStables: boolean, + network: Network, + authLevel: AuthLevel ) { const newRequest: RequestRecord = { beneficiary, status: RequestStatus.Pending, type: RequestType.Faucet, - tokens: skipStables ? RequestedTokenSet.Celo : RequestedTokenSet.Stables + tokens: skipStables ? RequestedTokenSet.Celo : RequestedTokenSet.Stables, + authLevel, } try { const db = await getDB() - const ref: firebase.database.Reference = await db.ref(`${NETWORK}/requests`).push(newRequest) + const ref: firebase.database.Reference = await db.ref(`${network}/requests`).push(newRequest) return ref.key } catch (e) { console.error(`Error while sendRequest: ${e}`) diff --git a/apps/web/src/github-auth.tsx b/apps/web/src/github-auth.tsx new file mode 100644 index 000000000..a05107a07 --- /dev/null +++ b/apps/web/src/github-auth.tsx @@ -0,0 +1,26 @@ +import { Inter } from '@next/font/google' +import { FC } from 'react' +import { useSession, signIn, signOut } from 'next-auth/react' +import Image from 'next/image' +import styles from 'styles/Github.module.css' + +export const inter = Inter({ subsets: ['latin'] }) + +export const GitHubAuth: FC = () => { + const { data: session } = useSession() + + return ( +
+ {session?.user + ?
+ Authenticated with GitHub + +
+ : + } +
+ ) +} diff --git a/apps/web/src/request-form.tsx b/apps/web/src/request-form.tsx index 44e60f467..60ed0b2e2 100644 --- a/apps/web/src/request-form.tsx +++ b/apps/web/src/request-form.tsx @@ -7,6 +7,7 @@ import { FaucetAPIResponse } from 'src/faucet-interfaces' import { saveAddress } from 'src/history' import { useLastAddress } from 'src/useLastAddress' import styles from 'styles/Form.module.css' + const FaucetStatus = dynamic(() => import('src/faucet-status'), {}) export const inter = Inter({ subsets: ['latin'] }) @@ -14,10 +15,7 @@ interface Props { isOutOfCELO: boolean } -const STANDARD_AMOUNT = 2.5 - export default function RequestForm({isOutOfCELO}:Props) { - const inputRef = useRef(null) const { executeRecaptcha } = useGoogleReCaptcha(); @@ -46,12 +44,13 @@ export default function RequestForm({isOutOfCELO}:Props) { const captchaToken = await executeRecaptcha('faucet'); console.info("received captcha token...posting faucet request") + const network = 'alfajores' const response = await fetch("api/faucet", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({beneficiary, captchaToken, skipStables}), + body: JSON.stringify({beneficiary, captchaToken, skipStables, network }), }) // TODO get key from result and set const result = await response.json() as FaucetAPIResponse @@ -80,12 +79,13 @@ export default function RequestForm({isOutOfCELO}:Props) { }, []) const previousAddress = useLastAddress() + const buttonDisabled = !executeRecaptcha || !!faucetRequestKey || disableCELOWhenOut return <>

- Enter your testnet address below. Each request gives you: {STANDARD_AMOUNT} CELO{!skipStables && `, ${STANDARD_AMOUNT} cUSD, ${STANDARD_AMOUNT} cEUR, & ${STANDARD_AMOUNT} cREAL` }*. + Enter your testnet address below. Authenticate with GitHub to receive more tokens.

@@ -95,7 +95,7 @@ export default function RequestForm({isOutOfCELO}:Props) { - +
: } ) } + +const GitHubIcon: FC = () => ( + + + + ) diff --git a/apps/web/styles/Github.module.css b/apps/web/styles/Github.module.css index df46eb6a1..4a37abae9 100644 --- a/apps/web/styles/Github.module.css +++ b/apps/web/styles/Github.module.css @@ -14,6 +14,17 @@ justify-content: space-between; } -.signInButton img { +.signInButton svg { margin-left: 4px; } + +.githubIcon { + fill: #000; +} + +@media (prefers-color-scheme: dark) { + .githubIcon { + fill: #FFF; + } + +}