mirror of
https://git.42l.fr/neil/sncf.git
synced 2024-05-19 22:26:35 +02:00
Compare commits
32 commits
Author | SHA1 | Date | |
---|---|---|---|
507e8877c8 | |||
44c0e27a72 | |||
d1c112f12a | |||
fd4d721be4 | |||
d419aea412 | |||
11504c00e0 | |||
b1fd3fccae | |||
8d67cb340c | |||
730a023e55 | |||
e354b5b14b | |||
002a0c9ef2 | |||
fafe2bf3fe | |||
7a4839541d | |||
15c73715a8 | |||
8fc232d022 | |||
b45e65d427 | |||
ade36cf053 | |||
8d6a68b33c | |||
9612086790 | |||
05a15b1680 | |||
162cdad7fe | |||
7fbfcf485c | |||
d6a4a6591a | |||
83a80c0969 | |||
9fecf9ace9 | |||
0d38e2f2d4 | |||
112ea773a2 | |||
c24b98bcca | |||
5a521b0497 | |||
1251b431a6 | |||
6e231a73b6 | |||
240baca044 |
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sncf"
|
||||
version = "1.3.0"
|
||||
version = "1.5.0"
|
||||
authors = ["Association 42l <contact@noreply.example.org>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -11,9 +11,10 @@ mysql = [ "diesel/mysql" ]
|
|||
sqlite = [ "diesel/sqlite" ]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = "2.1.0"
|
||||
actix-rt = "2.2.0"
|
||||
actix-web = "3.3.2"
|
||||
actix-files = "0.5.0"
|
||||
actix-session = "0.4"
|
||||
diesel = { version = "1.4", features = ["r2d2", "chrono"] }
|
||||
diesel_migrations = "1.4"
|
||||
url = "2.2"
|
||||
|
@ -23,7 +24,7 @@ serde = "1.0"
|
|||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
regex = "1.3"
|
||||
regex = "1.5"
|
||||
base64 = "0.13"
|
||||
rand = "0.8"
|
||||
askama = "0.10"
|
||||
|
|
10
README.md
10
README.md
|
@ -1,5 +1,8 @@
|
|||
# sncf
|
||||
|
||||
**Warning: Breaking changes introduced on a minor Nextcloud release (>= 22.3.0) broke sncf. Please do not update until it is fixed. It seems easy to fix (use `/login` instead of `/csrftoken`) but I need time, feel free to try to fix it.**
|
||||
|
||||
|
||||
Simple Nextcloud Forms (sncf) is a lightweight proxy written in Rust with the [Actix](https://actix.rs) framework.
|
||||
|
||||
It is meant to make form creation easier, through the use of the [Nextcloud Forms](https://github.com/nextcloud/forms) application, by generating administration links for forms: **users do not need to log in or register**, they just need to keep a link (in the form of `https://your-instance.com/admin/<45-byte base64 key>`) to log them in and give them access to their forms.
|
||||
|
@ -31,8 +34,9 @@ Compatibility with sncf has been tested for the following Nextcloud and Nextclou
|
|||
|--------------|------------|------------------|
|
||||
| 1.0.0 | 19.0.1, 19.0.2 | 2.0.2, 2.0.3 |
|
||||
| 1.0.1, 1.0.2, 1.1.0, 1.2.0 | 19.0.1, 19.0.2, 20.0.0\*, 20.0.1 | 2.0.4 |
|
||||
| **No support** \*\* | above 20.0.1, below 21.x | above 2.0.4, below 2.2.2
|
||||
| 1.3.0 | 21.0.0 | 2.2.2 |
|
||||
| **Unsupported** \*\* | above 20.0.1, below 21.x | above 2.0.4, below 2.2.2
|
||||
| 1.3.0, 1.4.0 | 21.0.0 | 2.2.2, 2.2.3, 2.2.4 |
|
||||
| 1.5.0 | 22.2.0 | 2.3.0 |
|
||||
|
||||
|
||||
\* Breaking changes, please check [the wiki](https://git.42l.fr/neil/sncf/wiki/Upgrade-from-a-previous-version) if you need to upgrade from a previous version.
|
||||
|
@ -48,7 +52,7 @@ If you upgrade anyway and notice a breaking change, please file an issue.
|
|||
|
||||
#### Donations
|
||||
|
||||
If you like this work, please donate to the [42l association](https://42l.fr) (maintaining sncf) or [Nextcloud](https://www.bountysource.com/teams/nextcloud) (maintaining Nextcloud and Nextcloud Forms).
|
||||
If you like this work, please donate to the [42l association](https://42l.fr) (maintaining sncf) or [Nextcloud](https://nextcloud.com/include/) (maintaining Nextcloud and Nextcloud Forms).
|
||||
|
||||
#### Translating
|
||||
|
||||
|
|
10
lang.json
10
lang.json
|
@ -39,6 +39,10 @@
|
|||
"fr": "Créer un formulaire",
|
||||
"de": "Erstellen einer Umfrage"
|
||||
},
|
||||
"index_continueform_button": {
|
||||
"en": "Access your forms",
|
||||
"fr": "Accéder à vos formulaires"
|
||||
},
|
||||
"index_beta_banner_title": {
|
||||
"en": "Warning: Service in beta.",
|
||||
"fr": "Attention : Service en bêta.",
|
||||
|
@ -105,9 +109,9 @@
|
|||
"de": "Wählen und Ordnen Sie ihre Felder"
|
||||
},
|
||||
"index_panel2_desc1": {
|
||||
"en": "The software currently supports five field types.",
|
||||
"fr": "Pour le moment, le logiciel supporte cinq types de champs.",
|
||||
"de": "Im Moment unterstützt die Software fünf Typen von Feldern."
|
||||
"en": "The software currently supports seven field types.",
|
||||
"fr": "Pour le moment, le logiciel supporte sept types de champs.",
|
||||
"de": "Im Moment unterstützt die Software sieben Typen von Feldern."
|
||||
},
|
||||
"index_panel2_desc2": {
|
||||
"en": "New field types are ",
|
||||
|
|
|
@ -36,23 +36,6 @@ pub fn is_logged_in(req: &HttpRequest) -> Option<&str> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn has_admintoken(req: &HttpRequest) -> Option<String> {
|
||||
get_cookie(req, "sncf_admin_token")
|
||||
}
|
||||
|
||||
pub fn has_csrftoken(req: &HttpRequest) -> Option<String> {
|
||||
get_cookie(req, "sncf_csrf_cookie")
|
||||
}
|
||||
|
||||
fn get_cookie(req: &HttpRequest, cookie_name: &str) -> Option<String> {
|
||||
let c = req.headers().get("Cookie")?.to_str().ok()?;
|
||||
if c.contains(cookie_name) {
|
||||
Some(c.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// attempts to create the account from Nextcloud's API
|
||||
// returns the newly created username.
|
||||
// if it fails (bad return code), returns None.
|
||||
|
@ -207,8 +190,6 @@ pub async fn login(
|
|||
str_cookiepair.push_str(&format!("{}={}; ", cookie_k, cookie_v));
|
||||
}
|
||||
|
||||
println!("SET-COOKIE: {}", str_cookiepair);
|
||||
|
||||
// load requesttoken regex
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r#"\{"token":"(?P<token>[^"]*)"\}"#)
|
||||
|
|
|
@ -7,8 +7,8 @@ use crate::database::schema::form::dsl::*;
|
|||
use crate::database::structs::Form;
|
||||
use crate::DbConn;
|
||||
|
||||
#[table_name = "form"]
|
||||
#[derive(Serialize, Insertable)]
|
||||
#[table_name = "form"]
|
||||
pub struct InsertableForm {
|
||||
pub created_at: NaiveDateTime,
|
||||
pub lastvisit_at: NaiveDateTime,
|
||||
|
|
|
@ -4,8 +4,8 @@ use chrono::NaiveDateTime;
|
|||
use crate::database::schema::form;
|
||||
//use crate::config::CONFIG;
|
||||
|
||||
#[table_name = "form"]
|
||||
#[derive(Serialize, Queryable, Insertable, Debug, Clone)]
|
||||
#[table_name = "form"]
|
||||
pub struct Form {
|
||||
pub id: i32,
|
||||
pub created_at: NaiveDateTime,
|
||||
|
|
193
src/forward.rs
193
src/forward.rs
|
@ -1,9 +1,9 @@
|
|||
use actix_web::client::{Client, ClientRequest};
|
||||
use actix_web::{http, web, HttpRequest, HttpResponse};
|
||||
use actix_session::Session;
|
||||
use askama::Template;
|
||||
use chrono::Utc;
|
||||
use csrf::{AesGcmCsrfProtection, CsrfProtection};
|
||||
use regex::Regex;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
|
@ -31,7 +31,14 @@ pub async fn forward(
|
|||
// if check_route returns true,
|
||||
// the user supposedly tried to access a restricted page.
|
||||
// They get redirected to the main page.
|
||||
if check_route(route) {
|
||||
if route.starts_with("/apps/files") {
|
||||
// exception for /apps/files: always redirect to /apps/forms
|
||||
debug(&format!("Files route blocked: {}", route));
|
||||
return Ok(web_redir("/apps/forms").await.map_err(|e| {
|
||||
eprintln!("error_redirect: {}", e);
|
||||
crash(get_lang(&req), "error_redirect")
|
||||
})?);
|
||||
} else if check_route(route) {
|
||||
debug(&format!("Restricted route blocked: {}", route));
|
||||
return Ok(web_redir("/").await.map_err(|e| {
|
||||
eprintln!("error_redirect: {}", e);
|
||||
|
@ -45,8 +52,8 @@ pub async fn forward(
|
|||
// (prevents the user from sending some specific POST requests)
|
||||
if check_request(route, &body) {
|
||||
debug(&format!(
|
||||
"Restricted request: {}",
|
||||
String::from_utf8_lossy(&body)
|
||||
"Restricted request: {}",
|
||||
String::from_utf8_lossy(&body)
|
||||
));
|
||||
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||
}
|
||||
|
@ -63,8 +70,8 @@ pub async fn forward(
|
|||
// and basic-auth, because this feature is not needed.
|
||||
for (header_name, header_value) in res
|
||||
.headers()
|
||||
.iter()
|
||||
.filter(|(h, _)| *h != "connection" && *h != "content-encoding")
|
||||
.iter()
|
||||
.filter(|(h, _)| *h != "connection" && *h != "content-encoding")
|
||||
{
|
||||
client_resp.header(header_name.clone(), header_value.clone());
|
||||
}
|
||||
|
@ -72,7 +79,7 @@ pub async fn forward(
|
|||
// sparing the use of a mutable body when not needed
|
||||
// For now, the body only needs to be modified when the route
|
||||
// is "create a new form" route
|
||||
if route == "/ocs/v2.php/apps/forms/api/v1/form" {
|
||||
if route == "/ocs/v2.php/apps/forms/api/v1.1/form" {
|
||||
// retreive the body from the request result
|
||||
let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||
eprintln!("error_forward_resp: {}", e);
|
||||
|
@ -84,8 +91,8 @@ pub async fn forward(
|
|||
let form_id = check_new_form(&response_body);
|
||||
if form_id > 0 {
|
||||
debug(&format!(
|
||||
"New form. Forging request to set isAnonymous for id {}",
|
||||
form_id
|
||||
"New form. Forging request to set isAnonymous for id {}",
|
||||
form_id
|
||||
));
|
||||
|
||||
let forged_body = format!(
|
||||
|
@ -93,13 +100,13 @@ pub async fn forward(
|
|||
form_id
|
||||
);
|
||||
let update_req = forge_from(
|
||||
"/ocs/v2.php/apps/forms/api/v1/form/update",
|
||||
"/ocs/v2.php/apps/forms/api/v1.1/form/update",
|
||||
&req,
|
||||
&url,
|
||||
&client,
|
||||
)
|
||||
.set_header("content-length", forged_body.len())
|
||||
.set_header("content-type", "application/json;charset=utf-8");
|
||||
.set_header("content-length", forged_body.len())
|
||||
.set_header("content-type", "application/json;charset=utf-8");
|
||||
|
||||
let res = update_req.send_body(forged_body).await.map_err(|e| {
|
||||
eprintln!("error_forward_isanon: {}", e);
|
||||
|
@ -122,8 +129,8 @@ pub async fn forward(
|
|||
|
||||
// check the response before returning it (unused)
|
||||
/*if check_response(route, &response_body) {
|
||||
return Ok(web_redir("/"));
|
||||
}*/
|
||||
return Ok(web_redir("/"));
|
||||
}*/
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -138,17 +145,11 @@ pub struct CsrfToken {
|
|||
|
||||
pub async fn forward_login(
|
||||
req: HttpRequest,
|
||||
s: Session,
|
||||
params: web::Path<LoginToken>,
|
||||
client: web::Data<Client>,
|
||||
dbpool: web::Data<DbPool>,
|
||||
) -> Result<HttpResponse, TrainCrash> {
|
||||
// if the user is already logged in, redirect to the Forms app
|
||||
if is_logged_in(&req).is_some() {
|
||||
return Ok(web_redir("/apps/forms").await.map_err(|e| {
|
||||
eprintln!("error_redirect (1:/apps/forms/): {}", e);
|
||||
crash(get_lang(&req), "error_redirect")
|
||||
})?);
|
||||
}
|
||||
|
||||
// check if the provided token seems valid. If not, early return.
|
||||
if !check_token(¶ms.token) {
|
||||
|
@ -162,6 +163,7 @@ pub async fn forward_login(
|
|||
crash(get_lang(&req), "error_forwardlogin_db")
|
||||
})?;
|
||||
|
||||
let moved_token = params.token.clone();
|
||||
// check if the link exists in DB. if it does, update lastvisit_at.
|
||||
let formdata = web::block(move || Form::get_from_token(¶ms.token, &conn))
|
||||
.await
|
||||
|
@ -169,89 +171,55 @@ pub async fn forward_login(
|
|||
eprintln!("error_forwardlogin_db_get (diesel error): {}", e);
|
||||
crash(get_lang(&req), "error_forwardlogin_db_get")
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
debug("Token not found.");
|
||||
crash(get_lang(&req), "error_forwardlogin_notfound")
|
||||
})?;
|
||||
.ok_or_else(|| {
|
||||
debug("error: Token not found.");
|
||||
crash(get_lang(&req), "error_forwardlogin_notfound")
|
||||
})?;
|
||||
|
||||
// copy the token in cookies.
|
||||
s.set("sncf_admin_token", &moved_token).map_err(|e| {
|
||||
eprintln!("error_login_setcookie (in login): {}", e);
|
||||
crash(get_lang(&req),"error_login_setcookie")
|
||||
})?;
|
||||
|
||||
// if the user is already logged in, skip the login process
|
||||
// we don't care if someone edits their cookies, Nextcloud will properly
|
||||
// check them anyway
|
||||
if let Some(nc_username) = is_logged_in(&req) {
|
||||
if nc_username.contains(&format!("nc_username={}", formdata.nc_username)) {
|
||||
return Ok(web_redir("/apps/forms").await.map_err(|e| {
|
||||
eprintln!("error_redirect (1:/apps/forms/): {}", e);
|
||||
crash(get_lang(&req), "error_redirect")
|
||||
})?);
|
||||
}
|
||||
}
|
||||
|
||||
// else, try to log the user in with DB data, then redirect.
|
||||
// try to log the user in with DB data, then redirect.
|
||||
login(&client, &req, &formdata.nc_username, &formdata.nc_password).await
|
||||
}
|
||||
|
||||
// creates a NC account using a random name and password.
|
||||
// the account gets associated with a token in sqlite DB.
|
||||
// POST /link route
|
||||
pub async fn forward_register(
|
||||
req: HttpRequest,
|
||||
s: Session,
|
||||
csrf_post: web::Form<CsrfToken>,
|
||||
client: web::Data<Client>,
|
||||
dbpool: web::Data<DbPool>,
|
||||
) -> Result<HttpResponse, TrainCrash> {
|
||||
let lang = get_lang(&req);
|
||||
|
||||
// if the user is already logged in, redirect to the Forms app
|
||||
if is_logged_in(&req).is_some() {
|
||||
return Ok(web_redir("/apps/forms").await.map_err(|e| {
|
||||
eprintln!("error_redirect (2:/apps/forms/): {}", e);
|
||||
crash(get_lang(&req), "error_redirect")
|
||||
})?);
|
||||
}
|
||||
|
||||
// if the user has already generated an admin token, redirect too
|
||||
if let Some(token) = has_admintoken(&req) {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r#"sncf_admin_token=(?P<token>[0-9A-Za-z_\-]*)"#)
|
||||
.expect("Error while parsing the sncf_admin_token regex");
|
||||
}
|
||||
let admin_token = RE
|
||||
.captures(&token)
|
||||
.ok_or_else(|| {
|
||||
eprintln!("error_forwardregister_tokenparse (no capture)");
|
||||
crash(get_lang(&req), "error_forwardregister_tokenparse")
|
||||
})?
|
||||
.name("token")
|
||||
.ok_or_else(|| {
|
||||
eprintln!("error_forwardregister_tokenparse (no capture named token)");
|
||||
crash(get_lang(&req), "error_forwardregister_tokenparse")
|
||||
})?
|
||||
.as_str();
|
||||
// sanitize the token beforehand, cookies are unsafe
|
||||
if check_token(&admin_token) {
|
||||
return Ok(
|
||||
web_redir(&format!("{}/admin/{}", CONFIG.sncf_url, &admin_token))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("error_redirect (admin): {}", e);
|
||||
crash(get_lang(&req), "error_redirect")
|
||||
})?,
|
||||
);
|
||||
} else {
|
||||
debug("Incorrect admin token given in cookies.");
|
||||
debug(&format!("Token: {:#?}", &admin_token));
|
||||
return Err(crash(lang, "error_dirtyhacker"));
|
||||
}
|
||||
}
|
||||
// do not check for existing admin tokens and force a new registration
|
||||
|
||||
// check if the csrf token is OK
|
||||
if let Some(cookie_token) = has_csrftoken(&req) {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r#"sncf_csrf_cookie=(?P<token>[0-9A-Za-z_\-]*)"#)
|
||||
.expect("Error while parsing the sncf_csrf_cookie regex");
|
||||
}
|
||||
let cookie_csrf_token = RE
|
||||
.captures(&cookie_token)
|
||||
.ok_or_else(|| {
|
||||
eprintln!("error_csrf_cookie: no capture");
|
||||
crash(get_lang(&req), "error_csrf_cookie")
|
||||
})?
|
||||
.name("token")
|
||||
.ok_or_else(|| {
|
||||
eprintln!("error_csrf_cookie: no capture named token");
|
||||
crash(get_lang(&req), "error_csrf_cookie")
|
||||
})?
|
||||
.as_str();
|
||||
|
||||
let cookie_csrf_token = s.get::<String>("sncf_csrf_token").map_err(|e| {
|
||||
eprintln!("error_csrf_cookie: {}", e);
|
||||
crash(get_lang(&req), "error_csrf_cookie")
|
||||
})?;
|
||||
if let Some(cookie_token) = cookie_csrf_token {
|
||||
let raw_ctoken =
|
||||
base64::decode_config(cookie_csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(
|
||||
base64::decode_config(cookie_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(
|
||||
|e| {
|
||||
eprintln!("error_csrf_cookie (base64): {}", e);
|
||||
crash(get_lang(&req), "error_csrf_cookie")
|
||||
|
@ -260,14 +228,14 @@ pub async fn forward_register(
|
|||
|
||||
let raw_token =
|
||||
base64::decode_config(csrf_post.csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD)
|
||||
.map_err(|e| {
|
||||
eprintln!("error_csrf_token (base64): {}", e);
|
||||
crash(get_lang(&req), "error_csrf_token")
|
||||
})?;
|
||||
.map_err(|e| {
|
||||
eprintln!("error_csrf_token (base64): {}", e);
|
||||
crash(get_lang(&req), "error_csrf_token")
|
||||
})?;
|
||||
|
||||
let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
|
||||
let parsed_token = seed.parse_token(&raw_token).expect("token not parsed");
|
||||
let parsed_cookie = seed.parse_cookie(&raw_ctoken).expect("cookie not parsed");
|
||||
let parsed_token = seed.parse_token(&raw_token).expect("error: token not parsed");
|
||||
let parsed_cookie = seed.parse_cookie(&raw_ctoken).expect("error: cookie not parsed");
|
||||
if !seed.verify_token_pair(&parsed_token, &parsed_cookie) {
|
||||
debug("warn: CSRF token doesn't match.");
|
||||
return Err(crash(lang, "error_csrf_token"));
|
||||
|
@ -313,23 +281,23 @@ pub async fn forward_register(
|
|||
return Err(crash(lang, "error_forwardregister_db"));
|
||||
}
|
||||
|
||||
s.set("sncf_admin_token", &token).map_err(|e| {
|
||||
eprintln!("error_login_setcookie (in register): {}", e);
|
||||
crash(lang.clone(), "error_login_setcookie")
|
||||
})?;
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.set_header(
|
||||
"Set-Cookie",
|
||||
format!("sncf_admin_token={}; HttpOnly; SameSite=Strict", &token),
|
||||
)
|
||||
.body(
|
||||
TplLink {
|
||||
lang: &lang,
|
||||
admin_token: &token,
|
||||
config: &CONFIG,
|
||||
}
|
||||
.render()
|
||||
.map_err(|e| {
|
||||
eprintln!("error_tplrender (TplLink): {}", e);
|
||||
crash(lang.clone(), "error_tplrender")
|
||||
})?,
|
||||
}
|
||||
.render()
|
||||
.map_err(|e| {
|
||||
eprintln!("error_tplrender (TplLink): {}", e);
|
||||
crash(lang.clone(), "error_tplrender")
|
||||
})?,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
|
@ -370,25 +338,28 @@ fn web_redir(location: &str) -> HttpResponse {
|
|||
.finish()
|
||||
}
|
||||
|
||||
pub async fn index(req: HttpRequest) -> Result<HttpResponse, TrainCrash> {
|
||||
pub async fn index(req: HttpRequest, s: Session) -> Result<HttpResponse, TrainCrash> {
|
||||
let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
|
||||
let (csrf_token, csrf_cookie) = seed
|
||||
.generate_token_pair(None, 43200)
|
||||
.expect("couldn't generate token/cookie pair");
|
||||
|
||||
s.set("sncf_csrf_token", &base64::encode_config(&csrf_cookie.value(), base64::URL_SAFE_NO_PAD)).map_err(|e| {
|
||||
eprintln!("error_login_setcookie (in index): {}", e);
|
||||
crash(get_lang(&req), "error_login_setcookie")
|
||||
})?;
|
||||
|
||||
let cookie_admin_token = s.get::<String>("sncf_admin_token").map_err(|e| {
|
||||
eprintln!("error_forwardregister_tokenparse (index): {}", e);
|
||||
crash(get_lang(&req), "error_forwardregister_tokenparse")
|
||||
})?;
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.set_header(
|
||||
"Set-Cookie",
|
||||
format!(
|
||||
"sncf_csrf_cookie={}; HttpOnly; SameSite=Strict",
|
||||
base64::encode_config(&csrf_cookie.value(), base64::URL_SAFE_NO_PAD)
|
||||
),
|
||||
)
|
||||
.body(
|
||||
TplIndex {
|
||||
lang: &get_lang(&req),
|
||||
csrf_token: &base64::encode_config(&csrf_token.value(), base64::URL_SAFE_NO_PAD),
|
||||
sncf_admin_token: cookie_admin_token,
|
||||
}
|
||||
.render()
|
||||
.map_err(|e| {
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -7,6 +7,9 @@ extern crate diesel;
|
|||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
|
||||
|
||||
use actix_session::CookieSession;
|
||||
use actix_web::cookie::SameSite;
|
||||
use actix_files::Files;
|
||||
use actix_web::client::Client;
|
||||
use actix_web::{web, App, FromRequest, HttpServer};
|
||||
|
@ -89,6 +92,13 @@ async fn main() -> std::io::Result<()> {
|
|||
.data(pool.clone())
|
||||
.data(Client::new())
|
||||
.data(forward_url.clone())
|
||||
.wrap(
|
||||
CookieSession::signed(&[0; 32])
|
||||
.secure(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.http_only(true)
|
||||
.name("sncf_cookies")
|
||||
)
|
||||
/*.route("/mimolette", web::get().to(login))*/
|
||||
/*.route("/login", web::post().to(forward))*/
|
||||
/*.wrap(middleware::Compress::default())*/
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::debug;
|
|||
// if it returns true, cancels the request
|
||||
pub fn check_request(route: &str, body: &web::Bytes) -> bool {
|
||||
match route {
|
||||
"/ocs/v2.php/apps/forms/api/v1/form/update" => rq_form_update(body),
|
||||
"/ocs/v2.php/apps/forms/api/v1.1/form/update" => rq_form_update(body),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,6 @@ const BLOCKED_ROUTES: &[&str] = &[
|
|||
"/settings",
|
||||
"/ocs/v",
|
||||
"/remote.php",
|
||||
"/apps/files",
|
||||
"/core/templates/filepicker.html",
|
||||
];
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::config::Config;
|
|||
pub struct TplIndex<'a> {
|
||||
pub lang: &'a str,
|
||||
pub csrf_token: &'a str,
|
||||
pub sncf_admin_token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
|
|
|
@ -5,6 +5,26 @@
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
:root {
|
||||
scrollbar-color: #4684f9 #c8dbfd;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #568aec;
|
||||
border-radius: 20px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -144,6 +164,10 @@ body, html {
|
|||
transition: all .25s ease-in-out;
|
||||
}
|
||||
|
||||
.ncstyle-button:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.margin-bottom {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
36
templates/assets/index.js
Normal file
36
templates/assets/index.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
// on clicking Previous button in browser, reset the page
|
||||
// needed to get another CSRF token and remove the spinning wheel
|
||||
window.onpageshow = function() {
|
||||
if (performance.getEntriesByType("navigation")[0].type == "back_forward") {
|
||||
location.reload(false);
|
||||
}
|
||||
}
|
||||
|
||||
let browse_forms_button = get('browse_forms_button');
|
||||
let new_link_button = get('new_link_button');
|
||||
|
||||
// csrf_token is retrieved from server-side template
|
||||
new_link_button.addEventListener('click', function() {
|
||||
get("csrf_token").value = csrf_token;
|
||||
get("new_link").submit();
|
||||
hideButtonsAndSpin();
|
||||
});
|
||||
|
||||
if (browse_forms_button != undefined) {
|
||||
browse_forms_button.addEventListener('click', function () {
|
||||
hideButtonsAndSpin();
|
||||
});
|
||||
}
|
||||
|
||||
function hideButtonsAndSpin() {
|
||||
new_link_button.classList.add("hidden");
|
||||
// hide the access forms button if it exists
|
||||
if (browse_forms_button != undefined) {
|
||||
browse_forms_button.classList.add("hidden");
|
||||
}
|
||||
get('loading_ring').classList.remove("hidden");
|
||||
}
|
||||
|
||||
function get(elemId) {
|
||||
return document.getElementById(elemId);
|
||||
}
|
|
@ -10,26 +10,8 @@
|
|||
<link rel="stylesheet" href="/assets/index.css?v=1.2" />
|
||||
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
|
||||
<noscript><style> .jsonly { display: none } </style></noscript>
|
||||
<script>
|
||||
/* junk javascript with basic spambot protection features.
|
||||
Drunk indentation is vim's fault.
|
||||
unsatisifed? Please make a PR. : ) */
|
||||
window.onload = function() {
|
||||
// retrieved from server-side template
|
||||
let csrf_token = "{{ csrf_token }}";
|
||||
document.getElementById('new_link_button').addEventListener('click', function () {
|
||||
new_link(csrf_token);
|
||||
});
|
||||
}
|
||||
|
||||
function new_link(csrf) {
|
||||
document.getElementById("csrf_token").value = csrf;
|
||||
document.getElementById('new_link').submit();
|
||||
document.getElementById('new_link_button').classList.add("hidden");
|
||||
document.getElementById('loading_ring').classList.remove("hidden");
|
||||
}
|
||||
|
||||
</script>
|
||||
<script>const csrf_token = "{{ csrf_token }}";</script>
|
||||
<script src="/assets/index.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flex page-heading fullheight">
|
||||
|
@ -48,9 +30,13 @@
|
|||
<noscript>
|
||||
<a class="ncstyle-button margin-bottom">{{ "index_nojs"|tr(lang) }}</a>
|
||||
</noscript>
|
||||
{% if sncf_admin_token.is_some() %}
|
||||
<a id="browse_forms_button" href="/admin/{{ sncf_admin_token.as_ref().unwrap() }}" class="ncstyle-button margin-bottom">{{ "index_continueform_button"|tr(lang) }}</a>
|
||||
{% endif %}
|
||||
<form id="new_link" action="/link" method="post">
|
||||
<input id="csrf_token" name="csrf_token" type="text" class="hidden">
|
||||
<a id="new_link_button" class="click jsonly ncstyle-button margin-bottom">{{ "index_createform_button"|tr(lang) }}</a> </form>
|
||||
<a id="new_link_button" class="click jsonly ncstyle-button margin-bottom">{{ "index_createform_button"|tr(lang) }}</a>
|
||||
</form>
|
||||
<div id="loading_ring" class="hidden lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<a class="scroll-down-link scroll-down-arrow"></a>
|
||||
|
@ -66,7 +52,7 @@
|
|||
<br />
|
||||
<br />
|
||||
<p>{{ "index_disclaimer1"|tr(lang) }}</p>
|
||||
<p>{{ "index_disclaimer2"|tr(lang) }}<a href="https://42l.fr/Faire-un-don">{{ "index_disclaimer2_link_org"|tr(lang) }}</a>{{ "index_disclaimer2_or"|tr(lang) }}<a href="https://www.bountysource.com/teams/nextcloud">{{ "index_disclaimer2_nc"|tr(lang) }}</a>.</p>
|
||||
<p>{{ "index_disclaimer2"|tr(lang) }}<a href="https://42l.fr/Faire-un-don">{{ "index_disclaimer2_link_org"|tr(lang) }}</a>{{ "index_disclaimer2_or"|tr(lang) }}<a href="https://nextcloud.com/include/">{{ "index_disclaimer2_nc"|tr(lang) }}</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -125,7 +111,7 @@
|
|||
</div>
|
||||
<div class="c-subelem">
|
||||
<h3>{{ "index_panel6_title"|tr(lang) }}</h3>
|
||||
<p>{{ "index_panel5_desc1"|tr(lang) }}</p>
|
||||
<p>{{ "index_panel6_desc1"|tr(lang) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue