Skip to content

Commit

Permalink
Payment plugin fixes (#396)
Browse files Browse the repository at this point in the history
* Avoid deducting from free quota for queries with unconfigured rates

* Check query selections on an introspection query

* Update comment
  • Loading branch information
prathamesh0 authored Jul 31, 2023
1 parent 198d3e6 commit 47d4b66
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 16 deletions.
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
"debug": "^4.3.1",
"express": "^4.18.2",
"graphql-subscriptions": "^2.0.0",
"pluralize": "^8.0.0",
"reflect-metadata": "^0.1.13",
"typeorm": "0.2.37",
"yargs": "^17.0.1"
},
"devDependencies": {
"@types/express": "^4.17.14",
"@types/node": "16.11.7",
"@types/pluralize": "^0.0.29",
"@types/yargs": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
Expand Down
49 changes: 33 additions & 16 deletions packages/util/src/payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { BaseRatesConfig, PaymentsConfig } from './config';
const log = debug('laconic:payments');

const IntrospectionQuery = 'IntrospectionQuery';
const IntrospectionQuerySelection = '__schema';

const PAYMENT_HEADER_KEY = 'x-payment';
const PAYMENT_HEADER_REGEX = /vhash:(.*),vsig:(.*)/;

Expand Down Expand Up @@ -84,8 +86,12 @@ export class PaymentsManager {
return this.ratesConfig.freeQueriesList ?? DEFAULT_FREE_QUERIES_LIST;
}

get queryRates (): { [key: string]: string } {
return this.ratesConfig.queries ?? {};
}

get mutationRates (): { [key: string]: string } {
return this.ratesConfig.mutations;
return this.ratesConfig.mutations ?? {};
}

async subscribeToVouchers (client: Client): Promise<void> {
Expand Down Expand Up @@ -151,9 +157,7 @@ export class PaymentsManager {
await this.stopSubscriptionLoop.close();
}

async allowRequest (voucherHash: string, voucherSig: string, querySelection: string): Promise<[false, string] | [true, null]> {
const signerAddress = nitroUtils.getSignerAddress(voucherHash, voucherSig);

async allowRequest (voucherHash: string, signerAddress: string, querySelection: string): Promise<[false, string] | [true, null]> {
// Use free quota if EMPTY_VOUCHER_HASH passed
if (voucherHash === EMPTY_VOUCHER_HASH) {
let remainingFreeQueries = this.remainingFreeQueriesMap.get(signerAddress);
Expand All @@ -173,14 +177,8 @@ export class PaymentsManager {
return [false, ERR_FREE_QUOTA_EXHUASTED];
}

// Serve a query for free if rate is not configured
const configuredQueryCost = this.ratesConfig.queries[querySelection];
if (configuredQueryCost === undefined) {
log(`Query rate not configured for "${querySelection}", serving a free query to ${signerAddress}`);
return [true, null];
}

// Check if required payment received from the Nitro account
const configuredQueryCost = this.ratesConfig.queries[querySelection];
const [paymentReceived, paymentError] = await this.authenticatePayment(voucherHash, signerAddress, BigInt(configuredQueryCost));

if (paymentReceived) {
Expand Down Expand Up @@ -282,8 +280,21 @@ export const paymentsPlugin = (paymentsManager?: PaymentsManager): ApolloServerP
async requestDidStart (requestContext: GraphQLRequestContext) {
return {
async responseForOperation (requestContext: GraphQLRequestContext): Promise<GraphQLResponse | null> {
// Continue if payments is not setup or it's an introspection query
if (!paymentsManager || requestContext.operationName === IntrospectionQuery) {
// Continue if payments is not setup
if (!paymentsManager) {
return null;
}

const querySelections = requestContext.operation?.selectionSet.selections
.map((selection: any) => (selection as FieldNode).name.value);

// Continue if it's an introspection query for schema
// (made by ApolloServer playground / default landing page)
if (
requestContext.operationName === IntrospectionQuery &&
querySelections && querySelections.length === 1 &&
querySelections[0] === IntrospectionQuerySelection
) {
return null;
}

Expand Down Expand Up @@ -313,16 +324,22 @@ export const paymentsPlugin = (paymentsManager?: PaymentsManager): ApolloServerP
};
}

const querySelections = requestContext.operation?.selectionSet.selections
.map((selection: any) => (selection as FieldNode).name.value);
const signerAddress = nitroUtils.getSignerAddress(vhash, vsig);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const querySelection of querySelections ?? []) {
if (paymentsManager.freeQueriesList.includes(querySelection)) {
continue;
}

const [allowRequest, rejectionMessage] = await paymentsManager.allowRequest(vhash, vsig, querySelection);
// Serve a query for free if rate is not configured
const configuredQueryCost = paymentsManager.queryRates[querySelection];
if (configuredQueryCost === undefined) {
log(`Query rate not configured for "${querySelection}", serving a free query to ${signerAddress}`);
continue;
}

const [allowRequest, rejectionMessage] = await paymentsManager.allowRequest(vhash, signerAddress, querySelection);
if (!allowRequest) {
const failResponse: GraphQLResponse = {
errors: [{ message: rejectionMessage }],
Expand Down

0 comments on commit 47d4b66

Please sign in to comment.