-
-
Notifications
You must be signed in to change notification settings - Fork 599
/
index.ts
90 lines (84 loc) · 2.52 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* @module
* CSRF Protection Middleware for Hono.
*/
import type { Context } from '../../context'
import { HTTPException } from '../../http-exception'
import type { MiddlewareHandler } from '../../types'
type IsAllowedOriginHandler = (origin: string, context: Context) => boolean
interface CSRFOptions {
origin?: string | string[] | IsAllowedOriginHandler
}
const isSafeMethodRe = /^(GET|HEAD)$/
const isRequestedByFormElementRe =
/^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/
/**
* CSRF Protection Middleware for Hono.
*
* @see {@link https://hono.dev/docs/middleware/builtin/csrf}
*
* @param {CSRFOptions} [options] - The options for the CSRF protection middleware.
* @param {string|string[]|(origin: string, context: Context) => boolean} [options.origin] - Specify origins.
* @returns {MiddlewareHandler} The middleware handler function.
*
* @example
* ```ts
* const app = new Hono()
*
* app.use(csrf())
*
* // Specifying origins with using `origin` option
* // string
* app.use(csrf({ origin: 'myapp.example.com' }))
*
* // string[]
* app.use(
* csrf({
* origin: ['myapp.example.com', 'development.myapp.example.com'],
* })
* )
*
* // Function
* // It is strongly recommended that the protocol be verified to ensure a match to `$`.
* // You should *never* do a forward match.
* app.use(
* '*',
* csrf({
* origin: (origin) => /https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin),
* })
* )
* ```
*/
export const csrf = (options?: CSRFOptions): MiddlewareHandler => {
const handler: IsAllowedOriginHandler = ((optsOrigin) => {
if (!optsOrigin) {
return (origin, c) => origin === new URL(c.req.url).origin
} else if (typeof optsOrigin === 'string') {
return (origin) => origin === optsOrigin
} else if (typeof optsOrigin === 'function') {
return optsOrigin
} else {
return (origin) => optsOrigin.includes(origin)
}
})(options?.origin)
const isAllowedOrigin = (origin: string | undefined, c: Context) => {
if (origin === undefined) {
// denied always when origin header is not present
return false
}
return handler(origin, c)
}
return async function csrf(c, next) {
if (
!isSafeMethodRe.test(c.req.method) &&
isRequestedByFormElementRe.test(c.req.header('content-type') || '') &&
!isAllowedOrigin(c.req.header('origin'), c)
) {
const res = new Response('Forbidden', {
status: 403,
})
throw new HTTPException(403, { res })
}
await next()
}
}