Skip to content

Commit

Permalink
feat: bypass maintenance mode for allowed tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos committed Jan 4, 2024
1 parent f3c661f commit 0aeeff7
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/api/src/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ function verifyAuthToken (token, decoded, env) {
return env.db.getKey(decoded.sub, token)
}

function getTokenFromRequest (request, { magic }) {
export function getTokenFromRequest (request, { magic }) {
const authHeader = request.headers.get('Authorization') || ''
if (!authHeader) {
throw new NoTokenError()
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { Factory as ClaimFactory } from './utils/content-claims.js'
* @property {string} [SENTRY_RELEASE]
* @property {string} [LOGTAIL_TOKEN]
* @property {string} MAINTENANCE_MODE
* @property {string} [MODE_SKIP_LIST]
* @property {string} [DANGEROUSLY_BYPASS_MAGIC_AUTH]
* @property {string} [ELASTIC_IPFS_PEER_ID]
* @property {string} [ENABLE_ADD_TO_CLUSTER]
Expand Down Expand Up @@ -71,6 +72,7 @@ import { Factory as ClaimFactory } from './utils/content-claims.js'
* @property {import('./utils/billing-types').CustomersService} customers
* @property {string} stripeSecretKey
* @property {string[]} gatewayUrls
* @property {string[]} modeSkipList
* @property {import('./utils/content-claims').Factory} [claimFactory]
*/

Expand Down Expand Up @@ -150,6 +152,8 @@ export async function envAll (req, env, ctx) {
// @ts-ignore
env.MODE = env.MAINTENANCE_MODE || DEFAULT_MODE

env.modeSkipList = env.MODE_SKIP_LIST ? JSON.parse(env.MODE_SKIP_LIST) : []

env.ELASTIC_IPFS_PEER_ID = env.ELASTIC_IPFS_PEER_ID ?? 'bafzbeibhqavlasjc7dvbiopygwncnrtvjd2xmryk5laib7zyjor6kf3avm'

if (!env.LINKDEX_URL && env.ENV !== 'dev') {
Expand Down
16 changes: 15 additions & 1 deletion packages/api/src/maintenance.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HTTPError, MaintenanceError } from './errors.js'
import { getTokenFromRequest } from './auth.js'

/**
* @typedef {'rw' | 'r-' | '--'} Mode
Expand Down Expand Up @@ -61,8 +62,21 @@ export function withMode (mode) {
})
}

const modeSkip = () => {
if (!request || !env) {
return false
}
const list = env.modeSkipList
const token = getTokenFromRequest(request, env)

if (list.includes(token)) {
return true
}
return false
}

// Not enabled, use maintenance handler.
if (!enabled()) {
if (!enabled() && !modeSkip()) {
return maintenanceHandler()
}
}
Expand Down
44 changes: 43 additions & 1 deletion packages/api/test/maintenance.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
/* eslint-env mocha */
import assert from 'assert'
import { CID } from 'multiformats/cid'
import { sha256 } from 'multiformats/hashes/sha2'
import fetch from '@web-std/fetch'

import {
withMode,
READ_WRITE,
READ_ONLY,
NO_READ_OR_WRITE
} from '../src/maintenance.js'
import { CAR_CODE } from '../src/constants.js'

import { endpoint } from './scripts/constants.js'
import { createCar } from './scripts/car.js'
import { getTestJWT } from './scripts/helpers.js'

// client needs global fetch
Object.assign(global, { fetch })

describe('maintenance middleware', () => {
describe.only('maintenance middleware', () => {
it('should throw error when in maintenance for a READ_ONLY route', () => {
const handler = withMode(READ_ONLY)
const block = (request, env) => {
Expand Down Expand Up @@ -52,6 +64,36 @@ describe('maintenance middleware', () => {
}), /API undergoing maintenance/)
})

it('should bypass maintenance mode with a allowed token', async () => {
const issuer = 'test-upload'
const token = await getTestJWT(issuer, issuer)
const { root, car: carBody } = await createCar('dude where\'s my CAR')
const carBytes = new Uint8Array(await carBody.arrayBuffer())
const expectedCid = root.toString()
const expectedCarCid = CID.createV1(CAR_CODE, await sha256.digest(carBytes)).toString()

/** @type {import('miniflare').Miniflare} */
const mf = globalThis.miniflare
const bindings = await mf.getBindings()
bindings['MAINTENANCE_MODE'] = 'r-'

Check failure on line 78 in packages/api/test/maintenance.spec.js

View workflow job for this annotation

GitHub Actions / Lint

["MAINTENANCE_MODE"] is better written in dot notation
bindings['MODE_SKIP_LIST'] = JSON.stringify([token])

Check failure on line 79 in packages/api/test/maintenance.spec.js

View workflow job for this annotation

GitHub Actions / Lint

["MODE_SKIP_LIST"] is better written in dot notation

const res = await fetch(new URL('car', endpoint), {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/vnd.ipld.car'
},
body: carBody
})
const { cid, carCid } = await res.json()
assert.strictEqual(cid, expectedCid, 'Server responded with expected CID')
assert.strictEqual(carCid, expectedCarCid, 'Server responded with expected CAR CID')

// fallback maintenance mode
bindings['MAINTENANCE_MODE'] = 'rw'

Check failure on line 94 in packages/api/test/maintenance.spec.js

View workflow job for this annotation

GitHub Actions / Lint

["MAINTENANCE_MODE"] is better written in dot notation
})

it('should throw for invalid maintenance mode', () => {
const handler = withMode(READ_WRITE)
const block = (request, env) => {
Expand Down

0 comments on commit 0aeeff7

Please sign in to comment.