{"record":{"id":"16fec37068082aec","repo":"koala73/worldmonitor","slug":"invalid-name","errorCode":"INVALID_NAME","errorMessage":"INVALID_NAME","messagePattern":"INVALID_NAME","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/apiKeys.ts","lineNumber":78,"sourceCode":"      !entitlement ||\n      entitlement.validUntil < Date.now() ||\n      !entitlement.features.apiAccess\n    ) {\n      throw new ConvexError(\"API_ACCESS_REQUIRED\");\n    }\n\n    const scopes = normalizeCompanyMonitoringScopes(args.scopes);\n    // Issuing a scoped key is a first-use entry point, so it provisions the\n    // root. Requesting no scopes must stay entirely off Company Monitoring.\n    const companyMonitoringAccount = scopes\n      ? await ensureActiveAccount(ctx, userId, entitlement)\n      : null;\n    if (scopes && !companyMonitoringAccount) {\n      throw new ConvexError(\"COMPANY_MONITORING_ACCESS_DENIED\");\n    }\n\n    if (!args.name.trim()) {\n      throw new ConvexError(\"INVALID_NAME\");\n    }\n    if (!/^wm_[a-f0-9]{5}$/.test(args.keyPrefix)) {\n      throw new ConvexError(\"INVALID_PREFIX\");\n    }\n    if (!/^[a-f0-9]{64}$/.test(args.keyHash)) {\n      throw new ConvexError(\"INVALID_HASH\");\n    }\n\n    // Enforce per-user key limit (count only non-revoked keys).\n    //\n    // API keys intentionally reject at the cap instead of silently rotating a\n    // valid key. If a prior race left too many active rows, converge by\n    // revoking enough oldest overflow rows to make room for this create.\n    const existing = await ctx.db\n      .query(\"userApiKeys\")\n      .withIndex(\"by_userId\", (q) => q.eq(\"userId\", userId))\n      .collect();\n    const active = existing.filter((k) => !k.revokedAt);","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/apiKeys.ts#L60-L96","documentation":"Thrown by createApiKey when args.name, after trimming whitespace, is empty. The name is a required human-readable label for the key and must contain at least one non-whitespace character. This is a client-side validation failure caught server-side as the last defense.","triggerScenarios":"Calling createApiKey with name=\"\", name=\"   \" (all whitespace), or name passed as an empty string from a form field that was not validated before submission.","commonSituations":"A UI form submitted the key-creation request without populating the name field; a test fixture or script passed an empty name; whitespace-only input from copy-paste that the client failed to trim and reject.","solutions":["Trim and validate the name client-side before calling createApiKey — reject empty/whitespace-only values.","Ensure the form input for key name is marked required and validated on submit.","Pass args.name.trim() result only if non-empty."],"exampleFix":"// before\nawait createApiKey(ctx, { name: \"  \", keyPrefix, keyHash });\n// after\nconst name = userInput.trim();\nif (!name) throw new Error(\"Name required\");\nawait createApiKey(ctx, { name, keyPrefix, keyHash });","handlingStrategy":"validation","validationCode":"const name = (args.name ?? \"\").trim();\nif (!name) throw new Error(\"Name is required before calling createApiKey\");\nawait createApiKey(ctx, { name, keyPrefix, keyHash });","typeGuard":"function isValidKeyName(name: unknown): name is string {\n  return typeof name === \"string\" && name.trim().length > 0;\n}","tryCatchPattern":"try {\n  await createApiKey(ctx, { name, keyPrefix, keyHash });\n} catch (e) {\n  if (e instanceof ConvexError && e.message === \"INVALID_NAME\") {\n    // prompt user for a name\n  } else throw e;\n}","preventionTips":["Make the name input required and validated in the UI form.","Trim whitespace client-side and reject empty before submitting.","Run a unit test on the createApiKey form for empty-name rejection."],"tags":["convex","api-keys","validation","input-validation"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}