Skip to content

Commit

Permalink
Merge pull request #2 from DappyKit/feat/improvements-1
Browse files Browse the repository at this point in the history
feat: callback with signature
  • Loading branch information
IgorShadurin authored May 28, 2024
2 parents f960016 + f6640f3 commit 08435ea
Show file tree
Hide file tree
Showing 18 changed files with 365 additions and 29 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Test

on:
push:
branches:
- master
pull_request:
branches:
- '**'

jobs:
test:
name: Run tests
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 18

- name: Install dependencies
run: npm ci

- name: Lint check
run: npm run lint:check

- name: Check types
run: npm run check:types
6 changes: 5 additions & 1 deletion frame/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ export async function answerAuthRequest(
return req.json()
}

/**
* Get the auth data.
* @param messageBytesProof Message bytes proof
*/
export async function getAuthData(messageBytesProof: string): Promise<IListResponse> {
const url = getUrl(`v1/authorization/list`)
const url = getUrl(`v1/authorization/list?rand=${Math.random()}`)
const req = await fetch(url, {
method: 'POST',
headers: {
Expand Down
39 changes: 39 additions & 0 deletions src/controllers/v1/app/exists-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Request, Response, NextFunction } from 'express'
import { getAppBySignerAddress } from '../../../db/app'
import { isAnyEthAddress, prepareEthAddress } from '../../../utils/eth'
import { IExistsResponse } from './interface/IExistsResponse'

export function getAppExistsParams(req: Request): { applicationAddress: string } {
const { applicationAddress } = req.query

if (!applicationAddress) {
throw new Error('"applicationAddress" is required')
}

if (!isAnyEthAddress(applicationAddress)) {
throw new Error('Invalid "applicationAddress"')
}

return { applicationAddress: prepareEthAddress(applicationAddress as string) }
}

/**
* Checks if the app with the address already exists.
* @param req Request
* @param res Response
* @param next Next function
*/
export default async (req: Request, res: Response<IExistsResponse>, next: NextFunction): Promise<void> => {
try {
const { applicationAddress } = getAppExistsParams(req)

const isExists = Boolean(await getAppBySignerAddress(applicationAddress))

res.json({
status: 'ok',
isExists,
})
} catch (e) {
next(e)
}
}
2 changes: 2 additions & 0 deletions src/controllers/v1/app/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import express from 'express'
import createAction from './create-action'
import existsAction from './exists-action'
import testFrameAction from './test-frame-action'
import testWebhookAction from './test-webhook-action'
import testWebhookFailAction from './test-webhook-fail-action'

const router = express.Router()
router.post('/create', createAction)
router.get('/exists', existsAction)
router.get('/test-frame', testFrameAction)
router.all('/test-webhook', testWebhookAction)
router.all('/test-webhook-fail', testWebhookFailAction)
Expand Down
4 changes: 4 additions & 0 deletions src/controllers/v1/app/interface/IExistsResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IExistsResponse {
status: string
isExists: boolean
}
15 changes: 12 additions & 3 deletions src/controllers/v1/authorization/answer-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getRequestAnswerData } from './utils/request-create-utils'
import { callbackFrameUrl, ICallbackFailRequest, ICallbackSuccessRequest } from '../../../utils/http'
import { DelegatedFs } from '../../../service/delegated-fs/delegated-fs'
import { Wallet } from 'ethers'
import { INVALID_CHALLENGE_ANSWER } from './utils/error-message'

/**
* Handles the answer of the user's answer to the challenge. Called from trusted Frame's service.
Expand All @@ -36,17 +37,25 @@ export default async (
await updateAuthorizationRequestStatus(requestId, AuthorizationRequestStatus.ACCEPTED)
} else {
await updateAuthorizationRequestStatus(requestId, AuthorizationRequestStatus.INCORRECT)
const errorMessage = 'Invalid answer for the challenge'
const proof = await DelegatedFs.createDelegateSignature(
authRequest.user_main_address,
authRequest.user_delegated_address,
authRequest.app_signer_address,
new Wallet(signer),
INVALID_CHALLENGE_ANSWER,
)
const data: ICallbackFailRequest = {
success: false,
requestId,
userMainAddress: authRequest.user_main_address,
userDelegatedAddress: authRequest.user_delegated_address,
applicationAddress: authRequest.app_signer_address,
errorMessage,
errorMessage: INVALID_CHALLENGE_ANSWER,
proof,
}

await callbackFrameUrl(app.callback_url, data)
throw new Error(errorMessage)
throw new Error(INVALID_CHALLENGE_ANSWER)
}

const proof = await DelegatedFs.createDelegateSignature(
Expand Down
31 changes: 30 additions & 1 deletion src/controllers/v1/authorization/reject-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ import {
import { getRequestListData } from './utils/request-create-utils'
import { IGeneralResponse } from './interface/IGeneralResponse'
import { IRejectRequest } from './interface/IRejectRequest'
import { DelegatedFs } from '../../../service/delegated-fs/delegated-fs'
import { Wallet } from 'ethers'
import { USER_REJECTED_REQUEST } from './utils/error-message'
import { callbackFrameUrl, ICallbackFailRequest } from '../../../utils/http'
import { getAppBySignerAddress } from '../../../db/app'

export default async (
req: Request<IRejectRequest>,
res: Response<IGeneralResponse>,
next: NextFunction,
): Promise<void> => {
try {
const { neynarApiKey, authorizedFrameUrl } = getConfigData()
const { neynarApiKey, authorizedFrameUrl, signer } = getConfigData()
const { fid } = await getRequestListData(neynarApiKey, authorizedFrameUrl, req.body)
const authRequest = await getActiveAuthorizationRequestByUser(fid)

Expand All @@ -28,6 +33,30 @@ export default async (
}

await updateAuthorizationRequestStatus(authRequest.id, AuthorizationRequestStatus.REJECTED)
const proof = await DelegatedFs.createDelegateSignature(
authRequest.user_main_address,
authRequest.user_delegated_address,
authRequest.app_signer_address,
new Wallet(signer),
USER_REJECTED_REQUEST,
)
const data: ICallbackFailRequest = {
success: false,
requestId: authRequest.id,
userMainAddress: authRequest.user_main_address,
userDelegatedAddress: authRequest.user_delegated_address,
applicationAddress: authRequest.app_signer_address,
errorMessage: USER_REJECTED_REQUEST,
proof,
}

const app = await getAppBySignerAddress(authRequest.app_signer_address)

if (!app) {
throw new Error('App not found')
}

await callbackFrameUrl(app.callback_url, data)

res.json({
status: 'ok',
Expand Down
8 changes: 8 additions & 0 deletions src/controllers/v1/authorization/utils/error-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Error message for invalid challenge answer.
*/
export const INVALID_CHALLENGE_ANSWER = 'Invalid answer for the challenge'
/**
* Error message for user rejection.
*/
export const USER_REJECTED_REQUEST = 'User rejected the request'
6 changes: 3 additions & 3 deletions src/controllers/v1/authorization/utils/get-proof.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { IGetProofRequest } from '../interface/IGetProofRequest'
import { Request } from 'express'
import { is0xEthAddress, prepareEthAddress } from '../../../../utils/eth'
import { isAnyEthAddress, prepareEthAddress } from '../../../../utils/eth'

export function getGetProofParams(req: Request): IGetProofRequest {
const { userAddress, applicationAddress } = req.query

if (!is0xEthAddress(userAddress)) {
if (!isAnyEthAddress(userAddress)) {
throw new Error('Invalid "userAddress"')
}

if (!is0xEthAddress(applicationAddress)) {
if (!isAnyEthAddress(applicationAddress)) {
throw new Error('Invalid "applicationAddress"')
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isWithinMaxMinutes } from '../../../../utils/time'
import { MAX_REQUESTS_TIME_MINUTES } from '../../app/utils/app-create-utils'
import { getAppBySignerAddress, getAppByUrl, IApp } from '../../../../db/app'
import { extractSignerAddress } from '../../../../utils/crypto'
import { is0xEthAddress, prepareEthAddress, prepareEthSignature } from '../../../../utils/eth'
import { isAnyEthAddress, prepareEthAddress, prepareEthSignature } from '../../../../utils/eth'
import { IAnswerRequest } from '../interface/IAnswerRequest'
import {
AuthorizationRequestStatus,
Expand Down Expand Up @@ -188,7 +188,7 @@ export function getRequestIsAuthorizedData(data: IIsAuthorizedRequest): { fid: n
throw new Error('"appSignerAddress" is required')
}

if (!is0xEthAddress(appSignerAddress)) {
if (!isAnyEthAddress(appSignerAddress)) {
throw new Error('"appSignerAddress" is invalid ETH address')
}

Expand Down
8 changes: 4 additions & 4 deletions src/controllers/v1/delegated-fs/utils/get.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { is0xEthAddress } from '../../../../utils/eth'
import { isAnyEthAddress, prepareEthAddress } from '../../../../utils/eth'
import { Request } from 'express'

/**
Expand All @@ -12,17 +12,17 @@ export function getUserAppParams(req: Request): { userAddress: string; applicati
throw new Error('"userAddress" is required')
}

if (!is0xEthAddress(userAddress)) {
if (!isAnyEthAddress(userAddress)) {
throw new Error('Invalid "userAddress"')
}

if (!applicationAddress) {
throw new Error('"applicationAddress" is required')
}

if (!is0xEthAddress(applicationAddress)) {
if (!isAnyEthAddress(applicationAddress)) {
throw new Error('Invalid "applicationAddress"')
}

return { userAddress, applicationAddress }
return { userAddress: prepareEthAddress(userAddress), applicationAddress: prepareEthAddress(applicationAddress) }
}
22 changes: 19 additions & 3 deletions src/service/delegated-fs/delegated-fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,14 @@ export class DelegatedFs {
* @param userAddress User address
* @param userDelegatedAddress User delegated address
* @param applicationAddress Application address
* @param errorText Error text
*/
static getDelegatedText(userAddress: string, userDelegatedAddress: string, applicationAddress: string): string {
static getDelegatedText(
userAddress: string,
userDelegatedAddress: string,
applicationAddress: string,
errorText?: string,
): string {
userAddress = prepareEthAddress(userAddress)
userDelegatedAddress = prepareEthAddress(userDelegatedAddress)
applicationAddress = prepareEthAddress(applicationAddress)
Expand All @@ -60,7 +66,13 @@ export class DelegatedFs {
throw new Error('Delegated text addresses must be unique')
}

return `${userAddress}${userDelegatedAddress}${applicationAddress}`
let text = `${userAddress}${userDelegatedAddress}${applicationAddress}`

if (errorText) {
text = `ERROR${text}${errorText}`
}

return text
}

/**
Expand All @@ -69,15 +81,19 @@ export class DelegatedFs {
* @param userDelegatedAddress User delegated address
* @param applicationAddress Application address
* @param signer Signer
* @param errorText Error text
*/
static async createDelegateSignature(
userAddress: string,
userDelegatedAddress: string,
applicationAddress: string,
signer: ISigner,
errorText?: string,
): Promise<string> {
return prepareEthSignature(
await signer.signMessage(DelegatedFs.getDelegatedText(userAddress, userDelegatedAddress, applicationAddress)),
await signer.signMessage(
DelegatedFs.getDelegatedText(userAddress, userDelegatedAddress, applicationAddress, errorText),
),
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ export function assertStringLength(data: unknown, length: number): asserts data
*
* @param value Value to check
*/
export function isHexString(value: string): boolean {
export function isHexString(value: unknown): value is string {
const hexRegEx = /^[0-9A-Fa-f]*$/

return hexRegEx.test(value)
return hexRegEx.test(value as string)
}

/**
Expand Down
15 changes: 14 additions & 1 deletion src/utils/eth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ethers, isHexString } from 'ethers'
import { ethers } from 'ethers'
import { Bytes, is0xHexString } from './bytes'
import { isHexString } from '../utils'

export type EthAddress = Bytes<20>
export const ETH_ADDR_HEX_LENGTH = 40
Expand Down Expand Up @@ -50,10 +51,22 @@ export function prepareEthAddress(address: string): string {
return address.toLowerCase()
}

/**
* Prepares the Ethereum signature by removing the 0x prefix.
* @param signature Ethereum signature
*/
export function prepareEthSignature(signature: string): string {
if (is0xEthSignature(signature)) {
signature = signature.replace(/^0x/, '')
}

return signature
}

/**
* Checks if the value is any Ethereum address.
* @param value Value to check
*/
export function isAnyEthAddress(value: unknown): value is string {
return is0xEthAddress(value) || isEthAddress(value)
}
Loading

0 comments on commit 08435ea

Please sign in to comment.