Skip to content

Commit

Permalink
Merge pull request #305 from EigenExplorer/dev
Browse files Browse the repository at this point in the history
Dev to Main v0.3.4
  • Loading branch information
uditdc authored Dec 17, 2024
2 parents 3c81974 + 8881cd6 commit a08e251
Show file tree
Hide file tree
Showing 88 changed files with 10,933 additions and 1,878 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion packages/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ NETWORK_CHAIN_WSS_URL = ""
DATABASE_URL = ""
DIRECT_URL = ""
CMC_API_KEY = ""
JWT_SECRET = ""
EE_AUTH_TOKEN = ""
SUPABASE_SERVICE_ROLE_KEY = ""
SUPABASE_FETCH_ALL_USERS_URL = "https://<SUPABASE_PROJECT_REF>.supabase.co/functions/v1/fetch-all-users"
SUPABASE_FETCH_ACCESS_LEVEL_URL = "https://<SUPABASE_PROJECT_REF>.supabase.co/functions/v1/fetch-access-level"
SUPABASE_POST_REQUESTS_URL = "https://<SUPABASE_PROJECT_REF>.supabase.co/functions/v1/post-requests"
3 changes: 3 additions & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"joi": "^17.12.2",
"jsonwebtoken": "^9.0.2",
"morgan": "~1.9.1",
"node-cache": "^5.1.2",
"node-cron": "^3.0.3",
"route-cache": "^0.7.0",
"viem": "^2.8.14",
"zod": "^3.23.4",
Expand All @@ -35,6 +37,7 @@
"@types/debug": "^4.1.12",
"@types/express": "^4.17.21",
"@types/morgan": "^1.9.9",
"@types/node-cron": "^3.0.11",
"@types/node": "^20.12.2",
"@types/route-cache": "^0.5.5",
"prisma": "^5.11.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import helmet from 'helmet'
import cors from 'cors'
import apiRouter from './routes'
import { EigenExplorerApiError, handleAndReturnErrorResponse } from './schema/errors'
import { startUserRequestsSync } from './utils/userRequestsSync'

const PORT = process.env.PORT ? Number.parseInt(process.env.PORT) : 3002

Expand Down Expand Up @@ -50,4 +51,6 @@ app.use((err: Error, req: Request, res: Response) => {
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`)

startUserRequestsSync()
})
186 changes: 186 additions & 0 deletions packages/api/src/routes/auth/authController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import type { Request, Response } from 'express'
import { handleAndReturnErrorResponse } from '../../schema/errors'
import { EthereumAddressSchema } from '../../schema/zod/schemas/base/ethereumAddress'
import { refreshAuthStore } from '../../utils/authMiddleware'
import { RegisterUserBodySchema, RequestHeadersSchema } from '../../schema/zod/schemas/auth'
import { verifyMessage } from 'viem'
import prisma from '../../utils/prismaClient'
import crypto from 'node:crypto'

/**
* Function for route /auth/users/:address/check-status
* Protected route, returns whether a given address is registered on EE, if they are an EL staker & if we track their rewards
*
* @param req
* @param res
* @returns
*/
export async function checkUserStatus(req: Request, res: Response) {
const headerCheck = RequestHeadersSchema.safeParse(req.headers)
if (!headerCheck.success) {
return handleAndReturnErrorResponse(req, res, headerCheck.error)
}

const paramCheck = EthereumAddressSchema.safeParse(req.params.address)
if (!paramCheck.success) {
return handleAndReturnErrorResponse(req, res, paramCheck.error)
}

try {
const apiToken = headerCheck.data['X-API-Token']
const authToken = process.env.EE_AUTH_TOKEN

if (!apiToken || apiToken !== authToken) {
throw new Error('Unauthorized access.')
}

const { address } = req.params

const [user, staker] = await Promise.all([
prisma.user.findUnique({
where: { address: address.toLowerCase() }
}),
prisma.staker.findUnique({
where: { address: address.toLowerCase() }
})
])

const isRegistered = !!user
const isStaker = !!staker
const isTracked = !!user?.isTracked

res.send({ isRegistered, isStaker, isTracked })
} catch (error) {
handleAndReturnErrorResponse(req, res, error)
}
}

/**
* Function for route /auth/users/:address/nonce
* Protected route, generates a nonce to be used by frontend for registering a new user via wallet
*
* @param req
* @param res
* @returns
*/
export async function generateNonce(req: Request, res: Response) {
const headerCheck = RequestHeadersSchema.safeParse(req.headers)
if (!headerCheck.success) {
return handleAndReturnErrorResponse(req, res, headerCheck.error)
}

try {
const apiToken = headerCheck.data['X-API-Token']
const authToken = process.env.EE_AUTH_TOKEN

if (!apiToken || apiToken !== authToken) {
throw new Error('Unauthorized access.')
}

const nonce = `0x${crypto.randomBytes(32).toString('hex')}`

res.send({ nonce })
} catch (error) {
handleAndReturnErrorResponse(req, res, error)
}
}

/**
* Function for route /auth/users/:address/register
* Protected route, adds an address to the User table if it doesn't exist
*
* @param req
* @param res
* @returns
*/
export async function registerUser(req: Request, res: Response) {
const headerCheck = RequestHeadersSchema.safeParse(req.headers)
if (!headerCheck.success) {
return handleAndReturnErrorResponse(req, res, headerCheck.error)
}

const paramCheck = EthereumAddressSchema.safeParse(req.params.address)
if (!paramCheck.success) {
return handleAndReturnErrorResponse(req, res, paramCheck.error)
}

const bodyCheck = RegisterUserBodySchema.safeParse(req.body)
if (!bodyCheck.success) {
return handleAndReturnErrorResponse(req, res, bodyCheck.error)
}

try {
const apiToken = headerCheck.data['X-API-Token']
const authToken = process.env.EE_AUTH_TOKEN

if (!apiToken || apiToken !== authToken) {
throw new Error('Unauthorized access.')
}

const { address } = req.params
const { signature, nonce } = bodyCheck.data

const message = `Welcome to EigenExplorer!\n\nPlease sign this message to verify your wallet ownership.\n\nNonce: ${nonce}`

const isValid = await verifyMessage({
address: address as `0x${string}`,
message,
signature: signature as `0x${string}`
})

if (!isValid) {
throw new Error('Invalid signature')
}

const existingUser = await prisma.user.findUnique({
where: { address: address.toLowerCase() }
})

if (!existingUser) {
await prisma.user.create({
data: {
address: address.toLowerCase(),
isTracked: false
}
})
}

res.send({ isNewUser: !existingUser })
} catch (error) {
handleAndReturnErrorResponse(req, res, error)
}
}

/**
* Protected route, refreshes the server's entire auth store. Called by Supabase edge fn signal-refresh.
* This function will fail if the caller does not use admin-level auth token
*
* @param req
* @param res
* @returns
*/
export async function signalRefreshAuthStore(req: Request, res: Response) {
const headerCheck = RequestHeadersSchema.safeParse(req.headers)
if (!headerCheck.success) {
return handleAndReturnErrorResponse(req, res, headerCheck.error)
}

try {
const apiToken = headerCheck.data['X-API-Token']
const authToken = process.env.EE_AUTH_TOKEN

if (!apiToken || apiToken !== authToken) {
throw new Error('Unauthorized access.')
}

const status = await refreshAuthStore()

if (!status) {
throw new Error('Refresh auth store failed.')
}

res.status(200).json({ message: 'Auth store refreshed.' })
} catch (error) {
handleAndReturnErrorResponse(req, res, error)
}
}
15 changes: 15 additions & 0 deletions packages/api/src/routes/auth/authRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import express from 'express'
import routeCache from 'route-cache'
import { signalRefreshAuthStore } from './authController'
import { checkUserStatus, generateNonce, registerUser } from './authController'

const router = express.Router()

// API routes for /auth

router.get('/refresh-store', routeCache.cacheSeconds(5), signalRefreshAuthStore)
router.get('/users/:address/check-status', routeCache.cacheSeconds(30), checkUserStatus)
router.get('/users/:address/nonce', routeCache.cacheSeconds(10), generateNonce)
router.post('/users/:address/register', routeCache.cacheSeconds(10), registerUser)

export default router
Loading

0 comments on commit a08e251

Please sign in to comment.