-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #305 from EigenExplorer/dev
Dev to Main v0.3.4
- Loading branch information
Showing
88 changed files
with
10,933 additions
and
1,878 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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
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 |
---|---|---|
@@ -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) | ||
} | ||
} |
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,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 |
Oops, something went wrong.