Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
Alex Kotov | 58cebbac0b | |
Alex Kotov | b318019955 | |
Alex Kotov | c0211870f0 | |
Alex Kotov | f9068fca7f | |
Alex Kotov | d4d233526b | |
Alex Kotov | 2caf37e455 | |
Alex Kotov | e2fc0f052b | |
Alex Kotov | 7e3aaecc45 | |
Fenhl | 5002bdbab5 | |
Fenhl | 610f27297c | |
Alex Kotov | 0004bd2992 | |
Alex Kotov | 10a2780b42 | |
Alex Kotov | 113b69505e |
|
@ -16,5 +16,4 @@ publish = true
|
|||
base64 = { version = "0.13.0" }
|
||||
bcrypt = { version = "0.9" }
|
||||
rand = { version = "0.8.3" }
|
||||
rocket = { version = "0.4.5", features = ["private-cookies"] }
|
||||
time = { version = "0.1.38" }
|
||||
rocket = { version = "0.5.0-rc.2", features = ["secrets"] }
|
||||
|
|
44
README.md
44
README.md
|
@ -25,7 +25,7 @@ Table of contents
|
|||
Usage
|
||||
-----
|
||||
|
||||
Attach [fairing](https://rocket.rs/v0.4/guide/fairings/#fairings) to the Rocket
|
||||
Attach [fairing](https://rocket.rs/v0.5-rc/guide/fairings/#fairings) to the Rocket
|
||||
instance:
|
||||
|
||||
```rust
|
||||
|
@ -34,26 +34,44 @@ instance:
|
|||
#[macro_use] extern crate rocket;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket_dyn_templates::Template;
|
||||
|
||||
fn main() {
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::ignite()
|
||||
.attach(rocket_csrf::Fairing::default())
|
||||
.attach(Template::fairing())
|
||||
.mount("/", routes![new, create])
|
||||
.launch();
|
||||
}
|
||||
```
|
||||
|
||||
Add [guard](https://rocket.rs/v0.4/guide/requests/#request-guards) to any
|
||||
You also can configure
|
||||
[fairing](https://rocket.rs/v0.5-rc/guide/fairings/#fairings):
|
||||
|
||||
```rust
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::ignite()
|
||||
.attach(rocket_csrf::Fairing::new(
|
||||
rocket_csrf::CsrfConfig::default()
|
||||
.with_cookie_name("foobar")
|
||||
.with_cookie_len(64)
|
||||
.with_lifetime(time::Duration::days(3)),
|
||||
))
|
||||
.attach(Template::fairing())
|
||||
.mount("/", routes![new, create])
|
||||
}
|
||||
```
|
||||
|
||||
Add [guard](https://rocket.rs/v0.5-rc/guide/requests/#request-guards) to any
|
||||
request where you want to have access to session's CSRF token (e.g. to include
|
||||
it in forms) or verify it (e.g. to validate form):
|
||||
|
||||
```rust
|
||||
use rocket::form::Form;
|
||||
use rocket::response::Redirect;
|
||||
use rocket::request::Form;
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket_csrf::CsrfToken;
|
||||
use rocket_dyn_templates::Template;
|
||||
|
||||
#[get("/comments/new")]
|
||||
fn new(csrf_token: CsrfToken) -> Template {
|
||||
|
@ -67,8 +85,8 @@ fn create(csrf_token: CsrfToken, form: Form<Comment>) -> Redirect {
|
|||
```
|
||||
|
||||
Get CSRF token from
|
||||
[guard](https://rocket.rs/v0.4/guide/requests/#request-guards)
|
||||
to use it in [templates](https://rocket.rs/v0.4/guide/responses/#templates):
|
||||
[guard](https://rocket.rs/v0.5-rc/guide/requests/#request-guards)
|
||||
to use it in [templates](https://rocket.rs/v0.5-rc/guide/responses/#templates):
|
||||
|
||||
```rust
|
||||
#[get("/comments/new")]
|
||||
|
@ -80,7 +98,7 @@ fn new(csrf_token: CsrfToken) -> Template {
|
|||
```
|
||||
|
||||
Add CSRF token to your HTML forms in
|
||||
[templates](https://rocket.rs/v0.4/guide/responses/#templates):
|
||||
[templates](https://rocket.rs/v0.5-rc/guide/responses/#templates):
|
||||
|
||||
```html
|
||||
<form method="post" action="/comments">
|
||||
|
@ -90,7 +108,7 @@ Add CSRF token to your HTML forms in
|
|||
```
|
||||
|
||||
Add attribute `authenticity_token` to your
|
||||
[forms](https://rocket.rs/v0.4/guide/requests/#forms):
|
||||
[forms](https://rocket.rs/v0.5-rc/guide/requests/#forms):
|
||||
|
||||
```rust
|
||||
#[derive(FromForm)]
|
||||
|
@ -100,7 +118,7 @@ struct Comment {
|
|||
}
|
||||
```
|
||||
|
||||
Validate [forms](https://rocket.rs/v0.4/guide/requests/#forms) to have valid
|
||||
Validate [forms](https://rocket.rs/v0.5-rc/guide/requests/#forms) to have valid
|
||||
authenticity token:
|
||||
|
||||
```rust
|
||||
|
@ -122,7 +140,7 @@ TODO
|
|||
----
|
||||
|
||||
* [ ] Add fairing to verify all requests as an option.
|
||||
* [ ] Add [data guard](https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html) to verify forms with a guard.
|
||||
* [ ] Add [data guard](https://api.rocket.rs/v0.5-rc/rocket/data/trait.FromData.html) to verify forms with a guard.
|
||||
* [ ] Add helpers to render form field.
|
||||
* [ ] Add helpers to add HTML meta tags for Ajax with `X-CSRF-Token` header.
|
||||
* [ ] Verify `X-CSRF-Token` header.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,8 +6,8 @@ edition = "2018"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
rocket = "0.4.5"
|
||||
rocket = "0.5.0-rc.2"
|
||||
rocket_csrf = { path = "../.." }
|
||||
rocket_contrib = { version = "0.4.5", features = ["handlebars_templates"] }
|
||||
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["handlebars"] }
|
||||
serde = "1.0.117"
|
||||
serde_derive = "1.0.117"
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
#![feature(decl_macro)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use rocket::form::Form;
|
||||
use rocket::request::FlashMessage;
|
||||
use rocket::response::{Flash, Redirect};
|
||||
use rocket::request::{FlashMessage, Form};
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket_csrf::CsrfToken;
|
||||
use rocket_dyn_templates::Template;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TemplateContext {
|
||||
|
@ -20,12 +23,12 @@ struct Comment {
|
|||
text: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
rocket::ignite()
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.attach(rocket_csrf::Fairing::default())
|
||||
.attach(Template::fairing())
|
||||
.mount("/", routes![index, new, create])
|
||||
.launch();
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
|
@ -37,7 +40,7 @@ fn index() -> Redirect {
|
|||
fn new(csrf_token: CsrfToken, flash: Option<FlashMessage>) -> Template {
|
||||
let template_context = TemplateContext {
|
||||
authenticity_token: csrf_token.authenticity_token().to_string(),
|
||||
flash: flash.map(|msg| format!("{}! {}", msg.name(), msg.msg())),
|
||||
flash: flash.map(|flash| flash.message().to_string()),
|
||||
};
|
||||
|
||||
Template::render("comments/new", &template_context)
|
||||
|
@ -46,10 +49,7 @@ fn new(csrf_token: CsrfToken, flash: Option<FlashMessage>) -> Template {
|
|||
#[post("/comments", data = "<form>")]
|
||||
fn create(csrf_token: CsrfToken, form: Form<Comment>) -> Flash<Redirect> {
|
||||
if let Err(_) = csrf_token.verify(&form.authenticity_token) {
|
||||
return Flash::error(
|
||||
Redirect::to(uri!(new)),
|
||||
"invalid authenticity token",
|
||||
);
|
||||
return Flash::error(Redirect::to(uri!(new)), "invalid authenticity token");
|
||||
}
|
||||
|
||||
Flash::success(
|
||||
|
|
24
src/lib.rs
24
src/lib.rs
|
@ -1,13 +1,14 @@
|
|||
use bcrypt::{hash, verify};
|
||||
use rand::{distributions::Standard, Rng};
|
||||
use rocket::{
|
||||
fairing::{Fairing as RocketFairing, Info, Kind},
|
||||
async_trait,
|
||||
fairing::{self, Fairing as RocketFairing, Info, Kind},
|
||||
http::{Cookie, Status},
|
||||
request::{FromRequest, Outcome},
|
||||
time::{Duration, OffsetDateTime},
|
||||
Data, Request, Rocket, State,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use time::Duration;
|
||||
|
||||
const BCRYPT_COST: u32 = 8;
|
||||
|
||||
|
@ -94,20 +95,21 @@ impl CsrfToken {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RocketFairing for Fairing {
|
||||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "CSRF",
|
||||
kind: Kind::Attach | Kind::Request,
|
||||
kind: Kind::Ignite | Kind::Request,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_attach(&self, rocket: Rocket) -> std::result::Result<Rocket, Rocket> {
|
||||
async fn on_ignite(&self, rocket: Rocket<rocket::Build>) -> fairing::Result {
|
||||
Ok(rocket.manage(self.config.clone()))
|
||||
}
|
||||
|
||||
fn on_request(&self, request: &mut Request, _: &Data) {
|
||||
let config = request.guard::<State<CsrfConfig>>().unwrap();
|
||||
async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) {
|
||||
let config = request.guard::<&State<CsrfConfig>>().await.unwrap();
|
||||
|
||||
if let Some(_) = request.valid_csrf_token_from_session(&config) {
|
||||
return;
|
||||
|
@ -120,7 +122,7 @@ impl RocketFairing for Fairing {
|
|||
|
||||
let encoded = base64::encode(&values[..]);
|
||||
|
||||
let expires = time::now_utc() + config.lifespan;
|
||||
let expires = OffsetDateTime::now_utc() + config.lifespan;
|
||||
|
||||
request.cookies().add_private(
|
||||
Cookie::build(config.cookie_name.clone(), encoded)
|
||||
|
@ -130,11 +132,13 @@ impl RocketFairing for Fairing {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for CsrfToken {
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for CsrfToken {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||
let config = request.guard::<State<CsrfConfig>>().unwrap();
|
||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let config = request.guard::<&State<CsrfConfig>>().await.unwrap();
|
||||
|
||||
match request.valid_csrf_token_from_session(&config) {
|
||||
None => Outcome::Failure((Status::Forbidden, ())),
|
||||
Some(token) => Outcome::Success(Self(base64::encode(token))),
|
||||
|
|
|
@ -1,32 +1,38 @@
|
|||
#![feature(decl_macro)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
const COOKIE_NAME: &str = "foobar";
|
||||
const COOKIE_LEN: usize = 64;
|
||||
|
||||
fn client() -> rocket::local::Client {
|
||||
rocket::local::Client::new(rocket()).unwrap()
|
||||
fn client() -> rocket::local::blocking::Client {
|
||||
rocket::local::blocking::Client::tracked(rocket()).unwrap()
|
||||
}
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
rocket::ignite()
|
||||
fn rocket() -> rocket::Rocket<rocket::Build> {
|
||||
rocket::build()
|
||||
.attach(rocket_csrf::Fairing::new(
|
||||
rocket_csrf::CsrfConfig::default()
|
||||
.with_cookie_name(COOKIE_NAME)
|
||||
.with_cookie_len(COOKIE_LEN)
|
||||
.with_lifetime(time::Duration::days(3))
|
||||
.with_lifetime(rocket::time::Duration::days(3)),
|
||||
))
|
||||
.mount("/", routes![index])
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index() {
|
||||
}
|
||||
fn index() {}
|
||||
|
||||
#[test]
|
||||
fn add_csrf_token_to_cookies() {
|
||||
base64::decode(client().get("/").dispatch().cookies().iter().find(|cookie| {
|
||||
cookie.name() == COOKIE_NAME
|
||||
}).unwrap().value()).unwrap();
|
||||
base64::decode(
|
||||
client()
|
||||
.get("/")
|
||||
.dispatch()
|
||||
.cookies()
|
||||
.iter()
|
||||
.find(|cookie| cookie.name() == COOKIE_NAME)
|
||||
.unwrap()
|
||||
.value(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
#![feature(decl_macro)]
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
|
||||
fn client() -> rocket::local::Client {
|
||||
rocket::local::Client::new(rocket()).unwrap()
|
||||
fn client() -> rocket::local::blocking::Client {
|
||||
rocket::local::blocking::Client::tracked(rocket()).unwrap()
|
||||
}
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
rocket::ignite()
|
||||
fn rocket() -> rocket::Rocket<rocket::Build> {
|
||||
rocket::build()
|
||||
.attach(rocket_csrf::Fairing::default())
|
||||
.mount("/", routes![index])
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index() {
|
||||
}
|
||||
fn index() {}
|
||||
|
||||
#[test]
|
||||
fn add_csrf_token_to_cookies() {
|
||||
base64::decode(client().get("/").dispatch().cookies().iter().find(|cookie| {
|
||||
cookie.name() == "csrf_token"
|
||||
}).unwrap().value()).unwrap();
|
||||
base64::decode(
|
||||
client()
|
||||
.get("/")
|
||||
.dispatch()
|
||||
.cookies()
|
||||
.iter()
|
||||
.find(|cookie| cookie.name() == "csrf_token")
|
||||
.unwrap()
|
||||
.value(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
#![feature(decl_macro)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use bcrypt::verify;
|
||||
use rand::RngCore;
|
||||
use rocket::http::Cookie;
|
||||
use rocket_csrf::CsrfToken;
|
||||
use bcrypt::verify;
|
||||
|
||||
const COOKIE_NAME: &str = "foobar";
|
||||
const COOKIE_LEN: usize = 64;
|
||||
|
||||
fn client() -> rocket::local::Client {
|
||||
rocket::local::Client::new(rocket()).unwrap()
|
||||
fn client() -> rocket::local::blocking::Client {
|
||||
rocket::local::blocking::Client::tracked(rocket()).unwrap()
|
||||
}
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
rocket::ignite()
|
||||
fn rocket() -> rocket::Rocket<rocket::Build> {
|
||||
rocket::build()
|
||||
.attach(rocket_csrf::Fairing::new(
|
||||
rocket_csrf::CsrfConfig::default()
|
||||
.with_cookie_name(COOKIE_NAME)
|
||||
.with_cookie_len(COOKIE_LEN)
|
||||
.with_lifetime(time::Duration::days(3))
|
||||
.with_lifetime(rocket::time::Duration::days(3)),
|
||||
))
|
||||
.mount("/", routes![index])
|
||||
}
|
||||
|
@ -41,8 +40,6 @@ fn respond_with_valid_authenticity_token() {
|
|||
.get("/")
|
||||
.private_cookie(Cookie::new(COOKIE_NAME, encoded.to_string()))
|
||||
.dispatch()
|
||||
.body()
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
#![feature(decl_macro)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use bcrypt::verify;
|
||||
use rand::RngCore;
|
||||
use rocket::http::Cookie;
|
||||
use rocket_csrf::CsrfToken;
|
||||
use bcrypt::verify;
|
||||
|
||||
fn client() -> rocket::local::Client {
|
||||
rocket::local::Client::new(rocket()).unwrap()
|
||||
fn client() -> rocket::local::blocking::Client {
|
||||
rocket::local::blocking::Client::tracked(rocket()).unwrap()
|
||||
}
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
rocket::ignite()
|
||||
fn rocket() -> rocket::Rocket<rocket::Build> {
|
||||
rocket::build()
|
||||
.attach(rocket_csrf::Fairing::default())
|
||||
.mount("/", routes![index])
|
||||
}
|
||||
|
@ -33,8 +32,6 @@ fn respond_with_valid_authenticity_token() {
|
|||
.get("/")
|
||||
.private_cookie(Cookie::new("csrf_token", encoded.to_string()))
|
||||
.dispatch()
|
||||
.body()
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
|
|
Loading…
Reference in New Issue