Skip to content

feat(core): add complete schema validation to all authentication flows#162

Merged
halvaradop merged 4 commits into
masterfrom
feat/support-full-validation
May 21, 2026
Merged

feat(core): add complete schema validation to all authentication flows#162
halvaradop merged 4 commits into
masterfrom
feat/support-full-validation

Conversation

@halvaradop
Copy link
Copy Markdown
Member

@halvaradop halvaradop commented May 13, 2026

Description

This pull request adds complete schema validation coverage across all authentication flows in the library, including full support for:

  • Zod
  • ArkType
  • Valibot

Validation is now consistently applied throughout the authentication system, including the PATCH /session endpoint used to update session identity fields such as:

  • sub
  • email
  • name
  • image

and any additional fields defined by the configured identity schema.

Additionally, this update improves HTTP status handling for schema validation failures. Validation errors related to request parameters, search parameters, or request bodies now correctly return 422 Unprocessable Entity instead of 401 Unauthorized.

Key Changes

  • Added complete schema validation support for:
    • Zod
    • ArkType
    • Valibot
  • Added validation coverage for PATCH /session
  • Fixed HTTP status codes for validation errors:
    • 401422
  • Updated @aura-stack/router dependency from 0.6.0 to 0.7.0
  • Adopted router-level schema validation improvements introduced in @aura-stack/router@0.7.0

Related PRs

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
auth Ready Ready Preview, Comment May 21, 2026 3:37pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Warning

Rate limit exceeded

@halvaradop has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minute and 26 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f43c3ce-225b-470b-a79f-b9367a5e985c

📥 Commits

Reviewing files that changed from the base of the PR and between 387fbe1 and cc32f9c.

⛔ Files ignored due to path filters (3)
  • bun.lock is excluded by !**/*.lock
  • deno.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • package.json
  • packages/core/src/@types/config.ts
  • packages/core/src/@types/session.ts
  • packages/core/src/actions/callback/callback.ts
  • packages/core/src/actions/updateSession/updateSession.ts
  • packages/core/src/client/client.ts
  • packages/core/src/router/context.ts
  • packages/core/src/session/stateless.ts
  • packages/core/src/validator/registry.ts
  • packages/core/test/identity.test.ts
📝 Walkthrough

Walkthrough

Adds a universal validator and a schema-registry (deriveSchema, deriveSchemaWithJWT, getFullSchema, createSchemaRegistry), tightens schema type constraints/type-guards, rewires context/session/updateSession to use SchemaRegistryContext, updates tests, and adjusts minor package/TS config entries.

Changes

Schema Validation Refactoring

Layer / File(s) Summary
Dependency & build config fixes
package.json, packages/*/package.json, packages/core/tsconfig.json
Bumps/formatting of workspace package.json files and root devDependency edits; enables skipLibCheck in packages/core/tsconfig.json; updates @aura-stack/router to ^0.7.0.
Schema type surfaces and type-guards
packages/core/src/@types/config.ts, packages/core/src/@types/session.ts, packages/core/src/shared/assert.ts, packages/core/src/shared/identity.ts
Narrows IdentityConfig generic constraints, adds schemaAsPartial?, exports SchemaRegistryContext, tightens isValibotSchema predicate to ObjectSchema<any, undefined>, and introduces SchemaTypes union.
Validator adapter
packages/core/src/validator/validator.ts
Adds createValidator, ValidationResult, and SchemaAdapter to normalize validation across Zod, Valibot, and ArkType schemas.
Schema registry and derivation
packages/core/src/validator/registry.ts, packages/core/src/schema-registry.ts
Implements deriveSchema (modes: strip/passthrough/strict/partial), deriveSchemaWithJWT, getFullSchema, and expands createSchemaRegistry to return { parse, parseAsPartial, parseWithJWT, schema, schemaAsPartial, schemaWithJWT }; marks legacy schema-registry.ts for replacement.
Context, session and updateSession wiring
packages/core/src/router/context.ts, packages/core/src/session/stateless.ts, packages/core/src/actions/updateSession/updateSession.ts, packages/core/src/createAuth.ts
Replaces inline Zod-only schema wiring with SchemaRegistryContext; createContext builds schemaRegistry; session and JWT flows use registry parse/parseWithJWT/parseAsPartial; updateSession body schema is derived via getFullSchema.
Tests: identity and updateSession
packages/core/test/identity.test.ts, packages/core/test/actions/updateSession/updateSession.test.ts
Switches tests from stripUnknownKeys to deriveSchema across Zod/Valibot/Arktype, adds partial cases, and adds updateSession tests for Valibot and Arktype identity schemas.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

feature, enhancement

Poem

"I nibble schemas with a careful paw,
from Zod to Valibot and ArkType's law.
One registry grows where many once stood,
tokens tidy, validations good.
Hoppity hops — tests all pass — hurrah!" 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main objective: adding complete schema validation across authentication flows, supported by extensive validator/registry enhancements and schema handling updates throughout the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/support-full-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
packages/core/tsconfig.json (1)

8-8: ⚡ Quick win

For published packages, keep skipLibCheck disabled to catch type declaration errors.

Setting skipLibCheck to true skips type-checking of .d.ts files from dependencies, which can hide incompatible declaration-file problems. For a published core package, TypeScript's recommended practice is to keep this disabled when validating the package's own types. Instead of masking issues, address root causes like dependency conflicts.

Proposed change
-    "skipLibCheck": true,
+    "skipLibCheck": false,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/tsconfig.json` at line 8, The tsconfig currently enables
"skipLibCheck": true which hides declaration-file type errors; change the
compiler option to "skipLibCheck": false (or remove the option) in the project's
tsconfig so dependency .d.ts files are type-checked, then run the TypeScript
build/emit and fix any revealed declaration or dependency type issues (resolve
version mismatches, update types, or add necessary type overrides) until the
build passes; look for the "skipLibCheck" key in the tsconfig to locate and
update it.
packages/core/src/validator/validator.ts (1)

46-50: ⚡ Quick win

TypeBox validation provides no error details.

When TypeBox validation fails, the code returns a generic Error("Validation failed") with no details about what failed. This makes debugging difficult for users. Consider using TypeBox's error reporting utilities to provide more context.

💡 Consider using TypeBox's Errors utility for detailed validation errors
 if (IsObject(schema)) {
     const isValid = Check(schema, data)
-    return isValid
-        ? { success: true, data: data as T, error: null }
-        : { success: false, data: null, error: new Error("Validation failed") }
+    if (isValid) {
+        return { success: true, data: data as T, error: null }
+    }
+    // Import Errors from 'typebox/value' at the top
+    const errors = Errors(schema, data)
+    return { success: false, data: null, error: Array.from(errors) }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/validator/validator.ts` around lines 46 - 50, The current
IsObject(schema) branch returns a generic Error("Validation failed") when
Check(schema, data) is false; replace that generic error with a detailed
validation error using TypeBox's Errors utility: call Errors(schema, data) (or
Errors(CheckSchema, data) as appropriate) to collect the validation error items,
format or stringify those items into a concise message, and return that message
(or the error array) inside the returned error (e.g., new
Error(formattedErrors)) so the function (in
packages/core/src/validator/validator.ts around the IsObject/Check logic)
returns meaningful validation details instead of the generic message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/package.json`:
- Line 89: The package manifest was changed to bump the dependency
"@aura-stack/router" in package.json but the pnpm lockfile is out of sync; run
pnpm install at the repo root to regenerate pnpm-lock.yaml (or pnpm install
--lockfile-only if preferred), verify the updated pnpm-lock.yaml includes the
new "@aura-stack/router@^0.7.0" entry, and commit the updated lockfile alongside
the package.json change so CI no longer errors with ERR_PNPM_OUTDATED_LOCKFILE.

In `@packages/core/src/validator/registry.ts`:
- Around line 68-77: The error-handling checks inside parseAsPartial use the
wrong schema variable; update the isZodSchema checks and the AuthValidationError
cause to reference partialSchema (the schema validated by partialValidator)
instead of schema so zod errors are formatted and attached correctly—modify the
conditional calls to isZodSchema(partialSchema), use formatZodError(error) when
partialSchema is Zod, and pass cause: isZodSchema(partialSchema) ? error :
undefined in the AuthValidationError instantiation within parseAsPartial
(references: parseAsPartial, partialValidator, partialSchema, isZodSchema,
formatZodError, AuthValidationError).
- Around line 57-66: The parse function currently only formats validation errors
for Zod (using isZodSchema and formatZodError) which leaves Valibot and ArkType
errors unformatted; update parse (the block that calls validator.validate and
throws AuthValidationError) to detect Valibot and ArkType schemas (e.g., via
isValibotSchema / isArkTypeSchema or by inspecting validator/error shape),
generate human-friendly error details with appropriate formatters (e.g.,
formatValibotError, formatArkTypeError or inline formatting logic), include
those details in the JSON.stringify output instead of an empty object, and set
the AuthValidationError cause to the original error for non-Zod cases as well so
all schema types produce consistent, informative error messages.
- Line 19: The code calls the removed Zod v4 instance method schema.loose() in
the passthrough logic; replace that call with the top-level z.looseObject(...)
usage by passing the object shape (the same schema shape used before) into
z.looseObject to produce a loose object schema. Locate the passthrough branch
that references schema.loose() in registry.ts and change it to call
z.looseObject(shape) (using the original schema shape variable) so it conforms
to Zod v4's API.

In `@packages/core/src/validator/validator.ts`:
- Around line 34-44: The current error detection in validator.ts uses shape
inspection of parsed.summary; instead compute validation failure using ArkType's
API by replacing that logic with const isError = !schema.allows(data) (keep the
existing parsed = schema(data) call so you can still return the parsed error
when isError is true). Update the block guarded by isArkType(schema) to use
schema.allows(data) for the isError check instead of checking for a summary
property, preserving the return branches that use parsed for success/error
cases.

---

Nitpick comments:
In `@packages/core/src/validator/validator.ts`:
- Around line 46-50: The current IsObject(schema) branch returns a generic
Error("Validation failed") when Check(schema, data) is false; replace that
generic error with a detailed validation error using TypeBox's Errors utility:
call Errors(schema, data) (or Errors(CheckSchema, data) as appropriate) to
collect the validation error items, format or stringify those items into a
concise message, and return that message (or the error array) inside the
returned error (e.g., new Error(formattedErrors)) so the function (in
packages/core/src/validator/validator.ts around the IsObject/Check logic)
returns meaningful validation details instead of the generic message.

In `@packages/core/tsconfig.json`:
- Line 8: The tsconfig currently enables "skipLibCheck": true which hides
declaration-file type errors; change the compiler option to "skipLibCheck":
false (or remove the option) in the project's tsconfig so dependency .d.ts files
are type-checked, then run the TypeScript build/emit and fix any revealed
declaration or dependency type issues (resolve version mismatches, update types,
or add necessary type overrides) until the build passes; look for the
"skipLibCheck" key in the tsconfig to locate and update it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e04da9e7-5dc5-4d7e-ab8a-3ec168e873d8

📥 Commits

Reviewing files that changed from the base of the PR and between a32ca7d and f322d38.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • package.json
  • packages/core/package.json
  • packages/core/src/@types/config.ts
  • packages/core/src/schema-registry.ts
  • packages/core/src/shared/assert.ts
  • packages/core/src/validator/registry.ts
  • packages/core/src/validator/validator.ts
  • packages/core/test/actions/updateSession/updateSession.test.ts
  • packages/core/test/identity.test.ts
  • packages/core/tsconfig.json
💤 Files with no reviewable changes (1)
  • package.json

Comment thread packages/core/package.json
Comment thread packages/core/src/validator/registry.ts Outdated
Comment thread packages/core/src/validator/registry.ts Outdated
Comment thread packages/core/src/validator/registry.ts Outdated
Comment thread packages/core/src/validator/validator.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/core/src/@types/session.ts (1)

238-255: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace inline identity shapes with SchemaRegistryContext.

Both CreateSessionStrategyOptions.identity and JWTStrategyOptions.identity are structurally identical to the newly exported SchemaRegistryContext from @/@types/config.ts. Importing the type keeps the registry-backed identity contract in one place; right now any change to SchemaRegistryContext (e.g. adding a field) has to be hand-mirrored in two more spots, which is exactly the kind of drift the registry refactor is trying to avoid.

♻️ Proposed change
-import type { CookieStoreConfig, IdentityConfig, InternalLogger, JoseInstance } from "`@/`@types/config.ts"
+import type { CookieStoreConfig, InternalLogger, JoseInstance, SchemaRegistryContext } from "`@/`@types/config.ts"
@@
 export interface CreateSessionStrategyOptions<Identity extends Identities> {
     config?: SessionConfig
     jose: JoseInstance<FromShapeToObject<Identity> & User>
     cookies: () => CookieStoreConfig
     logger?: InternalLogger
-    identity: {
-        schemaRegistry: ReturnType<typeof createSchemaRegistry>
-        skipValidation?: boolean
-        unknownKeys: "passthrough" | "strict" | "strip"
-    }
+    identity: SchemaRegistryContext
 }
@@
 export interface JWTStrategyOptions<DefaultUser extends User = User> {
     config?: StatelessStrategyConfig
     jose: JoseInstance<DefaultUser>
     logger?: InternalLogger
     cookies: () => CookieStoreConfig
-    identity: {
-        schemaRegistry: ReturnType<typeof createSchemaRegistry>
-        skipValidation?: boolean
-        unknownKeys: "passthrough" | "strict" | "strip"
-    }
+    identity: SchemaRegistryContext
 }

After this, the local createSchemaRegistry import (line 10) and the leftover IdentityConfig import (line 5) are no longer needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/`@types/session.ts around lines 238 - 255, Replace the
inline identity object types on CreateSessionStrategyOptions.identity and
JWTStrategyOptions.identity with the centralized SchemaRegistryContext type
exported from `@/`@types/config.ts: import SchemaRegistryContext and use it as the
type for both identity properties instead of the current inline shape (which
references createSchemaRegistry ReturnType and skipValidation/unknownKeys).
After switching the types, remove the now-unnecessary local import of
createSchemaRegistry and the leftover IdentityConfig import so the file only
references the single shared SchemaRegistryContext.
packages/core/src/validator/registry.ts (1)

15-41: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Asymmetric partial mode across libraries — Zod branch additionally wraps in .optional().

For mode === "partial":

  • Zod (Line 22): schema.partial().optional() — the whole object also becomes optional, so undefined validates successfully and parseAsPartial(undefined) returns undefined instead of {}.
  • Valibot (Line 31): valibot.partial(schema) — keys partial, top-level still required.
  • ArkType (Line 40): schema.partial() — keys partial, top-level still required.

This divergence will surface as different runtime behavior between identity providers (e.g., Zod silently passes undefined while Valibot/ArkType reject it). Drop the trailing .optional() unless the optional-root semantics are intentional and documented.

🐛 Proposed fix
-                : schema.partial().optional()
+                : schema.partial()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/validator/registry.ts` around lines 15 - 41, The Zod branch
in registry's schema normalization makes the entire object optional by returning
schema.partial().optional(), causing parseAsPartial(undefined) to succeed while
Valibot/ArkType still require a top-level object; update the isZodSchema branch
to drop the trailing .optional() so it returns schema.partial() (keeping other
modes intact) to match the valibot/ark behavior and ensure consistent semantics
across isZodSchema, isValibotSchema, and isArkType handling.
🧹 Nitpick comments (5)
packages/core/src/@types/config.ts (1)

289-294: ⚡ Quick win

Reuse SchemaTypes for the generic constraint.

The ZodObject<any> | ObjectSchema<any, undefined> | Type<{}> union here is identical to the new SchemaTypes exported from @/shared/identity.ts. Reusing it removes the duplication and keeps the schema-type surface in one place if it ever needs to grow.

♻️ Proposed change
-import { Identities, UserIdentity } from "`@/shared/identity.ts`"
+import { Identities, UserIdentity, type SchemaTypes } from "`@/shared/identity.ts`"
@@
-export interface IdentityConfig<Schema extends ZodObject<any> | ObjectSchema<any, undefined> | Type<{}> = typeof UserIdentity> {
+export interface IdentityConfig<Schema extends SchemaTypes = typeof UserIdentity> {
     schema?: Schema
     schemaAsPartial?: Schema
     skipValidation?: boolean
     unknownKeys?: "passthrough" | "strict" | "strip"
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/`@types/config.ts around lines 289 - 294, Replace the
duplicated union type in the IdentityConfig generic constraint with the
centralized SchemaTypes export: import SchemaTypes from "`@/shared/identity.ts`"
(or the named export) and change IdentityConfig<Schema extends ZodObject<any> |
ObjectSchema<any, undefined> | Type<{}> = typeof UserIdentity> to use
SchemaTypes (e.g., IdentityConfig<Schema extends SchemaTypes = typeof
UserIdentity>); update the imports at the top of the file to reference
SchemaTypes and ensure the default remains typeof UserIdentity.
packages/core/test/actions/updateSession/updateSession.test.ts (1)

60-148: ⚡ Quick win

Reduce duplication and add a negative case for valibot/arktype tests.

The three "updates user session with X schema" tests share ~30 lines of identical setup and assertions; only schema (and the resulting handlers/jose) differ. Consider parameterizing with test.each([["valibot", UserIdentityValibot], ["arktype", UserIdentityArkType]]) to keep them in lockstep.

Also, none of the new tests assert that the valibot/arktype validators actually reject invalid payloads — they only exercise the happy path, which the existing zod test already covers. A single negative-path test per validator (e.g., invalid email or extra disallowed field under strict) would justify the added test surface and guard against the validator silently no-op'ing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/test/actions/updateSession/updateSession.test.ts` around lines
60 - 148, Consolidate the two near-identical tests by parameterizing them with
test.each over the schemas (UserIdentityValibot and UserIdentityArkType) and
reusing the shared setup (createAuth, jose.encodeJWT, createCSRF,
handlers.PATCH) and assertions; then add one negative test per validator (e.g.,
with the same test.each) that sends an invalid user payload (bad email format or
an extra disallowed field) to handlers.PATCH and asserts a validation failure
response (HTTP 400 or error body) to ensure UserIdentityValibot and
UserIdentityArkType actually reject invalid input.
packages/core/src/validator/registry.ts (2)

11-14: ⚖️ Poor tradeoff

: any return types erase library-specific schema inference.

deriveSchema, deriveSchemaWithJWT, and getFullSchema all declare : any, which propagates through createSchemaRegistry's returned schema/schemaAsPartial/schemaWithJWT and downstream consumers (SchemaRegistryContext, callers in context/session). Even returning Schema (or a narrow per-branch return type) would preserve enough information for consumers to keep type-safety on the registry surface.

Also applies to: 48-48, 83-83

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/validator/registry.ts` around lines 11 - 14, The current :
any return types on deriveSchema, deriveSchemaWithJWT, and getFullSchema erase
type inference; change their signatures to return the generic Schema (or a
properly narrowed mapped type based on the mode) rather than any so the registry
preserves type information—for example, update deriveSchema<Schema extends
SchemaTypes>(schema: Schema, mode: ...) to return Schema or a mode-specific
mapped Schema type; do the same for deriveSchemaWithJWT and getFullSchema so
createSchemaRegistry's returned properties (schema, schemaAsPartial,
schemaWithJWT) and downstream consumers (SchemaRegistryContext and
context/session callers) retain compile-time schema types instead of any.

125-180: ⚡ Quick win

Extract the duplicated error-formatting block into a helper.

The 8-line errorDetails discrimination + JSON.stringify + AuthValidationError throw is repeated almost verbatim in parse, parseAsPartial, and parseWithJWT. Drift between copies has already started — note that parseAsPartial checks isZodSchema(schema) for errorDetails (Lines 148/150/152) while the cause correctly checks schemaAsPartial (Line 157). Centralizing prevents future inconsistencies and trims ~30 lines.

♻️ Proposed helper
+    const throwValidationError = (activeSchema: SchemaTypes, error: unknown): never => {
+        let errorDetails: unknown = {}
+        if (isZodSchema(activeSchema)) {
+            errorDetails = formatZodError(error as any)
+        } else if (isValibotSchema(activeSchema)) {
+            errorDetails = { issues: error }
+        } else if (isArkType(activeSchema)) {
+            errorDetails = { error }
+        }
+        throw new AuthValidationError(
+            "INVALID_IDENTITY_VALIDATION_FAILED",
+            JSON.stringify(errorDetails, null, 2),
+            { cause: isZodSchema(activeSchema) ? error : undefined }
+        )
+    }

Then each parse function becomes:

if (!success) throwValidationError(schemaAsPartial, error)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/validator/registry.ts` around lines 125 - 180, There’s
duplicated error-formatting + throw logic in parse, parseAsPartial, and
parseWithJWT; extract it into a helper (e.g., throwValidationError) that accepts
the schema to check (schema | schemaAsPartial | schemaWithJWT) and the validator
error, builds errorDetails using isZodSchema/formatZodError, isValibotSchema,
isArkType, JSON.stringifys the details, and throws AuthValidationError with
cause set only when isZodSchema(schemaArg) is true; then replace each failure
branch in parse, parseAsPartial, and parseWithJWT with a single call like
throwValidationError(schemaOrPartialOrWithJWT, error) so the cause check uses
the correct schema reference and removes the duplicated block.
packages/core/src/router/context.ts (1)

22-25: ⚡ Quick win

Normalize identity defaults once before building the registry.

schemaRegistry is created from raw optional config values, but ctx.identity publishes defaulted values. That makes the registry and the exposed flags easy to desync the next time one default changes.

♻️ Proposed refactor
+    const unknownKeys = config?.identity?.unknownKeys ?? "strip"
+    const skipValidation = config?.identity?.skipValidation ?? false
+
     const schemaRegistry = createSchemaRegistry({
         schema: config?.identity?.schema,
-        skipValidation: config?.identity?.skipValidation,
-        unknownKeys: config?.identity?.unknownKeys,
+        skipValidation,
+        unknownKeys,
     })
@@
         identity: {
             schemaRegistry,
-            unknownKeys: config?.identity?.unknownKeys ?? "strip",
-            skipValidation: config?.identity?.skipValidation ?? false,
+            unknownKeys,
+            skipValidation,
         },

Also applies to: 41-43

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/router/context.ts` around lines 22 - 25, Normalize the
identity config once into a single defaulted object (e.g., let
normalizedIdentity = { schema: ..., skipValidation: ..., unknownKeys: ... } or a
helper like normalizeIdentity(config?.identity)) and use that normalizedIdentity
when constructing schemaRegistry via createSchemaRegistry and when assigning
ctx.identity (instead of reading raw optional fields). Update both the
createSchemaRegistry call (currently creating schemaRegistry) and the later uses
around lines referenced (41-43) to consume the same normalizedIdentity so the
published ctx.identity flags and the registry stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/actions/updateSession/updateSession.ts`:
- Around line 7-12: The current config function calls
getFullSchema(identity.schemaRegistry.schemaAsPartial!) unguarded and will throw
when schemaAsPartial is absent; update the config in the exported config
function to first check whether identity.schemaRegistry?.schemaAsPartial exists
and only call getFullSchema when present (else set body schema to undefined or
an empty/fallback schema), then pass that conditional bodySchema into
createEndpointConfig (symbols: config, identity.schemaRegistry.schemaAsPartial,
getFullSchema, bodySchema, createEndpointConfig).

In `@packages/core/src/session/stateless.ts`:
- Around line 125-127: The early-return when updateExpires(...) yields no
expiresAt currently returns the incoming request headers variable `headers`,
which can echo request headers back; change this to return the response header
set used elsewhere (do not return `headers`) — e.g., return an empty response
headers object ({} or the module's response-headers variable) with the same
session payload (session.expires, userClaims). Update the return in the branch
containing `updateExpires({ exp: parsedClaims.exp })` so it returns `{ session:
{ expires: session.expires, user: userClaims }, headers: <response headers
object> }` instead of the incoming `headers`.
- Around line 115-117: The code calls
identity.schemaRegistry.parseWithJWT(claims) and then re-runs
identity.schemaRegistry.parse(parsedClaims), which fails strict unknownKeys
because parsedClaims still contains JWT fields (exp/iat/mexp). Fix by NOT
re-parsing the full parsedClaims; either pass only the application/user payload
into parse (e.g., extract the user-specific object from parsedClaims and call
identity.schemaRegistry.parse(userPayload)) or strip JWT fields (exp, iat, mexp,
nbf, jti) from parsedClaims before calling identity.schemaRegistry.parse;
alternatively simply use the result of parseWithJWT as the final user claims and
remove the second call to identity.schemaRegistry.parse to avoid the strict
unknownKeys error.
- Around line 180-186: The verified JWT claims are being validated with
identity.schemaRegistry.parse (via defaultPayload) which can strip or reject
standard JWT fields (exp/iat/mexp); instead use the JWT-aware/partial parser so
those JWT fields are preserved when refreshing claims. Change the logic that
sets defaultPayload to parse the verified claims with
identity.schemaRegistry.parseAsPartial (or the schema registry's JWT-aware
parse) rather than parse, keeping the subsequent sessionPayload logic intact
(i.e., use identity.schemaRegistry.parseAsPartial for claims when
identity.skipValidation is false, and keep identity.skipValidation branch
as-is).

In `@packages/core/src/validator/registry.ts`:
- Around line 137-139: The AuthValidationError currently sets cause only for Zod
schemas (throw new AuthValidationError(..., { cause: isZodSchema(schema) ? error
: undefined })); update those throws to always pass the original error object as
the cause (i.e., { cause: error }) so Valibot and ArkType errors retain their
original error instances for instanceof checks and structured logging; make the
same change in the parseAsPartial and parseWithJWT error-throwing blocks so
every validation path attaches the original error as cause.

In `@packages/core/test/identity.test.ts`:
- Line 285: The test titles inside the describe("createSchemaRegistry", ...)
block are misleading: they say "schema with '<mode>' deriveSchema" but the tests
call createSchemaRegistry({ unknownKeys }) not deriveSchema; update each test
title (including the zod, valibot, and arktype variants) to reflect the actual
API by replacing "deriveSchema" with "unknownKeys" (e.g., "zod schema with
'strip' unknownKeys" or similar), keeping the same mode strings and referencing
createSchemaRegistry so failures point to the correct function.

---

Outside diff comments:
In `@packages/core/src/`@types/session.ts:
- Around line 238-255: Replace the inline identity object types on
CreateSessionStrategyOptions.identity and JWTStrategyOptions.identity with the
centralized SchemaRegistryContext type exported from `@/`@types/config.ts: import
SchemaRegistryContext and use it as the type for both identity properties
instead of the current inline shape (which references createSchemaRegistry
ReturnType and skipValidation/unknownKeys). After switching the types, remove
the now-unnecessary local import of createSchemaRegistry and the leftover
IdentityConfig import so the file only references the single shared
SchemaRegistryContext.

In `@packages/core/src/validator/registry.ts`:
- Around line 15-41: The Zod branch in registry's schema normalization makes the
entire object optional by returning schema.partial().optional(), causing
parseAsPartial(undefined) to succeed while Valibot/ArkType still require a
top-level object; update the isZodSchema branch to drop the trailing .optional()
so it returns schema.partial() (keeping other modes intact) to match the
valibot/ark behavior and ensure consistent semantics across isZodSchema,
isValibotSchema, and isArkType handling.

---

Nitpick comments:
In `@packages/core/src/`@types/config.ts:
- Around line 289-294: Replace the duplicated union type in the IdentityConfig
generic constraint with the centralized SchemaTypes export: import SchemaTypes
from "`@/shared/identity.ts`" (or the named export) and change
IdentityConfig<Schema extends ZodObject<any> | ObjectSchema<any, undefined> |
Type<{}> = typeof UserIdentity> to use SchemaTypes (e.g., IdentityConfig<Schema
extends SchemaTypes = typeof UserIdentity>); update the imports at the top of
the file to reference SchemaTypes and ensure the default remains typeof
UserIdentity.

In `@packages/core/src/router/context.ts`:
- Around line 22-25: Normalize the identity config once into a single defaulted
object (e.g., let normalizedIdentity = { schema: ..., skipValidation: ...,
unknownKeys: ... } or a helper like normalizeIdentity(config?.identity)) and use
that normalizedIdentity when constructing schemaRegistry via
createSchemaRegistry and when assigning ctx.identity (instead of reading raw
optional fields). Update both the createSchemaRegistry call (currently creating
schemaRegistry) and the later uses around lines referenced (41-43) to consume
the same normalizedIdentity so the published ctx.identity flags and the registry
stay in sync.

In `@packages/core/src/validator/registry.ts`:
- Around line 11-14: The current : any return types on deriveSchema,
deriveSchemaWithJWT, and getFullSchema erase type inference; change their
signatures to return the generic Schema (or a properly narrowed mapped type
based on the mode) rather than any so the registry preserves type
information—for example, update deriveSchema<Schema extends SchemaTypes>(schema:
Schema, mode: ...) to return Schema or a mode-specific mapped Schema type; do
the same for deriveSchemaWithJWT and getFullSchema so createSchemaRegistry's
returned properties (schema, schemaAsPartial, schemaWithJWT) and downstream
consumers (SchemaRegistryContext and context/session callers) retain
compile-time schema types instead of any.
- Around line 125-180: There’s duplicated error-formatting + throw logic in
parse, parseAsPartial, and parseWithJWT; extract it into a helper (e.g.,
throwValidationError) that accepts the schema to check (schema | schemaAsPartial
| schemaWithJWT) and the validator error, builds errorDetails using
isZodSchema/formatZodError, isValibotSchema, isArkType, JSON.stringifys the
details, and throws AuthValidationError with cause set only when
isZodSchema(schemaArg) is true; then replace each failure branch in parse,
parseAsPartial, and parseWithJWT with a single call like
throwValidationError(schemaOrPartialOrWithJWT, error) so the cause check uses
the correct schema reference and removes the duplicated block.

In `@packages/core/test/actions/updateSession/updateSession.test.ts`:
- Around line 60-148: Consolidate the two near-identical tests by parameterizing
them with test.each over the schemas (UserIdentityValibot and
UserIdentityArkType) and reusing the shared setup (createAuth, jose.encodeJWT,
createCSRF, handlers.PATCH) and assertions; then add one negative test per
validator (e.g., with the same test.each) that sends an invalid user payload
(bad email format or an extra disallowed field) to handlers.PATCH and asserts a
validation failure response (HTTP 400 or error body) to ensure
UserIdentityValibot and UserIdentityArkType actually reject invalid input.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 38b792f2-d173-44e4-a195-1fae981d5f9c

📥 Commits

Reviewing files that changed from the base of the PR and between f322d38 and 387fbe1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • package.json
  • packages/core/src/@types/config.ts
  • packages/core/src/@types/session.ts
  • packages/core/src/actions/callback/callback.ts
  • packages/core/src/actions/updateSession/updateSession.ts
  • packages/core/src/createAuth.ts
  • packages/core/src/router/context.ts
  • packages/core/src/session/stateless.ts
  • packages/core/src/shared/identity.ts
  • packages/core/src/validator/registry.ts
  • packages/core/src/validator/validator.ts
  • packages/core/test/actions/updateSession/updateSession.test.ts
  • packages/core/test/identity.test.ts
  • packages/elysia/package.json
  • packages/express/package.json
  • packages/hono/package.json
  • packages/next/package.json
  • packages/react-router/package.json
  • packages/react/package.json
✅ Files skipped from review due to trivial changes (8)
  • packages/core/src/actions/callback/callback.ts
  • packages/next/package.json
  • packages/react/package.json
  • packages/express/package.json
  • packages/hono/package.json
  • packages/core/src/createAuth.ts
  • packages/elysia/package.json
  • packages/react-router/package.json

Comment thread packages/core/src/actions/updateSession/updateSession.ts
Comment thread packages/core/src/session/stateless.ts Outdated
Comment thread packages/core/src/session/stateless.ts Outdated
Comment thread packages/core/src/session/stateless.ts
Comment thread packages/core/src/validator/registry.ts Outdated
Comment thread packages/core/test/identity.test.ts Outdated
@halvaradop halvaradop merged commit e98f9f2 into master May 21, 2026
7 checks passed
@halvaradop halvaradop deleted the feat/support-full-validation branch May 21, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant