Initial commit

This commit is contained in:
Devin Zuczek
2026-06-08 18:23:40 -04:00
commit 8e644a5c20
141 changed files with 21414 additions and 0 deletions
@@ -0,0 +1,18 @@
import { HTTPException } from 'hono/http-exception'
import type { ContentfulStatusCode } from 'hono/utils/http-status'
/** Generates a new HTTPException with the given status and message as a JSON response.
*
* **Example:** `throw newHTTPException(401, 'unauthorized')`
*/
export function newHTTPException(status: ContentfulStatusCode, message: string): HTTPException {
return new HTTPException(status, { message })
}
export interface APIError {
success: false
error: {
message: string
}
}
@@ -0,0 +1,9 @@
import { WorkersLogger } from 'workers-tagged-logger'
export type LogTagHints = {
// add common tags here so that they show up as hints
// in `logger.setTags()` and `logger.withTags()`
url: string
}
export const logger = new WorkersLogger<LogTagHints>()
@@ -0,0 +1,72 @@
import { redactUrl } from './url'
import type { Context } from 'hono'
import type { HonoApp } from '../types'
export interface LogDataRequest {
url: string
method: string
path: string
/** Hono route for the request */
routePath: string
/* URL search params */
searchParams: string
headers: string
/** Eyeball IP address of the request */
ip?: string
timestamp: string
}
/**
* Get logdata from request
*/
export function getRequestLogData<T extends HonoApp>(
c: Context<T>,
requestStartTimestamp: number
): LogDataRequest {
const redactedUrl = redactUrl(c.req.url)
return {
url: redactedUrl.toString(),
method: c.req.method,
path: c.req.path,
routePath: c.req.routePath,
searchParams: redactedUrl.searchParams.toString(),
headers: stringifyHeaders(c.req.raw.headers),
ip:
c.req.header('cf-connecting-ip') ||
c.req.header('x-real-ip') ||
c.req.header('x-forwarded-for'),
timestamp: new Date(requestStartTimestamp).toISOString(),
}
}
const SENSITIVE_HEADER_NAMES = new Set([
'authorization',
'proxy-authorization',
'cookie',
'set-cookie',
'cf-access-jwt-assertion',
])
function isSensitiveHeader(name: string): boolean {
const normalizedName = name.toLowerCase()
return (
SENSITIVE_HEADER_NAMES.has(normalizedName) ||
normalizedName.includes('token') ||
normalizedName.includes('secret') ||
normalizedName.includes('jwt') ||
normalizedName.includes('signature') ||
normalizedName.includes('session') ||
normalizedName.endsWith('-key') ||
normalizedName.endsWith('_key')
)
}
function stringifyHeaders(headers: Headers): string {
return JSON.stringify(
Object.fromEntries(
Array.from(headers, ([name, value]) => [name, isSensitiveHeader(name) ? 'REDACTED' : value])
)
)
}
+29
View File
@@ -0,0 +1,29 @@
/** Redacts keys from a url */
export function redactUrl(_url: URL | string): URL {
let url: URL
if (typeof _url === 'string') {
url = new URL(_url)
} else {
url = new URL(_url.toString()) // clone
}
for (const [key] of url.searchParams) {
if (/key|token|secret|password|passwd|auth|credential/i.test(key)) {
url.searchParams.set(key, 'REDACTED')
}
}
return url
}
/**
* Converts a URLSearchParams object into an array of "key=value" strings.
*
* @param searchParams - The URLSearchParams object to convert.
* @returns An array of strings, where each string is in the format "key=value".
*/
export function searchParamsToArray(searchParams: URLSearchParams): string[] {
const result: string[] = []
for (const [key, value] of searchParams.entries()) {
result.push(`${key}=${value}`)
}
return result
}