chore: use the latest eslint and `@hono/eslint-config` (#904)
* chore: use the latest eslint and `@hono/eslint-config` * update codespull/905/head
parent
755d5cb84d
commit
7a401b0850
|
@ -1 +0,0 @@
|
|||
dist
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
extends: ['./packages/eslint-config/index.js'],
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import baseConfig from './packages/eslint-config/index.js'
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
ignores: ['**/dist/*'],
|
||||
},
|
||||
]
|
|
@ -61,9 +61,7 @@
|
|||
"@types/node": "^20.10.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||
"@typescript-eslint/parser": "^8.7.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-import-x": "^4.1.1",
|
||||
"eslint-plugin-n": "^17.10.2",
|
||||
"eslint": "^9.17.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-miniflare": "^2.14.1",
|
||||
"npm-run-all2": "^6.2.2",
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono';
|
||||
import { validator } from 'hono/validator';
|
||||
import { Ajv, type JSONSchemaType, type ErrorObject } from 'ajv';
|
||||
import { Ajv } from 'ajv'
|
||||
import type { JSONSchemaType, ErrorObject } from 'ajv'
|
||||
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||
import { validator } from 'hono/validator'
|
||||
|
||||
type Hook<T, E extends Env, P extends string> = (
|
||||
result: { success: true; data: T } | { success: false; errors: ErrorObject[] },
|
||||
c: Context<E, P>
|
||||
) => Response | Promise<Response> | void;
|
||||
) => Response | Promise<Response> | void
|
||||
|
||||
/**
|
||||
* Hono middleware that validates incoming data via an Ajv JSON schema.
|
||||
|
@ -75,32 +76,32 @@ export function ajvValidator<
|
|||
E,
|
||||
P,
|
||||
{
|
||||
in: { [K in Target]: T };
|
||||
out: { [K in Target]: T };
|
||||
in: { [K in Target]: T }
|
||||
out: { [K in Target]: T }
|
||||
}
|
||||
> {
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(schema);
|
||||
const ajv = new Ajv()
|
||||
const validate = ajv.compile(schema)
|
||||
|
||||
return validator(target, (data, c) => {
|
||||
const valid = validate(data);
|
||||
const valid = validate(data)
|
||||
if (valid) {
|
||||
if (hook) {
|
||||
const hookResult = hook({ success: true, data: data as T }, c);
|
||||
const hookResult = hook({ success: true, data: data as T }, c)
|
||||
if (hookResult instanceof Response || hookResult instanceof Promise) {
|
||||
return hookResult;
|
||||
return hookResult
|
||||
}
|
||||
}
|
||||
return data as T;
|
||||
return data as T
|
||||
}
|
||||
|
||||
const errors = validate.errors || [];
|
||||
const errors = validate.errors || []
|
||||
if (hook) {
|
||||
const hookResult = hook({ success: false, errors }, c);
|
||||
const hookResult = hook({ success: false, errors }, c)
|
||||
if (hookResult instanceof Response || hookResult instanceof Promise) {
|
||||
return hookResult;
|
||||
return hookResult
|
||||
}
|
||||
}
|
||||
return c.json({ success: false, errors }, 400);
|
||||
});
|
||||
return c.json({ success: false, errors }, 400)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Hono } from 'hono';
|
||||
import type { Equal, Expect } from 'hono/utils/types';
|
||||
import { ajvValidator } from '../src';
|
||||
import { JSONSchemaType, type ErrorObject } from 'ajv';
|
||||
import type { JSONSchemaType, type ErrorObject } from 'ajv'
|
||||
import { Hono } from 'hono'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { ajvValidator } from '../src'
|
||||
|
||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never;
|
||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||
|
||||
describe('Basic', () => {
|
||||
const app = new Hono();
|
||||
const app = new Hono()
|
||||
|
||||
const schema: JSONSchemaType<{ name: string; age: number }> = {
|
||||
type: 'object',
|
||||
|
@ -16,35 +16,35 @@ describe('Basic', () => {
|
|||
},
|
||||
required: ['name', 'age'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
|
||||
const route = app.post('/author', ajvValidator('json', schema), (c) => {
|
||||
const data = c.req.valid('json');
|
||||
const data = c.req.valid('json')
|
||||
return c.json({
|
||||
success: true,
|
||||
message: `${data.name} is ${data.age}`,
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
type Actual = ExtractSchema<typeof route>;
|
||||
type Actual = ExtractSchema<typeof route>
|
||||
type Expected = {
|
||||
'/author': {
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
name: string;
|
||||
age: number;
|
||||
};
|
||||
};
|
||||
name: string
|
||||
age: number
|
||||
}
|
||||
}
|
||||
output: {
|
||||
success: boolean;
|
||||
message: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
success: boolean
|
||||
message: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type verify = Expect<Equal<Expected, Actual>>;
|
||||
type verify = Expect<Equal<Expected, Actual>>
|
||||
|
||||
it('Should return 200 response', async () => {
|
||||
const req = new Request('http://localhost/author', {
|
||||
|
@ -56,15 +56,15 @@ describe('Basic', () => {
|
|||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await app.request(req);
|
||||
expect(res).not.toBeNull();
|
||||
expect(res.status).toBe(200);
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
success: true,
|
||||
message: 'Superman is 20',
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
it('Should return 400 response', async () => {
|
||||
const req = new Request('http://localhost/author', {
|
||||
|
@ -76,17 +76,17 @@ describe('Basic', () => {
|
|||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await app.request(req);
|
||||
expect(res).not.toBeNull();
|
||||
expect(res.status).toBe(400);
|
||||
const data = (await res.json()) as { success: boolean };
|
||||
expect(data.success).toBe(false);
|
||||
});
|
||||
});
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(400)
|
||||
const data = (await res.json()) as { success: boolean }
|
||||
expect(data.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('With Hook', () => {
|
||||
const app = new Hono();
|
||||
const app = new Hono()
|
||||
|
||||
const schema: JSONSchemaType<{ id: number; title: string }> = {
|
||||
type: 'object',
|
||||
|
@ -96,39 +96,39 @@ describe('With Hook', () => {
|
|||
},
|
||||
required: ['id', 'title'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
}
|
||||
|
||||
app
|
||||
.post(
|
||||
'/post',
|
||||
ajvValidator('json', schema, (result, c) => {
|
||||
if (!result.success) {
|
||||
return c.text('Invalid!', 400);
|
||||
return c.text('Invalid!', 400)
|
||||
}
|
||||
const data = result.data;
|
||||
return c.text(`${data.id} is valid!`);
|
||||
const data = result.data
|
||||
return c.text(`${data.id} is valid!`)
|
||||
}),
|
||||
(c) => {
|
||||
const data = c.req.valid('json');
|
||||
const data = c.req.valid('json')
|
||||
return c.json({
|
||||
success: true,
|
||||
message: `${data.id} is ${data.title}`,
|
||||
});
|
||||
})
|
||||
}
|
||||
)
|
||||
.post(
|
||||
'/errorTest',
|
||||
ajvValidator('json', schema, (result, c) => {
|
||||
return c.json(result, 400);
|
||||
return c.json(result, 400)
|
||||
}),
|
||||
(c) => {
|
||||
const data = c.req.valid('json');
|
||||
const data = c.req.valid('json')
|
||||
return c.json({
|
||||
success: true,
|
||||
message: `${data.id} is ${data.title}`,
|
||||
});
|
||||
})
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
it('Should return 200 response', async () => {
|
||||
const req = new Request('http://localhost/post', {
|
||||
|
@ -140,12 +140,12 @@ describe('With Hook', () => {
|
|||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await app.request(req);
|
||||
expect(res).not.toBeNull();
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('123 is valid!');
|
||||
});
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('123 is valid!')
|
||||
})
|
||||
|
||||
it('Should return 400 response', async () => {
|
||||
const req = new Request('http://localhost/post', {
|
||||
|
@ -157,11 +157,11 @@ describe('With Hook', () => {
|
|||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await app.request(req);
|
||||
expect(res).not.toBeNull();
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('Should return 400 response and error array', async () => {
|
||||
const req = new Request('http://localhost/errorTest', {
|
||||
|
@ -172,17 +172,17 @@ describe('With Hook', () => {
|
|||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await app.request(req);
|
||||
expect(res).not.toBeNull();
|
||||
expect(res.status).toBe(400);
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(400)
|
||||
|
||||
const { errors, success } = (await res.json()) as {
|
||||
success: boolean;
|
||||
errors: ErrorObject[];
|
||||
};
|
||||
expect(success).toBe(false);
|
||||
expect(Array.isArray(errors)).toBe(true);
|
||||
success: boolean
|
||||
errors: ErrorObject[]
|
||||
}
|
||||
expect(success).toBe(false)
|
||||
expect(Array.isArray(errors)).toBe(true)
|
||||
expect(
|
||||
errors.map((e: ErrorObject) => ({
|
||||
keyword: e.keyword,
|
||||
|
@ -195,6 +195,6 @@ describe('With Hook', () => {
|
|||
instancePath: '',
|
||||
message: "must have required property 'title'",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { type, type Type, type ArkErrors } from 'arktype'
|
||||
import { type } from 'arktype'
|
||||
import type { Type, ArkErrors } from 'arktype'
|
||||
import type { Context, MiddlewareHandler, Env, ValidationTargets, TypedResponse } from 'hono'
|
||||
import { validator } from 'hono/validator'
|
||||
|
||||
|
|
|
@ -163,8 +163,7 @@ export function now() {
|
|||
|
||||
export function parseUrl(url?: string) {
|
||||
const defaultUrl = 'http://localhost:3000/api/auth'
|
||||
const parsedUrl = new URL(url?.startsWith('http') ? url : `https://${url}` || defaultUrl)
|
||||
|
||||
const parsedUrl = new URL(url ? (url.startsWith('http') ? url : `https://${url}`) : defaultUrl)
|
||||
const path = parsedUrl.pathname === '/' ? '/api/auth' : parsedUrl.pathname.replace(/\/$/, '')
|
||||
const base = `${parsedUrl.origin}${path}`
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import type { AuthConfig as AuthConfigCore } from '@auth/core'
|
||||
import { Auth } from '@auth/core'
|
||||
import { Auth, setEnvDefaults as coreSetEnvDefaults } from '@auth/core'
|
||||
import type { AdapterUser } from '@auth/core/adapters'
|
||||
import type { JWT } from '@auth/core/jwt'
|
||||
import type { Session } from '@auth/core/types'
|
||||
import type { Context, MiddlewareHandler } from 'hono'
|
||||
import { env } from 'hono/adapter'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
import { setEnvDefaults as coreSetEnvDefaults } from '@auth/core'
|
||||
|
||||
declare module 'hono' {
|
||||
interface ContextVariableMap {
|
||||
|
@ -43,7 +42,9 @@ export function reqWithEnvUrl(req: Request, authUrl?: string) {
|
|||
const authUrlObj = new URL(authUrl)
|
||||
const props = ['hostname', 'protocol', 'port', 'password', 'username'] as const
|
||||
for (const prop of props) {
|
||||
if (authUrlObj[prop]) reqUrlObj[prop] = authUrlObj[prop]
|
||||
if (authUrlObj[prop]) {
|
||||
reqUrlObj[prop] = authUrlObj[prop]
|
||||
}
|
||||
}
|
||||
return new Request(reqUrlObj.href, req)
|
||||
}
|
||||
|
@ -51,12 +52,17 @@ export function reqWithEnvUrl(req: Request, authUrl?: string) {
|
|||
const newReq = new Request(url.href, req)
|
||||
const proto = newReq.headers.get('x-forwarded-proto')
|
||||
const host = newReq.headers.get('x-forwarded-host') ?? newReq.headers.get('host')
|
||||
if (proto != null) url.protocol = proto.endsWith(':') ? proto : `${proto}:`
|
||||
if (proto != null) {
|
||||
url.protocol = proto.endsWith(':') ? proto : `${proto}:`
|
||||
}
|
||||
if (host != null) {
|
||||
url.host = host
|
||||
const portMatch = host.match(/:(\d+)$/)
|
||||
if (portMatch) url.port = portMatch[1]
|
||||
else url.port = ''
|
||||
if (portMatch) {
|
||||
url.port = portMatch[1]
|
||||
} else {
|
||||
url.port = ''
|
||||
}
|
||||
newReq.headers.delete('x-forwarded-host')
|
||||
newReq.headers.delete('Host')
|
||||
newReq.headers.set('Host', host)
|
||||
|
|
|
@ -1,28 +1,24 @@
|
|||
import type { BuiltInProviderType, RedirectableProviderType } from '@auth/core/providers'
|
||||
import type { LoggerInstance, Session } from '@auth/core/types'
|
||||
import * as React from 'react'
|
||||
import {
|
||||
type AuthClientConfig,
|
||||
ClientSessionError,
|
||||
fetchData,
|
||||
now,
|
||||
parseUrl,
|
||||
useOnline,
|
||||
type SessionContextValue,
|
||||
type SessionProviderProps,
|
||||
type GetSessionParams,
|
||||
type UseSessionOptions,
|
||||
type LiteralUnion,
|
||||
type SignInOptions,
|
||||
type SignInAuthorizationParams,
|
||||
type SignInResponse,
|
||||
type ClientSafeProvider,
|
||||
type SignOutParams,
|
||||
type SignOutResponse,
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
import { ClientSessionError, fetchData, now, parseUrl, useOnline } from './client'
|
||||
import type {
|
||||
WindowProps,
|
||||
AuthState,
|
||||
AuthClientConfig,
|
||||
SessionContextValue,
|
||||
SessionProviderProps,
|
||||
GetSessionParams,
|
||||
UseSessionOptions,
|
||||
LiteralUnion,
|
||||
SignInOptions,
|
||||
SignInAuthorizationParams,
|
||||
SignInResponse,
|
||||
ClientSafeProvider,
|
||||
SignOutParams,
|
||||
SignOutResponse,
|
||||
} from './client'
|
||||
import type { LoggerInstance, Session } from '@auth/core/types'
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
import type { BuiltInProviderType, RedirectableProviderType } from '@auth/core/providers'
|
||||
|
||||
const logger: LoggerInstance = {
|
||||
debug: console.debug,
|
||||
|
@ -431,7 +427,9 @@ export const useOauthPopupLogin = (
|
|||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent<AuthState>) => {
|
||||
if (event.origin !== window.location.origin) return
|
||||
if (event.origin !== window.location.origin) {
|
||||
return
|
||||
}
|
||||
if (event.data.status) {
|
||||
setState(event.data)
|
||||
if (event.data.status === 'success') {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { webcrypto } from 'node:crypto'
|
||||
import { skipCSRFCheck } from '@auth/core'
|
||||
import type { Adapter } from '@auth/core/adapters'
|
||||
import Credentials from '@auth/core/providers/credentials'
|
||||
import { Hono } from 'hono'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { webcrypto } from 'node:crypto'
|
||||
import type { AuthConfig } from '../src'
|
||||
import { authHandler, verifyAuth, initAuthConfig, reqWithEnvUrl } from '../src'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { decode } from 'hono/jwt'
|
||||
import type { Enforcer } from 'casbin'
|
||||
import type { Context } from 'hono'
|
||||
import { decode } from 'hono/jwt'
|
||||
import type { JWTPayload } from 'hono/utils/jwt/types'
|
||||
|
||||
export const jwtAuthorizer = async (
|
||||
|
@ -14,10 +14,14 @@ export const jwtAuthorizer = async (
|
|||
|
||||
if (!payload) {
|
||||
const credentials = c.req.header('Authorization')
|
||||
if (!credentials) return false
|
||||
if (!credentials) {
|
||||
return false
|
||||
}
|
||||
|
||||
const parts = credentials.split(/\s+/)
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') return false
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||
return false
|
||||
}
|
||||
|
||||
const token = parts[1]
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Enforcer } from 'casbin'
|
||||
import { type Context, MiddlewareHandler } from 'hono'
|
||||
import type { MiddlewareHandler, type Context } from 'hono'
|
||||
|
||||
interface CasbinOptions {
|
||||
newEnforcer: Promise<Enforcer>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { Hono } from 'hono'
|
||||
import { newEnforcer } from 'casbin'
|
||||
import { Hono } from 'hono'
|
||||
import { basicAuth } from 'hono/basic-auth'
|
||||
import { jwt, sign } from 'hono/jwt'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { casbin } from '../src'
|
||||
import { basicAuthorizer, jwtAuthorizer } from '../src/helper'
|
||||
import { jwt, sign } from 'hono/jwt'
|
||||
import { basicAuth } from 'hono/basic-auth'
|
||||
|
||||
describe('Casbin Middleware Tests', () => {
|
||||
describe('BasicAuthorizer', () => {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'reflect-metadata'
|
||||
import type { ClassConstructor, ClassTransformOptions } from 'class-transformer'
|
||||
import { plainToClass } from 'class-transformer'
|
||||
import type { ValidationError } from 'class-validator'
|
||||
import { validate } from 'class-validator'
|
||||
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
||||
import { validator } from 'hono/validator'
|
||||
import { ClassConstructor, ClassTransformOptions, plainToClass } from 'class-transformer'
|
||||
import { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
||||
import { ValidationError, validate } from 'class-validator'
|
||||
|
||||
/**
|
||||
* Hono middleware that validates incoming data using class-validator(https://github.com/typestack/class-validator).
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Hono } from 'hono'
|
||||
import { classValidator } from '../src'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { IsInt, IsString, ValidateNested, ValidationError } from 'class-validator'
|
||||
import { ExtractSchema } from 'hono/types'
|
||||
import { Type } from 'class-transformer'
|
||||
import type { ValidationError } from 'class-validator'
|
||||
import { IsInt, IsString, ValidateNested } from 'class-validator'
|
||||
import { Hono } from 'hono'
|
||||
import type { ExtractSchema } from 'hono/types'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { classValidator } from '../src'
|
||||
|
||||
describe('Basic', () => {
|
||||
const app = new Hono()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { type ClerkClient, type ClerkOptions, createClerkClient } from '@clerk/backend'
|
||||
import { createClerkClient } from '@clerk/backend'
|
||||
import type { ClerkClient, ClerkOptions } from '@clerk/backend'
|
||||
import type { Context, MiddlewareHandler } from 'hono'
|
||||
import { env } from 'hono/adapter'
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { Hono } from 'hono'
|
||||
import { cloudflareAccess } from '../src'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import crypto from 'crypto';
|
||||
import { promisify } from 'util';
|
||||
import crypto from 'crypto'
|
||||
import { promisify } from 'util'
|
||||
import { cloudflareAccess } from '../src'
|
||||
|
||||
const generateKeyPair = promisify(crypto.generateKeyPair);
|
||||
const generateKeyPair = promisify(crypto.generateKeyPair)
|
||||
|
||||
interface KeyPairResult {
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
publicKey: string
|
||||
privateKey: string
|
||||
}
|
||||
|
||||
interface JWK {
|
||||
kid: string;
|
||||
kty: string;
|
||||
alg: string;
|
||||
use: string;
|
||||
e: string;
|
||||
n: string;
|
||||
kid: string
|
||||
kty: string
|
||||
alg: string
|
||||
use: string
|
||||
e: string
|
||||
n: string
|
||||
}
|
||||
|
||||
async function generateJWTKeyPair(): Promise<KeyPairResult> {
|
||||
|
@ -27,38 +27,38 @@ async function generateJWTKeyPair(): Promise<KeyPairResult> {
|
|||
modulusLength: 2048,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
format: 'pem',
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem'
|
||||
}
|
||||
});
|
||||
format: 'pem',
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
publicKey,
|
||||
privateKey
|
||||
};
|
||||
privateKey,
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to generate key pair: ${(error as Error).message}`);
|
||||
throw new Error(`Failed to generate key pair: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
function generateKeyThumbprint(modulusBase64: string): string {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(Buffer.from(modulusBase64, 'base64'));
|
||||
return hash.digest('hex');
|
||||
const hash = crypto.createHash('sha256')
|
||||
hash.update(Buffer.from(modulusBase64, 'base64'))
|
||||
return hash.digest('hex')
|
||||
}
|
||||
|
||||
function publicKeyToJWK(publicKey: string): JWK {
|
||||
// Convert PEM to key object
|
||||
const keyObject = crypto.createPublicKey(publicKey);
|
||||
const keyObject = crypto.createPublicKey(publicKey)
|
||||
|
||||
// Export the key in JWK format
|
||||
const jwk = keyObject.export({ format: 'jwk' });
|
||||
const jwk = keyObject.export({ format: 'jwk' })
|
||||
|
||||
// Generate key ID using the modulus
|
||||
const kid = generateKeyThumbprint(jwk.n as string);
|
||||
const kid = generateKeyThumbprint(jwk.n as string)
|
||||
|
||||
return {
|
||||
kid,
|
||||
|
@ -67,66 +67,65 @@ function publicKeyToJWK(publicKey: string): JWK {
|
|||
use: 'sig',
|
||||
e: jwk.e as string,
|
||||
n: jwk.n as string,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function base64URLEncode(str: string): string {
|
||||
return Buffer.from(str)
|
||||
.toString('base64')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=/g, '');
|
||||
.replace(/=/g, '')
|
||||
}
|
||||
|
||||
function generateJWT(privateKey: string, payload: Record<string, any>, expiresIn: number = 3600): string {
|
||||
function generateJWT(
|
||||
privateKey: string,
|
||||
payload: Record<string, any>,
|
||||
expiresIn: number = 3600
|
||||
): string {
|
||||
// Create header
|
||||
const header = {
|
||||
alg: 'RS256',
|
||||
typ: 'JWT'
|
||||
};
|
||||
typ: 'JWT',
|
||||
}
|
||||
|
||||
// Add expiration to payload
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const fullPayload = {
|
||||
...payload,
|
||||
iat: now,
|
||||
exp: now + expiresIn
|
||||
};
|
||||
exp: now + expiresIn,
|
||||
}
|
||||
|
||||
// Encode header and payload
|
||||
const encodedHeader = base64URLEncode(JSON.stringify(header));
|
||||
const encodedPayload = base64URLEncode(JSON.stringify(fullPayload));
|
||||
const encodedHeader = base64URLEncode(JSON.stringify(header))
|
||||
const encodedPayload = base64URLEncode(JSON.stringify(fullPayload))
|
||||
|
||||
// Create signature
|
||||
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
||||
const signer = crypto.createSign('RSA-SHA256');
|
||||
signer.update(signatureInput);
|
||||
const signature = signer.sign(privateKey);
|
||||
// @ts-ignore
|
||||
const encodedSignature = base64URLEncode(signature);
|
||||
const signatureInput = `${encodedHeader}.${encodedPayload}`
|
||||
const signer = crypto.createSign('RSA-SHA256')
|
||||
signer.update(signatureInput)
|
||||
const signature = signer.sign(privateKey)
|
||||
// @ts-expect-error signature is not typed correctly
|
||||
const encodedSignature = base64URLEncode(signature)
|
||||
|
||||
// Combine all parts
|
||||
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
|
||||
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`
|
||||
}
|
||||
|
||||
|
||||
describe('Cloudflare Access middleware', async () => {
|
||||
const keyPair1 = await generateJWTKeyPair();
|
||||
const keyPair2 = await generateJWTKeyPair();
|
||||
const keyPair3 = await generateJWTKeyPair();
|
||||
const keyPair1 = await generateJWTKeyPair()
|
||||
const keyPair2 = await generateJWTKeyPair()
|
||||
const keyPair3 = await generateJWTKeyPair()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.clearAllMocks()
|
||||
vi.stubGlobal('fetch', async () => {
|
||||
return Response.json({
|
||||
keys: [
|
||||
publicKeyToJWK(keyPair1.publicKey),
|
||||
publicKeyToJWK(keyPair2.publicKey),
|
||||
],
|
||||
keys: [publicKeyToJWK(keyPair1.publicKey), publicKeyToJWK(keyPair2.publicKey)],
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
|
@ -135,9 +134,12 @@ describe('Cloudflare Access middleware', async () => {
|
|||
app.get('/access-payload', (c) => c.json(c.get('accessPayload')))
|
||||
|
||||
app.onError((err, c) => {
|
||||
return c.json({
|
||||
return c.json(
|
||||
{
|
||||
err: err.toString(),
|
||||
}, 500)
|
||||
},
|
||||
500
|
||||
)
|
||||
})
|
||||
|
||||
it('Should be throw Missing bearer token when nothing is sent', async () => {
|
||||
|
@ -150,8 +152,8 @@ describe('Cloudflare Access middleware', async () => {
|
|||
it('Should be throw Unable to decode Bearer token when sending garbage', async () => {
|
||||
const res = await app.request('http://localhost/hello-behind-access', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': 'asdasdasda'
|
||||
}
|
||||
'cf-access-jwt-assertion': 'asdasdasda',
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(401)
|
||||
|
@ -159,14 +161,18 @@ describe('Cloudflare Access middleware', async () => {
|
|||
})
|
||||
|
||||
it('Should be throw Token is expired when sending expired token', async () => {
|
||||
const token = generateJWT(keyPair1.privateKey, {
|
||||
const token = generateJWT(
|
||||
keyPair1.privateKey,
|
||||
{
|
||||
sub: '1234567890',
|
||||
}, -3600);
|
||||
},
|
||||
-3600
|
||||
)
|
||||
|
||||
const res = await app.request('http://localhost/hello-behind-access', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': token
|
||||
}
|
||||
'cf-access-jwt-assertion': token,
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(401)
|
||||
|
@ -177,28 +183,30 @@ describe('Cloudflare Access middleware', async () => {
|
|||
const token = generateJWT(keyPair1.privateKey, {
|
||||
sub: '1234567890',
|
||||
iss: 'https://different-team.cloudflareaccess.com',
|
||||
});
|
||||
})
|
||||
|
||||
const res = await app.request('http://localhost/hello-behind-access', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': token
|
||||
}
|
||||
'cf-access-jwt-assertion': token,
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(401)
|
||||
expect(await res.text()).toBe('Authentication error: Expected team name https://my-cool-team-name.cloudflareaccess.com, but received https://different-team.cloudflareaccess.com')
|
||||
expect(await res.text()).toBe(
|
||||
'Authentication error: Expected team name https://my-cool-team-name.cloudflareaccess.com, but received https://different-team.cloudflareaccess.com'
|
||||
)
|
||||
})
|
||||
|
||||
it('Should be throw Invalid token when sending token signed with private key not in the allowed list', async () => {
|
||||
const token = generateJWT(keyPair3.privateKey, {
|
||||
sub: '1234567890',
|
||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||
});
|
||||
})
|
||||
|
||||
const res = await app.request('http://localhost/hello-behind-access', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': token
|
||||
}
|
||||
'cf-access-jwt-assertion': token,
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(401)
|
||||
|
@ -209,12 +217,12 @@ describe('Cloudflare Access middleware', async () => {
|
|||
const token = generateJWT(keyPair1.privateKey, {
|
||||
sub: '1234567890',
|
||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||
});
|
||||
})
|
||||
|
||||
const res = await app.request('http://localhost/hello-behind-access', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': token
|
||||
}
|
||||
'cf-access-jwt-assertion': token,
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
|
@ -225,12 +233,12 @@ describe('Cloudflare Access middleware', async () => {
|
|||
const token = generateJWT(keyPair2.privateKey, {
|
||||
sub: '1234567890',
|
||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||
});
|
||||
})
|
||||
|
||||
const res = await app.request('http://localhost/hello-behind-access', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': token
|
||||
}
|
||||
'cf-access-jwt-assertion': token,
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
|
@ -241,50 +249,54 @@ describe('Cloudflare Access middleware', async () => {
|
|||
const token = generateJWT(keyPair1.privateKey, {
|
||||
sub: '1234567890',
|
||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||
});
|
||||
})
|
||||
|
||||
const res = await app.request('http://localhost/access-payload', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': token
|
||||
}
|
||||
'cf-access-jwt-assertion': token,
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
"sub":"1234567890",
|
||||
"iss":"https://my-cool-team-name.cloudflareaccess.com",
|
||||
"iat":expect.any(Number),
|
||||
"exp":expect.any(Number)
|
||||
sub: '1234567890',
|
||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||
iat: expect.any(Number),
|
||||
exp: expect.any(Number),
|
||||
})
|
||||
})
|
||||
|
||||
it('Should throw an error, if the access organization does not exist', async () => {
|
||||
vi.stubGlobal('fetch', async () => {
|
||||
return Response.json({success: false}, {status: 404})
|
||||
return Response.json({ success: false }, { status: 404 })
|
||||
})
|
||||
|
||||
const res = await app.request('http://localhost/hello-behind-access', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': 'asdads'
|
||||
}
|
||||
'cf-access-jwt-assertion': 'asdads',
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(500)
|
||||
expect(await res.json()).toEqual({"err":"Error: Authentication error: The Access Organization 'my-cool-team-name' does not exist"})
|
||||
expect(await res.json()).toEqual({
|
||||
err: "Error: Authentication error: The Access Organization 'my-cool-team-name' does not exist",
|
||||
})
|
||||
})
|
||||
|
||||
it('Should throw an error, if the access certs url is unavailable', async () => {
|
||||
vi.stubGlobal('fetch', async () => {
|
||||
return Response.json({success: false}, {status: 500})
|
||||
return Response.json({ success: false }, { status: 500 })
|
||||
})
|
||||
|
||||
const res = await app.request('http://localhost/hello-behind-access', {
|
||||
headers: {
|
||||
'cf-access-jwt-assertion': 'asdads'
|
||||
}
|
||||
'cf-access-jwt-assertion': 'asdads',
|
||||
},
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(500)
|
||||
expect(await res.json()).toEqual({"err":"Error: Authentication error: Received unexpected HTTP code 500 from Cloudflare Access"})
|
||||
expect(await res.json()).toEqual({
|
||||
err: 'Error: Authentication error: Received unexpected HTTP code 500 from Cloudflare Access',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import type { Context } from 'hono'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import { Context } from 'hono'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
export type CloudflareAccessPayload = {
|
||||
aud: string[],
|
||||
email: string,
|
||||
exp: number,
|
||||
iat: number,
|
||||
nbf: number,
|
||||
iss: string,
|
||||
type: string,
|
||||
identity_nonce: string,
|
||||
sub: string,
|
||||
country: string,
|
||||
aud: string[]
|
||||
email: string
|
||||
exp: number
|
||||
iat: number
|
||||
nbf: number
|
||||
iss: string
|
||||
type: string
|
||||
identity_nonce: string
|
||||
sub: string
|
||||
country: string
|
||||
}
|
||||
|
||||
export type CloudflareAccessVariables = {
|
||||
|
@ -39,7 +39,9 @@ export const cloudflareAccess = (accessTeamName: string) => {
|
|||
|
||||
return createMiddleware(async (c, next) => {
|
||||
const encodedToken = getJwt(c)
|
||||
if (encodedToken === null) return c.text('Authentication error: Missing bearer token', 401)
|
||||
if (encodedToken === null) {
|
||||
return c.text('Authentication error: Missing bearer token', 401)
|
||||
}
|
||||
|
||||
// Load jwt keys if they are not in memory or already expired
|
||||
if (Object.keys(cacheKeys).length === 0 || Math.floor(Date.now() / 1000) < cacheExpiration) {
|
||||
|
@ -59,19 +61,23 @@ export const cloudflareAccess = (accessTeamName: string) => {
|
|||
// Is the token expired?
|
||||
const expiryDate = new Date(token.payload.exp * 1000)
|
||||
const currentDate = new Date(Date.now())
|
||||
if (expiryDate <= currentDate) return c.text('Authentication error: Token is expired', 401)
|
||||
if (expiryDate <= currentDate) {
|
||||
return c.text('Authentication error: Token is expired', 401)
|
||||
}
|
||||
|
||||
// Check is token is valid against at least one public key?
|
||||
if (!(await isValidJwtSignature(token, cacheKeys)))
|
||||
if (!(await isValidJwtSignature(token, cacheKeys))) {
|
||||
return c.text('Authentication error: Invalid Token', 401)
|
||||
}
|
||||
|
||||
// Is signed from the correct team?
|
||||
const expectedIss = `https://${accessTeamName}.cloudflareaccess.com`
|
||||
if (token.payload?.iss !== expectedIss)
|
||||
if (token.payload?.iss !== expectedIss) {
|
||||
return c.text(
|
||||
`Authentication error: Expected team name ${expectedIss}, but received ${token.payload?.iss}`,
|
||||
401
|
||||
)
|
||||
}
|
||||
|
||||
c.set('accessPayload', token.payload)
|
||||
await next()
|
||||
|
@ -83,7 +89,6 @@ async function getPublicKeys(accessTeamName: string) {
|
|||
|
||||
const result = await fetch(jwtUrl, {
|
||||
method: 'GET',
|
||||
// @ts-ignore
|
||||
cf: {
|
||||
// Dont cache error responses
|
||||
cacheTtlByStatus: { '200-299': 30, '300-599': 0 },
|
||||
|
@ -92,16 +97,20 @@ async function getPublicKeys(accessTeamName: string) {
|
|||
|
||||
if (!result.ok) {
|
||||
if (result.status === 404) {
|
||||
throw new HTTPException(500, { message: `Authentication error: The Access Organization '${accessTeamName}' does not exist` })
|
||||
throw new HTTPException(500, {
|
||||
message: `Authentication error: The Access Organization '${accessTeamName}' does not exist`,
|
||||
})
|
||||
}
|
||||
|
||||
throw new HTTPException(500, { message: `Authentication error: Received unexpected HTTP code ${result.status} from Cloudflare Access` })
|
||||
throw new HTTPException(500, {
|
||||
message: `Authentication error: Received unexpected HTTP code ${result.status} from Cloudflare Access`,
|
||||
})
|
||||
}
|
||||
|
||||
const data: any = await result.json()
|
||||
|
||||
// Because we keep CryptoKey's in memory between requests, we need to make sure they are refreshed once in a while
|
||||
let cacheExpiration = Math.floor(Date.now() / 1000) + 3600 // 1h
|
||||
const cacheExpiration = Math.floor(Date.now() / 1000) + 3600 // 1h
|
||||
|
||||
const importedKeys: Record<string, CryptoKey> = {}
|
||||
for (const key of data.keys) {
|
||||
|
@ -158,7 +167,9 @@ async function isValidJwtSignature(token: DecodedToken, keys: Record<string, Cry
|
|||
for (const key of Object.values(keys)) {
|
||||
const isValid = await validateSingleKey(key, signature, data)
|
||||
|
||||
if (isValid) return true
|
||||
if (isValid) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import type { Context, Env, Input as HonoInput, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||
import type { Submission } from '@conform-to/dom'
|
||||
import type { Context, Env, Input as HonoInput, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||
import { getFormDataFromContext } from './utils'
|
||||
|
||||
type FormTargetValue = ValidationTargets['form']['string']
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { parseWithZod } from '@conform-to/zod'
|
||||
import { Hono } from 'hono'
|
||||
import { z } from 'zod'
|
||||
import { parseWithZod } from '@conform-to/zod'
|
||||
import { conformValidator } from '../src'
|
||||
|
||||
describe('Validate common processing', () => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as z from 'zod'
|
||||
import { parseWithZod } from '@conform-to/zod'
|
||||
import { Hono } from 'hono'
|
||||
import { hc } from 'hono/client'
|
||||
import { parseWithZod } from '@conform-to/zod'
|
||||
import { conformValidator } from '../src'
|
||||
import { vi } from 'vitest'
|
||||
import * as z from 'zod'
|
||||
import { conformValidator } from '../src'
|
||||
|
||||
describe('Validate the hook option processing', () => {
|
||||
const app = new Hono()
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import type { StatusCode } from 'hono/utils/http-status'
|
||||
import * as v from 'valibot'
|
||||
import { parseWithValibot } from 'conform-to-valibot'
|
||||
import { Hono } from 'hono'
|
||||
import { hc } from 'hono/client'
|
||||
import { parseWithValibot } from 'conform-to-valibot'
|
||||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||
import type { StatusCode } from 'hono/utils/http-status'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import * as v from 'valibot'
|
||||
import { conformValidator } from '../src'
|
||||
|
||||
describe('Validate requests using a Valibot schema', () => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import type { StatusCode } from 'hono/utils/http-status'
|
||||
import * as y from 'yup'
|
||||
import { parseWithYup } from '@conform-to/yup'
|
||||
import { Hono } from 'hono'
|
||||
import { hc } from 'hono/client'
|
||||
import { parseWithYup } from '@conform-to/yup'
|
||||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||
import type { StatusCode } from 'hono/utils/http-status'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import * as y from 'yup'
|
||||
import { conformValidator } from '../src'
|
||||
|
||||
describe('Validate requests using a Yup schema', () => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import type { StatusCode } from 'hono/utils/http-status'
|
||||
import * as z from 'zod'
|
||||
import { parseWithZod } from '@conform-to/zod'
|
||||
import { Hono } from 'hono'
|
||||
import { hc } from 'hono/client'
|
||||
import { parseWithZod } from '@conform-to/zod'
|
||||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||
import type { StatusCode } from 'hono/utils/http-status'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import * as z from 'zod'
|
||||
import { conformValidator } from '../src'
|
||||
|
||||
describe('Validate requests using a Zod schema', () => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Hono } from 'hono'
|
||||
import type {Context} from 'hono'
|
||||
import type { Context } from 'hono'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createEmitter, defineHandler, defineHandlers, emitter } from './index'
|
||||
import type {Emitter} from './index' // Adjust the import path as needed
|
||||
import type { Emitter } from './index' // Adjust the import path as needed
|
||||
|
||||
describe('Event Emitter Middleware', () => {
|
||||
describe('createEmitter', () => {
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
interface CloseEventInit extends EventInit {
|
||||
code?: number;
|
||||
reason?: string;
|
||||
wasClean?: boolean;
|
||||
code?: number
|
||||
reason?: string
|
||||
wasClean?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||
*/
|
||||
export const CloseEvent = globalThis.CloseEvent ?? class extends Event {
|
||||
export const CloseEvent =
|
||||
globalThis.CloseEvent ??
|
||||
class extends Event {
|
||||
#eventInitDict
|
||||
|
||||
constructor(
|
||||
type: string,
|
||||
eventInitDict: CloseEventInit = {}
|
||||
) {
|
||||
constructor(type: string, eventInitDict: CloseEventInit = {}) {
|
||||
super(type, eventInitDict)
|
||||
this.#eventInitDict = eventInitDict
|
||||
}
|
||||
|
@ -29,4 +28,4 @@ export const CloseEvent = globalThis.CloseEvent ?? class extends Event {
|
|||
get reason(): string {
|
||||
return this.#eventInitDict.reason ?? ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { serve } from '@hono/node-server'
|
||||
import type { ServerType } from '@hono/node-server/dist/types'
|
||||
import { Hono } from 'hono'
|
||||
import type { WSMessageReceive } from 'hono/ws'
|
||||
import { WebSocket } from 'ws'
|
||||
import { createNodeWebSocket } from '.'
|
||||
import type { WSMessageReceive } from 'hono/ws'
|
||||
|
||||
describe('WebSocket helper', () => {
|
||||
let app: Hono
|
||||
|
@ -13,7 +13,6 @@ describe('WebSocket helper', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
app = new Hono()
|
||||
|
||||
;({ injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }))
|
||||
|
||||
server = await new Promise<ServerType>((resolve) => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { Server } from 'node:http'
|
||||
import type { Http2SecureServer, Http2Server } from 'node:http2'
|
||||
import type { Hono } from 'hono'
|
||||
import type { UpgradeWebSocket, WSContext } from 'hono/ws'
|
||||
import type { WebSocket } from 'ws'
|
||||
import { WebSocketServer } from 'ws'
|
||||
import type { IncomingMessage } from 'http'
|
||||
import type { Server } from 'node:http'
|
||||
import type { Http2SecureServer, Http2Server } from 'node:http2'
|
||||
import { CloseEvent } from './events'
|
||||
|
||||
export interface NodeWebSocket {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { MiddlewareHandler } from 'hono'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { env } from 'hono/adapter'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import { getRandomState } from '../../utils/getRandomState'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { MiddlewareHandler } from 'hono'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { env } from 'hono/adapter'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import { getRandomState } from '../../utils/getRandomState'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { MiddlewareHandler } from 'hono'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { env } from 'hono/adapter'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import { getRandomState } from '../../utils/getRandomState'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { MiddlewareHandler } from 'hono'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { env } from 'hono/adapter'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import { getRandomState } from '../../utils/getRandomState'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { MiddlewareHandler } from 'hono'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { env } from 'hono/adapter'
|
||||
import { getCookie, setCookie } from 'hono/cookie'
|
||||
import { HTTPException } from 'hono/http-exception'
|
||||
|
||||
import { getCodeChallenge } from '../../utils/getCodeChallenge'
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"hono": ">=3.*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^29.7.0",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"hono": "^4.0.1",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import crypto from 'node:crypto'
|
||||
import { jest } from '@jest/globals'
|
||||
import { Hono } from 'hono'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import * as oauth2 from 'oauth4webapi'
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
const MOCK_ISSUER = 'https://accounts.google.com'
|
||||
const MOCK_CLIENT_ID = 'CLIENT_ID_001'
|
||||
|
@ -387,7 +387,9 @@ describe('processOAuthCallback()', () => {
|
|||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(302)
|
||||
expect(res.headers.get('set-cookie')).toMatch(
|
||||
new RegExp(`${MOCK_COOKIE_NAME}=[^;]+; Path=${process.env.OIDC_COOKIE_PATH}; HttpOnly; Secure`)
|
||||
new RegExp(
|
||||
`${MOCK_COOKIE_NAME}=[^;]+; Path=${process.env.OIDC_COOKIE_PATH}; HttpOnly; Secure`
|
||||
)
|
||||
)
|
||||
})
|
||||
test('Should return an error if the state parameter does not match', async () => {
|
||||
|
|
|
@ -2,11 +2,8 @@ import type { Context } from 'hono'
|
|||
import { createMiddleware } from 'hono/factory'
|
||||
import type { DefaultMetricsCollectorConfiguration, RegistryContentType } from 'prom-client'
|
||||
import { Registry, collectDefaultMetrics as promCollectDefaultMetrics } from 'prom-client'
|
||||
import {
|
||||
type MetricOptions,
|
||||
type CustomMetricsOptions,
|
||||
createStandardMetrics,
|
||||
} from './standardMetrics'
|
||||
import { createStandardMetrics } from './standardMetrics'
|
||||
import type { MetricOptions, CustomMetricsOptions } from './standardMetrics'
|
||||
|
||||
interface PrometheusOptions {
|
||||
registry?: Registry
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { Context } from 'hono'
|
||||
import type { CounterConfiguration, HistogramConfiguration, Metric } from 'prom-client'
|
||||
import { Counter, Histogram, type Registry } from 'prom-client'
|
||||
import { Counter, Histogram } from 'prom-client'
|
||||
import type { CounterConfiguration, HistogramConfiguration, Metric, Registry } from 'prom-client'
|
||||
|
||||
export type MetricOptions = {
|
||||
disabled?: boolean
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
import type { MiddlewareHandler } from 'hono'
|
||||
|
||||
export const qwikMiddleware = (opts: ServerRenderOptions): MiddlewareHandler => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
||||
;(globalThis as any).TextEncoderStream = TextEncoderStream
|
||||
const qwikSerializer = {
|
||||
_deserializeData,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import type { Context } from 'hono'
|
||||
import type { Env, MiddlewareHandler } from 'hono/types'
|
||||
import React from 'react'
|
||||
import { renderToString, type RenderToReadableStreamOptions } from 'react-dom/server'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import type { RenderToReadableStreamOptions } from 'react-dom/server'
|
||||
import type { Props } from '.'
|
||||
|
||||
type RendererOptions = {
|
||||
|
|
|
@ -69,7 +69,7 @@ const SwaggerUI = (options: SwaggerUIOptions) => {
|
|||
const middleware =
|
||||
<E extends Env>(options: SwaggerUIOptions): MiddlewareHandler<E> =>
|
||||
async (c) => {
|
||||
const title = options?.title ?? "SwaggerUI"
|
||||
const title = options?.title ?? 'SwaggerUI'
|
||||
return c.html(/* html */ `
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'reflect-metadata'
|
||||
import { injectable, inject } from 'tsyringe'
|
||||
import { Hono } from 'hono'
|
||||
import { injectable, inject } from 'tsyringe'
|
||||
import { tsyringe } from '../src'
|
||||
|
||||
class Config {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { container, DependencyContainer, InjectionToken } from 'tsyringe'
|
||||
import type { Context, MiddlewareHandler } from 'hono'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import type { DependencyContainer, InjectionToken } from 'tsyringe'
|
||||
import { container } from 'tsyringe'
|
||||
|
||||
declare module 'hono' {
|
||||
interface ContextVariableMap {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { TSchema, Static, TypeGuard, ValueGuard } from '@sinclair/typebox'
|
||||
import { Value, type ValueError } from '@sinclair/typebox/value'
|
||||
import type { TSchema, Static } from '@sinclair/typebox'
|
||||
import { TypeGuard, ValueGuard } from '@sinclair/typebox'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
import type { ValueError } from '@sinclair/typebox/value'
|
||||
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||
import { validator } from 'hono/validator'
|
||||
import IsObject = ValueGuard.IsObject
|
||||
|
@ -7,7 +9,7 @@ import IsArray = ValueGuard.IsArray
|
|||
|
||||
export type Hook<T, E extends Env, P extends string> = (
|
||||
result: { success: true; data: T } | { success: false; errors: ValueError[] },
|
||||
c: Context<E, P>,
|
||||
c: Context<E, P>
|
||||
) => Response | Promise<Response> | void
|
||||
|
||||
/**
|
||||
|
@ -61,11 +63,18 @@ export function tbValidator<
|
|||
E extends Env,
|
||||
P extends string,
|
||||
V extends { in: { [K in Target]: Static<T> }; out: { [K in Target]: Static<T> } }
|
||||
>(target: Target, schema: T, hook?: Hook<Static<T>, E, P>, stripNonSchemaItems?: boolean): MiddlewareHandler<E, P, V> {
|
||||
>(
|
||||
target: Target,
|
||||
schema: T,
|
||||
hook?: Hook<Static<T>, E, P>,
|
||||
stripNonSchemaItems?: boolean
|
||||
): MiddlewareHandler<E, P, V> {
|
||||
// Compile the provided schema once rather than per validation. This could be optimized further using a shared schema
|
||||
// compilation pool similar to the Fastify implementation.
|
||||
return validator(target, (unprocessedData, c) => {
|
||||
const data = stripNonSchemaItems ? removeNonSchemaItems(schema, unprocessedData) : unprocessedData
|
||||
const data = stripNonSchemaItems
|
||||
? removeNonSchemaItems(schema, unprocessedData)
|
||||
: unprocessedData
|
||||
|
||||
if (Value.Check(schema, data)) {
|
||||
if (hook) {
|
||||
|
@ -90,7 +99,9 @@ export function tbValidator<
|
|||
}
|
||||
|
||||
function removeNonSchemaItems<T extends TSchema>(schema: T, obj: any): Static<T> {
|
||||
if (typeof obj !== 'object' || obj === null) return obj
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return obj
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => removeNonSchemaItems(schema.items, item))
|
||||
|
@ -98,12 +109,9 @@ function removeNonSchemaItems<T extends TSchema>(schema: T, obj: any): Static<T>
|
|||
|
||||
const result: any = {}
|
||||
for (const key in schema.properties) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const propertySchema = schema.properties[key]
|
||||
if (
|
||||
IsObject(propertySchema) &&
|
||||
!IsArray(propertySchema)
|
||||
) {
|
||||
if (IsObject(propertySchema) && !IsArray(propertySchema)) {
|
||||
result[key] = removeNonSchemaItems(propertySchema as unknown as TSchema, obj[key])
|
||||
} else {
|
||||
result[key] = obj[key]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Type as T } from '@sinclair/typebox'
|
||||
import type { ValueError } from '@sinclair/typebox/value'
|
||||
import { Hono } from 'hono'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { tbValidator } from '../src'
|
||||
import { ValueError } from '@sinclair/typebox/value'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||
|
@ -91,7 +91,8 @@ describe('With Hook', () => {
|
|||
title: T.String(),
|
||||
})
|
||||
|
||||
app.post(
|
||||
app
|
||||
.post(
|
||||
'/post',
|
||||
tbValidator('json', schema, (result, c) => {
|
||||
if (!result.success) {
|
||||
|
@ -106,8 +107,9 @@ describe('With Hook', () => {
|
|||
success: true,
|
||||
message: `${data.id} is ${data.title}`,
|
||||
})
|
||||
},
|
||||
).post(
|
||||
}
|
||||
)
|
||||
.post(
|
||||
'/errorTest',
|
||||
tbValidator('json', schema, (result, c) => {
|
||||
return c.json(result, 400)
|
||||
|
@ -118,7 +120,7 @@ describe('With Hook', () => {
|
|||
success: true,
|
||||
message: `${data.id} is ${data.title}`,
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
it('Should return 200 response', async () => {
|
||||
|
@ -171,20 +173,22 @@ describe('With Hook', () => {
|
|||
const { errors, success } = (await res.json()) as { success: boolean; errors: any[] }
|
||||
expect(success).toBe(false)
|
||||
expect(Array.isArray(errors)).toBe(true)
|
||||
expect(errors.map((e: ValueError) => ({
|
||||
'type': e?.schema?.type,
|
||||
expect(
|
||||
errors.map((e: ValueError) => ({
|
||||
type: e?.schema?.type,
|
||||
path: e?.path,
|
||||
message: e?.message,
|
||||
}))).toEqual([
|
||||
}))
|
||||
).toEqual([
|
||||
{
|
||||
'type': 'string',
|
||||
'path': '/title',
|
||||
'message': 'Required property',
|
||||
type: 'string',
|
||||
path: '/title',
|
||||
message: 'Required property',
|
||||
},
|
||||
{
|
||||
'type': 'string',
|
||||
'path': '/title',
|
||||
'message': 'Expected string',
|
||||
type: 'string',
|
||||
path: '/title',
|
||||
message: 'Expected string',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
@ -207,25 +211,19 @@ describe('Remove non schema items', () => {
|
|||
}),
|
||||
})
|
||||
|
||||
app.post(
|
||||
'/stripValuesNested',
|
||||
tbValidator('json', nestedSchema, undefined, true),
|
||||
(c) => {
|
||||
app
|
||||
.post('/stripValuesNested', tbValidator('json', nestedSchema, undefined, true), (c) => {
|
||||
return c.json({
|
||||
success: true,
|
||||
message: c.req.valid('json'),
|
||||
})
|
||||
},
|
||||
).post(
|
||||
'/stripValuesArray',
|
||||
tbValidator('json', T.Array(schema), undefined, true),
|
||||
(c) => {
|
||||
})
|
||||
.post('/stripValuesArray', tbValidator('json', T.Array(schema), undefined, true), (c) => {
|
||||
return c.json({
|
||||
success: true,
|
||||
message: c.req.valid('json'),
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
it('Should remove all the values in the nested object and return a 200 response', async () => {
|
||||
const req = new Request('http://localhost/stripValuesNested', {
|
||||
|
@ -274,20 +272,21 @@ describe('Remove non schema items', () => {
|
|||
|
||||
const { message, success } = (await res.json()) as { success: boolean; message: any }
|
||||
expect(success).toBe(true)
|
||||
expect(message).toEqual(
|
||||
expect(message).toEqual({
|
||||
id: 123,
|
||||
itemArray: [
|
||||
{ id: 123, title: 'Hello' },
|
||||
{
|
||||
'id': 123,
|
||||
'itemArray': [{ 'id': 123, 'title': 'Hello' }, {
|
||||
'id': 123,
|
||||
'title': 'Hello',
|
||||
}],
|
||||
'item': { 'id': 123, 'title': 'Hello' },
|
||||
'itemObject': {
|
||||
'item1': { 'id': 123, 'title': 'Hello' },
|
||||
'item2': { 'id': 123, 'title': 'Hello' },
|
||||
id: 123,
|
||||
title: 'Hello',
|
||||
},
|
||||
],
|
||||
item: { id: 123, title: 'Hello' },
|
||||
itemObject: {
|
||||
item1: { id: 123, title: 'Hello' },
|
||||
item2: { id: 123, title: 'Hello' },
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('Should remove all the values in the array and return a 200 response', async () => {
|
||||
|
@ -303,7 +302,8 @@ describe('Remove non schema items', () => {
|
|||
title: 'Hello 2',
|
||||
nonExistentKey: 'error',
|
||||
},
|
||||
]), method: 'POST',
|
||||
]),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
|
@ -313,11 +313,12 @@ describe('Remove non schema items', () => {
|
|||
const { message, success } = (await res.json()) as { success: boolean; message: Array<any> }
|
||||
expect(res.status).toBe(200)
|
||||
expect(success).toBe(true)
|
||||
expect(message).toEqual([{ 'id': 123, 'title': 'Hello' }, {
|
||||
'id': 123,
|
||||
'title': 'Hello 2',
|
||||
}],
|
||||
)
|
||||
expect(message).toEqual([
|
||||
{ id: 123, title: 'Hello' },
|
||||
{
|
||||
id: 123,
|
||||
title: 'Hello 2',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ export const typiaValidator: TypiaValidator = (
|
|||
validate: (input: any) => IValidation<any>,
|
||||
hook?: Hook<any, any, any>
|
||||
): MiddlewareHandler => {
|
||||
if (target === 'query' || target === 'header')
|
||||
if (target === 'query' || target === 'header') {
|
||||
return async (c, next) => {
|
||||
let value: any
|
||||
if (target === 'query') {
|
||||
|
@ -140,15 +140,20 @@ export const typiaValidator: TypiaValidator = (
|
|||
} satisfies IReadableURLSearchParams
|
||||
} else {
|
||||
value = Object.create(null)
|
||||
for (const [key, headerValue] of c.req.raw.headers) value[key.toLowerCase()] = headerValue
|
||||
if (c.req.raw.headers.has('Set-Cookie'))
|
||||
for (const [key, headerValue] of c.req.raw.headers) {
|
||||
value[key.toLowerCase()] = headerValue
|
||||
}
|
||||
if (c.req.raw.headers.has('Set-Cookie')) {
|
||||
value['Set-Cookie'] = c.req.raw.headers.getSetCookie()
|
||||
}
|
||||
}
|
||||
const result = validate(value)
|
||||
|
||||
if (hook) {
|
||||
const res = await hook(result as never, c)
|
||||
if (res instanceof Response) return res
|
||||
if (res instanceof Response) {
|
||||
return res
|
||||
}
|
||||
}
|
||||
if (!result.success) {
|
||||
return c.json({ success: false, error: result.errors }, 400)
|
||||
|
@ -157,6 +162,7 @@ export const typiaValidator: TypiaValidator = (
|
|||
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
||||
return validator(target, async (value, c) => {
|
||||
const result = validate(value)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import typia, { tags } from 'typia'
|
||||
import type { tags } from 'typia'
|
||||
import typia from 'typia'
|
||||
import { typiaValidator } from '../src/http'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import type { Context, Env, Input as HonoInput, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||
import { validator } from 'hono/validator'
|
||||
import type { GenericSchema, GenericSchemaAsync, InferInput, InferOutput, SafeParseResult } from 'valibot'
|
||||
import type {
|
||||
GenericSchema,
|
||||
GenericSchemaAsync,
|
||||
InferInput,
|
||||
InferOutput,
|
||||
SafeParseResult,
|
||||
} from 'valibot'
|
||||
import { safeParseAsync } from 'valibot'
|
||||
|
||||
export type Hook<T extends GenericSchema | GenericSchemaAsync, E extends Env, P extends string> = (
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { StatusCode } from 'hono/utils/http-status'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { number, object, objectAsync, optional, optionalAsync, string } from 'valibot'
|
||||
import { vValidator } from '../src'
|
||||
import { StatusCode } from 'hono/utils/http-status'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { defineConfig } from "tsup";
|
||||
import { defineConfig } from 'tsup'
|
||||
|
||||
export default defineConfig({
|
||||
entryPoints: ["src/index.ts"],
|
||||
format: ["cjs", "esm"],
|
||||
entryPoints: ['src/index.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
outDir: "dist",
|
||||
outDir: 'dist',
|
||||
clean: true,
|
||||
});
|
||||
})
|
||||
|
|
|
@ -26,7 +26,6 @@ import type {
|
|||
ValidationTargets,
|
||||
} from 'hono'
|
||||
import type { MergePath, MergeSchemaPath } from 'hono/types'
|
||||
import type { JSONParsed, JSONValue, RemoveBlankRecord, SimplifyDeepArray } from 'hono/utils/types'
|
||||
import type {
|
||||
ClientErrorStatusCode,
|
||||
InfoStatusCode,
|
||||
|
@ -35,6 +34,7 @@ import type {
|
|||
StatusCode,
|
||||
SuccessStatusCode,
|
||||
} from 'hono/utils/http-status'
|
||||
import type { JSONParsed, JSONValue, RemoveBlankRecord, SimplifyDeepArray } from 'hono/utils/types'
|
||||
import { mergePath } from 'hono/utils/url'
|
||||
import type { ZodError, ZodSchema } from 'zod'
|
||||
import { ZodType, z } from 'zod'
|
||||
|
@ -657,7 +657,6 @@ export class OpenAPIHono<
|
|||
}
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return this as any
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { assertType, describe, it } from 'vitest'
|
||||
import { MiddlewareToHandlerType, OfHandlerType, OpenAPIHono, createRoute, z } from '../src/index'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import type { ExtractSchema } from 'hono/types'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { assertType, describe, it } from 'vitest'
|
||||
import { OpenAPIHono, createRoute, z } from '../src/index'
|
||||
import type { MiddlewareToHandlerType, OfHandlerType } from '../src/index'
|
||||
|
||||
describe('Types', () => {
|
||||
const RequestSchema = z.object({
|
||||
|
@ -276,7 +277,7 @@ describe('Middleware', () => {
|
|||
})
|
||||
|
||||
it('Should infer Env from router middleware', async () => {
|
||||
const app = new OpenAPIHono<{ Variables: { too: Symbol } }>()
|
||||
const app = new OpenAPIHono<{ Variables: { too: symbol } }>()
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: 'get',
|
||||
|
@ -308,7 +309,7 @@ describe('Middleware', () => {
|
|||
|
||||
type verifyFoo = Expect<Equal<typeof c.var.foo, string>>
|
||||
type verifyBar = Expect<Equal<typeof c.var.bar, number>>
|
||||
type verifyToo = Expect<Equal<typeof c.var.too, Symbol>>
|
||||
type verifyToo = Expect<Equal<typeof c.var.too, symbol>>
|
||||
|
||||
return c.json({})
|
||||
}
|
||||
|
@ -316,7 +317,7 @@ describe('Middleware', () => {
|
|||
})
|
||||
|
||||
it('Should infer Env root when no middleware provided', async () => {
|
||||
const app = new OpenAPIHono<{ Variables: { too: Symbol } }>()
|
||||
const app = new OpenAPIHono<{ Variables: { too: symbol } }>()
|
||||
app.openapi(
|
||||
createRoute({
|
||||
method: 'get',
|
||||
|
@ -331,7 +332,7 @@ describe('Middleware', () => {
|
|||
(c) => {
|
||||
c.var.too
|
||||
|
||||
type verify = Expect<Equal<typeof c.var.too, Symbol>>
|
||||
type verify = Expect<Equal<typeof c.var.too, symbol>>
|
||||
|
||||
return c.json({})
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import type { RouteConfig } from '@asteasolutions/zod-to-openapi'
|
||||
import type { Context, TypedResponse } from 'hono'
|
||||
import { accepts } from 'hono/accepts'
|
||||
import { bearerAuth } from 'hono/bearer-auth'
|
||||
import { hc } from 'hono/client'
|
||||
import type { ServerErrorStatusCode } from 'hono/utils/http-status'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { describe, expect, expectTypeOf, it, vi } from 'vitest'
|
||||
import { stringify } from 'yaml'
|
||||
import type { RouteConfigToTypedResponse } from '../src/index'
|
||||
import { OpenAPIHono, createRoute, z } from '../src/index'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import type { ServerErrorStatusCode } from 'hono/utils/http-status'
|
||||
import { stringify } from 'yaml'
|
||||
import { accepts } from 'hono/accepts'
|
||||
|
||||
describe('Constructor', () => {
|
||||
it('Should not require init object', () => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
||||
import { validator } from 'hono/validator'
|
||||
import { ZodObject, type ZodError, type ZodSchema, type z } from 'zod'
|
||||
import { ZodObject } from 'zod'
|
||||
import type { ZodError, ZodSchema, z } from 'zod'
|
||||
|
||||
export type Hook<
|
||||
T,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { vi } from 'vitest'
|
||||
import { z } from 'zod'
|
||||
import { zValidator } from '../src'
|
||||
import { vi } from 'vitest'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||
|
|
197
yarn.lock
197
yarn.lock
|
@ -2221,6 +2221,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/regexpp@npm:^4.12.1":
|
||||
version: 4.12.1
|
||||
resolution: "@eslint-community/regexpp@npm:4.12.1"
|
||||
checksum: a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/regexpp@npm:^4.4.0":
|
||||
version: 4.11.1
|
||||
resolution: "@eslint-community/regexpp@npm:4.11.1"
|
||||
|
@ -2246,6 +2253,26 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/config-array@npm:^0.19.0":
|
||||
version: 0.19.1
|
||||
resolution: "@eslint/config-array@npm:0.19.1"
|
||||
dependencies:
|
||||
"@eslint/object-schema": "npm:^2.1.5"
|
||||
debug: "npm:^4.3.1"
|
||||
minimatch: "npm:^3.1.2"
|
||||
checksum: 43b01f596ddad404473beae5cf95c013d29301c72778d0f5bf8a6699939c8a9a5663dbd723b53c5f476b88b0c694f76ea145d1aa9652230d140fe1161e4a4b49
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/core@npm:^0.9.0":
|
||||
version: 0.9.1
|
||||
resolution: "@eslint/core@npm:0.9.1"
|
||||
dependencies:
|
||||
"@types/json-schema": "npm:^7.0.15"
|
||||
checksum: 638104b1b5833a9bbf2329f0c0ddf322e4d6c0410b149477e02cd2b78c04722be90c14b91b8ccdef0d63a2404dff72a17b6b412ce489ea429ae6a8fcb8abff28
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/eslintrc@npm:^2.1.4":
|
||||
version: 2.1.4
|
||||
resolution: "@eslint/eslintrc@npm:2.1.4"
|
||||
|
@ -2280,6 +2307,23 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/eslintrc@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "@eslint/eslintrc@npm:3.2.0"
|
||||
dependencies:
|
||||
ajv: "npm:^6.12.4"
|
||||
debug: "npm:^4.3.2"
|
||||
espree: "npm:^10.0.1"
|
||||
globals: "npm:^14.0.0"
|
||||
ignore: "npm:^5.2.0"
|
||||
import-fresh: "npm:^3.2.1"
|
||||
js-yaml: "npm:^4.1.0"
|
||||
minimatch: "npm:^3.1.2"
|
||||
strip-json-comments: "npm:^3.1.1"
|
||||
checksum: 43867a07ff9884d895d9855edba41acf325ef7664a8df41d957135a81a477ff4df4196f5f74dc3382627e5cc8b7ad6b815c2cea1b58f04a75aced7c43414ab8b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/js@npm:8.57.0":
|
||||
version: 8.57.0
|
||||
resolution: "@eslint/js@npm:8.57.0"
|
||||
|
@ -2294,6 +2338,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/js@npm:9.17.0":
|
||||
version: 9.17.0
|
||||
resolution: "@eslint/js@npm:9.17.0"
|
||||
checksum: a0fda8657a01c60aa540f95397754267ba640ffb126e011b97fd65c322a94969d161beeaef57c1441c495da2f31167c34bd38209f7c146c7225072378c3a933d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/object-schema@npm:^2.1.4":
|
||||
version: 2.1.4
|
||||
resolution: "@eslint/object-schema@npm:2.1.4"
|
||||
|
@ -2301,6 +2352,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/object-schema@npm:^2.1.5":
|
||||
version: 2.1.5
|
||||
resolution: "@eslint/object-schema@npm:2.1.5"
|
||||
checksum: 5320691ed41ecd09a55aff40ce8e56596b4eb81f3d4d6fe530c50fdd6552d88102d1c1a29d970ae798ce30849752a708772de38ded07a6f25b3da32ebea081d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/plugin-kit@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "@eslint/plugin-kit@npm:0.1.0"
|
||||
|
@ -2310,6 +2368,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/plugin-kit@npm:^0.2.3":
|
||||
version: 0.2.4
|
||||
resolution: "@eslint/plugin-kit@npm:0.2.4"
|
||||
dependencies:
|
||||
levn: "npm:^0.4.1"
|
||||
checksum: 1bcfc0a30b1df891047c1d8b3707833bded12a057ba01757a2a8591fdc8d8fe0dbb8d51d4b0b61b2af4ca1d363057abd7d2fb4799f1706b105734f4d3fa0dbf1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/busboy@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "@fastify/busboy@npm:2.1.0"
|
||||
|
@ -2719,6 +2786,7 @@ __metadata:
|
|||
version: 0.0.0-use.local
|
||||
resolution: "@hono/oidc-auth@workspace:packages/oidc-auth"
|
||||
dependencies:
|
||||
"@jest/globals": "npm:^29.7.0"
|
||||
"@types/jest": "npm:^29.5.11"
|
||||
"@types/jsonwebtoken": "npm:^9.0.5"
|
||||
hono: "npm:^4.0.1"
|
||||
|
@ -2958,6 +3026,23 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@humanfs/core@npm:^0.19.1":
|
||||
version: 0.19.1
|
||||
resolution: "@humanfs/core@npm:0.19.1"
|
||||
checksum: aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@humanfs/node@npm:^0.16.6":
|
||||
version: 0.16.6
|
||||
resolution: "@humanfs/node@npm:0.16.6"
|
||||
dependencies:
|
||||
"@humanfs/core": "npm:^0.19.1"
|
||||
"@humanwhocodes/retry": "npm:^0.3.0"
|
||||
checksum: 8356359c9f60108ec204cbd249ecd0356667359b2524886b357617c4a7c3b6aace0fd5a369f63747b926a762a88f8a25bc066fa1778508d110195ce7686243e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@humanwhocodes/config-array@npm:^0.11.14":
|
||||
version: 0.11.14
|
||||
resolution: "@humanwhocodes/config-array@npm:0.11.14"
|
||||
|
@ -2990,6 +3075,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@humanwhocodes/retry@npm:^0.4.1":
|
||||
version: 0.4.1
|
||||
resolution: "@humanwhocodes/retry@npm:0.4.1"
|
||||
checksum: be7bb6841c4c01d0b767d9bb1ec1c9359ee61421ce8ba66c249d035c5acdfd080f32d55a5c9e859cdd7868788b8935774f65b2caf24ec0b7bd7bf333791f063b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@iarna/toml@npm:^2.2.5":
|
||||
version: 2.2.5
|
||||
resolution: "@iarna/toml@npm:2.2.5"
|
||||
|
@ -4908,7 +5000,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/estree@npm:1.0.6":
|
||||
"@types/estree@npm:1.0.6, @types/estree@npm:^1.0.6":
|
||||
version: 1.0.6
|
||||
resolution: "@types/estree@npm:1.0.6"
|
||||
checksum: cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a
|
||||
|
@ -5002,7 +5094,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.9":
|
||||
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.9":
|
||||
version: 7.0.15
|
||||
resolution: "@types/json-schema@npm:7.0.15"
|
||||
checksum: a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
|
||||
|
@ -6105,6 +6197,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"acorn@npm:^8.14.0":
|
||||
version: 8.14.0
|
||||
resolution: "acorn@npm:8.14.0"
|
||||
bin:
|
||||
acorn: bin/acorn
|
||||
checksum: 6d4ee461a7734b2f48836ee0fbb752903606e576cc100eb49340295129ca0b452f3ba91ddd4424a1d4406a98adfb2ebb6bd0ff4c49d7a0930c10e462719bbfd7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"agent-base@npm:6":
|
||||
version: 6.0.2
|
||||
resolution: "agent-base@npm:6.0.2"
|
||||
|
@ -8011,6 +8112,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.6":
|
||||
version: 7.0.6
|
||||
resolution: "cross-spawn@npm:7.0.6"
|
||||
dependencies:
|
||||
path-key: "npm:^3.1.0"
|
||||
shebang-command: "npm:^2.0.0"
|
||||
which: "npm:^2.0.1"
|
||||
checksum: 053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"crypto-random-string@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "crypto-random-string@npm:2.0.0"
|
||||
|
@ -9695,6 +9807,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-scope@npm:^8.2.0":
|
||||
version: 8.2.0
|
||||
resolution: "eslint-scope@npm:8.2.0"
|
||||
dependencies:
|
||||
esrecurse: "npm:^4.3.0"
|
||||
estraverse: "npm:^5.2.0"
|
||||
checksum: 8d2d58e2136d548ac7e0099b1a90d9fab56f990d86eb518de1247a7066d38c908be2f3df477a79cf60d70b30ba18735d6c6e70e9914dca2ee515a729975d70d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3":
|
||||
version: 3.4.3
|
||||
resolution: "eslint-visitor-keys@npm:3.4.3"
|
||||
|
@ -9709,6 +9831,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-visitor-keys@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "eslint-visitor-keys@npm:4.2.0"
|
||||
checksum: 2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:^8.57.0":
|
||||
version: 8.57.0
|
||||
resolution: "eslint@npm:8.57.0"
|
||||
|
@ -9806,6 +9935,55 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:^9.17.0":
|
||||
version: 9.17.0
|
||||
resolution: "eslint@npm:9.17.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.2.0"
|
||||
"@eslint-community/regexpp": "npm:^4.12.1"
|
||||
"@eslint/config-array": "npm:^0.19.0"
|
||||
"@eslint/core": "npm:^0.9.0"
|
||||
"@eslint/eslintrc": "npm:^3.2.0"
|
||||
"@eslint/js": "npm:9.17.0"
|
||||
"@eslint/plugin-kit": "npm:^0.2.3"
|
||||
"@humanfs/node": "npm:^0.16.6"
|
||||
"@humanwhocodes/module-importer": "npm:^1.0.1"
|
||||
"@humanwhocodes/retry": "npm:^0.4.1"
|
||||
"@types/estree": "npm:^1.0.6"
|
||||
"@types/json-schema": "npm:^7.0.15"
|
||||
ajv: "npm:^6.12.4"
|
||||
chalk: "npm:^4.0.0"
|
||||
cross-spawn: "npm:^7.0.6"
|
||||
debug: "npm:^4.3.2"
|
||||
escape-string-regexp: "npm:^4.0.0"
|
||||
eslint-scope: "npm:^8.2.0"
|
||||
eslint-visitor-keys: "npm:^4.2.0"
|
||||
espree: "npm:^10.3.0"
|
||||
esquery: "npm:^1.5.0"
|
||||
esutils: "npm:^2.0.2"
|
||||
fast-deep-equal: "npm:^3.1.3"
|
||||
file-entry-cache: "npm:^8.0.0"
|
||||
find-up: "npm:^5.0.0"
|
||||
glob-parent: "npm:^6.0.2"
|
||||
ignore: "npm:^5.2.0"
|
||||
imurmurhash: "npm:^0.1.4"
|
||||
is-glob: "npm:^4.0.0"
|
||||
json-stable-stringify-without-jsonify: "npm:^1.0.1"
|
||||
lodash.merge: "npm:^4.6.2"
|
||||
minimatch: "npm:^3.1.2"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
optionator: "npm:^0.9.3"
|
||||
peerDependencies:
|
||||
jiti: "*"
|
||||
peerDependenciesMeta:
|
||||
jiti:
|
||||
optional: true
|
||||
bin:
|
||||
eslint: bin/eslint.js
|
||||
checksum: 9edd8dd782b4ae2eb00a158ed4708194835d4494d75545fa63a51f020ed17f865c49b4ae1914a2ecbc7fdb262bd8059e811aeef9f0bae63dced9d3293be1bbdd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"espree@npm:^10.0.1, espree@npm:^10.1.0":
|
||||
version: 10.1.0
|
||||
resolution: "espree@npm:10.1.0"
|
||||
|
@ -9817,6 +9995,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"espree@npm:^10.3.0":
|
||||
version: 10.3.0
|
||||
resolution: "espree@npm:10.3.0"
|
||||
dependencies:
|
||||
acorn: "npm:^8.14.0"
|
||||
acorn-jsx: "npm:^5.3.2"
|
||||
eslint-visitor-keys: "npm:^4.2.0"
|
||||
checksum: 272beeaca70d0a1a047d61baff64db04664a33d7cfb5d144f84bc8a5c6194c6c8ebe9cc594093ca53add88baa23e59b01e69e8a0160ab32eac570482e165c462
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"espree@npm:^9.0.0, espree@npm:^9.6.0, espree@npm:^9.6.1":
|
||||
version: 9.6.1
|
||||
resolution: "espree@npm:9.6.1"
|
||||
|
@ -11398,9 +11587,7 @@ __metadata:
|
|||
"@types/node": "npm:^20.10.4"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^8.7.0"
|
||||
"@typescript-eslint/parser": "npm:^8.7.0"
|
||||
eslint: "npm:^8.57.0"
|
||||
eslint-plugin-import-x: "npm:^4.1.1"
|
||||
eslint-plugin-n: "npm:^17.10.2"
|
||||
eslint: "npm:^9.17.0"
|
||||
jest: "npm:^29.5.0"
|
||||
jest-environment-miniflare: "npm:^2.14.1"
|
||||
npm-run-all2: "npm:^6.2.2"
|
||||
|
|
Loading…
Reference in New Issue