1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

vweb: adding a vweb.csrf protection module (#15586)

This commit is contained in:
flopetautschnig 2022-09-06 12:18:39 +02:00 committed by GitHub
parent 95a328be98
commit 1c63ce479c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 155 additions and 0 deletions

View File

@ -127,6 +127,7 @@ const (
'vlib/v/tests/orm_joined_tables_select_test.v',
'vlib/v/tests/sql_statement_inside_fn_call_test.v',
'vlib/vweb/tests/vweb_test.v',
'vlib/vweb/csrf/csrf_test.v',
'vlib/vweb/request_test.v',
'vlib/net/http/request_test.v',
'vlib/net/http/response_test.v',
@ -172,6 +173,7 @@ const (
'vlib/clipboard/clipboard_test.v',
'vlib/vweb/tests/vweb_test.v',
'vlib/vweb/request_test.v',
'vlib/vweb/csrf/csrf_test.v',
'vlib/net/http/request_test.v',
'vlib/vweb/route_test.v',
'vlib/net/websocket/websocket_test.v',

View File

@ -0,0 +1,56 @@
module csrf
import rand
const chars = 'QWERTZUIOPASDFGHJKLYXCVBNMqwertzuiopasdfghjklyxcvbnm1234567890_-'
const cookie_key = '__Host-Csrf-Token'
// set_csrf_cookie - generates a CSRF-Token and sets the CSRF-Cookie. It is possible to set the http-only-status of the cookie to false by adding an argument of the HttpOnly-struct like this:
// `app.set_csrf_cookie(csrf.HttpOnly{false})`
// If no argument is set, http_only will be set to `true`by default.
pub fn (mut app App) set_csrf_cookie(h ...HttpOnly) App {
mut http_only := true
if h.len > 0 {
http_only = h[0].http_only
}
cookie := create_cookie(http_only)
app = App{app.Context, cookie.value}
app.set_cookie(cookie)
return app
}
// generate - generates the CSRF-Token
fn generate() string {
mut out := ''
for _ in 0 .. 42 {
i := rand.intn(csrf.chars.len_utf8()) or {
panic('Error while trying to generate Csrf-Token: $err')
}
out = out + csrf.chars[i..i + 1]
}
return out
}
// create_cookie - creates the cookie
fn create_cookie(h bool) CsrfCookie {
return CsrfCookie{
name: csrf.cookie_key
value: generate()
path: '/'
max_age: 0
secure: true
http_only: h
}
}
// get_csrf_token - returns the CSRF-Token that has been set. Make sure that you set one by using `set_csrf_cookie()`. If it's value is empty or no cookie has been generated, the function will thor an error.
pub fn (mut app App) get_csrf_token() ?string {
if app.csrf_cookie_value != '' {
return app.csrf_cookie_value
} else {
return IError(CsrfError{
m: 'The CSRF-Token-Value is empty. Please check if you have setted a cookie!'
})
}
}

View File

@ -0,0 +1,30 @@
import time
import net.http
import vweb
import vweb.csrf
const sport = 10801
struct App {
csrf.App
}
// index - will handle requests to path '/'
fn (mut app App) index() vweb.Result {
// Set a Csrf-Cookie(Token will be generated automatically) and set http_only-status. If no argument ist passed, it will be true by default.
app.set_csrf_cookie(csrf.HttpOnly{false})
// Get the token-value from the csrf-cookie that was just setted
token := app.get_csrf_token() or { panic(err) }
return app.text("Csrf-Token set! It's value is: $token")
}
fn test_send_a_request_to_homepage_expecting_a_csrf_cookie() ? {
go vweb.run_at(&App{}, vweb.RunParams{ port: sport })
time.sleep(500 * time.millisecond)
res := http.get('http://localhost:$sport/')?
if res.header.str().contains('__Host-Csrf-Token') {
assert true
} else {
assert false
}
}

37
vlib/vweb/csrf/protect.v Normal file
View File

@ -0,0 +1,37 @@
module csrf
import net.http
// csrf_protect - protects a handler-function against CSRF. Should be set at the beginning of the handler-function.
pub fn (mut app App) csrf_protect() CheckedApp {
req_cookies := app.req.cookies.clone()
app_csrf_cookie_str := app.get_cookie(cookie_key) or {
// Do not return normally!! No Csrf-Token was set!
app.set_status(403, '')
return app.text('Error 403 - Forbidden')
}
if cookie_key in req_cookies && req_cookies[cookie_key] == app_csrf_cookie_str {
// Csrf-Check OK - return app as normal in order to handle request normally
return app
} else if app.check_headers(app_csrf_cookie_str) {
// Csrf-Check OK - return app as normal in order to handle request normally
return app
} else {
// Do not return normally!! The client has not passed the Csrf-Check!!
app.set_status(403, '')
return app.text('Error 403 - Forbidden')
}
}
// check_headers - checks if there is a CSRF-Token that was sent with the headers of a request
fn (app App) check_headers(app_csrf_cookie_str string) bool {
token := app.req.header.get_custom('Csrf-Token', http.HeaderQueryConfig{true}) or {
return false
}
if token == app_csrf_cookie_str {
return true
} else {
return false
}
}

30
vlib/vweb/csrf/structs.v Normal file
View File

@ -0,0 +1,30 @@
// This module provides csrf-protection for apps written with libe vweb.
module csrf
import vweb
import net.http
type CsrfCookie = http.Cookie
interface CheckedApp {}
pub struct App {
vweb.Context
csrf_cookie_value string
}
pub struct HttpOnly {
http_only bool
}
struct CsrfError {
Error
m string
}
fn (err CsrfError) msg() string {
return err.m
}
// Written by flopetautschnig (floscodes) 2022