Limit scope of API Keys (#989)

* first pass of disallowing certain APIs with API keys

Disallow the following APIs:
* item.act (zap)
* create withdrawal
* unlink auth method
* link unverified email

* disallow creating lnauths via API key to stop the flow of linking via lnauth

* undo the limitation on donating to rewards

* revert the assertion on createAuth

* assert no api key on createWithdrawal and sendToLNAddr

* incorporate PR feedback by adding API Key negative assertion to more mutations:

* `createInvite`
* `createAuth`
* `upsertWalletLND` by way of `upsertWallet`
* `upsertWalletLNAddr` by way of `upsertWallet`
This commit is contained in:
SatsAllDay 2024-04-03 16:11:06 -04:00 committed by GitHub
parent 0c0f303a11
commit 4b77e7a1a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 23 additions and 1 deletions

7
api/resolvers/apiKey.js Normal file
View File

@ -0,0 +1,7 @@
import { GraphQLError } from 'graphql'
export default function assertApiKeyNotPermitted ({ me }) {
if (me?.apiKey === true) {
throw new GraphQLError('this operation is not allowed to be performed via API Key', { extensions: { code: 'FORBIDDEN' } })
}
}

View File

@ -1,6 +1,7 @@
import { GraphQLError } from 'graphql'
import { inviteSchema, ssValidate } from '@/lib/validate'
import { msatsToSats } from '@/lib/format'
import assertApiKeyNotPermitted from './apiKey'
export default {
Query: {
@ -32,6 +33,7 @@ export default {
if (!me) {
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
}
assertApiKeyNotPermitted({ me })
await ssValidate(inviteSchema, { gift, limit })

View File

@ -20,6 +20,7 @@ import { defaultCommentSort, isJob, deleteItemByAuthor, getDeleteCommand, hasDel
import { datePivot, whenRange } from '@/lib/time'
import { imageFeesInfo, uploadIdsFromText } from './image'
import assertGofacYourself from './ofac'
import assertApiKeyNotPermitted from './apiKey'
function commentsOrderByClause (me, models, sort) {
if (sort === 'recent') {
@ -856,6 +857,7 @@ export default {
return id
},
act: async (parent, { id, sats, act = 'TIP', idempotent, hash, hmac }, { me, models, lnd, headers }) => {
assertApiKeyNotPermitted({ me })
await ssValidate(actSchema, { sats, act })
await assertGofacYourself({ models, headers })

View File

@ -2,6 +2,7 @@ import { randomBytes } from 'crypto'
import { bech32 } from 'bech32'
import { GraphQLError } from 'graphql'
import assertGofacYourself from './ofac'
import assertApiKeyNotPermitted from './apiKey'
function encodedUrl (iurl, tag, k1) {
const url = new URL(iurl)
@ -26,7 +27,8 @@ export default {
}
},
Mutation: {
createAuth: async (parent, args, { models }) => {
createAuth: async (parent, args, { models, me }) => {
assertApiKeyNotPermitted({ me })
return await models.lnAuth.create({ data: { k1: k1() } })
},
createWith: async (parent, args, { me, models, headers }) => {
@ -36,6 +38,8 @@ export default {
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
}
assertApiKeyNotPermitted({ me })
return await models.lnWith.create({ data: { k1: k1(), userId: me.id } })
}
},

View File

@ -8,6 +8,7 @@ import { getItem, updateItem, filterClause, createItem, whereClause, muteClause
import { ANON_USER_ID, DELETE_USER_ID, RESERVED_MAX_USER_ID, SN_NO_REWARDS_IDS } from '@/lib/constants'
import { viewGroup } from './growth'
import { whenRange } from '@/lib/time'
import assertApiKeyNotPermitted from './apiKey'
const contributors = new Set()
@ -562,6 +563,7 @@ export default {
if (!me) {
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
}
assertApiKeyNotPermitted({ me })
let user
if (authType === 'twitter' || authType === 'github') {
@ -592,6 +594,7 @@ export default {
if (!me) {
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
}
assertApiKeyNotPermitted({ me })
await ssValidate(emailSchema, { email })

View File

@ -11,6 +11,7 @@ import { LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSc
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '@/lib/constants'
import { datePivot } from '@/lib/time'
import assertGofacYourself from './ofac'
import assertApiKeyNotPermitted from './apiKey'
export async function getInvoice (parent, { id }, { me, models, lnd }) {
const inv = await models.invoice.findUnique({
@ -492,6 +493,7 @@ async function upsertWallet (
if (!me) {
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
}
assertApiKeyNotPermitted({ me })
await ssValidate(schema, { ...data, ...settings }, { me, models })
@ -562,6 +564,7 @@ async function upsertWallet (
}
export async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd, headers, autoWithdraw = false }) {
assertApiKeyNotPermitted({ me })
await ssValidate(withdrawlSchema, { invoice, maxFee })
await assertGofacYourself({ models, headers })
@ -620,6 +623,7 @@ export async function sendToLnAddr (parent, { addr, amount, maxFee, comment, ...
if (!me) {
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
}
assertApiKeyNotPermitted({ me })
const options = await lnAddrOptions(addr)
await ssValidate(lnAddrSchema, { addr, amount, maxFee, comment, ...payer }, options)