Compare commits
8 Commits
@hono/zod-
...
main
Author | SHA1 | Date |
---|---|---|
|
0dc6a1f5e6 | |
|
d89fed7eec | |
|
b24925168a | |
|
2a46bfbba0 | |
|
28601b311b | |
|
5a3cbc4cc6 | |
|
e835cf6183 | |
|
2339eefa1c |
|
@ -1,5 +1,11 @@
|
||||||
# @hono/auth-js
|
# @hono/auth-js
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- [#1324](https://github.com/honojs/middleware/pull/1324) [`d89fed7eecfa151c18b0a8cf95dae1dfe83dfec2`](https://github.com/honojs/middleware/commit/d89fed7eecfa151c18b0a8cf95dae1dfe83dfec2) Thanks [@jamestalmage](https://github.com/jamestalmage)! - Allow async authjs Config
|
||||||
|
|
||||||
## 1.0.17
|
## 1.0.17
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@hono/auth-js",
|
"name": "@hono/auth-js",
|
||||||
"version": "1.0.17",
|
"version": "1.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@hono/auth-js",
|
"name": "@hono/auth-js",
|
||||||
"version": "1.0.17",
|
"version": "1.1.0",
|
||||||
"description": "A third-party Auth js middleware for Hono",
|
"description": "A third-party Auth js middleware for Hono",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
@ -47,6 +47,27 @@ describe('Config', () => {
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should allow async ConfigHandler', async () => {
|
||||||
|
globalThis.process.env = { AUTH_SECRET: 'secret' }
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
'/*',
|
||||||
|
initAuthConfig(async () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1))
|
||||||
|
return {
|
||||||
|
basePath: '/api/auth',
|
||||||
|
providers: [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
app.use('/api/auth/*', authHandler())
|
||||||
|
const req = new Request('http://localhost/api/auth/signin')
|
||||||
|
const res = await app.request(req)
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should return 401 is if auth cookie is invalid or missing', async () => {
|
it('Should return 401 is if auth cookie is invalid or missing', async () => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ export type AuthUser = {
|
||||||
|
|
||||||
export interface AuthConfig extends Omit<AuthConfigCore, 'raw'> {}
|
export interface AuthConfig extends Omit<AuthConfigCore, 'raw'> {}
|
||||||
|
|
||||||
export type ConfigHandler = (c: Context) => AuthConfig
|
export type ConfigHandler = (c: Context) => AuthConfig | Promise<AuthConfig>
|
||||||
|
|
||||||
export function setEnvDefaults(env: AuthEnv, config: AuthConfig): void {
|
export function setEnvDefaults(env: AuthEnv, config: AuthConfig): void {
|
||||||
config.secret ??= env.AUTH_SECRET
|
config.secret ??= env.AUTH_SECRET
|
||||||
|
@ -118,7 +118,7 @@ export function verifyAuth(): MiddlewareHandler {
|
||||||
|
|
||||||
export function initAuthConfig(cb: ConfigHandler): MiddlewareHandler {
|
export function initAuthConfig(cb: ConfigHandler): MiddlewareHandler {
|
||||||
return async (c, next) => {
|
return async (c, next) => {
|
||||||
const config = cb(c)
|
const config = await cb(c)
|
||||||
c.set('authConfig', config)
|
c.set('authConfig', config)
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
# @hono/standard-validator
|
# @hono/standard-validator
|
||||||
|
|
||||||
|
## 0.1.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1282](https://github.com/honojs/middleware/pull/1282) [`2a46bfbba0695ae713b58211fd5e83a08b829ad6`](https://github.com/honojs/middleware/commit/2a46bfbba0695ae713b58211fd5e83a08b829ad6) Thanks [@MonsterDeveloper](https://github.com/MonsterDeveloper)! - Fix cookies output for arktype in standard schema validator
|
||||||
|
|
||||||
## 0.1.3
|
## 0.1.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -26,7 +26,12 @@ const querySortSchema = type({
|
||||||
order: "'asc'|'desc'",
|
order: "'asc'|'desc'",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const headerSchema = type({
|
||||||
|
'user-agent': 'string',
|
||||||
|
})
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
headerSchema,
|
||||||
idJSONSchema,
|
idJSONSchema,
|
||||||
personJSONSchema,
|
personJSONSchema,
|
||||||
postJSONSchema,
|
postJSONSchema,
|
||||||
|
|
|
@ -28,7 +28,12 @@ const querySortSchema = object({
|
||||||
order: picklist(['asc', 'desc']),
|
order: picklist(['asc', 'desc']),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const headerSchema = object({
|
||||||
|
'user-agent': string(),
|
||||||
|
})
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
headerSchema,
|
||||||
idJSONSchema,
|
idJSONSchema,
|
||||||
personJSONSchema,
|
personJSONSchema,
|
||||||
postJSONSchema,
|
postJSONSchema,
|
||||||
|
|
|
@ -28,7 +28,12 @@ const querySortSchema = z.object({
|
||||||
order: z.enum(['asc', 'desc']),
|
order: z.enum(['asc', 'desc']),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const headerSchema = z.object({
|
||||||
|
'user-agent': z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
headerSchema,
|
||||||
idJSONSchema,
|
idJSONSchema,
|
||||||
personJSONSchema,
|
personJSONSchema,
|
||||||
postJSONSchema,
|
postJSONSchema,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@hono/standard-validator",
|
"name": "@hono/standard-validator",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@hono/standard-validator",
|
"name": "@hono/standard-validator",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"description": "Validator middleware using Standard Schema",
|
"description": "Validator middleware using Standard Schema",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.cjs",
|
"main": "dist/index.cjs",
|
||||||
|
|
|
@ -352,6 +352,47 @@ describe('Standard Schema Validation', () => {
|
||||||
>
|
>
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Sensitive Data Removal', () => {
|
||||||
|
it("doesn't return cookies after headers validation", async () => {
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
const schema = schemas.headerSchema
|
||||||
|
|
||||||
|
app.get('/headers', sValidator('header', schema), (c) =>
|
||||||
|
c.json({ success: true, userAgent: c.req.header('User-Agent') })
|
||||||
|
)
|
||||||
|
|
||||||
|
const req = new Request('http://localhost/headers', {
|
||||||
|
headers: {
|
||||||
|
// Not passing the User-Agent header to trigger the validation error
|
||||||
|
Cookie: 'SECRET=123',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await app.request(req)
|
||||||
|
expect(res.status).toBe(400)
|
||||||
|
const data = (await res.json()) as { success: false; error: unknown[] }
|
||||||
|
expect(data.success).toBe(false)
|
||||||
|
expect(data.error).toBeDefined()
|
||||||
|
|
||||||
|
if (lib === 'arktype') {
|
||||||
|
expect(
|
||||||
|
(data.error as { data: Record<string, unknown> }[]).some(
|
||||||
|
(error) => error.data && error.data.cookie
|
||||||
|
)
|
||||||
|
).toBe(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lib === 'valibot') {
|
||||||
|
expect(
|
||||||
|
(data.error as { path: { input: Record<string, unknown> }[] }[]).some((error) =>
|
||||||
|
error.path.some((path) => path.input.cookie)
|
||||||
|
)
|
||||||
|
).toBe(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||||
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
||||||
import { validator } from 'hono/validator'
|
import { validator } from 'hono/validator'
|
||||||
|
import { sanitizeIssues } from './sanitize-issues'
|
||||||
|
|
||||||
type HasUndefined<T> = undefined extends T ? true : false
|
type HasUndefined<T> = undefined extends T ? true : false
|
||||||
type TOrPromiseOfT<T> = T | Promise<T>
|
type TOrPromiseOfT<T> = T | Promise<T>
|
||||||
|
@ -21,6 +22,67 @@ type Hook<
|
||||||
c: Context<E, P>
|
c: Context<E, P>
|
||||||
) => TOrPromiseOfT<Response | void | TypedResponse<O>>
|
) => TOrPromiseOfT<Response | void | TypedResponse<O>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation middleware for libraries that support [Standard Schema](https://standardschema.dev/) specification.
|
||||||
|
*
|
||||||
|
* This middleware validates incoming request data against a provided schema
|
||||||
|
* that conforms to the Standard Schema specification. It supports validation
|
||||||
|
* of JSON bodies, headers, queries, forms, and other request targets.
|
||||||
|
*
|
||||||
|
* @param target - The request target to validate ('json', 'header', 'query', 'form', etc.)
|
||||||
|
* @param schema - A schema object conforming to Standard Schema specification
|
||||||
|
* @param hook - Optional hook function called with validation results for custom error handling
|
||||||
|
* @returns A Hono middleware handler that validates requests and makes validated data available via `c.req.valid()`
|
||||||
|
*
|
||||||
|
* @example Basic JSON validation
|
||||||
|
* ```ts
|
||||||
|
* import { z } from 'zod'
|
||||||
|
* import { sValidator } from '@hono/standard-validator'
|
||||||
|
*
|
||||||
|
* const schema = z.object({
|
||||||
|
* name: z.string(),
|
||||||
|
* age: z.number(),
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* app.post('/author', sValidator('json', schema), (c) => {
|
||||||
|
* const data = c.req.valid('json')
|
||||||
|
* return c.json({
|
||||||
|
* success: true,
|
||||||
|
* message: `${data.name} is ${data.age}`,
|
||||||
|
* })
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example With custom error handling hook
|
||||||
|
* ```ts
|
||||||
|
* app.post(
|
||||||
|
* '/post',
|
||||||
|
* sValidator('json', schema, (result, c) => {
|
||||||
|
* if (!result.success) {
|
||||||
|
* return c.text('Invalid!', 400)
|
||||||
|
* }
|
||||||
|
* }),
|
||||||
|
* (c) => {
|
||||||
|
* // Handler code
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example Header validation
|
||||||
|
* ```ts
|
||||||
|
* import { object, string } from 'valibot'
|
||||||
|
*
|
||||||
|
* const schema = object({
|
||||||
|
* 'content-type': string(),
|
||||||
|
* 'user-agent': string(),
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* app.post('/author', sValidator('header', schema), (c) => {
|
||||||
|
* const headers = c.req.valid('header')
|
||||||
|
* // do something with headers
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
const sValidator = <
|
const sValidator = <
|
||||||
Schema extends StandardSchemaV1,
|
Schema extends StandardSchemaV1,
|
||||||
Target extends keyof ValidationTargets,
|
Target extends keyof ValidationTargets,
|
||||||
|
@ -71,7 +133,9 @@ const sValidator = <
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.issues) {
|
if (result.issues) {
|
||||||
return c.json({ data: value, error: result.issues, success: false }, 400)
|
const processedIssues = sanitizeIssues(result.issues, schema['~standard'].vendor, target)
|
||||||
|
|
||||||
|
return c.json({ data: value, error: processedIssues, success: false }, 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.value as StandardSchemaV1.InferOutput<Schema>
|
return result.value as StandardSchemaV1.InferOutput<Schema>
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||||
|
import type { ValidationTargets } from 'hono'
|
||||||
|
|
||||||
|
const RESTRICTED_DATA_FIELDS = {
|
||||||
|
header: ['cookie'],
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes validation issues by removing sensitive data fields from error messages.
|
||||||
|
*
|
||||||
|
* This function removes potentially sensitive information (like cookies) from validation
|
||||||
|
* error messages before they are returned to the client. It handles different validation
|
||||||
|
* library formats based on the vendor string.
|
||||||
|
*
|
||||||
|
* @param issues - Array of validation issues from Standard Schema validation
|
||||||
|
* @param vendor - The validation library vendor identifier (e.g., 'arktype', 'valibot')
|
||||||
|
* @param target - The validation target being processed ('header', 'json', etc.)
|
||||||
|
* @returns Sanitized array of validation issues with sensitive data removed
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const issues = [{ message: 'Invalid header', data: { cookie: 'secret' } }]
|
||||||
|
* const sanitized = sanitizeIssues(issues, 'arktype', 'header')
|
||||||
|
* // Returns issues with cookie field removed from data
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function sanitizeIssues(
|
||||||
|
issues: readonly StandardSchemaV1.Issue[],
|
||||||
|
vendor: string,
|
||||||
|
target: keyof ValidationTargets
|
||||||
|
): readonly StandardSchemaV1.Issue[] {
|
||||||
|
if (!(target in RESTRICTED_DATA_FIELDS)) {
|
||||||
|
return issues
|
||||||
|
}
|
||||||
|
|
||||||
|
const restrictedFields =
|
||||||
|
RESTRICTED_DATA_FIELDS[target as keyof typeof RESTRICTED_DATA_FIELDS] || []
|
||||||
|
|
||||||
|
if (vendor === 'arktype') {
|
||||||
|
return sanitizeArktypeIssues(issues, restrictedFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vendor === 'valibot') {
|
||||||
|
return sanitizeValibotIssues(issues, restrictedFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
return issues
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeArktypeIssues(
|
||||||
|
issues: readonly StandardSchemaV1.Issue[],
|
||||||
|
restrictedFields: string[]
|
||||||
|
): readonly StandardSchemaV1.Issue[] {
|
||||||
|
return issues.map((issue) => {
|
||||||
|
if (
|
||||||
|
issue &&
|
||||||
|
typeof issue === 'object' &&
|
||||||
|
'data' in issue &&
|
||||||
|
typeof issue.data === 'object' &&
|
||||||
|
issue.data !== null &&
|
||||||
|
!Array.isArray(issue.data)
|
||||||
|
) {
|
||||||
|
const dataCopy = { ...(issue.data as Record<string, unknown>) }
|
||||||
|
for (const field of restrictedFields) {
|
||||||
|
delete dataCopy[field]
|
||||||
|
}
|
||||||
|
return { ...issue, data: dataCopy }
|
||||||
|
}
|
||||||
|
return issue
|
||||||
|
}) as readonly StandardSchemaV1.Issue[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeValibotIssues(
|
||||||
|
issues: readonly StandardSchemaV1.Issue[],
|
||||||
|
restrictedFields: string[]
|
||||||
|
): readonly StandardSchemaV1.Issue[] {
|
||||||
|
return issues.map((issue) => {
|
||||||
|
if (issue && typeof issue === 'object' && 'path' in issue && Array.isArray(issue.path)) {
|
||||||
|
for (const path of issue.path) {
|
||||||
|
if (
|
||||||
|
typeof path === 'object' &&
|
||||||
|
'input' in path &&
|
||||||
|
typeof path.input === 'object' &&
|
||||||
|
path.input !== null &&
|
||||||
|
!Array.isArray(path.input)
|
||||||
|
) {
|
||||||
|
for (const field of restrictedFields) {
|
||||||
|
delete path.input[field]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issue
|
||||||
|
}) as readonly StandardSchemaV1.Issue[]
|
||||||
|
}
|
|
@ -1,5 +1,11 @@
|
||||||
# @hono/zod-openapi
|
# @hono/zod-openapi
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#1320](https://github.com/honojs/middleware/pull/1320) [`e835cf6183fb4717cfdbe156d9b3d423625a0e15`](https://github.com/honojs/middleware/commit/e835cf6183fb4717cfdbe156d9b3d423625a0e15) Thanks [@yusukebe](https://github.com/yusukebe)! - fix: correct importing `zod`
|
||||||
|
|
||||||
## 1.0.1
|
## 1.0.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@hono/zod-openapi",
|
"name": "@hono/zod-openapi",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@hono/zod-openapi",
|
"name": "@hono/zod-openapi",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"description": "A wrapper class of Hono which supports OpenAPI.",
|
"description": "A wrapper class of Hono which supports OpenAPI.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "dist/index.js",
|
"module": "dist/index.js",
|
||||||
|
|
|
@ -39,8 +39,8 @@ import type { JSONParsed, JSONValue, RemoveBlankRecord, SimplifyDeepArray } from
|
||||||
import { mergePath } from 'hono/utils/url'
|
import { mergePath } from 'hono/utils/url'
|
||||||
import type { OpenAPIObject } from 'openapi3-ts/oas30'
|
import type { OpenAPIObject } from 'openapi3-ts/oas30'
|
||||||
import type { OpenAPIObject as OpenAPIV31bject } from 'openapi3-ts/oas31'
|
import type { OpenAPIObject as OpenAPIV31bject } from 'openapi3-ts/oas31'
|
||||||
import { ZodType, z } from 'zod/v4'
|
import { ZodType, z } from 'zod'
|
||||||
import type { ZodError } from 'zod/v4'
|
import type { ZodError } from 'zod'
|
||||||
|
|
||||||
type MaybePromise<T> = Promise<T> | T
|
type MaybePromise<T> = Promise<T> | T
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue