Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@
"@types/node": "catalog:node",
"@vitest/coverage-v8": "catalog:vitest",
"eslint": "^9.35.0",
"oxfmt": "^0.41.0",
"oxlint": "^1.56.0",
"tsdown": "^0.21.8",
"turbo": "^2.7.5",
"typescript": "catalog:typescript",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"license": "MIT",
"dependencies": {
"@aura-stack/jose": "workspace:*",
"@aura-stack/router": "^0.6.0",
"@aura-stack/router": "^0.7.0",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only verification of manifest/lockfile drift related to current CI failure.

set -euo pipefail

echo "== package specifiers mentioning typebox/router =="
rg -n --glob '**/package.json' '"(`@aura-stack/router`|typebox)"\s*:'

echo
echo "== lockfile entries mentioning typebox/router =="
rg -n '(`@aura-stack/router`|typebox)' pnpm-lock.yaml

echo
echo "== CI-sensitive lockfile drift indicators =="
rg -n 'specifiers:|dependencies:' pnpm-lock.yaml | head -n 40

Repository: aura-stack-ts/auth

Length of output: 2518


Regenerate pnpm-lock.yaml after dependency version changes.

Line 89 updates @aura-stack/router to ^0.7.0, but the lockfile must be synchronized. CI currently fails with ERR_PNPM_OUTDATED_LOCKFILE because frozen-lockfile installations cannot proceed until the manifest and lockfile are in sync. Run pnpm install to regenerate the lockfile and resolve this blocker.

🧰 Tools
🪛 GitHub Actions: CI / 0_Node.js.txt

[error] Lockfile specifiers do not match package.json specifiers during pnpm install --frozen-lockfile (ERR_PNPM_OUTDATED_LOCKFILE).

🤖 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/package.json` at 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.

"arktype": "^2.2.0",
"valibot": "^1.3.1",
"zod": "catalog:zod-v4"
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/@types/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Type } from "arktype"
import { ObjectSchema } from "valibot"
import { createJoseInstance } from "@/jose.ts"
import { createAuthAPI } from "@/api/createApi.ts"
import { createLogEntry } from "@/shared/logger.ts"
Expand All @@ -8,8 +10,6 @@ import type { SerializeOptions } from "@aura-stack/router/cookie"
import type { ConfigSchema, FromShapeToObject, Prettify } from "@/@types/utility.ts"
import type { OAuthProviderCredentials, OAuthProviderRecord } from "@/@types/oauth.ts"
import type { JWTKey, SessionConfig, SessionStrategy, User } from "@/@types/session.ts"
import { ObjectSchema } from "valibot"
import { Type } from "arktype"

/**
* Main configuration interface for Aura Auth.
Expand Down Expand Up @@ -285,7 +285,7 @@ export interface InternalLogger {
* Identity validation settings used when building session strategy and OAuth profile mapping.
* Controls the Zod schema and how unknown keys are handled on user objects.
*/
export interface IdentityConfig<Schema extends ZodObject<any> | ObjectSchema<any, undefined | Type<any>> = typeof UserIdentity> {
export interface IdentityConfig<Schema extends ZodObject<any> | ObjectSchema<any, undefined> | Type<{}> = typeof UserIdentity> {
schema?: Schema
skipValidation?: boolean
unknownKeys?: "passthrough" | "strict" | "strip"
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/schema-registry.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* This file will be replaced by `validator/registry.ts`
*
*/
import { isArkType, isValibotSchema, isZodSchema } from "@/shared/assert.ts"
import { formatZodError } from "@/shared/utils.ts"
import { UserIdentity } from "@/shared/identity.ts"
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/shared/assert.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Type } from "arktype"
import { ZodObject, ZodTypeAny } from "zod"
import { BaseSchema, ObjectSchema } from "valibot"
import { equals, patternToRegex } from "@/shared/utils.ts"
import type {
AsymmetricKeyPair,
Expand All @@ -9,9 +12,6 @@ import type {
SessionConfig,
} from "@/@types/index.ts"
import type { JWK } from "@aura-stack/jose/jose"
import { BaseSchema } from "valibot"
import { ZodObject, ZodTypeAny } from "zod"
import { Type } from "arktype"

export const isFalsy = (value: unknown): boolean => {
return value === false || value === 0 || value === "" || value === null || value === undefined || Number.isNaN(value)
Expand Down Expand Up @@ -177,7 +177,7 @@ export const isJWKFormattedKey = (value: unknown): value is JWK => {
return typeof value === "object" && value !== null && "kty" in value && typeof (value as any).kty === "string"
}

export const isValibotSchema = (value: unknown): value is BaseSchema<any, any, any> => {
export const isValibotSchema = (value: unknown): value is ObjectSchema<any, undefined> => {
return typeof value === "object" && value !== null && "~run" in value && typeof (value as any)["~run"] === "function"
}

Expand All @@ -186,7 +186,7 @@ export const isValibotEntries = (value: unknown): value is Record<string, BaseSc
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
Object.values(value).length > 0 && // optional but useful
Object.values(value).length > 0 &&
Object.values(value).every(isValibotSchema)
)
}
Expand Down
80 changes: 80 additions & 0 deletions packages/core/src/validator/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { formatZodError } from "@/shared/utils.ts"
import { UserIdentity } from "@/shared/identity.ts"
import { IdentityConfig } from "@/@types/config.ts"
import { AuthValidationError } from "@/shared/errors.ts"
import { createValidator } from "@/validator/validator.ts"
import { isArkType, isValibotSchema, isZodSchema } from "@/shared/assert.ts"
import { strictObject, partial, looseObject, type ObjectSchema, object } from "valibot"
import type { Type } from "arktype"
import type { ZodObject } from "zod/v4"

export const deriveSchema = <T extends ZodObject<any> | ObjectSchema<any, undefined> | Type<{}>>(
schema: T,
mode: "strip" | "passthrough" | "strict" | "partial" = "strip"
): any => {
if (isZodSchema(schema)) {
return mode === "strip"
? schema.strip()
: mode === "passthrough"
? schema.loose()
Comment thread
halvaradop marked this conversation as resolved.
: mode === "strict"
? schema.strict()
: schema.partial()
}
if (isValibotSchema(schema)) {
return mode === "strip"
? object(schema.entries)
: mode === "passthrough"
? looseObject(schema.entries)
: mode === "strict"
? strictObject(schema.entries)
: partial(schema as ObjectSchema<any, undefined>)
}
if (isArkType(schema)) {
return mode === "strip"
? schema.onUndeclaredKey("delete")
: mode === "passthrough"
? schema.onUndeclaredKey("ignore")
: mode === "strict"
? schema.onUndeclaredKey("reject")
: schema.partial()
}
throw new AuthValidationError(
"INVALID_IDENTITY_VALIDATION_FAILED",
`Unsupported schema mode configuration. Valid options are: "strip", "passthrough", "strict" and "partial".`
)
}

export const createSchemaRegistry = <Identity extends ZodObject<any> | ObjectSchema<any, undefined> | Type<{}>>(
config: IdentityConfig<Identity>
) => {
const schema = deriveSchema(config.schema ?? UserIdentity, config.unknownKeys)
const partialSchema = deriveSchema(config.schema ?? UserIdentity, "partial")

const validator = createValidator(schema)
const partialValidator = createValidator(partialSchema)

const parse = async (data: unknown = {}) => {
const { data: output, success, error } = validator.validate(data)
if (!success) {
const details = JSON.stringify(isZodSchema(schema) ? formatZodError(error) : {}, null, 2)
throw new AuthValidationError("INVALID_IDENTITY_VALIDATION_FAILED", details, {
cause: isZodSchema(schema) ? error : undefined,
})
}
return output
}
Comment thread
halvaradop marked this conversation as resolved.

const parseAsPartial = async (data: unknown = {}) => {
const { data: output, success, error } = partialValidator.validate(data)
if (!success) {
const details = JSON.stringify(isZodSchema(schema) ? formatZodError(error) : {}, null, 2)
throw new AuthValidationError("INVALID_IDENTITY_VALIDATION_FAILED", details, {
cause: isZodSchema(schema) ? error : undefined,
})
}
return output
}
Comment thread
halvaradop marked this conversation as resolved.

return { parse, parseAsPartial }
}
58 changes: 58 additions & 0 deletions packages/core/src/validator/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { IsObject } from "typebox"
import { safeParse } from "valibot"
import { Check } from "typebox/value"
import { isValibotSchema, isZodSchema, isArkType } from "@/shared/assert.ts"

export type ValidationResult<T> = { success: true; data: T; error: null } | { success: false; data: null; error: any }

export interface SchemaAdapter<T> {
validate: (data: unknown) => ValidationResult<T>
}

/**
* Universal wrapper for Zod, Valibot, ArkType, etc.
*/
export const createValidator = <T>(schema: any): SchemaAdapter<T> => {
if (!isZodSchema(schema) && !isValibotSchema(schema) && !isArkType(schema) && !IsObject(schema)) {
throw new Error("Unsupported schema type")
}
return {
validate: (data: unknown): ValidationResult<T> => {
try {
if (isZodSchema(schema)) {
const parsed = schema.safeParse(data)
return parsed.success
? { success: true, data: parsed.data as T, error: null }
: { success: false, data: null, error: parsed.error }
}
if (isValibotSchema(schema)) {
const parsed = safeParse(schema, data)
return parsed.success
? { success: true, data: parsed.output as T, error: null }
: { success: false, data: null, error: parsed.issues }
}
if (isArkType(schema)) {
const parsed = schema(data)
const isError =
parsed !== null &&
typeof parsed === "object" &&
"summary" in parsed &&
typeof (parsed as any).summary === "string"

return isError
? { success: false, data: null, error: parsed }
: { success: true, data: parsed as T, error: null }
Comment thread
halvaradop marked this conversation as resolved.
}
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") }
}
return { success: false, data: null, error: new Error("Unsupported schema type") }
} catch (e) {
return { success: false, data: null, error: e }
}
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe("updateSession action", () => {
body: JSON.stringify({}),
})
)
expect(response.status).toBe(401)
expect(response.status).toBe(422)
expect(await response.json()).toMatchObject({
session: null,
success: false,
Expand Down
18 changes: 9 additions & 9 deletions packages/core/test/identity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createIdentity, InferUser, UserIdentity, UserIdentityArkType, UserIdent
import { z } from "zod/v4"
import * as valibot from "valibot"
import { createAuth } from "@/createAuth.ts"
import { createSchemaRegistry, stripUnknownKeys } from "@/schema-registry.ts"
import { createSchemaRegistry, deriveSchema } from "@/validator/registry.ts"
import { type } from "arktype"

describe("createIdentity", () => {
Expand Down Expand Up @@ -143,7 +143,7 @@ describe("stripUnknownKeys", () => {

describe("zod schemas", () => {
test("zod schema with 'strip' unknownKeys", () => {
const schema = stripUnknownKeys(zodSchema, "strip")
const schema = deriveSchema(zodSchema, "strip")
expect(schema.safeParse(payload)).toMatchObject({
success: true,
data: {
Expand All @@ -155,7 +155,7 @@ describe("stripUnknownKeys", () => {
})

test("zod schema with 'passthrough' unknownKeys", () => {
const schema = stripUnknownKeys(zodSchema, "passthrough")
const schema = deriveSchema(zodSchema, "passthrough")
expect(schema.safeParse(payload)).toMatchObject({
success: true,
data: {
Expand All @@ -168,7 +168,7 @@ describe("stripUnknownKeys", () => {
})

test("zod schema with 'strict' unknownKeys", () => {
const schema = stripUnknownKeys(zodSchema, "strict")
const schema = deriveSchema(zodSchema, "strict")
expect(schema.safeParse(payload)).toMatchObject({
success: false,
})
Expand All @@ -177,7 +177,7 @@ describe("stripUnknownKeys", () => {

describe("valibot schemas", () => {
test("valibot schema with 'strip' unknownKeys", () => {
const schema = stripUnknownKeys(valibotSchema, "strip")
const schema = deriveSchema(valibotSchema, "strip")
expect(valibot.safeParse(schema, payload)).toMatchObject({
success: true,
output: {
Expand All @@ -189,7 +189,7 @@ describe("stripUnknownKeys", () => {
})

test("valibot schema with 'passthrough' unknownKeys", () => {
const schema = stripUnknownKeys(valibotSchema, "passthrough")
const schema = deriveSchema(valibotSchema, "passthrough")
expect(valibot.safeParse(schema, payload)).toMatchObject({
success: true,
output: {
Expand All @@ -202,7 +202,7 @@ describe("stripUnknownKeys", () => {
})

test("valibot schema with 'strict' unknownKeys", () => {
const schema = stripUnknownKeys(valibotSchema, "strict")
const schema = deriveSchema(valibotSchema, "strict")
expect(valibot.safeParse(schema, payload)).toMatchObject({
success: false,
})
Expand All @@ -211,7 +211,7 @@ describe("stripUnknownKeys", () => {

describe("arktype schemas", () => {
test("arktype schema with 'strip' unknownKeys", () => {
const Schema = stripUnknownKeys(arktypeSchema, "strip")
const Schema = deriveSchema(arktypeSchema, "strip")

const out = Schema(payload)
expect(out).toMatchObject({
Expand All @@ -222,7 +222,7 @@ describe("stripUnknownKeys", () => {
})

test("arktype schema with 'passthrough' unknownKeys", () => {
const Schema = stripUnknownKeys(arktypeSchema, "passthrough")
const Schema = deriveSchema(arktypeSchema, "passthrough")
const out = Schema(payload)
expect(out).toMatchObject({
sub: "user123",
Expand Down
1 change: 1 addition & 0 deletions packages/core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"@/*": ["./src/*"],
"@test/*": ["./test/*"]
},
"skipLibCheck": true,
"allowImportingTsExtensions": true,
"noEmit": true
},
Expand Down
Loading
Loading