Skip to content

Commit

Permalink
feat: compliance check infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
SgtPooki committed Mar 28, 2022
1 parent c58f25d commit 7aa5663
Show file tree
Hide file tree
Showing 23 changed files with 507 additions and 420 deletions.
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
direnv 2.30.3
12 changes: 11 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
{
"editor.tabSize": 2
"editor.tabSize": 2,
"debug.javascript.autoAttachFilter": "disabled",
"debug.javascript.autoAttachSmartPattern": [
"${workspaceFolder}/**",
"!**/node_modules/**",
"**/$KNOWN_TOOLS$/**",
],
"javascript.preferences.importModuleSpecifier": "relative",
"typescript.preferences.importModuleSpecifier": "relative",
"javascript.preferences.importModuleSpecifierEnding": "minimal",
"typescript.preferences.importModuleSpecifierEnding": "minimal"
}
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@
npm install
npm run setup

ts-node src/cli/index.ts -s https://api.pinata.cloud/psa $API_TOKEN
ts-node src/cli/index.ts -s $PINATA_API_ENDPOINT $PINATA_API_TOKEN -s $ESTUARY_API_ENDPOINT $ESTUARY_API_TOKEN -s $NFT_API_ENDPOINT $NFT_API_TOKEN -s $WEB3_API_ENDPOINT $WEB3_API_TOKEN
```


## TODO:
### Eventually add Crustio
https://wiki.crust.network/docs/en/buildIPFSW3AuthPin
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,18 @@
"setup:generate-joi": "ts-node ./src/generate-joi.ts"
},
"devDependencies": {
"@types/chalk": "^2.2.0",
"@types/hapi__joi": "^17.1.8",
"@types/listr": "^0.14.4",
"@types/marked": "^4.0.3",
"@types/marked-terminal": "^3.1.3",
"@types/node": "^17.0.21",
"@types/yargs": "^17.0.9",
"aegir": "^36.1.3",
"chalk": "^4.1.2",
"check-aegir-project": "^1.1.1",
"marked": "^4.0.12",
"marked-terminal": "^5.1.1",
"npm-run-all": "^4.1.5",
"ts-node": "^10.7.0",
"typescript": "^4.6.2"
Expand Down
105 changes: 50 additions & 55 deletions src/checks/Check.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,61 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable no-console */
import type { PinsApi } from '@ipfs-shipyard/pinning-service-client'
import type { ListrTaskObject } from 'listr'
import type { Schema, ValidationResult } from '@hapi/joi'
import type { RemotePinningServiceClient, ResponseContext } from '@ipfs-shipyard/pinning-service-client'
import { clientFromServiceAndTokenPair } from '../clientFromServiceAndTokenPair'
import { renderComplianceCheckResults } from '../output/renderComplianceCheckResults'
import { buildComplianceCheckDetails } from '../utils/buildComplianceCheckDetails'

type ImplementableMethods = keyof Omit<PinsApi, 'withMiddleware' | 'withPreMiddleware' | 'withPostMiddleware'>
type PinsApiMethod<T extends ImplementableMethods = ImplementableMethods> = PinsApi[T] extends never ? never : T
// type ImplementableMethods = keyof Omit<PinsApi, 'withMiddleware' | 'withPreMiddleware' | 'withPostMiddleware'>

// const method: PinsApiMethod = 'pinsGet'

// console.log(method)
/**
* An abstract representation of a single compliance check
*
* Every compliance check should extend from this class
*/
class Check<T extends ImplementableMethods & string = ImplementableMethods & string> {
// protected readonly client: PinsApi

// abstract method: T

// args: Parameters<PinsApi[T]>[0]
constructor (protected readonly client: PinsApi, protected readonly method: T, protected readonly args: Exclude<Parameters<PinsApi[T]>[0], undefined>) {
this.task = this.task.bind(this)
}
interface ComplianceCheckOptions<T> {
/**
* The title of the compliance check
*/
title: string
pair: ServiceAndTokenPair
runCheck: (details: ComplianceCheckDetailsCallbackArg & {result: T|null}) => Promise<boolean>
apiCall: (client: RemotePinningServiceClient) => Promise<T>
schema?: Schema
}

get apiMethod () {
return this.client[this.method]
const Check = async <T>({ pair, runCheck, apiCall, title, schema }: ComplianceCheckOptions<T>) => {
let details: ComplianceCheckDetailsCallbackArg | ResponseContext | undefined
const getDetails: ComplianceCheckDetailsCallback = async (d) => {
details = d
}

get title () {
return this.method
const client = clientFromServiceAndTokenPair(pair, getDetails)

let result: T | null = null
let validationResult: ValidationResult | null = null
try {
result = await apiCall(client)
if (schema != null) {
validationResult = schema.validate(result)
}
} catch (err) {
// ignore thrown errors. They should be handled by the provided `runCheck` function
}

// public get task

/**
* The primary method called when testing compliance of an IPFS Pinning Service implementation
*/
public async task (ctx: any, task: ListrTaskObject<any>): Promise<ReturnType<PinsApi[T]>> {
// console.log('this.apiMethod: ', this.apiMethod)
// console.log('this.client[this.method]: ', this.client[this.method])

try {
const response = await this.client[this.method](this.args)
task.title = `${task.title} - SUCCESS`
return response
} catch (err: any) {
task.title = `${task.title} - ERROR: ${err?.message ?? ''}`
console.log('err: ', err)
// task.hasFailed()
console.log('task', task)
// process.stdout.write('task:')
// process.stdout.write(JSON.stringify(Object.keys(task)))
await task.skip('FAILED')
if (details == null) {
// console.error('details is null')
throw new Error('The details object was not set in the middleware')
} else {
let successful = await runCheck({
result,
...details as ComplianceCheckDetailsCallbackArg
})
if (validationResult != null) {
if (validationResult.error != null || validationResult.errors != null) {
successful = false
}
}

// if (!response.ok) { return response }
// .catch(() => {
// // no-op
// }).finally(() => {
// // done
// })
renderComplianceCheckResults(buildComplianceCheckDetails({
title,
details,
successful,
result,
validationResult: validationResult as ValidationResult
}))
}
}

Expand Down
85 changes: 0 additions & 85 deletions src/checks/CheckRunner.ts

This file was deleted.

52 changes: 0 additions & 52 deletions src/checks/Renderer.ts

This file was deleted.

32 changes: 6 additions & 26 deletions src/checks/auth/checkEmptyBearerToken.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,10 @@
/* eslint-disable no-console */
import { Configuration, PinResults, PinsApi } from '@ipfs-shipyard/pinning-service-client'
import type { ListrRenderer, ListrTask } from 'listr2'
import { requestLog, responseLog } from '../../middleware/requestReponseLogger'
import { Check } from '../Check'

const checkEmptyBearerToken = (pair: ServiceAndTokenPair): ListrTask<any, typeof ListrRenderer> => ({
title: 'Returns 403 without bearer token',
task: async (ctx, task) => {
const config = new Configuration({
basePath: pair[0],
accessToken: undefined // no bearer token
})

const client = new PinsApi(config)
await client.pinsGet({}).then((response: PinResults) => {
console.log(requestLog.pop(), 'checkEmptyBearerToken')
console.log(responseLog.pop(), 'checkEmptyBearerToken')
throw new Error('')
}).catch((response: any | Record<string, any>) => {
if (response.status === 403) {
task.output = `${task.title} -> Received 403`
return
}
throw new Error(`${task.title} -> Received ${response.status as string}`)
})
},
exitOnError: false
const checkEmptyBearerToken = async (pair: ServiceAndTokenPair) => await Check({
pair: [pair[0], undefined],
title: 'Return 403 for requests with no authentication token',
runCheck: async (details) => details.response.status === 403,
apiCall: async (client) => await client.pinsGet({})
})

export { checkEmptyBearerToken }
36 changes: 6 additions & 30 deletions src/checks/auth/checkInvalidBearerToken.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,10 @@
import { Configuration, PinResults, PinsApi } from '@ipfs-shipyard/pinning-service-client'
import type { ListrRenderer, ListrTask } from 'listr2'
import { requestLog, responseLog } from '../../middleware/requestReponseLogger'
import { Check } from '../Check'

const checkInvalidBearerToken = (pair: ServiceAndTokenPair): ListrTask<any, typeof ListrRenderer> => ({
title: 'Returns 403 for invalid bearer token',
task: async (ctx, task) => {
const config = new Configuration({
basePath: pair[0],
accessToken: 'foobarabc123' // invalid bearer token
})

const client = new PinsApi(config)
await client.pinsGet({}).then((response: PinResults) => {
throw new Error('')
}).catch((response: any | Record<string, any>) => {
if (response.status === 403) {
task.output = `${task.title} -> Received 403`
return
}
throw new Error(`${task.title} -> Received ${response.status as string}
checkInvalidBearerToken
Request:
${JSON.stringify(requestLog.pop())}
Response:
${JSON.stringify(responseLog.pop())}
`)
})
},
exitOnError: false
const checkInvalidBearerToken = async (pair: ServiceAndTokenPair) => await Check({
pair: [pair[0], 'purposefullyInvalid'],
title: 'Return 403 for requests with an invalid authentication token',
runCheck: async (details) => details.response.status === 403,
apiCall: async (client) => await client.pinsGet({})
})

export { checkInvalidBearerToken }
Loading

0 comments on commit 7aa5663

Please sign in to comment.