feat: add 7 built-in middleware components inspired by Hono.js
New Middleware: - CORS: Cross-Origin Resource Sharing with full configuration support - Cookie Helper: Cookie management with signed cookie support (HMAC-SHA256) - JWT: JSON Web Token authentication (HS256/384/512) - Bearer Auth: Simple Bearer token authentication - Compression: Gzip and deflate response compression - Rate Limiting: Request rate limiting with customizable stores - Validator: Schema-based request validation Features: - Context store for middleware data sharing (get/set/get_client_ip) - Middleware export module with utility functions - Comprehensive property-based tests for all middleware - Example file demonstrating all middleware usage Files Added: - cors.v, cookie.v, jwt.v, bearer_auth.v, compress.v, rate_limit.v, validator.v - middleware.v (unified exports) - examples/middleware_demo.v - tests/test_*.v (9 new test files) Documentation: - Updated README.md with detailed middleware usage guide - Added Schema Builder API documentation
This commit is contained in:
+137
@@ -0,0 +1,137 @@
|
||||
module hono
|
||||
|
||||
import net.http
|
||||
|
||||
// BearerToken 类型 - 支持单 token 或多 token 配置
|
||||
pub type BearerToken = string | []string
|
||||
|
||||
// BearerAuthOptions 结构体 - Bearer Auth 配置选项
|
||||
pub struct BearerAuthOptions {
|
||||
pub:
|
||||
token BearerToken // Token 配置(必需)
|
||||
realm string // WWW-Authenticate realm
|
||||
prefix string = 'Bearer' // 认证前缀,默认 "Bearer"
|
||||
header_name string = 'Authorization' // 请求头名称,默认 "Authorization"
|
||||
hash_function ?fn (string) string // 哈希函数(用于安全比较)
|
||||
verify_token ?fn (string, Context) bool // 自定义验证回调
|
||||
}
|
||||
|
||||
// bearer_auth - Bearer Auth 中间件工厂函数
|
||||
// 返回一个 ContextMiddleware,用于验证 Bearer Token
|
||||
pub fn bearer_auth(options BearerAuthOptions) ContextMiddleware {
|
||||
return fn [options] (mut c Context, next fn (mut Context) http.Response) http.Response {
|
||||
// 获取 Authorization 头
|
||||
auth_header := c.req.header.get_custom(options.header_name) or {
|
||||
return unauthorized_response(mut c, options.realm, 'Missing authorization header')
|
||||
}
|
||||
|
||||
if auth_header.len == 0 {
|
||||
return unauthorized_response(mut c, options.realm, 'Missing authorization header')
|
||||
}
|
||||
|
||||
// 解析 token
|
||||
token := extract_bearer_token(auth_header, options.prefix) or {
|
||||
return unauthorized_response(mut c, options.realm, 'Invalid token format')
|
||||
}
|
||||
|
||||
// 验证 token
|
||||
if !validate_bearer_token(token, options, c) {
|
||||
return unauthorized_response(mut c, options.realm, 'Invalid token')
|
||||
}
|
||||
|
||||
// 将 token 存储到 Context(便于后续使用)
|
||||
c.set('bearer_token', token)
|
||||
|
||||
// 继续处理请求
|
||||
return next(mut c)
|
||||
}
|
||||
}
|
||||
|
||||
// extract_bearer_token - 从 Authorization 头提取 token
|
||||
fn extract_bearer_token(auth_header string, prefix string) !string {
|
||||
expected_prefix := '${prefix} '
|
||||
|
||||
if !auth_header.starts_with(expected_prefix) {
|
||||
return error('Invalid authorization format')
|
||||
}
|
||||
|
||||
token := auth_header[expected_prefix.len..].trim_space()
|
||||
|
||||
if token.len == 0 {
|
||||
return error('Empty token')
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// validate_bearer_token - 验证 Bearer Token
|
||||
fn validate_bearer_token(token string, options BearerAuthOptions, c Context) bool {
|
||||
// 优先使用自定义验证回调
|
||||
if verify_fn := options.verify_token {
|
||||
return verify_fn(token, c)
|
||||
}
|
||||
|
||||
// 使用配置的 token 进行验证
|
||||
match options.token {
|
||||
string {
|
||||
// 单 token 验证
|
||||
return secure_token_compare(token, options.token, options.hash_function)
|
||||
}
|
||||
[]string {
|
||||
// 多 token 验证
|
||||
for valid_token in options.token {
|
||||
if secure_token_compare(token, valid_token, options.hash_function) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// secure_token_compare - 安全的 token 比较(防止时序攻击)
|
||||
fn secure_token_compare(provided string, expected string, hash_fn ?fn (string) string) bool {
|
||||
// 如果提供了哈希函数,先对 token 进行哈希
|
||||
if hash_function := hash_fn {
|
||||
hashed_provided := hash_function(provided)
|
||||
hashed_expected := hash_function(expected)
|
||||
return constant_time_compare_bearer(hashed_provided, hashed_expected)
|
||||
}
|
||||
|
||||
// 直接使用常量时间比较
|
||||
return constant_time_compare_bearer(provided, expected)
|
||||
}
|
||||
|
||||
// constant_time_compare_bearer - 常量时间字符串比较,防止时序攻击
|
||||
fn constant_time_compare_bearer(a string, b string) bool {
|
||||
if a.len != b.len {
|
||||
return false
|
||||
}
|
||||
|
||||
mut result := u8(0)
|
||||
for i := 0; i < a.len; i++ {
|
||||
result |= a[i] ^ b[i]
|
||||
}
|
||||
|
||||
return result == 0
|
||||
}
|
||||
|
||||
// unauthorized_response - 返回 401 未授权响应
|
||||
fn unauthorized_response(mut c Context, realm string, message string) http.Response {
|
||||
c.status(401)
|
||||
|
||||
// 设置 WWW-Authenticate 头
|
||||
mut www_auth := 'Bearer'
|
||||
if realm.len > 0 {
|
||||
www_auth = 'Bearer realm="${realm}"'
|
||||
}
|
||||
c.headers['WWW-Authenticate'] = www_auth
|
||||
|
||||
return c.json('{"error":"Unauthorized","message":"${message}"}')
|
||||
}
|
||||
|
||||
// get_bearer_token - 从 Context 获取已验证的 Bearer Token
|
||||
// 这是一个便捷方法,用于在 handler 中获取已验证的 token
|
||||
pub fn get_bearer_token(c Context) ?string {
|
||||
return c.get('bearer_token')
|
||||
}
|
||||
Reference in New Issue
Block a user