Payment plugin fixes (#396)

* Avoid deducting from free quota for queries with unconfigured rates

* Check query selections on an introspection query

* Update comment
This commit is contained in:
prathamesh0 2023-07-31 17:09:58 +05:30 committed by GitHub
parent 198d3e65db
commit 47d4b667f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 35 additions and 16 deletions

View File

@ -21,6 +21,7 @@
"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"
@ -28,6 +29,7 @@
"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",

View File

@ -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:(.*)/;
@ -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> {
@ -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);
@ -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) {
@ -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;
}
@ -313,8 +324,7 @@ 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 ?? []) {
@ -322,7 +332,14 @@ export const paymentsPlugin = (paymentsManager?: PaymentsManager): ApolloServerP
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 }],