{"record":{"id":"987701390bc7b0b0","repo":"halo-dev/halo","slug":"json-errors-0-987701","errorCode":null,"errorMessage":"json.errors[0]","messagePattern":"json\\.errors\\[0\\]","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"application/src/main/resources/templates/login_email-code.html","lineNumber":68,"sourceCode":"      async function sendRequest() {\n        const email = document.getElementById(\"email\").value.trim();\n        if (!email) {\n          throw new Error(/*[[#{form.emailCode.send.emptyValidation}]]*/ \"\");\n        }\n\n        const response = await fetch(\"/login/email-code/send\", {\n          method: \"POST\",\n          body: JSON.stringify({ email: email }),\n          headers: {\n            \"Content-Type\": \"application/json\",\n            [headerName]: token,\n          },\n        });\n\n        if (!response.ok) {\n          const json = await response.json();\n          if (json.errors && json.errors.length) {\n            throw new Error(json.errors[0]);\n          }\n          if (json.detail) {\n            throw new Error(json.detail);\n          }\n          throw new Error(i18nResources.sendVerificationCodeFailed);\n        }\n\n        return response;\n      }\n\n      const emailCodeSendButton = document.getElementById(\"emailCodeSendButton\");\n      sendVerificationCode(emailCodeSendButton, sendRequest);\n    });\n  </script>\n</div>\n","sourceCodeStart":50,"sourceCodeEnd":84,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/resources/templates/login_email-code.html#L50-L84","documentation":"Thrown in the email-code login Thymeleaf template after POSTing to /login/email-code/send. The Spring WebFlux backend returned a non-2xx response whose RFC 7807 Problem Details body contains an `errors` array (Spring's ProblemDetail.errors, populated by MethodArgumentNotValidException / field validation). The first element of that array — typically a string or object serialized to a message — becomes the Error message. It surfaces a per-field validation failure for the submitted email payload.","triggerScenarios":"The browser POSTs {email} with the CSRF header to /login/email-code/send and the server rejects with a body like {\"errors\":[\"email must be a valid address\"]}. response.ok is false, json.errors is a non-empty array, so json.errors[0] is re-thrown. Common when the email is malformed, the email-code send endpoint enforces format validation, or a plugin/hook adds validators on that route.","commonSituations":"Email field submitted empty or with an invalid format that bypassed the client-side trim/empty check; a newer backend version tightened validation rules; a CSRF token that expired returns 403 whose body Spring still formats with an errors array; reverse proxy (Nginx) rewrites the path and returns its own JSON error shape that happens to include `errors`.","solutions":["Inspect the actual response body in the browser Network tab for the /login/email-code/send request to see the real errors[0] text — that text identifies the failing field/rule.","Verify the email value is non-empty and well-formed before submit; the client only checks emptiness (line 52), not format.","Confirm the CSRF header name and token are populated (headerName/token come from Thymeleaf ${_csrf.*}); a blank headerName causes Spring to treat the request as unauthenticated and return 401/403.","If a custom validator or plugin attaches to the email-code endpoint, review its rules against the submitted payload.","Reproduce with curl: POST /login/email-code/send with the same JSON and CSRF header to read the full ProblemDetail."],"exampleFix":"// before\nif (json.errors && json.errors.length) {\n  throw new Error(json.errors[0]);\n}\n// after — defensive: errors[] entries may be objects, coerce to a string message\nif (json.errors && json.errors.length) {\n  const first = json.errors[0];\n  throw new Error(\n    typeof first === \"string\" ? first : first?.message || first?.defaultMessage || JSON.stringify(first)\n  );\n}","handlingStrategy":"try-catch","validationCode":"// Validate payload + CSRF before sending the request\nfunction validateEmailCodeRequest(email, headerName, token) {\n  const errors = [];\n  if (!email || !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) {\n    errors.push(\"A valid email address is required\");\n  }\n  if (!headerName || !token) {\n    errors.push(\"Missing CSRF token — reload the login page\");\n  }\n  return errors;\n}\n// call before fetch():\nconst issues = validateEmailCodeRequest(email, headerName, token);\nif (issues.length) { showErrors(issues); return; }","typeGuard":"// RFC 7807 ProblemDetail with a non-empty errors array\nfunction hasProblemErrors(body: unknown): body is { errors: unknown[] } {\n  return (\n    typeof body === \"object\" && body !== null &&\n    Array.isArray((body as any).errors) &&\n    (body as any).errors.length > 0\n  );\n}","tryCatchPattern":"// Wrap sendRequest so the thrown message reaches the UI instead of rejecting unhandled\ntry {\n  await sendRequest();\n} catch (e) {\n  // e.message is json.errors[0] from the server; display it to the user\n  showError(e instanceof Error ? e.message : i18nResources.sendVerificationCodeFailed);\n}","preventionTips":["Always send the CSRF header populated by Thymeleaf (${_csrf.headerName}/${_csrf.token}); a blank header guarantees a 4xx.","Validate email format client-side, not just emptiness, before the POST.","Log response.status alongside the parsed body during development to distinguish 400-validation from 401/403-auth."],"tags":["authentication","csrf","rfc7807","login","email-verification","fetch"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}