{"record":{"id":"2064c5cf419c346e","repo":"nocobase/nocobase","slug":"invalid-temporary-file-access-token","errorCode":null,"errorMessage":"Invalid temporary file access token","messagePattern":"Invalid temporary file access token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/@nocobase/plugin-file-manager/src/server/temporary-access.ts","lineNumber":91,"sourceCode":"      ...(resource.role ? { role: resource.role } : {}),\n    },\n    deriveTemporaryFileAccessSecret(plugin),\n    {\n      algorithm: 'HS256',\n      audience: TEMPORARY_FILE_ACCESS_AUDIENCE,\n      ...(currentUserId ? { subject: String(currentUserId) } : {}),\n      expiresIn: options.expiresIn || getTemporaryFileAccessExpiresIn(),\n    },\n  );\n}\n\nexport function verifyTemporaryFileAccessToken(plugin: PluginFileManagerServer, token: string) {\n  const decoded = jwt.verify(token, deriveTemporaryFileAccessSecret(plugin), {\n    algorithms: ['HS256'],\n    audience: TEMPORARY_FILE_ACCESS_AUDIENCE,\n  });\n  if (!decoded || typeof decoded === 'string') {\n    throw new Error('Invalid temporary file access token');\n  }\n  return decoded as TemporaryFileAccessPayload;\n}\n\nexport async function createTemporaryURLAction(ctx: Context, next: Next) {\n  const collectionName = ctx.action.resourceName;\n  const collection = ctx.dataSource.collectionManager.getCollection(collectionName) as Collection | undefined;\n  if (!isFileCollection(collection)) {\n    return ctx.throw(404);\n  }\n  if (!hasStandardFileId(collection)) {\n    ctx.logger.error('file collection is missing standard id field', {\n      method: 'file-manager.createTemporaryURL',\n      collection: collection.name,\n    });\n    return ctx.throw(500);\n  }\n","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/plugins/@nocobase/plugin-file-manager/src/server/temporary-access.ts#L73-L109","documentation":"verifyTemporaryFileAccessToken() verifies a temporary-file-access JWT with HS256 against the plugin-derived secret and the expected audience (TEMPORARY_FILE_ACCESS_AUDIENCE). If jwt.verify passes (signature/expiry valid) but the decoded payload is a string or falsy — i.e. not the expected JSON object payload — it throws 'Invalid temporary file access token'. Note that jwt.verify itself throws separately for bad signatures and expired tokens; this error specifically means the token decoded but its shape is not a valid TemporaryFileAccessPayload object.","triggerScenarios":"Calling the file access endpoint (getFile path) with a token that: was created with a different payload format (e.g. signed by another feature or an older plugin version using a string sub-only payload), was crafted as a string-payload JWT, or otherwise verifies cryptographically but does not decode to the expected object with the right audience.","commonSituations":"A token issued by a different NocoBase app name (secret derivation uses plugin.app.name, so verification would fail there rather than here); hand-rolled tokens in integrations/scripts that pass a plain string as the JWT payload; plugin upgrades changing the payload schema so old tokens no longer match the expected object shape.","solutions":["Re-request a fresh token via the temporary-URL action instead of reusing old/hand-made tokens","Ensure the token was signed by the same app (deriveTemporaryFileAccessSecret uses plugin.app.name) with the expected audience and an object payload","Check that the client is not mangling the token in transit (truncation, URL-encoding issues) — though typically that fails verification earlier","If old tokens fail after an upgrade, invalidate them and re-issue; keep the token payload matching TemporaryFileAccessPayload"],"exampleFix":"// before: token created manually with a string payload\nconst token = jwt.sign('some-file-id', secret, { algorithm: 'HS256' });\n\n// after: use the plugin's signing function / object payload\nconst token = signTemporaryFileAccessToken(plugin, { attachmentId: file.id });","handlingStrategy":"validation","validationCode":"// Client-side sanity check before sending the token\nfunction looksLikeTemporaryFileToken(token: string): boolean {\n  const parts = token.split('.');\n  if (parts.length !== 3) return false;\n  try {\n    const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));\n    return payload && typeof payload === 'object' && !Array.isArray(payload);\n  } catch {\n    return false;\n  }\n}\nif (!looksLikeTemporaryFileToken(token)) throw new Error('Refusing to send malformed temporary file token');","typeGuard":"function isTemporaryFileAccessPayload(\n  decoded: string | object | null,\n): decoded is TemporaryFileAccessPayload {\n  return (\n    !!decoded &&\n    typeof decoded === 'object' &&\n    !Array.isArray(decoded) &&\n    typeof (decoded as TemporaryFileAccessPayload).attachmentId !== 'undefined'\n  );\n}","tryCatchPattern":"import { TokenExpiredError, JsonWebTokenError } from 'jsonwebtoken';\ntry {\n  const payload = verifyTemporaryFileAccessToken(plugin, token);\n} catch (e) {\n  if (e instanceof TokenExpiredError) return respondTokenExpired();\n  if (e instanceof JsonWebTokenError || e.message === 'Invalid temporary file access token') {\n    return respondUnauthorized('Request a new temporary file URL');\n  }\n  throw e;\n}","preventionTips":["Always obtain tokens via the plugin's signTemporaryFileAccessToken / temporary-URL action, never hand-sign them","Ensure the verifying app name matches the signing app (secret derives from plugin.app.name)","Re-issue tokens after plugin upgrades that change the payload schema; treat tokens as short-lived and non-persistent","Guard the file endpoint to return 401/404 on invalid tokens instead of leaking raw errors"],"tags":["jwt","authentication","token-validation","file-manager"],"backgroundTag":"invalid-jwt-payload","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}