Fork of github.com/did-method-plc/did-method-plc

Strip unknown values from payload when parsing operation (#73)

fixes did-method-plc/did-method-plc#71

Changed files
+30 -14
packages
+28 -11
packages/server/src/constraints.ts
···
-
import { DAY, HOUR, cborEncode, check } from '@atproto/common'
+
import { DAY, HOUR, cborEncode } from '@atproto/common'
import * as plc from '@did-plc/lib'
import { ServerError } from './error'
import { parseDidKey } from '@atproto/crypto'
···
const MAX_SERVICE_ENDPOINT_LENGTH = 512
const MAX_ID_LENGTH = 32
-
export function assertValidIncomingOp(
-
op: unknown,
-
): asserts op is plc.OpOrTombstone {
-
const byteLength = cborEncode(op).byteLength
+
export function validateIncomingOp(input: unknown): plc.OpOrTombstone {
+
const byteLength = cborEncode(input).byteLength
if (byteLength > MAX_OP_BYTES) {
throw new ServerError(
400,
`Operation too large (${MAX_OP_BYTES} bytes maximum in cbor encoding)`,
)
}
-
if (!check.is(op, plc.def.opOrTombstone)) {
-
throw new ServerError(400, `Not a valid operation: ${JSON.stringify(op)}`)
+
+
// We *need* to parse, and use the result of the parsing, to ensure that any
+
// unknown fields are removed from the input. "@atproto/common"'s check
+
// function will not remove unknown fields.
+
const result = plc.def.opOrTombstone.safeParse(input)
+
+
if (!result.success) {
+
const errors = result.error.errors.map(
+
(e) => `${e.message} at /${e.path.join('/')}`,
+
)
+
throw new ServerError(
+
400,
+
errors.length
+
? errors.join('. ') + '.'
+
: `Not a valid operation: ${JSON.stringify(input)}`,
+
)
}
+
+
const op = result.data
+
if (op.type === 'plc_tombstone') {
-
return
+
return op
}
if (op.alsoKnownAs.length > MAX_AKA_ENTRIES) {
throw new ServerError(
···
`To many alsoKnownAs entries (max ${MAX_AKA_ENTRIES})`,
)
}
-
const akaDupe: Record<string, boolean> = {}
+
const akaDupe = new Set<string>()
for (const aka of op.alsoKnownAs) {
if (aka.length > MAX_AKA_LENGTH) {
throw new ServerError(
···
`alsoKnownAs entry too long (max ${MAX_AKA_LENGTH}): ${aka}`,
)
}
-
if (akaDupe[aka]) {
+
if (akaDupe.has(aka)) {
throw new ServerError(400, `duplicate alsoKnownAs entry: ${aka}`)
} else {
-
akaDupe[aka] = true
+
akaDupe.add(aka)
}
}
if (op.rotationKeys.length > MAX_ROTATION_ENTRIES) {
···
throw new ServerError(400, `Invalid verificationMethod key: ${key}`)
}
}
+
+
return op
}
const HOUR_LIMIT = 10
+2 -3
packages/server/src/routes.ts
···
import * as plc from '@did-plc/lib'
import { ServerError } from './error'
import { AppContext } from './context'
-
import { assertValidIncomingOp } from './constraints'
+
import { validateIncomingOp } from './constraints'
export const createRouter = (ctx: AppContext): express.Router => {
const router = express.Router()
···
// Update or create a DID doc
router.post('/:did', async function (req, res) {
const { did } = req.params
-
const op = req.body
-
assertValidIncomingOp(op)
+
const op = validateIncomingOp(req.body)
await ctx.db.validateAndAddOp(did, op)
res.sendStatus(200)
})