···
1
-
import { DAY, HOUR, cborEncode, check } from '@atproto/common'
1
+
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
15
-
export function assertValidIncomingOp(
17
-
): asserts op is plc.OpOrTombstone {
18
-
const byteLength = cborEncode(op).byteLength
15
+
export function validateIncomingOp(input: unknown): plc.OpOrTombstone {
16
+
const byteLength = cborEncode(input).byteLength
if (byteLength > MAX_OP_BYTES) {
`Operation too large (${MAX_OP_BYTES} bytes maximum in cbor encoding)`,
25
-
if (!check.is(op, plc.def.opOrTombstone)) {
26
-
throw new ServerError(400, `Not a valid operation: ${JSON.stringify(op)}`)
24
+
// We *need* to parse, and use the result of the parsing, to ensure that any
25
+
// unknown fields are removed from the input. "@atproto/common"'s check
26
+
// function will not remove unknown fields.
27
+
const result = plc.def.opOrTombstone.safeParse(input)
29
+
if (!result.success) {
30
+
const errors = result.error.errors.map(
31
+
(e) => `${e.message} at /${e.path.join('/')}`,
33
+
throw new ServerError(
36
+
? errors.join('. ') + '.'
37
+
: `Not a valid operation: ${JSON.stringify(input)}`,
41
+
const op = result.data
if (op.type === 'plc_tombstone') {
if (op.alsoKnownAs.length > MAX_AKA_ENTRIES) {
···
`To many alsoKnownAs entries (max ${MAX_AKA_ENTRIES})`,
37
-
const akaDupe: Record<string, boolean> = {}
52
+
const akaDupe = new Set<string>()
for (const aka of op.alsoKnownAs) {
if (aka.length > MAX_AKA_LENGTH) {
···
`alsoKnownAs entry too long (max ${MAX_AKA_LENGTH}): ${aka}`,
60
+
if (akaDupe.has(aka)) {
throw new ServerError(400, `duplicate alsoKnownAs entry: ${aka}`)
if (op.rotationKeys.length > MAX_ROTATION_ENTRIES) {
···
throw new ServerError(400, `Invalid verificationMethod key: ${key}`)