docker+rust
This commit is contained in:
parent
be4279eb64
commit
27f51a7114
47 changed files with 5250 additions and 52 deletions
BIN
build/deb-rust-pluriton-interface/Babelfish.png
Normal file
BIN
build/deb-rust-pluriton-interface/Babelfish.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 342 KiB |
BIN
build/deb-rust-pluriton-interface/Digi_3corner.png
Normal file
BIN
build/deb-rust-pluriton-interface/Digi_3corner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
BIN
build/deb-rust-pluriton-interface/Digi_3corner_up.png
Normal file
BIN
build/deb-rust-pluriton-interface/Digi_3corner_up.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
21
build/deb-rust-pluriton-interface/Dockerfile
Normal file
21
build/deb-rust-pluriton-interface/Dockerfile
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
FROM rust:slim
|
||||||
|
|
||||||
|
WORKDIR /opt
|
||||||
|
|
||||||
|
# Install needed dependecies
|
||||||
|
RUN echo "deb http://deb.debian.org/debian/ stretch main contrib non-free" >> /etc/apt/sources.list
|
||||||
|
|
||||||
|
RUN echo "deb-src http://deb.debian.org/debian/ stretch main contrib non-free" >> /etc/apt/sources.list
|
||||||
|
|
||||||
|
RUN apt-get update && apt-cache search libssl
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
build-essential checkinstall zlib1g-dev pkg-config libssl1.0-dev -y
|
||||||
|
|
||||||
|
COPY pluriton-interface pluriton-interface
|
||||||
|
|
||||||
|
WORKDIR /opt/pluriton-interface
|
||||||
|
|
||||||
|
CMD cargo run --no-default-features
|
||||||
|
|
||||||
|
|
||||||
|
|
48
build/deb-rust-pluriton-interface/Dockerfile.save
Normal file
48
build/deb-rust-pluriton-interface/Dockerfile.save
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
FROM rust:slim
|
||||||
|
|
||||||
|
WORKDIR /opt
|
||||||
|
|
||||||
|
# Install needed dependecies
|
||||||
|
|
||||||
|
RUN echo "deb http://ftp.de.debian.org/debian unstable main contrib" | tee -a /etc/apt/sources.list
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y libmysql++-dev git
|
||||||
|
|
||||||
|
RUN git clone https://git.42l.fr/neil/sncf.git
|
||||||
|
|
||||||
|
WORKDIR /opt/sncf
|
||||||
|
|
||||||
|
COPY config.toml /opt/sncf/config.toml
|
||||||
|
|
||||||
|
# graphics individualization
|
||||||
|
|
||||||
|
COPY foorms_logo_beta.svg /opt/sncf/templates/assets/foorms_logo_beta.svg
|
||||||
|
|
||||||
|
COPY white-background.png /opt/sncf/templates/assets/index-background.png
|
||||||
|
|
||||||
|
COPY Digi_3corner.png /opt/sncf/templates/assets/flavicon.ico
|
||||||
|
|
||||||
|
COPY index.css /opt/sncf/templates/assets/index.css
|
||||||
|
|
||||||
|
COPY cloud.css /opt/sncf/templates/assets/cloud.css
|
||||||
|
|
||||||
|
COPY bootstrap.min.css /opt/sncf/templates/assets/bootstrap.min.css
|
||||||
|
|
||||||
|
COPY digitalcourage.css /opt/sncf/templates/assets/digitalcourage.css
|
||||||
|
|
||||||
|
COPY index.html /opt/sncf/templates/index.html
|
||||||
|
|
||||||
|
COPY link.html /opt/sncf/templates/link.html
|
||||||
|
|
||||||
|
COPY forward.rs /opt/sncf/src/forward.rs
|
||||||
|
|
||||||
|
#COPY templates.rs /opt/sncf/src/templates.rs
|
||||||
|
|
||||||
|
# The written is just firstly a hack
|
||||||
|
|
||||||
|
COPY lang.json /opt/sncf/lang.json
|
||||||
|
|
||||||
|
CMD cargo run --no-default-features --features mysql
|
||||||
|
|
||||||
|
|
||||||
|
|
281
build/deb-rust-pluriton-interface/account.rs
Normal file
281
build/deb-rust-pluriton-interface/account.rs
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
use actix_web::client::Client;
|
||||||
|
use actix_web::{http, web, HttpRequest, HttpResponse};
|
||||||
|
use base64::URL_SAFE_NO_PAD;
|
||||||
|
use percent_encoding::percent_decode_str;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use rand::Rng;
|
||||||
|
use rand::RngCore;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::Duration;
|
||||||
|
use crate::config::{ADJ_LIST, NAME_LIST, PROXY_TIMEOUT, USER_AGENT};
|
||||||
|
use crate::debug;
|
||||||
|
use crate::errors::{crash, TrainCrash};
|
||||||
|
use crate::templates::get_lang;
|
||||||
|
use crate::CONFIG;
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct NCLoginForm<'a> {
|
||||||
|
pub user: &'a str,
|
||||||
|
pub password: &'a str,
|
||||||
|
pub timezone: &'a str,
|
||||||
|
pub timezone_offset: &'a str,
|
||||||
|
pub requesttoken: &'a str,
|
||||||
|
}
|
||||||
|
// check if the user is connected to Nextcloud
|
||||||
|
// returns Some(cookie_raw_value) if connected
|
||||||
|
// returns None if disconnected
|
||||||
|
pub fn is_logged_in(req: &HttpRequest) -> Option<&str> {
|
||||||
|
let c = req.headers().get("Cookie")?.to_str().ok()?;
|
||||||
|
if c.contains("nc_username") {
|
||||||
|
Some(c)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// attempts to create the account from Nextcloud's API
|
||||||
|
// returns the newly created username.
|
||||||
|
// if it fails (bad return code), returns None.
|
||||||
|
pub async fn create_account(
|
||||||
|
client: &web::Data<Client>,
|
||||||
|
user: &str,
|
||||||
|
password: &str,
|
||||||
|
lang: String,
|
||||||
|
) -> Result<String, TrainCrash> {
|
||||||
|
let mut register_query = client
|
||||||
|
.post(format!(
|
||||||
|
"{}/{}",
|
||||||
|
CONFIG.nextcloud_url, "ocs/v1.php/cloud/users"
|
||||||
|
))
|
||||||
|
.timeout(Duration::new(PROXY_TIMEOUT, 0))
|
||||||
|
.basic_auth(&CONFIG.admin_username, Some(&CONFIG.admin_password))
|
||||||
|
.header(
|
||||||
|
http::header::CONTENT_TYPE,
|
||||||
|
"application/x-www-form-urlencoded",
|
||||||
|
)
|
||||||
|
.header("OCS-APIRequest", "true")
|
||||||
|
.send_form(&NCCreateAccountForm {
|
||||||
|
userid: user,
|
||||||
|
password,
|
||||||
|
quota: "0B",
|
||||||
|
language: &lang,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_createaccount_post: {}", e);
|
||||||
|
crash(lang.clone(), "error_createaccount_post")
|
||||||
|
})?;
|
||||||
|
// only 200 http status code is allowed
|
||||||
|
if register_query.status() != 200 {
|
||||||
|
eprintln!("error_createaccount_status: {}", register_query.status());
|
||||||
|
// + extract response body for debugging purposes
|
||||||
|
let response_body = register_query.body().await.map_err(|e| {
|
||||||
|
eprintln!("error_createaccount_post_body: {}", e);
|
||||||
|
crash(lang.clone(), "error_createaccount_post_body")
|
||||||
|
})?;
|
||||||
|
debug(&format!("Body: {:#?}", response_body));
|
||||||
|
return Err(crash(lang.clone(), "error_createaccount_status"));
|
||||||
|
}
|
||||||
|
// extract response body
|
||||||
|
let response_body = register_query.body().await.map_err(|e| {
|
||||||
|
eprintln!("error_createaccount_post_body: {}", e);
|
||||||
|
crash(lang.clone(), "error_createaccount_post_body")
|
||||||
|
})?;
|
||||||
|
let response_body = String::from_utf8_lossy(&response_body);
|
||||||
|
// grasp NC status code
|
||||||
|
let status_start = response_body.find("<statuscode>").ok_or_else(|| {
|
||||||
|
eprintln!("error_createaccount_ncstatus_parse: start missing");
|
||||||
|
crash(lang.clone(), "error_createaccount_ncstatus_parse")
|
||||||
|
})? + 12;
|
||||||
|
let status_end = response_body.find("</statuscode>").ok_or_else(|| {
|
||||||
|
eprintln!("error_createaccount_ncstatus_parse: end missing");
|
||||||
|
crash(lang.clone(), "error_createaccount_ncstatus_parse")
|
||||||
|
})?;
|
||||||
|
let code = &response_body[status_start..status_end];
|
||||||
|
match code.parse::<u16>() {
|
||||||
|
Ok(100) => Ok(String::from(user)), // success
|
||||||
|
Ok(r) => {
|
||||||
|
eprintln!("error_createaccount_ncstatus: {}", r);
|
||||||
|
Err(crash(lang.clone(), "error_createaccount_ncstatus"))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("error_createaccount_ncstatus_parse: {}", e);
|
||||||
|
Err(crash(lang.clone(), "error_createaccount_ncstatus_parse"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct NCCreateAccountForm<'a> {
|
||||||
|
pub userid: &'a str,
|
||||||
|
pub password: &'a str,
|
||||||
|
pub quota: &'a str,
|
||||||
|
pub language: &'a str,
|
||||||
|
}
|
||||||
|
pub async fn login(
|
||||||
|
client: &web::Data<Client>,
|
||||||
|
req: &HttpRequest,
|
||||||
|
user: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
debug(&format!("Sending forged login for user {}", user));
|
||||||
|
// 1. GET /csrftoken
|
||||||
|
let mut login_get = client
|
||||||
|
.get(format!("{}/{}", CONFIG.nextcloud_url, "csrftoken"))
|
||||||
|
.timeout(Duration::new(PROXY_TIMEOUT, 0))
|
||||||
|
.header("User-Agent", USER_AGENT)
|
||||||
|
.header("Accept-Language" , "fr" )
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_login_get: {}", e);
|
||||||
|
crash(get_lang(&req), "error_login_get")
|
||||||
|
})?;
|
||||||
|
// rewrite cookie headers from GET to POST
|
||||||
|
let mut str_cookiepair = String::new();
|
||||||
|
// remove duplicate oc<id> cookie (nextcloud bug)
|
||||||
|
// leading to sncf being unable to forge logins
|
||||||
|
let cookie_set = login_get.headers().get_all("set-cookie");
|
||||||
|
let mut cookie_map: HashMap<String, String> = HashMap::new();
|
||||||
|
for c in cookie_set {
|
||||||
|
// get str version of cookie header
|
||||||
|
let c_str = c.to_str().map_err(|e| {
|
||||||
|
eprintln!("error_login_cookiepair (1): {}", e);
|
||||||
|
crash(get_lang(&req), "error_login_cookiepair")
|
||||||
|
})?;
|
||||||
|
// percent decode
|
||||||
|
let c_str = percent_decode_str(c_str).decode_utf8_lossy();
|
||||||
|
//then remove values after ';'
|
||||||
|
let c_str_arr = c_str.split(';').collect::<Vec<&str>>();
|
||||||
|
let c_str = c_str_arr
|
||||||
|
.first()
|
||||||
|
.expect("error: cookiepair split does not have a first value. shouldn't happen.");
|
||||||
|
// split cookie key and cookie value
|
||||||
|
// split_once would work best but it's nightly-only for now
|
||||||
|
let c_str_arr = c_str.split('=').collect::<Vec<&str>>();
|
||||||
|
let c_key = c_str_arr
|
||||||
|
.first()
|
||||||
|
.expect("error: cookie key split does not have a first value, shouldn't happen.");
|
||||||
|
let c_value = c_str.replace(&format!("{}=", c_key), "");
|
||||||
|
if c_key != c_str {
|
||||||
|
// if the key already exists in hashmap, replace its value
|
||||||
|
// else, insert it
|
||||||
|
if let Some(c_sel) = cookie_map.get_mut(*c_key) {
|
||||||
|
*c_sel = c_value;
|
||||||
|
} else {
|
||||||
|
cookie_map.insert(c_key.to_string(), c_value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("error_login_cookiepair (2)");
|
||||||
|
return Err(crash(get_lang(&req), "error_login_cookiepair"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (cookie_k, cookie_v) in cookie_map {
|
||||||
|
str_cookiepair.push_str(&format!("{}={}; ", cookie_k, cookie_v));
|
||||||
|
}
|
||||||
|
// load requesttoken regex
|
||||||
|
lazy_static! {
|
||||||
|
static ref RE: Regex = Regex::new(r#"\{"token":"(?P<token>[^"]*)"\}"#)
|
||||||
|
.expect("Error while parsing the requesttoken regex");
|
||||||
|
}
|
||||||
|
let post_body = login_get.body().await.map_err(|e| {
|
||||||
|
eprintln!("error_login_get_body: {}", e);
|
||||||
|
crash(get_lang(&req), "error_login_get_body")
|
||||||
|
})?;
|
||||||
|
let post_body_str = String::from_utf8_lossy(&post_body);
|
||||||
|
// save requesttoken (CSRF) for POST
|
||||||
|
let requesttoken = RE
|
||||||
|
.captures(&post_body_str)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
eprintln!("error_login_regex (no capture)");
|
||||||
|
crash(get_lang(&req), "error_login_regex")
|
||||||
|
})?
|
||||||
|
.name("token")
|
||||||
|
.ok_or_else(|| {
|
||||||
|
eprintln!("error_login_regex (no capture named token)");
|
||||||
|
crash(get_lang(&req), "error_login_regex")
|
||||||
|
})?
|
||||||
|
.as_str();
|
||||||
|
// 2. POST /login
|
||||||
|
let mut login_post = client
|
||||||
|
.post(format!("{}/{}", CONFIG.nextcloud_url, "login"))
|
||||||
|
.timeout(Duration::new(PROXY_TIMEOUT, 0))
|
||||||
|
.header("User-Agent", USER_AGENT)
|
||||||
|
.header("Accept-Language" , "fr" );
|
||||||
|
// include all NC cookies in one cookie (cookie pair)
|
||||||
|
login_post = login_post.header("Cookie", str_cookiepair);
|
||||||
|
// send the same POST data as you'd log in from a web browser
|
||||||
|
let response_post = login_post
|
||||||
|
.send_form(&NCLoginForm {
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
timezone: "UTC",
|
||||||
|
timezone_offset: "2",
|
||||||
|
requesttoken,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_login_post: {}", e);
|
||||||
|
crash(get_lang(&req), "error_login_post")
|
||||||
|
})?;
|
||||||
|
// 3. set the same cookies in the user's browser
|
||||||
|
let mut user_response = HttpResponse::SeeOther();
|
||||||
|
for item in response_post.headers().clone().get_all("set-cookie") {
|
||||||
|
user_response.header(
|
||||||
|
"Set-Cookie",
|
||||||
|
item.to_str().map_err(|e| {
|
||||||
|
eprintln!("error_login_setcookie: {}", e);
|
||||||
|
crash(get_lang(&req), "error_login_setcookie")
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// redirect to forms!
|
||||||
|
Ok(user_response
|
||||||
|
.header("Accept-Language", "fr" )
|
||||||
|
.header(http::header::LOCATION, "/apps/forms")
|
||||||
|
.finish()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_login_redir: {}", e);
|
||||||
|
crash(get_lang(&req), "error_login_redir")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
// checks if the token seems valid before asking the db.
|
||||||
|
// The token must be 45 bytes long and base64-encoded.
|
||||||
|
// returns true if the token is valid
|
||||||
|
pub fn check_token(token: &str) -> bool {
|
||||||
|
let token_dec = base64::decode_config(token, URL_SAFE_NO_PAD);
|
||||||
|
if let Ok(token_bytes) = token_dec {
|
||||||
|
token_bytes.len() == 45
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// generates a new token
|
||||||
|
pub fn gen_token(size: usize) -> String {
|
||||||
|
// Using /dev/random to generate random bytes
|
||||||
|
let mut r = OsRng;
|
||||||
|
let mut my_secure_bytes = vec![0u8; size];
|
||||||
|
r.fill_bytes(&mut my_secure_bytes);
|
||||||
|
base64::encode_config(my_secure_bytes, URL_SAFE_NO_PAD)
|
||||||
|
}
|
||||||
|
// generates a random username composed of
|
||||||
|
// an adjective, a name and a 4-byte base64-encoded token.
|
||||||
|
// with the default list, that represents:
|
||||||
|
// 141 * 880 = 124 080
|
||||||
|
// 255^4 / 2 = 2 114 125 312 (we lose approx. the half because of uppercase)
|
||||||
|
// 2 114 125 312 * 124 080 = 2.623206687*10^14 possible combinations??
|
||||||
|
pub fn gen_name() -> String {
|
||||||
|
// uppercasing gen_token because NC would probably refuse two
|
||||||
|
// users with the same name but a different case
|
||||||
|
// and that'd be a pain to debug
|
||||||
|
format!(
|
||||||
|
"{}{}-{}",
|
||||||
|
list_rand(&ADJ_LIST),
|
||||||
|
list_rand(&NAME_LIST),
|
||||||
|
gen_token(4).to_uppercase()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn list_rand(list: &[String]) -> &String {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let roll = rng.gen_range(0..list.len() - 1);
|
||||||
|
&list[roll]
|
||||||
|
}
|
6
build/deb-rust-pluriton-interface/bootstrap.min.css
vendored
Normal file
6
build/deb-rust-pluriton-interface/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
148
build/deb-rust-pluriton-interface/cloud.css
Normal file
148
build/deb-rust-pluriton-interface/cloud.css
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
.has-text-centered > * {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-subelem, .c-fullwidth > * {
|
||||||
|
color: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.c-blue {
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-blue > a {
|
||||||
|
color: white;
|
||||||
|
background: #4b97ca;
|
||||||
|
width: 154px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width:1280px) {
|
||||||
|
.c-flex.c-flex-reverse {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
.c-jumbo {
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
}
|
||||||
|
.c-subelem {
|
||||||
|
padding: 0;
|
||||||
|
max-width: 40vw;
|
||||||
|
margin: auto 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-jumbo.c-jumbo-big {
|
||||||
|
min-height: 25rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-jumbo.c-jumbo-medium {
|
||||||
|
min-height: 18rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-jumbo.c-jumbo-small {
|
||||||
|
min-height: 10rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-button {
|
||||||
|
display: block;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||||
|
border-radius: 10pt;
|
||||||
|
text-align: center;
|
||||||
|
transition: all .2s ease-in-out;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.4em;
|
||||||
|
width: max-content;
|
||||||
|
height: max-content;
|
||||||
|
min-width: 154px;
|
||||||
|
min-height: 35px;
|
||||||
|
margin: 0.5rem;
|
||||||
|
color: white;
|
||||||
|
text-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-button:only-child {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-button.c-big {
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-subelem {
|
||||||
|
margin: auto 2rem;
|
||||||
|
padding: 1rem 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-img-shadow {
|
||||||
|
height: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-img-center {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-fullwidth {
|
||||||
|
width: 100%;
|
||||||
|
margin: auto 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width:1279px) {
|
||||||
|
.c-no-margin-mobile {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-jumbo {
|
||||||
|
padding: .5rem 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-fade-left {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-100px);
|
||||||
|
animation: fadeInLeft 2s ease-in-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-fade-right {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100px);
|
||||||
|
animation: fadeInRight 2s ease-in-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInLeft {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-100px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInRight {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
34
build/deb-rust-pluriton-interface/config.toml
Normal file
34
build/deb-rust-pluriton-interface/config.toml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# The address and port sncf will listen
|
||||||
|
listening_address = "0.0.0.0"
|
||||||
|
listening_port = 8000
|
||||||
|
|
||||||
|
# Public-facing domain for sncf.
|
||||||
|
# includes protocol, FQDN and port, without the trailing slash.
|
||||||
|
sncf_url = "http://basabuuka.org"
|
||||||
|
|
||||||
|
# SQLite: path to the SQLite DB
|
||||||
|
# PostgreSQL: postgres://user:password@address:port/database
|
||||||
|
# MySQL: mysql://user:password@address:port/database
|
||||||
|
database_path = "mysql://nextcloud:KF8zUh1q4HovFmBa6lnk7xCmvoonfBoE@nextcloud-db:3306/nextcloud"
|
||||||
|
|
||||||
|
# IP address of the Nextcloud instance, including protocol and port
|
||||||
|
nextcloud_url = "http://nextcloud-web:80"
|
||||||
|
|
||||||
|
# Nextcloud admin account credentials
|
||||||
|
# TODO hash adminpw
|
||||||
|
admin_username = "sncf_admin"
|
||||||
|
admin_password = "DieHeiligeKuhDerNacht1635"
|
||||||
|
|
||||||
|
# How many days of inactivity for an admin token before deleting NC accounts
|
||||||
|
prune_days = 40
|
||||||
|
|
||||||
|
# Displays route names and a lot of information
|
||||||
|
debug_mode = true
|
||||||
|
|
||||||
|
# Used to encrypt csrf tokens and csrf cookies.
|
||||||
|
# Generate random bytes: openssl rand -base64 32
|
||||||
|
# Then paste the result in this variable
|
||||||
|
cookie_key = "Af3v5KMNPmwYYBRRjm/W5ds1rHDdyCEvpxVTMLKEKl0="
|
||||||
|
|
||||||
|
# Don't touch this unless you know what you're doing
|
||||||
|
config_version = 2
|
572
build/deb-rust-pluriton-interface/digitalcourage.css
Normal file
572
build/deb-rust-pluriton-interface/digitalcourage.css
Normal file
|
@ -0,0 +1,572 @@
|
||||||
|
/* This software is governed by the CeCILL-B license. If a copy of this license
|
||||||
|
* is not distributed with this file, you can obtain one at
|
||||||
|
* http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
|
||||||
|
*
|
||||||
|
* Authors of STUdS (initial project) : Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
|
||||||
|
* Authors of OpenSondage : Framasoft (https://github.com/framasoft)
|
||||||
|
*
|
||||||
|
* =============================
|
||||||
|
*
|
||||||
|
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
|
||||||
|
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
|
||||||
|
* http://www.cecill.info/licences/Licence_CeCILL_V2.1-fr.txt
|
||||||
|
*
|
||||||
|
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
|
||||||
|
* Auteurs d'OpenSondage : Framasoft (https://github.com/framasoft)
|
||||||
|
*/
|
||||||
|
@font-face {
|
||||||
|
font-family: "DejaVu Sans";
|
||||||
|
src: url('../fonts/DejaVuSans.ttf');
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
|
||||||
|
color:#333;
|
||||||
|
background:#eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trait { /* hr */
|
||||||
|
background-color: #EEE;
|
||||||
|
height: 5px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ombre {
|
||||||
|
background-color: #FFF;
|
||||||
|
box-shadow: -4px 6px 9px rgba(50, 50, 50, 0.5);
|
||||||
|
margin: 15px auto 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Commentaires */
|
||||||
|
div.comment{
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-left: 1px dashed #999;
|
||||||
|
background: #F5F5F5;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
padding-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment_date {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: -0.7px;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Règles générales */
|
||||||
|
|
||||||
|
a:focus { /* a11y */
|
||||||
|
outline:#000 dotted 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header, footer {
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
header h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
.container .jumbotron {
|
||||||
|
padding: 20px 20px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.container .jumbotron p {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
.container .jumbotron .btn-group >.btn {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary h4 {
|
||||||
|
margin-top:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary img {
|
||||||
|
max-width:100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.very-small {
|
||||||
|
font-size: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Effet sur les images en page d'accueil */
|
||||||
|
.opacity img {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity:hover img {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-choice {
|
||||||
|
margin-bottom:50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Description du sondage */
|
||||||
|
/* studs.php et adminstuds.php */
|
||||||
|
header .lead {
|
||||||
|
padding: 10px 0;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
header form .input-group .form-control {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
header form .input-group .input-group-btn {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin-link, #public-link {
|
||||||
|
cursor:text;
|
||||||
|
}
|
||||||
|
.admin-link, .public-link,
|
||||||
|
.admin-link:hover, .public-link:hover {
|
||||||
|
color:#333;
|
||||||
|
text-decoration:none;
|
||||||
|
border:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jumbotron h3, .jumbotron .js-title {
|
||||||
|
margin-bottom:20px;
|
||||||
|
margin-top:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-description {
|
||||||
|
font-family: inherit;
|
||||||
|
word-break: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Description in markdown **/
|
||||||
|
.form-group .CodeMirror, .form-group .CodeMirror-scroll {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
#description-form .CodeMirror {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.editor-toolbar {
|
||||||
|
margin-top: 10px;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
#poll_comments {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4.control-label {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.42857;
|
||||||
|
margin-top:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding: 0 10px 10px;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results a.btn-default.btn-sm {
|
||||||
|
padding: 3px 7px;
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* adminstuds.php */
|
||||||
|
#title-form h3 .btn-edit,
|
||||||
|
#email-form .btn-edit,
|
||||||
|
#description-form .btn-edit,
|
||||||
|
#poll-rules-form .btn-edit,
|
||||||
|
#poll-hidden-form .btn-edit,
|
||||||
|
#expiration-form .btn-edit,
|
||||||
|
#password-form .btn-edit,
|
||||||
|
#name-form .btn-edit {
|
||||||
|
position:absolute;
|
||||||
|
left:-2000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title-form .btn-edit:focus,
|
||||||
|
#title-form h3:hover .btn-edit,
|
||||||
|
#email-form .btn-edit:focus,
|
||||||
|
#email-form:hover .btn-edit,
|
||||||
|
#description-form .btn-edit:focus,
|
||||||
|
#description-form:hover .btn-edit,
|
||||||
|
#poll-rules-form .btn-edit:focus,
|
||||||
|
#poll-rules-form:hover .btn-edit,
|
||||||
|
#poll-hidden-form .btn-edit:focus,
|
||||||
|
#poll-hidden-form:hover .btn-edit,
|
||||||
|
#expiration-form .btn-edit:focus,
|
||||||
|
#expiration-form:hover .btn-edit,
|
||||||
|
#password-form .btn-edit:focus,
|
||||||
|
#password-form:hover .btn-edit,
|
||||||
|
#name-form .btn-edit:focus,
|
||||||
|
#name-form:hover .btn-edit {
|
||||||
|
position:relative !important;
|
||||||
|
left:0;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-desc textarea {
|
||||||
|
margin-bottom:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author-form .form-control-static {
|
||||||
|
margin-bottom:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#poll-rules-form p, #poll-hidden-form p,
|
||||||
|
.jumbotron p.well {
|
||||||
|
font-size:16px;
|
||||||
|
}
|
||||||
|
.jumbotron p {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tableau du sondage */
|
||||||
|
#tableContainer {
|
||||||
|
overflow-x:auto;
|
||||||
|
margin:5px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results {
|
||||||
|
margin:0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results > tbody > tr:hover > td,
|
||||||
|
table.results > tbody > tr:hover > th {
|
||||||
|
opacity:0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results > tbody > tr#vote-form:hover > td,
|
||||||
|
table.results > tbody > tr#vote-form:hover > th {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results tbody td {
|
||||||
|
text-align:center;
|
||||||
|
padding:1px 5px;
|
||||||
|
border-bottom: 2px solid white;
|
||||||
|
border-top: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results thead th {
|
||||||
|
text-align:center;
|
||||||
|
border:2px solid white;
|
||||||
|
padding: 5px;
|
||||||
|
min-width:40px;
|
||||||
|
font-size:12px;
|
||||||
|
max-width:100px;
|
||||||
|
overflow:hidden;
|
||||||
|
text-overflow:ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results thead th img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results thead .btn {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results th.rbd.day,
|
||||||
|
table.results th.rbd.bg-info,
|
||||||
|
table.results td.rbd {
|
||||||
|
border-right: 2px dotted white;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results th.bg-primary.month,
|
||||||
|
table.results th.day,
|
||||||
|
table.results th.bg-info {
|
||||||
|
border-bottom:none;
|
||||||
|
border-top:none;
|
||||||
|
border-right: 2px dotted white;
|
||||||
|
border-left: 2px dotted white;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results tbody th.bg-info {
|
||||||
|
border-right: 2px solid white;
|
||||||
|
border-left: 2px solid white;
|
||||||
|
text-align:center;
|
||||||
|
min-width:150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results th.bg-primary.month,
|
||||||
|
table.results th.day {
|
||||||
|
text-align:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results #nom {
|
||||||
|
width:115px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results .btn-link.btn-sm {
|
||||||
|
padding:2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addition {
|
||||||
|
vertical-align:top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#showChart {
|
||||||
|
margin-top:30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Chart {
|
||||||
|
padding-right:30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Formulaire de création de sondage */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
#formulaire .col-xs-12 {
|
||||||
|
padding-left: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Formulaire de vote */
|
||||||
|
#vote-form td ul, #vote-form td label {
|
||||||
|
margin:0;
|
||||||
|
font-size:12px;
|
||||||
|
}
|
||||||
|
#vote-form td label {
|
||||||
|
padding: 1px 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vote-form td {
|
||||||
|
border-top:2px solid white;
|
||||||
|
}
|
||||||
|
#vote-form td:first-child {
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
.yes input, .ifneedbe input,.no input {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0px, 0px, 0px, 0px);
|
||||||
|
border: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice input:focus + label {
|
||||||
|
outline: 2px dotted #000;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
.choice {
|
||||||
|
width: 35px;
|
||||||
|
margin:0 auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.btn-edit {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.edit-username-left {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes .btn, .ifneedbe .btn, .no .btn {
|
||||||
|
width: 35px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes .btn,.yes .btn:hover {
|
||||||
|
border-bottom-right-radius:0 !important;
|
||||||
|
border-bottom-left-radius:0 !important;
|
||||||
|
margin-bottom:-1px !important;
|
||||||
|
margin-top:4px !important;
|
||||||
|
color: #677835;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ifneedbe .btn,.ifneedbe .btn:hover {
|
||||||
|
border-radius: 0;
|
||||||
|
color: #C48A1B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no .btn,.no .btn:hover{
|
||||||
|
border-top-right-radius:0 !important;
|
||||||
|
border-top-left-radius:0 !important;
|
||||||
|
margin-bottom:4px !important;
|
||||||
|
margin-top:-1px !important;
|
||||||
|
color: #AD220F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes input[type="radio"]:checked + label { /* =.btn-success.active */
|
||||||
|
color: #fff;
|
||||||
|
background-color: #768745;
|
||||||
|
border-color: #67753C;
|
||||||
|
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.125) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ifneedbe input[type="radio"]:checked + label { /* =.btn-warning.active */
|
||||||
|
color: #fff;
|
||||||
|
background-color: #CF9800;
|
||||||
|
border-color: #BD8A00;
|
||||||
|
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.125) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no input[type="radio"]:checked + label { /* =.btn-danger.active */
|
||||||
|
color: #fff;
|
||||||
|
background-color: #BF2511;
|
||||||
|
border-color: #AD220F;
|
||||||
|
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.125) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button and results "No" */
|
||||||
|
.no .btn.startunchecked {
|
||||||
|
box-shadow:none !important;
|
||||||
|
color:#AD220F !important;
|
||||||
|
background:#fff !important;
|
||||||
|
border-color:#bdbdbd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no .btn.startunchecked:hover {
|
||||||
|
background-color: #E0E0E0 !important;
|
||||||
|
border-color: #949494 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results .bg-danger .glyphicon {
|
||||||
|
opacity:0;
|
||||||
|
|
||||||
|
-moz-animation-name: hideNoIcon;
|
||||||
|
-moz-animation-iteration-count: 1;
|
||||||
|
-moz-animation-timing-function: ease-in;
|
||||||
|
-moz-animation-duration: 2s;
|
||||||
|
|
||||||
|
-webkit-animation-name: hideNoIcon;
|
||||||
|
-webkit-animation-iteration-count: 1;
|
||||||
|
-webkit-animation-timing-function: ease-in;
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
|
||||||
|
animation-name: hideNoIcon;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
animation-duration: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes hideNoIcon {
|
||||||
|
0% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes hideNoIcon {
|
||||||
|
0% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes hideNoIcon {
|
||||||
|
0% {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity:0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.results > tbody > tr:hover > td .glyphicon {
|
||||||
|
opacity:1
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create_date_poll.php */
|
||||||
|
#selected-days .form-group {
|
||||||
|
margin-left:0;
|
||||||
|
margin-right:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selected-days legend input {
|
||||||
|
box-shadow: none;
|
||||||
|
border-width:0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 21px;
|
||||||
|
border-radius:0;
|
||||||
|
margin-bottom:-1px;
|
||||||
|
background:transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selected-days legend input:hover,
|
||||||
|
#selected-days legend input:focus {
|
||||||
|
border-bottom-width:1px;
|
||||||
|
background-color:#E6E6E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selected-days legend .input-group-addon {
|
||||||
|
border:none;
|
||||||
|
background:transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selected-days legend .input-group-addon:last-of-type {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
#selected-days legend {
|
||||||
|
height: 33px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create_classic_poll.php */
|
||||||
|
.md-a-img {
|
||||||
|
text-decoration:none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#md-a-imgModal .form-group {
|
||||||
|
margin:10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#md-a-imgModalLabel {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin */
|
||||||
|
#poll_search {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.table-of-polls {
|
||||||
|
overflow-x: scroll;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Studs */
|
||||||
|
.password_request {
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#password-form .btn-cancel {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
29
build/deb-rust-pluriton-interface/error.html
Normal file
29
build/deb-rust-pluriton-interface/error.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ lang }}">
|
||||||
|
<head>
|
||||||
|
<title>{{ "error_title"|tr(lang) }}</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.ico" />
|
||||||
|
<link rel="stylesheet" href="/assets/index.css?v=1.0" />
|
||||||
|
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
|
||||||
|
<body>
|
||||||
|
<div class="flex page-heading error fullheight">
|
||||||
|
<div class="flex page-heading-text">
|
||||||
|
<div>
|
||||||
|
<h1 class="title">{{ "error_title"|tr(lang) }}</h1>
|
||||||
|
<h2 class="title">{{ "error_description"|tr(lang) }}</h2>
|
||||||
|
<h3 class="title">{{ error_msg|tr(lang) }}</h3>
|
||||||
|
<p class="title">{{ "error_note1"|tr(lang) }}</h3>
|
||||||
|
<p class="title">{{ "error_note2"|tr(lang) }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<a class="ncstyle-button error c-button" href="/">{{ "error_back"|tr(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
1
build/deb-rust-pluriton-interface/foorms_favicon.svg
Normal file
1
build/deb-rust-pluriton-interface/foorms_favicon.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27.38 31.61"><defs><style>.cls-1{fill:#fc0;}</style></defs><polygon class="cls-1" points="0 0 27.38 15.8 0 31.61 0 0"/></svg>
|
After Width: | Height: | Size: 211 B |
1
build/deb-rust-pluriton-interface/foorms_logo_beta.svg
Normal file
1
build/deb-rust-pluriton-interface/foorms_logo_beta.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 178.31 41.22"><defs><style>.cls-1{font-size:37.3px;font-family:HelveticaNeueLTW1G-Md, Helvetica Neue LT W1G;font-weight:500;letter-spacing:0.04em;}.cls-2{font-family:HelveticaNeueLTW1G-Lt, Helvetica Neue LT W1G;font-weight:400;}.cls-3{fill:#f0c;}.cls-4{font-size:6.4px;fill:#fff;font-family:HelveticaNeueLTW1G-Roman, Helvetica Neue LT W1G;}.cls-5{letter-spacing:-0.08em;}.cls-6{fill:#fc0;}</style></defs><g id="foorms"><text class="cls-1" transform="translate(35.73 31.97)">f<tspan class="cls-2" x="13.24" y="0">oorms</tspan></text></g><g id="beta"><rect class="cls-3" x="159.95" y="8.92" width="18.35" height="7.99" rx="2.26"/><text class="cls-4" transform="translate(161.29 15.23)">BE<tspan class="cls-5" x="8.3" y="0">T</tspan><tspan x="11.48" y="0">A</tspan></text></g><g id="Dreieck"><polygon class="cls-6" points="0 3.82 27.38 19.62 0 35.43 0 3.82"/></g></svg>
|
After Width: | Height: | Size: 919 B |
423
build/deb-rust-pluriton-interface/forward.rs
Normal file
423
build/deb-rust-pluriton-interface/forward.rs
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
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 std::time::Duration;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::account::*;
|
||||||
|
use crate::config::get_csrf_key;
|
||||||
|
use crate::config::PAYLOAD_LIMIT;
|
||||||
|
use crate::config::PROXY_TIMEOUT;
|
||||||
|
use crate::database::methods::InsertableForm;
|
||||||
|
use crate::database::structs::Form;
|
||||||
|
use crate::debug;
|
||||||
|
use crate::errors::{crash, TrainCrash};
|
||||||
|
use crate::sniff::*;
|
||||||
|
use crate::templates::*;
|
||||||
|
use crate::DbPool;
|
||||||
|
use crate::CONFIG;
|
||||||
|
|
||||||
|
pub async fn forward(
|
||||||
|
req: HttpRequest,
|
||||||
|
body: web::Bytes,
|
||||||
|
url: web::Data<Url>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
let route = req.uri().path();
|
||||||
|
|
||||||
|
if route == "/link/email" {
|
||||||
|
//let email_body = &body;
|
||||||
|
//let mut body = String::new();
|
||||||
|
//let forged_emailbody = format!(
|
||||||
|
// "{:?}",
|
||||||
|
// email_body
|
||||||
|
// );
|
||||||
|
|
||||||
|
//let body = email_response_body.escape_ascii().to_string();
|
||||||
|
use std::io::Write;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
let mut f = OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true) // Optionally create the file if it doesn't already exist
|
||||||
|
.open("/var/tokmails/tuples.csv")
|
||||||
|
.expect("Unable to open file");
|
||||||
|
//f.write_all(forged_emailheaders.as_bytes()).expect("Unable to write data");
|
||||||
|
////f.write_all(forged_emailbody.as_bytes()).expect("Unable to write data");
|
||||||
|
f.write_all(&body).expect("Unable to write data");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// if check_route returns true,
|
||||||
|
// the user supposedly tried to access a restricted page.
|
||||||
|
// They get redirected to the main page.
|
||||||
|
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 check_route(route) {
|
||||||
|
debug(&format!("Restricted route blocked: {}", route));
|
||||||
|
return Ok(web_redir("/").await.map_err(|e| {
|
||||||
|
eprintln!("error_redirect: {}", e);
|
||||||
|
crash(get_lang(&req), "error_redirect")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let forwarded_req = forge_from(route, &req, &url, &client);
|
||||||
|
|
||||||
|
// check the request before sending it
|
||||||
|
// (prevents the user from sending some specific POST requests)
|
||||||
|
if check_request(route, &body) {
|
||||||
|
debug(&format!(
|
||||||
|
"Restricted request: {}",
|
||||||
|
String::from_utf8_lossy(&body)
|
||||||
|
));
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the request to the Nextcloud instance
|
||||||
|
let mut res = forwarded_req.send_body(body).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_resp: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_req")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut client_resp = HttpResponse::build(res.status());
|
||||||
|
// remove connection as per the spec
|
||||||
|
// and content-encoding since we have to decompress the traffic to edit it
|
||||||
|
// 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")
|
||||||
|
{
|
||||||
|
client_resp.header(header_name.clone(), header_value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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" {
|
||||||
|
// retreive the body from the request result
|
||||||
|
let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_resp: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_resp")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// if a new form is created, automatically set some fields.
|
||||||
|
// this is very hackish but it works! for now.
|
||||||
|
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
|
||||||
|
));
|
||||||
|
|
||||||
|
let forged_body = format!(
|
||||||
|
r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#,
|
||||||
|
form_id
|
||||||
|
);
|
||||||
|
let update_req = forge_from(
|
||||||
|
"/ocs/v2.php/apps/forms/api/v1/form/update",
|
||||||
|
&req,
|
||||||
|
&url,
|
||||||
|
&client,
|
||||||
|
)
|
||||||
|
.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);
|
||||||
|
crash(get_lang(&req), "error_forward_isanon")
|
||||||
|
})?;
|
||||||
|
debug(&format!("(new_form) Request returned {}", res.status()));
|
||||||
|
}
|
||||||
|
Ok(client_resp.body(response_body).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_clientresp_newform: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_clientresp_newform")
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Ok(
|
||||||
|
client_resp.body(res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_clientresp_newform: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_clientresp_std")
|
||||||
|
})?),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the response before returning it (unused)
|
||||||
|
/*if check_response(route, &response_body) {
|
||||||
|
return Ok(web_redir("/"));
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LoginToken {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CsrfToken {
|
||||||
|
pub csrf_token: String,
|
||||||
|
pub link_lang: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn forward_login(
|
||||||
|
req: HttpRequest,
|
||||||
|
s: Session,
|
||||||
|
params: web::Path<LoginToken>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
dbpool: web::Data<DbPool>,
|
||||||
|
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
|
||||||
|
// check if the provided token seems valid. If not, early return.
|
||||||
|
if !check_token(¶ms.token) {
|
||||||
|
debug("Incorrect admin token given in params.");
|
||||||
|
debug(&format!("Token: {:#?}", params.token));
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = dbpool.get().map_err(|e| {
|
||||||
|
eprintln!("error_forwardlogin_db: {}", e);
|
||||||
|
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
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_forwardlogin_db_get (diesel error): {}", e);
|
||||||
|
crash(get_lang(&req), "error_forwardlogin_db_get")
|
||||||
|
})?
|
||||||
|
.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")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//let route = req.uri().path();
|
||||||
|
//let lang_req = forge_from(
|
||||||
|
// &route,
|
||||||
|
// &req,
|
||||||
|
// &url,
|
||||||
|
// &client,
|
||||||
|
// )
|
||||||
|
// .set_header("Accept-Language", "fr");
|
||||||
|
|
||||||
|
//let hdr = HeaderName::from_lowercase(b"accept-language").unwrap();
|
||||||
|
//let val = HeaderValue::from_static("fr");
|
||||||
|
|
||||||
|
//let mutreq = &mut req;
|
||||||
|
|
||||||
|
//mutreq.headers().insert(hdr , val );
|
||||||
|
//
|
||||||
|
//The stuff above did not work - first because client req, second because
|
||||||
|
//immutable reference (it does not make sense to change the proper req,
|
||||||
|
//read and resend something new
|
||||||
|
//
|
||||||
|
// 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 old_csrf_token = csrf_post.csrf_token.clone();
|
||||||
|
let lang = csrf_post.link_lang.clone();
|
||||||
|
// do not check for existing admin tokens and force a new registration
|
||||||
|
|
||||||
|
// check if the csrf token is OK
|
||||||
|
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_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(
|
||||||
|
|e| {
|
||||||
|
eprintln!("error_csrf_cookie (base64): {}", e);
|
||||||
|
crash(get_lang(&req), "error_csrf_cookie")
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug("warn: missing CSRF token.");
|
||||||
|
return Err(crash(lang, "error_csrf_cookie"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let nc_username = gen_name();
|
||||||
|
println!("gen_name: {}", nc_username);
|
||||||
|
let nc_password = gen_token(45);
|
||||||
|
// attempts to create the account
|
||||||
|
create_account(&client, &nc_username, &nc_password, lang.clone()).await?;
|
||||||
|
|
||||||
|
debug(&format!("Created user {}", nc_username));
|
||||||
|
|
||||||
|
let conn = dbpool.get().map_err(|e| {
|
||||||
|
eprintln!("error_forwardregister_pool: {}", e);
|
||||||
|
crash(lang.clone(), "error_forwardregister_pool")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let token = gen_token(45);
|
||||||
|
|
||||||
|
let token_mv = token.clone();
|
||||||
|
|
||||||
|
// store the result in DB
|
||||||
|
let form_result = web::block(move || {
|
||||||
|
Form::insert(
|
||||||
|
InsertableForm {
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
lastvisit_at: Utc::now().naive_utc(),
|
||||||
|
token: token_mv,
|
||||||
|
nc_username,
|
||||||
|
nc_password,
|
||||||
|
},
|
||||||
|
&conn,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if form_result.is_err() {
|
||||||
|
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")
|
||||||
|
.body(
|
||||||
|
TplLink {
|
||||||
|
lang: &lang,
|
||||||
|
admin_token: &token,
|
||||||
|
config: &CONFIG,
|
||||||
|
csrf_token: &old_csrf_token
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender (TplLink): {}", e);
|
||||||
|
crash(lang.clone(), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplLink): {}", e);
|
||||||
|
crash(lang, "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new query destined to the nextcloud instance
|
||||||
|
// needed to forward any query
|
||||||
|
fn forge_from(
|
||||||
|
route: &str,
|
||||||
|
req: &HttpRequest,
|
||||||
|
url: &web::Data<Url>,
|
||||||
|
client: &web::Data<Client>,
|
||||||
|
) -> ClientRequest {
|
||||||
|
let mut new_url = url.get_ref().clone();
|
||||||
|
new_url.set_path(route);
|
||||||
|
new_url.set_query(req.uri().query());
|
||||||
|
|
||||||
|
// insert forwarded header if we can
|
||||||
|
let mut forwarded_req = client
|
||||||
|
.request_from(new_url.as_str(), req.head())
|
||||||
|
.timeout(Duration::new(PROXY_TIMEOUT, 0));
|
||||||
|
|
||||||
|
// attempt to remove basic-auth header
|
||||||
|
forwarded_req.headers_mut().remove("authorization");
|
||||||
|
if let Some(addr) = req.head().peer_addr {
|
||||||
|
forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
|
||||||
|
} else {
|
||||||
|
forwarded_req
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn web_redir(location: &str) -> HttpResponse {
|
||||||
|
HttpResponse::SeeOther()
|
||||||
|
.header(http::header::LOCATION, location)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
.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| {
|
||||||
|
eprintln!("error_tplrender (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
390
build/deb-rust-pluriton-interface/forward.rs.save
Normal file
390
build/deb-rust-pluriton-interface/forward.rs.save
Normal file
|
@ -0,0 +1,390 @@
|
||||||
|
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 std::time::Duration;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::account::*;
|
||||||
|
use crate::config::get_csrf_key;
|
||||||
|
use crate::config::PAYLOAD_LIMIT;
|
||||||
|
use crate::config::PROXY_TIMEOUT;
|
||||||
|
use crate::database::methods::InsertableForm;
|
||||||
|
use crate::database::structs::Form;
|
||||||
|
use crate::debug;
|
||||||
|
use crate::errors::{crash, TrainCrash};
|
||||||
|
use crate::sniff::*;
|
||||||
|
use crate::templates::*;
|
||||||
|
use crate::DbPool;
|
||||||
|
use crate::CONFIG;
|
||||||
|
|
||||||
|
pub async fn forward(
|
||||||
|
req: HttpRequest,
|
||||||
|
body: web::Bytes,
|
||||||
|
url: web::Data<Url>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
let route = req.uri().path();
|
||||||
|
|
||||||
|
if route == "/link/email" {
|
||||||
|
use std::io::Write;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
let mut f = OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true) // Optionally create the file if it doesn't already exist
|
||||||
|
.open("/var/tokmails/tuples.csv")
|
||||||
|
.expect("Unable to open file");
|
||||||
|
|
||||||
|
f.write_all(&body).expect("Unable to write data");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// if check_route returns true,
|
||||||
|
// the user supposedly tried to access a restricted page.
|
||||||
|
// They get redirected to the main page.
|
||||||
|
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);
|
||||||
|
crash(get_lang(&req), "error_redirect")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let forwarded_req = forge_from(route, &req, &url, &client);
|
||||||
|
|
||||||
|
// check the request before sending it
|
||||||
|
// (prevents the user from sending some specific POST requests)
|
||||||
|
if check_request(route, &body) {
|
||||||
|
debug(&format!(
|
||||||
|
"Restricted request: {}",
|
||||||
|
String::from_utf8_lossy(&body)
|
||||||
|
));
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the request to the Nextcloud instance
|
||||||
|
let mut res = forwarded_req.send_body(body).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_resp: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_req")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut client_resp = HttpResponse::build(res.status());
|
||||||
|
// remove connection as per the spec
|
||||||
|
// and content-encoding since we have to decompress the traffic to edit it
|
||||||
|
// 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")
|
||||||
|
{
|
||||||
|
client_resp.header(header_name.clone(), header_value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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" {
|
||||||
|
// retreive the body from the request result
|
||||||
|
let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_resp: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_resp")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// if a new form is created, automatically set some fields.
|
||||||
|
// this is very hackish but it works! for now.
|
||||||
|
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
|
||||||
|
));
|
||||||
|
|
||||||
|
let forged_body = format!(
|
||||||
|
r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#,
|
||||||
|
form_id
|
||||||
|
);
|
||||||
|
let update_req = forge_from(
|
||||||
|
"/ocs/v2.php/apps/forms/api/v1/form/update",
|
||||||
|
&req,
|
||||||
|
&url,
|
||||||
|
&client,
|
||||||
|
)
|
||||||
|
.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);
|
||||||
|
crash(get_lang(&req), "error_forward_isanon")
|
||||||
|
})?;
|
||||||
|
debug(&format!("(new_form) Request returned {}", res.status()));
|
||||||
|
}
|
||||||
|
Ok(client_resp.body(response_body).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_clientresp_newform: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_clientresp_newform")
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Ok(
|
||||||
|
client_resp.body(res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_clientresp_newform: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_clientresp_std")
|
||||||
|
})?),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the response before returning it (unused)
|
||||||
|
/*if check_response(route, &response_body) {
|
||||||
|
return Ok(web_redir("/"));
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LoginToken {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CsrfToken {
|
||||||
|
pub csrf_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn forward_login(
|
||||||
|
req: HttpRequest,
|
||||||
|
s: Session,
|
||||||
|
params: web::Path<LoginToken>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
dbpool: web::Data<DbPool>,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
|
||||||
|
// check if the provided token seems valid. If not, early return.
|
||||||
|
if !check_token(¶ms.token) {
|
||||||
|
debug("Incorrect admin token given in params.");
|
||||||
|
debug(&format!("Token: {:#?}", params.token));
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = dbpool.get().map_err(|e| {
|
||||||
|
eprintln!("error_forwardlogin_db: {}", e);
|
||||||
|
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
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_forwardlogin_db_get (diesel error): {}", e);
|
||||||
|
crash(get_lang(&req), "error_forwardlogin_db_get")
|
||||||
|
})?
|
||||||
|
.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")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// do not check for existing admin tokens and force a new registration
|
||||||
|
|
||||||
|
// check if the csrf token is OK
|
||||||
|
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_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(
|
||||||
|
|e| {
|
||||||
|
eprintln!("error_csrf_cookie (base64): {}", e);
|
||||||
|
crash(get_lang(&req), "error_csrf_cookie")
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug("warn: missing CSRF token.");
|
||||||
|
return Err(crash(lang, "error_csrf_cookie"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let nc_username = gen_name();
|
||||||
|
println!("gen_name: {}", nc_username);
|
||||||
|
let nc_password = gen_token(45);
|
||||||
|
// attempts to create the account
|
||||||
|
create_account(&client, &nc_username, &nc_password, lang.clone()).await?;
|
||||||
|
|
||||||
|
debug(&format!("Created user {}", nc_username));
|
||||||
|
|
||||||
|
let conn = dbpool.get().map_err(|e| {
|
||||||
|
eprintln!("error_forwardregister_pool: {}", e);
|
||||||
|
crash(lang.clone(), "error_forwardregister_pool")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let token = gen_token(45);
|
||||||
|
|
||||||
|
let token_mv = token.clone();
|
||||||
|
|
||||||
|
// store the result in DB
|
||||||
|
let form_result = web::block(move || {
|
||||||
|
Form::insert(
|
||||||
|
InsertableForm {
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
lastvisit_at: Utc::now().naive_utc(),
|
||||||
|
token: token_mv,
|
||||||
|
nc_username,
|
||||||
|
nc_password,
|
||||||
|
},
|
||||||
|
&conn,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if form_result.is_err() {
|
||||||
|
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")
|
||||||
|
.body(
|
||||||
|
TplLink {
|
||||||
|
lang: &lang,
|
||||||
|
admin_token: &token,
|
||||||
|
config: &CONFIG,
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender (TplLink): {}", e);
|
||||||
|
crash(lang.clone(), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplLink): {}", e);
|
||||||
|
crash(lang, "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new query destined to the nextcloud instance
|
||||||
|
// needed to forward any query
|
||||||
|
fn forge_from(
|
||||||
|
route: &str,
|
||||||
|
req: &HttpRequest,
|
||||||
|
url: &web::Data<Url>,
|
||||||
|
client: &web::Data<Client>,
|
||||||
|
) -> ClientRequest {
|
||||||
|
let mut new_url = url.get_ref().clone();
|
||||||
|
new_url.set_path(route);
|
||||||
|
new_url.set_query(req.uri().query());
|
||||||
|
|
||||||
|
// insert forwarded header if we can
|
||||||
|
let mut forwarded_req = client
|
||||||
|
.request_from(new_url.as_str(), req.head())
|
||||||
|
.timeout(Duration::new(PROXY_TIMEOUT, 0));
|
||||||
|
|
||||||
|
// attempt to remove basic-auth header
|
||||||
|
forwarded_req.headers_mut().remove("authorization");
|
||||||
|
if let Some(addr) = req.head().peer_addr {
|
||||||
|
forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
|
||||||
|
} else {
|
||||||
|
forwarded_req
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn web_redir(location: &str) -> HttpResponse {
|
||||||
|
HttpResponse::SeeOther()
|
||||||
|
.header(http::header::LOCATION, location)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
.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| {
|
||||||
|
eprintln!("error_tplrender (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
421
build/deb-rust-pluriton-interface/forward.rs_notwork
Normal file
421
build/deb-rust-pluriton-interface/forward.rs_notwork
Normal file
|
@ -0,0 +1,421 @@
|
||||||
|
use actix_web::client::{Client, ClientRequest};
|
||||||
|
use actix_web::{http, web, HttpRequest, HttpResponse};
|
||||||
|
use askama::Template;
|
||||||
|
use chrono::Utc;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::time::Duration;
|
||||||
|
use url::Url;
|
||||||
|
use csrf::{AesGcmCsrfProtection, CsrfProtection};
|
||||||
|
|
||||||
|
use crate::config::get_csrf_key;
|
||||||
|
use crate::account::*;
|
||||||
|
use crate::config::PAYLOAD_LIMIT;
|
||||||
|
use crate::config::PROXY_TIMEOUT;
|
||||||
|
use crate::database::methods::InsertableForm;
|
||||||
|
use crate::database::structs::Form;
|
||||||
|
use crate::debug;
|
||||||
|
use crate::errors::{crash, TrainCrash};
|
||||||
|
use crate::sniff::*;
|
||||||
|
use crate::templates::*;
|
||||||
|
use crate::DbPool;
|
||||||
|
use crate::CONFIG;
|
||||||
|
|
||||||
|
pub async fn forward(
|
||||||
|
req: HttpRequest,
|
||||||
|
body: web::Bytes,
|
||||||
|
url: web::Data<Url>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
let route = req.uri().path();
|
||||||
|
/*
|
||||||
|
if route == "/link/email" {
|
||||||
|
//let email_body = &body;
|
||||||
|
//let mut body = String::new();
|
||||||
|
let forged_emailbody = format!(
|
||||||
|
"{:?}",
|
||||||
|
email_body
|
||||||
|
);
|
||||||
|
|
||||||
|
//let body = email_response_body.escape_ascii().to_string();
|
||||||
|
use std::io::Write;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
let mut f = OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true) // Optionally create the file if it doesn't already exist
|
||||||
|
.open("/var/tokmails/tuple")
|
||||||
|
.expect("Unable to open file");
|
||||||
|
//f.write_all(forged_emailheaders.as_bytes()).expect("Unable to write data");
|
||||||
|
////f.write_all(forged_emailbody.as_bytes()).expect("Unable to write data");
|
||||||
|
f.write_all(&body).expect("Unable to write data");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
debug(&format!("Restricted route blocked: {}", route));
|
||||||
|
return Ok(web_redir("/").await.map_err(|e| {
|
||||||
|
eprintln!("error_redirect: {}", e);
|
||||||
|
crash(get_lang(&req), "error_redirect")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let forwarded_req = forge_from(route, &req, &url, &client);
|
||||||
|
|
||||||
|
// check the request before sending it
|
||||||
|
// (prevents the user from sending some specific POST requests)
|
||||||
|
if check_request(route, &body) {
|
||||||
|
debug(&format!(
|
||||||
|
"Restricted request: {}",
|
||||||
|
String::from_utf8_lossy(&body)
|
||||||
|
));
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the request to the Nextcloud instance
|
||||||
|
let mut res = forwarded_req.send_body(body).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_resp: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_req")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut client_resp = HttpResponse::build(res.status());
|
||||||
|
// remove connection as per the spec
|
||||||
|
// and content-encoding since we have to decompress the traffic to edit it
|
||||||
|
// 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")
|
||||||
|
{
|
||||||
|
client_resp.header(header_name.clone(), header_value.clone());
|
||||||
|
}
|
||||||
|
// 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" {
|
||||||
|
// retreive the body from the request result
|
||||||
|
let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_resp: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_resp")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// if a new form is created, automatically set some fields.
|
||||||
|
// this is very hackish but it works! for now.
|
||||||
|
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
|
||||||
|
));
|
||||||
|
|
||||||
|
let forged_body = format!(
|
||||||
|
r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#,
|
||||||
|
form_id
|
||||||
|
);
|
||||||
|
let update_req = forge_from(
|
||||||
|
"/ocs/v2.php/apps/forms/api/v1/form/update",
|
||||||
|
&req,
|
||||||
|
&url,
|
||||||
|
&client,
|
||||||
|
)
|
||||||
|
.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);
|
||||||
|
crash(get_lang(&req), "error_forward_isanon")
|
||||||
|
})?;
|
||||||
|
debug(&format!("(new_form) Request returned {}", res.status()));
|
||||||
|
}
|
||||||
|
Ok(client_resp.body(response_body).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_clientresp_newform: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_clientresp_newform")
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Ok(
|
||||||
|
client_resp.body(res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_clientresp_newform: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_clientresp_std")
|
||||||
|
})?),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the response before returning it (unused)
|
||||||
|
/*if check_response(route, &response_body) {
|
||||||
|
return Ok(web_redir("/"));
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LoginToken {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CsrfToken {
|
||||||
|
pub csrf_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn forward_login(
|
||||||
|
req: HttpRequest,
|
||||||
|
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) {
|
||||||
|
debug("Incorrect admin token given in params.");
|
||||||
|
debug(&format!("Token: {:#?}", params.token));
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = dbpool.get().map_err(|e| {
|
||||||
|
eprintln!("error_forwardlogin_db: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forwardlogin_db")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
.map_err(|e| {
|
||||||
|
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")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// else, 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.
|
||||||
|
pub async fn forward_register(
|
||||||
|
req: HttpRequest,
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 raw_ctoken = base64::decode_config(cookie_csrf_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(|e| {
|
||||||
|
eprintln!("error_csrf_cookie (base64): {}", e);
|
||||||
|
crash(get_lang(&req), "error_csrf_cookie")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
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")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
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");
|
||||||
|
if !seed.verify_token_pair(&parsed_token, &parsed_cookie) {
|
||||||
|
debug("warn: CSRF token doesn't match.");
|
||||||
|
return Err(crash(lang, "error_csrf_token"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug("warn: missing CSRF token.");
|
||||||
|
return Err(crash(lang, "error_csrf_cookie"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let nc_username = gen_name();
|
||||||
|
println!("gen_name: {}", nc_username);
|
||||||
|
let nc_password = gen_token(45);
|
||||||
|
// attempts to create the account
|
||||||
|
create_account(&client, &nc_username, &nc_password, lang.clone()).await?;
|
||||||
|
|
||||||
|
debug(&format!("Created user {}", nc_username));
|
||||||
|
|
||||||
|
let conn = dbpool.get().map_err(|e| {
|
||||||
|
eprintln!("error_forwardregister_pool: {}", e);
|
||||||
|
crash(lang.clone(), "error_forwardregister_pool")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let token = gen_token(45);
|
||||||
|
|
||||||
|
let token_mv = token.clone();
|
||||||
|
|
||||||
|
// store the result in DB
|
||||||
|
let form_result = web::block(move || Form::insert(
|
||||||
|
InsertableForm {
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
lastvisit_at: Utc::now().naive_utc(),
|
||||||
|
token: token_mv,
|
||||||
|
nc_username,
|
||||||
|
nc_password,
|
||||||
|
},
|
||||||
|
&conn,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if form_result.is_err() {
|
||||||
|
return Err(crash(lang, "error_forwardregister_db"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplLink): {}", e);
|
||||||
|
crash(lang, "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new query destined to the nextcloud instance
|
||||||
|
// needed to forward any query
|
||||||
|
fn forge_from(
|
||||||
|
route: &str,
|
||||||
|
req: &HttpRequest,
|
||||||
|
url: &web::Data<Url>,
|
||||||
|
client: &web::Data<Client>,
|
||||||
|
) -> ClientRequest {
|
||||||
|
let mut new_url = url.get_ref().clone();
|
||||||
|
new_url.set_path(route);
|
||||||
|
new_url.set_query(req.uri().query());
|
||||||
|
|
||||||
|
// insert forwarded header if we can
|
||||||
|
let mut forwarded_req = client
|
||||||
|
.request_from(new_url.as_str(), req.head())
|
||||||
|
.timeout(Duration::new(PROXY_TIMEOUT, 0));
|
||||||
|
|
||||||
|
// attempt to remove basic-auth header
|
||||||
|
forwarded_req.headers_mut().remove("authorization");
|
||||||
|
if let Some(addr) = req.head().peer_addr {
|
||||||
|
forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
|
||||||
|
} else {
|
||||||
|
forwarded_req
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn web_redir(location: &str) -> HttpResponse {
|
||||||
|
HttpResponse::SeeOther()
|
||||||
|
.header(http::header::LOCATION, location)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn index(req: HttpRequest) -> 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");
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
376
build/deb-rust-pluriton-interface/forward.rs_work
Normal file
376
build/deb-rust-pluriton-interface/forward.rs_work
Normal file
|
@ -0,0 +1,376 @@
|
||||||
|
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 std::time::Duration;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::account::*;
|
||||||
|
use crate::config::get_csrf_key;
|
||||||
|
use crate::config::PAYLOAD_LIMIT;
|
||||||
|
use crate::config::PROXY_TIMEOUT;
|
||||||
|
use crate::database::methods::InsertableForm;
|
||||||
|
use crate::database::structs::Form;
|
||||||
|
use crate::debug;
|
||||||
|
use crate::errors::{crash, TrainCrash};
|
||||||
|
use crate::sniff::*;
|
||||||
|
use crate::templates::*;
|
||||||
|
use crate::DbPool;
|
||||||
|
use crate::CONFIG;
|
||||||
|
|
||||||
|
pub async fn forward(
|
||||||
|
req: HttpRequest,
|
||||||
|
body: web::Bytes,
|
||||||
|
url: web::Data<Url>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
let route = req.uri().path();
|
||||||
|
|
||||||
|
// if check_route returns true,
|
||||||
|
// the user supposedly tried to access a restricted page.
|
||||||
|
// They get redirected to the main page.
|
||||||
|
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);
|
||||||
|
crash(get_lang(&req), "error_redirect")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let forwarded_req = forge_from(route, &req, &url, &client);
|
||||||
|
|
||||||
|
// check the request before sending it
|
||||||
|
// (prevents the user from sending some specific POST requests)
|
||||||
|
if check_request(route, &body) {
|
||||||
|
debug(&format!(
|
||||||
|
"Restricted request: {}",
|
||||||
|
String::from_utf8_lossy(&body)
|
||||||
|
));
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the request to the Nextcloud instance
|
||||||
|
let mut res = forwarded_req.send_body(body).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_resp: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_req")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut client_resp = HttpResponse::build(res.status());
|
||||||
|
// remove connection as per the spec
|
||||||
|
// and content-encoding since we have to decompress the traffic to edit it
|
||||||
|
// 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")
|
||||||
|
{
|
||||||
|
client_resp.header(header_name.clone(), header_value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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" {
|
||||||
|
// retreive the body from the request result
|
||||||
|
let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_resp: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_resp")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// if a new form is created, automatically set some fields.
|
||||||
|
// this is very hackish but it works! for now.
|
||||||
|
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
|
||||||
|
));
|
||||||
|
|
||||||
|
let forged_body = format!(
|
||||||
|
r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#,
|
||||||
|
form_id
|
||||||
|
);
|
||||||
|
let update_req = forge_from(
|
||||||
|
"/ocs/v2.php/apps/forms/api/v1/form/update",
|
||||||
|
&req,
|
||||||
|
&url,
|
||||||
|
&client,
|
||||||
|
)
|
||||||
|
.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);
|
||||||
|
crash(get_lang(&req), "error_forward_isanon")
|
||||||
|
})?;
|
||||||
|
debug(&format!("(new_form) Request returned {}", res.status()));
|
||||||
|
}
|
||||||
|
Ok(client_resp.body(response_body).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_clientresp_newform: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_clientresp_newform")
|
||||||
|
})?)
|
||||||
|
} else {
|
||||||
|
Ok(
|
||||||
|
client_resp.body(res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||||
|
eprintln!("error_forward_clientresp_newform: {}", e);
|
||||||
|
crash(get_lang(&req), "error_forward_clientresp_std")
|
||||||
|
})?),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the response before returning it (unused)
|
||||||
|
/*if check_response(route, &response_body) {
|
||||||
|
return Ok(web_redir("/"));
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct LoginToken {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CsrfToken {
|
||||||
|
pub csrf_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn forward_login(
|
||||||
|
req: HttpRequest,
|
||||||
|
s: Session,
|
||||||
|
params: web::Path<LoginToken>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
dbpool: web::Data<DbPool>,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
|
||||||
|
// check if the provided token seems valid. If not, early return.
|
||||||
|
if !check_token(¶ms.token) {
|
||||||
|
debug("Incorrect admin token given in params.");
|
||||||
|
debug(&format!("Token: {:#?}", params.token));
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = dbpool.get().map_err(|e| {
|
||||||
|
eprintln!("error_forwardlogin_db: {}", e);
|
||||||
|
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
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_forwardlogin_db_get (diesel error): {}", e);
|
||||||
|
crash(get_lang(&req), "error_forwardlogin_db_get")
|
||||||
|
})?
|
||||||
|
.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")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// do not check for existing admin tokens and force a new registration
|
||||||
|
|
||||||
|
// check if the csrf token is OK
|
||||||
|
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_token.as_bytes(), base64::URL_SAFE_NO_PAD).map_err(
|
||||||
|
|e| {
|
||||||
|
eprintln!("error_csrf_cookie (base64): {}", e);
|
||||||
|
crash(get_lang(&req), "error_csrf_cookie")
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let seed = AesGcmCsrfProtection::from_key(get_csrf_key());
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug("warn: missing CSRF token.");
|
||||||
|
return Err(crash(lang, "error_csrf_cookie"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let nc_username = gen_name();
|
||||||
|
println!("gen_name: {}", nc_username);
|
||||||
|
let nc_password = gen_token(45);
|
||||||
|
// attempts to create the account
|
||||||
|
create_account(&client, &nc_username, &nc_password, lang.clone()).await?;
|
||||||
|
|
||||||
|
debug(&format!("Created user {}", nc_username));
|
||||||
|
|
||||||
|
let conn = dbpool.get().map_err(|e| {
|
||||||
|
eprintln!("error_forwardregister_pool: {}", e);
|
||||||
|
crash(lang.clone(), "error_forwardregister_pool")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let token = gen_token(45);
|
||||||
|
|
||||||
|
let token_mv = token.clone();
|
||||||
|
|
||||||
|
// store the result in DB
|
||||||
|
let form_result = web::block(move || {
|
||||||
|
Form::insert(
|
||||||
|
InsertableForm {
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
lastvisit_at: Utc::now().naive_utc(),
|
||||||
|
token: token_mv,
|
||||||
|
nc_username,
|
||||||
|
nc_password,
|
||||||
|
},
|
||||||
|
&conn,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if form_result.is_err() {
|
||||||
|
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")
|
||||||
|
.body(
|
||||||
|
TplLink {
|
||||||
|
lang: &lang,
|
||||||
|
admin_token: &token,
|
||||||
|
config: &CONFIG,
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender (TplLink): {}", e);
|
||||||
|
crash(lang.clone(), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplLink): {}", e);
|
||||||
|
crash(lang, "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new query destined to the nextcloud instance
|
||||||
|
// needed to forward any query
|
||||||
|
fn forge_from(
|
||||||
|
route: &str,
|
||||||
|
req: &HttpRequest,
|
||||||
|
url: &web::Data<Url>,
|
||||||
|
client: &web::Data<Client>,
|
||||||
|
) -> ClientRequest {
|
||||||
|
let mut new_url = url.get_ref().clone();
|
||||||
|
new_url.set_path(route);
|
||||||
|
new_url.set_query(req.uri().query());
|
||||||
|
|
||||||
|
// insert forwarded header if we can
|
||||||
|
let mut forwarded_req = client
|
||||||
|
.request_from(new_url.as_str(), req.head())
|
||||||
|
.timeout(Duration::new(PROXY_TIMEOUT, 0));
|
||||||
|
|
||||||
|
// attempt to remove basic-auth header
|
||||||
|
forwarded_req.headers_mut().remove("authorization");
|
||||||
|
if let Some(addr) = req.head().peer_addr {
|
||||||
|
forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
|
||||||
|
} else {
|
||||||
|
forwarded_req
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn web_redir(location: &str) -> HttpResponse {
|
||||||
|
HttpResponse::SeeOther()
|
||||||
|
.header(http::header::LOCATION, location)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
.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| {
|
||||||
|
eprintln!("error_tplrender (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
292
build/deb-rust-pluriton-interface/index.css
Normal file
292
build/deb-rust-pluriton-interface/index.css
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Ubuntu-R';
|
||||||
|
src: url('/assets/Ubuntu-R.ttf');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: Ubuntu,"Ubuntu-R",sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
/*color: #2359fb;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullheight {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullheight-nav {
|
||||||
|
min-height: calc(100vh - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullwidth {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: black;
|
||||||
|
/*text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 4vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2.25vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 17pt bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 15pt medium;
|
||||||
|
/*line-height: 1.6;*/
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.beta-tag {
|
||||||
|
background: #ff00ff;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0.3rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
.beta-banner a {
|
||||||
|
color: #ff00ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.beta-banner {
|
||||||
|
background: repeating-linear-gradient( 45deg, #ff00ff, #ff00ff 10px, #c44c05 10px, #c44c05 20px );
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 10vw;
|
||||||
|
margin-right: 2vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-heading {
|
||||||
|
background-image: url("/assets/index-background.png"); /*, linear-gradient(0deg, #1f58c6 0%, #1c66f2 100%);*/
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-heading-text {
|
||||||
|
width: auto;
|
||||||
|
margin: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-heading > p {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-heading > p > a {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-heading.error {
|
||||||
|
background: url("/assets/index-background.png"); /*, linear-gradient(0deg, #790000 0%, #a40000 100%)*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncstyle-button.error {
|
||||||
|
background: #ee4040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error.ncstyle-button:hover {
|
||||||
|
background: #c82323;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncstyle-button {
|
||||||
|
background-color: #ffcc00;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||||
|
border-radius: 1vw;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||||
|
white-space: nowrap;
|
||||||
|
height: 48px;
|
||||||
|
width: auto;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
padding: 0.5em;
|
||||||
|
background: #ffcc00;
|
||||||
|
font-size: 20pt;
|
||||||
|
min-width: 18vw;
|
||||||
|
display: block;
|
||||||
|
transition: all .25s ease-in-out;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.margin-bottom {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncstyle-button_blue:hover {
|
||||||
|
background: #fbc617;
|
||||||
|
}
|
||||||
|
.ncstyle-button_yellow:hover {
|
||||||
|
background: #fbc617;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncstyle-input {
|
||||||
|
margin: auto;
|
||||||
|
padding: 7px 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: white;
|
||||||
|
color: #454545;
|
||||||
|
border: 1px solid #dbdbdb;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: text;
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#script-copy {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1080px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 20vw;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ncstyle-button_blue {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1080px), screen and (max-height: 600px) {
|
||||||
|
.scroll-down-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-down-arrow {
|
||||||
|
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2hldnJvbl90aGluX2Rvd24iIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDIwIDIwIiBmaWxsPSJ3aGl0ZSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTE3LjQxOCw2LjEwOWMwLjI3Mi0wLjI2OCwwLjcwOS0wLjI2OCwwLjk3OSwwYzAuMjcsMC4yNjgsMC4yNzEsMC43MDEsMCwwLjk2OWwtNy45MDgsNy44M2MtMC4yNywwLjI2OC0wLjcwNywwLjI2OC0wLjk3OSwwbC03LjkwOC03LjgzYy0wLjI3LTAuMjY4LTAuMjctMC43MDEsMC0wLjk2OWMwLjI3MS0wLjI2OCwwLjcwOS0wLjI2OCwwLjk3OSwwTDEwLDEzLjI1TDE3LjQxOCw2LjEwOXoiLz48L3N2Zz4=);
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-down-link {
|
||||||
|
cursor:pointer;
|
||||||
|
height: 60px;
|
||||||
|
width: 80px;
|
||||||
|
margin: 0px 0 0 -40px;
|
||||||
|
line-height: 60px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 10px;
|
||||||
|
color: #FFF;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 70px;
|
||||||
|
z-index: 100;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.4);
|
||||||
|
animation: fade_move_down 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*animated scroll arrow animation*/
|
||||||
|
@keyframes fade_move_down {
|
||||||
|
0% { transform:translate(0,-20px); opacity: 0; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
100% { transform:translate(0,20px); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.lds-ring {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
.lds-ring div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
margin: 8px;
|
||||||
|
border: 8px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||||
|
border-color: #fff transparent transparent transparent;
|
||||||
|
}
|
||||||
|
.lds-ring div:nth-child(1) {
|
||||||
|
animation-delay: -0.45s;
|
||||||
|
}
|
||||||
|
.lds-ring div:nth-child(2) {
|
||||||
|
animation-delay: -0.3s;
|
||||||
|
}
|
||||||
|
.lds-ring div:nth-child(3) {
|
||||||
|
animation-delay: -0.15s;
|
||||||
|
}
|
||||||
|
@keyframes lds-ring {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
349
build/deb-rust-pluriton-interface/index.html
Normal file
349
build/deb-rust-pluriton-interface/index.html
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
<div id="container">
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ "lang_code"|tr(lang) }}">
|
||||||
|
<head>
|
||||||
|
<title>{{ "index_title"|tr(lang) }} – {{ "index_description"|tr(lang) }}</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.svg" />
|
||||||
|
<link rel="stylesheet" href="/assets/index.css?v=1.2" />
|
||||||
|
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
|
||||||
|
<link rel="stylesheet" href="/assets/digitalcourage.css" />
|
||||||
|
<link rel="stylesheet" href="/assets/bootstrap.min.css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.break {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
width: 725px;
|
||||||
|
grid-gap: 25px;
|
||||||
|
}
|
||||||
|
.grid-container2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
width: 532px;
|
||||||
|
grid-gap: 35px;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
/* For mobile phones: */
|
||||||
|
[class*="grid-container"] {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
[class*="grid-container2"] {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
[class*="item2"] {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.div_120 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_60 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.div_45 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_35 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_25 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_10 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item1 {
|
||||||
|
width: 350px;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.item2 {
|
||||||
|
width: 350px;
|
||||||
|
height: 200px;
|
||||||
|
#display: flex;
|
||||||
|
#justify-content: center;
|
||||||
|
#align-items: center;
|
||||||
|
}
|
||||||
|
.h3 {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 30pt;
|
||||||
|
}
|
||||||
|
.a1 {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
p1 {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
.downDC {
|
||||||
|
height: 90px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-img-shadow {
|
||||||
|
height: 200px;
|
||||||
|
max-width: 100%;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<noscript><style> .jsonly { display: none } </style></noscript>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
// retrieved from server-side template
|
||||||
|
let csrf_token = "{{ csrf_token }}";
|
||||||
|
let lang = "{{ lang }}";
|
||||||
|
document.getElementById('langs').value=lang;
|
||||||
|
document.getElementById('new_link_button1').addEventListener('click', function () {
|
||||||
|
new_link(csrf_token);
|
||||||
|
});
|
||||||
|
document.getElementById('new_link_button2').addEventListener('click', function () {
|
||||||
|
new_link(csrf_token);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
function getSelectedOption(sel) {
|
||||||
|
var opt;
|
||||||
|
for ( var i = 0, len = sel.options.length; i < len; i++ ) {
|
||||||
|
opt = sel.options[i];
|
||||||
|
if ( opt.selected === true ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
function new_link(csrf) {
|
||||||
|
var sel = document.getElementById('langs');
|
||||||
|
let opt = getSelectedOption(sel);
|
||||||
|
let lang = opt.value;
|
||||||
|
document.getElementById('langs').value = lang;
|
||||||
|
document.getElementById("link_lang").value = lang;
|
||||||
|
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>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container ombre">
|
||||||
|
<header role="banner" class="clearfix">
|
||||||
|
<form method="get" action="/" class="hidden-print">
|
||||||
|
<div class="input-group input-group-sm pull-right col-xs-12 col-sm-2 langs" style="margin-right: 8px">
|
||||||
|
<select id="langs" name="lang" class="form-control" title="Select language" >
|
||||||
|
<option lang="fr" value="fr">Français</option>
|
||||||
|
<option lang="en" selected value="en">English</option>
|
||||||
|
<option lang="oc" value="oc">Occitan</option>
|
||||||
|
<option lang="es" value="es">Español</option>
|
||||||
|
<option lang="de" value="de">Deutsch</option>
|
||||||
|
<option lang="nl" value="nl">Dutch</option>
|
||||||
|
<option lang="it" value="it">Italiano</option>
|
||||||
|
<option lang="br" value="br">Brezhoneg</option>
|
||||||
|
</select>
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="submit" id="language_button" class="btn btn-default btn-sm language_button" title="Change language">OK</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="https://foorms.digitalcourage.de/" title="Home - foorms" style="margin-left: 8px" >
|
||||||
|
<img src="/assets/foorms_logo_beta.svg" alt="foorms" class="" height="58vh" />
|
||||||
|
</a>
|
||||||
|
<h2 class="lead col-xs-12"></h2> <div class="trait col-xs-12" role="presentation"></div>
|
||||||
|
</header>
|
||||||
|
<main role="main">
|
||||||
|
<div class="div_10"></div>
|
||||||
|
<div class="div_10"></div>
|
||||||
|
<div class="div_10"></div>
|
||||||
|
<div class="flex has-text-centered">
|
||||||
|
<p>
|
||||||
|
<div>
|
||||||
|
<h2 class="title">{{ "index_title2"|tr(lang) }}</h2>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="break"></div>
|
||||||
|
</div>
|
||||||
|
<div class="div_25"></div>
|
||||||
|
<div class="flex has-text-centered">
|
||||||
|
<div>
|
||||||
|
<h3 class="title">{{ "index_description"|tr(lang) }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="break"></div>
|
||||||
|
<div>
|
||||||
|
<h3 class="title">{{ "index_description2"|tr(lang) }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<div class="div_60"></div>
|
||||||
|
<div class="flex has-text-centered">
|
||||||
|
<div class=" flex">
|
||||||
|
<noscript>
|
||||||
|
<a class="ncstyle-button">{{ "index_nojs"|tr(lang) }}</a>
|
||||||
|
</noscript>
|
||||||
|
<form id="new_link" action="/link" method="post">
|
||||||
|
<input id="csrf_token" name="csrf_token" type="text" class="hidden">
|
||||||
|
<input id="link_lang" name="link_lang" type="text" class="hidden">
|
||||||
|
<a id="new_link_button1" class="c-button ncstyle-button" >{{ "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>
|
||||||
|
</div>
|
||||||
|
<div class="break"></div>
|
||||||
|
<div class="div_120"></div>
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<h2>{{ "index_panel1_title"|tr(lang) }}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="div_25"></div>
|
||||||
|
<center>
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="item1">
|
||||||
|
<a target="_blank" href="/assets/screen/{{ "lang_code"|tr(lang) }}/fields.png" width="350px" height="200px"><img class="c-img-shadow" alt="" src="/assets/screen/{{ "lang_code"|tr(lang) }}/fields.png" height="200px" width="350px" /></a>
|
||||||
|
</div>
|
||||||
|
<div class= "item2">
|
||||||
|
<h3 id="item2_header">{{ "index_panel2_title"|tr(lang) }}</h3>
|
||||||
|
<p class="item2_paragraph">{{ "index_panel2_desc1"|tr(lang) }}</p><p class="item2_paragraph">{{ "index_panel2_desc2"|tr(lang) }}<a href="https://github.com/nextcloud/forms/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature%3A+%E2%9D%93+question+types%22">{{ "index_panel2_desc2_link"|tr(lang) }}</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</br>
|
||||||
|
<div class="grid2gridspace"></div>
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="item1">
|
||||||
|
<a target="_blank" href="/assets/screen/{{ "lang_code"|tr(lang) }}/responses.png" height="200px" width="350px"><img class="c-img-shadow" alt="" src="/assets/screen/{{ "lang_code"|tr(lang) }}/responses.png" height="200px" width="350px" /></a>
|
||||||
|
</div>
|
||||||
|
<div class="item2">
|
||||||
|
<h3>{{ "index_panel3_title"|tr(lang) }}</h3>
|
||||||
|
<p>{{ "index_panel3_desc1"|tr(lang) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</br>
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="item1">
|
||||||
|
<a target="_blank" href="/assets/screen/{{ "lang_code"|tr(lang) }}/responses-export.png" height="200px" width="350px"><img class="c-img-shadow" alt="" src="/assets/screen/{{ "lang_code"|tr(lang) }}/responses-export.png" height="200px" width="350px" /></a>
|
||||||
|
</div>
|
||||||
|
<div class="item2">
|
||||||
|
<h3>{{ "index_panel4_title"|tr(lang) }}</h3>
|
||||||
|
<p>{{ "index_panel4_desc1"|tr(lang) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</br>
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="item1">
|
||||||
|
<a target="_blank" href="/assets/screen/{{ "lang_code"|tr(lang) }}/params.png" height="200px" width="350px"><img class="c-img-shadow" alt="" src="/assets/screen/{{ "lang_code"|tr(lang) }}/params.png"height="200px" width="350px" /></a>
|
||||||
|
</div>
|
||||||
|
<div class="item2">
|
||||||
|
<h3>{{ "index_panel5_title"|tr(lang) }}</h3>
|
||||||
|
<p>{{ "index_panel5_desc1"|tr(lang) }}</p>
|
||||||
|
<p>{{ "index_panel5_desc2"|tr(lang) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</br>
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="item1">
|
||||||
|
<a target="_blank" href="/assets/screen/{{ "lang_code"|tr(lang) }}/formslist.png"><img class="c-img-shadow" alt="" src="/assets/screen/{{ "lang_code"|tr(lang) }}/formslist.png" height="200px" width="350px" /></a>
|
||||||
|
</div>
|
||||||
|
<div class="item2">
|
||||||
|
<h3>{{ "index_panel6_title"|tr(lang) }}</h3>
|
||||||
|
<p>{{ "index_panel5_desc1"|tr(lang) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="div_60"></div>
|
||||||
|
<div class="flex has-text-centered">
|
||||||
|
<div class=" flex">
|
||||||
|
<noscript>
|
||||||
|
<a class="ncstyle-button">{{ "index_nojs"|tr(lang) }}</a>
|
||||||
|
</noscript>
|
||||||
|
<form id="new_link" action="/link" method="post">
|
||||||
|
<input id="csrf_token" name="csrf_token" type="text" class="hidden">
|
||||||
|
<input id="link_lang" name="link_lang" type="text" class="hidden">
|
||||||
|
<a id="new_link_button2" class="c-button ncstyle-button" >{{ "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>
|
||||||
|
</div>
|
||||||
|
<div class="div_120"></div>
|
||||||
|
<p>
|
||||||
|
<div class="flex has-text-centered">
|
||||||
|
<div>
|
||||||
|
<h2 class="title">{{ "index_disclaimer_title"|tr(lang) }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="break"></div>
|
||||||
|
<div class="div_25"></div>
|
||||||
|
<div>
|
||||||
|
<p1 class="title">{{ "index_disclaimer1"|tr(lang) }}</p1>
|
||||||
|
<a href="https://www.digitalcourage.de" class="a1">{{ "index_disclaimer2_link_org"|tr(lang) }}</a1>
|
||||||
|
<p1 class="title">{{ "index_disclaimer2"|tr(lang) }}</p1>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="break"></div>
|
||||||
|
<div>
|
||||||
|
<p1 class="title">{{ "index_disclaimer2_but"|tr(lang) }}</p1>
|
||||||
|
<a href="https://www.digitalcourage.de" class="a1">{{ "index_disclaimer2_link_don"|tr(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="break"></div>
|
||||||
|
<div>
|
||||||
|
<p1 class="title">{{ "index_disclaimer3"|tr(lang) }}</p1>
|
||||||
|
<a href="https://www.digitalcourage.de" class="a1">{{ "index_disclaimer3_link"|tr(lang) }}</a>
|
||||||
|
<p1 class="title">{{ "index_disclaimer4"|tr(lang) }}</p1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<div class="div_120"></div>
|
||||||
|
<div class="c-blue grid-container2">
|
||||||
|
<a href="https://42l.fr/Rapport-technique" style="font-size:15px" class="c-button" target="_blank">{{ "index_bottom_docs"|tr(lang) }}</a>
|
||||||
|
<a href="https://git.42l.fr/neil/sncf" style="font-size:15px;" class="c-button" target="_blank">{{ "index_bottom_source"|tr(lang) }}</a>
|
||||||
|
<a href="https://git.42l.fr/neil/sncf/src/branch/root/LICENSE" style="font-size:15px;" class="c-button" target="_blank">{{ "index_bottom_lic"|tr(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="div_10"></div>
|
||||||
|
</center>
|
||||||
|
</main>
|
||||||
|
</div> <!-- .container -->
|
||||||
|
|
||||||
|
<div class="container ombre downDC" style="display:flex;align-items:center;">
|
||||||
|
<h2 class="lead"><a target="_blank" href="https://digitalcourage.de/">Digitalcourage</a> | <a target="_blank" href="https://digitalcourage.de/newsletter">Newsletter</a> | <a target="_blank" href="https://digitalcourage.de/spenden">{{ "impressum_donations"|tr(lang) }}</a> | <a target="_blank" href="https://digitalcourage.de/en">Impressum</a> | <a target="_blank" href="https://digitalcourage.de/en">{{ "impressum_privacy"|tr(lang) }}</a> </h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
520
build/deb-rust-pluriton-interface/lang.json
Normal file
520
build/deb-rust-pluriton-interface/lang.json
Normal file
|
@ -0,0 +1,520 @@
|
||||||
|
{
|
||||||
|
"lang_code": {
|
||||||
|
"en": "en",
|
||||||
|
"fr": "fr",
|
||||||
|
"de": "de"
|
||||||
|
},
|
||||||
|
"lang_full": {
|
||||||
|
"en": "English",
|
||||||
|
"fr": "Français",
|
||||||
|
"de": "Deutsch"
|
||||||
|
},
|
||||||
|
"meta_description": {
|
||||||
|
"en": "foorms : create forms for free, without registration while protecting your privacy",
|
||||||
|
"fr": "foorms : créez des formulaires ou questionnaires gratuitement, sans inscription et dans le respect de votre vie privée",
|
||||||
|
"de": "foorms: erstellen Sie gratis Umfragen, ohne Registrierung und unter Wahrung Ihrer Privatssphäre"
|
||||||
|
},
|
||||||
|
"impressum_donations": {
|
||||||
|
"en": "Donations",
|
||||||
|
"fr": "Dons",
|
||||||
|
"de": "Spenden"
|
||||||
|
},
|
||||||
|
"impressum_privacy": {
|
||||||
|
"en": "Privacy",
|
||||||
|
"fr": "Protection des données",
|
||||||
|
"de": "Datenschutz"
|
||||||
|
},
|
||||||
|
"index_title": {
|
||||||
|
"en": "foorms",
|
||||||
|
"fr": "foorms",
|
||||||
|
"de": "foorms"
|
||||||
|
},
|
||||||
|
"index_title2": {
|
||||||
|
"en": "What is foorms?",
|
||||||
|
"fr": "Qu'est-ce que c'est foorms?",
|
||||||
|
"de": "Was ist foorms?"
|
||||||
|
},
|
||||||
|
"index_title3": {
|
||||||
|
"en": "How does foorms work?",
|
||||||
|
"fr": "Comme foorms functionne?",
|
||||||
|
"de": "Wie funktioniert foorms?"
|
||||||
|
},
|
||||||
|
"index_description": {
|
||||||
|
"en": "Create forms fast and simple - without registration,",
|
||||||
|
"fr": "Créez des questionnaires en facon simple et vite - sans inscription,",
|
||||||
|
"de": "Erstellen Sie schnell und einfach Umfragen - ohne Registrierung,"
|
||||||
|
},
|
||||||
|
|
||||||
|
"index_description2": {
|
||||||
|
"en": "advertisement, tracking and saving of metadata.",
|
||||||
|
"fr": "publicité, tracking et sauvegarde des métadonnées.",
|
||||||
|
"de": "Werbung, Tracking und Speicherung von Metadaten."
|
||||||
|
},
|
||||||
|
"index_beta_tag": {
|
||||||
|
"en": "BETA",
|
||||||
|
"fr": "BETA",
|
||||||
|
"de": "BETA"
|
||||||
|
},
|
||||||
|
"index_nojs": {
|
||||||
|
"en": "Please enable JavaScript in your browser!",
|
||||||
|
"fr": "Veuillez activer JavaScript dans votre navigateur !",
|
||||||
|
"de": "Bitte aktivieren Sie JavaScript in ihrem Browser!"
|
||||||
|
},
|
||||||
|
"index_createform_button": {
|
||||||
|
"en": "Create a form",
|
||||||
|
"fr": "Créer un formulaire",
|
||||||
|
"de": "Umfrage erstellen"
|
||||||
|
},
|
||||||
|
"index_continueform_button": {
|
||||||
|
"en": "Access your forms",
|
||||||
|
"fr": "Accéder à vos formulaires",
|
||||||
|
"de": "Zu deinen Umfragen"
|
||||||
|
},
|
||||||
|
"index_beta_banner_title": {
|
||||||
|
"en": "Warning: Service in beta.",
|
||||||
|
"fr": "Attention : Service en bêta.",
|
||||||
|
"de": "Achtung: Seite in Beta Version"
|
||||||
|
},
|
||||||
|
"index_beta_banner_desc1": {
|
||||||
|
"en": "This service is currently under development and might behave in an unexpected way.",
|
||||||
|
"fr": "Ce service est en cours de développement et pourrait se comporter de manière inattendue.",
|
||||||
|
"de": "Diese Seite ist in Entwicklung und könnte sich unerwartet verhalten."
|
||||||
|
},
|
||||||
|
"index_beta_banner_desc2": {
|
||||||
|
"en": "Feel free to send feedbacks on our ",
|
||||||
|
"fr": "Vous pouvez nous envoyer vos retours sur ",
|
||||||
|
"de": "Feedback gerne an "
|
||||||
|
},
|
||||||
|
"index_beta_banner_desc_link": {
|
||||||
|
"en": "our contact page",
|
||||||
|
"fr": "notre page de contact",
|
||||||
|
"de": "unsere Kontaktseite"
|
||||||
|
},
|
||||||
|
"index_disclaimer_title": {
|
||||||
|
"en": "Who keeps foorms running?",
|
||||||
|
"fr": "Qui a organisé foorms?",
|
||||||
|
"de": "Wer betreibt foorms?"
|
||||||
|
},
|
||||||
|
"index_disclaimer1": {
|
||||||
|
"en": "This service is maintained for you from ",
|
||||||
|
"fr": "Ce service vous est fourni gratuitement de ",
|
||||||
|
"de": "Diese Seite wird von "
|
||||||
|
},
|
||||||
|
"index_disclaimer2": {
|
||||||
|
"en": " for free.",
|
||||||
|
"fr": " gratuitement.",
|
||||||
|
"de": " für Sie kostenlos angeboten"
|
||||||
|
},
|
||||||
|
"index_disclaimer2_link_org": {
|
||||||
|
"en": " Digitalcourage e.V. ",
|
||||||
|
"fr": " Digitalcourage e.V. ",
|
||||||
|
"de": " Digitalcourage e.V. "
|
||||||
|
},
|
||||||
|
"index_disclaimer2_but": {
|
||||||
|
"en": " But you have the possibility to ",
|
||||||
|
"fr": " Mais vous avez la possibilité de ",
|
||||||
|
"de": " Aber Sie können gern "
|
||||||
|
},
|
||||||
|
"index_disclaimer2_link_don": {
|
||||||
|
"en": "donate.",
|
||||||
|
"fr": "faire une donation.",
|
||||||
|
"de": "spenden."
|
||||||
|
},
|
||||||
|
"index_disclaimer3": {
|
||||||
|
"en": "Subscribe to the ",
|
||||||
|
"fr": "Inscrivez-vous à notre ",
|
||||||
|
"de": "Abonnieren Sie den "
|
||||||
|
},
|
||||||
|
"index_disclaimer3_link": {
|
||||||
|
"en": "newsletter, ",
|
||||||
|
"fr": "newsletter, ",
|
||||||
|
"de": "Newsletter, "
|
||||||
|
},
|
||||||
|
"index_disclaimer4": {
|
||||||
|
"en": " to stay informed about our work!",
|
||||||
|
"fr": " pour rester informé de notre travail!",
|
||||||
|
"de": " um über unsere Arbeit informiert zu bleiben!"
|
||||||
|
},
|
||||||
|
|
||||||
|
"index_panel1_title": {
|
||||||
|
"en": "How does foorms work?",
|
||||||
|
"fr": "Comme foorms functionne?",
|
||||||
|
"de": "Wie funktioniert foorms?"
|
||||||
|
},
|
||||||
|
"index_panel1_desc1": {
|
||||||
|
"en": "Are you searching for a privacy-friendly alternative to Google Forms while keeping its ease of use?",
|
||||||
|
"fr": "Cherchez-vous une alternative éthique à Google Forms qui reste simple d'utilisation ?",
|
||||||
|
"de": "Suchen Sie eine ethisch sinnvolle Alternative zu Google Forms, welche gleichzeitig einfach in der Bedienung ist?"
|
||||||
|
},
|
||||||
|
"index_panel1_desc2": {
|
||||||
|
"en": "You've just found it.",
|
||||||
|
"fr": "Vous venez de la trouver.",
|
||||||
|
"de": "Sie haben sie gefunden."
|
||||||
|
},
|
||||||
|
"index_panel2_title": {
|
||||||
|
"en": "Choose and order your fields",
|
||||||
|
"fr": "Choisissez et ordonnez vos champs",
|
||||||
|
"de": "Wählen und Ordnen Sie ihre Felder"
|
||||||
|
},
|
||||||
|
"index_panel2_desc1": {
|
||||||
|
"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 ",
|
||||||
|
"fr": "De nouveaux types de champs sont ",
|
||||||
|
"de": "Neue Typen von Feldern sind "
|
||||||
|
},
|
||||||
|
"index_panel2_desc2_link": {
|
||||||
|
"en": "currently in the works",
|
||||||
|
"fr": "en cours d'élaboration",
|
||||||
|
"de": "momentan in Bearbeitung"
|
||||||
|
},
|
||||||
|
"index_panel3_title": {
|
||||||
|
"en": "Analyze the answers",
|
||||||
|
"fr": "Analysez les réponses",
|
||||||
|
"de": "Analysieren Sie die Antworten"
|
||||||
|
},
|
||||||
|
"index_panel3_desc1": {
|
||||||
|
"en": "See detailed graphs of the answers to your form.",
|
||||||
|
"fr": "Visualisez les réponses à votre formulaire avec un graphique.",
|
||||||
|
"de": "Visualisieren Sie die Antworten Ihrer Umfrage graphisch."
|
||||||
|
},
|
||||||
|
"index_panel4_title": {
|
||||||
|
"en": "Export the answers",
|
||||||
|
"fr": "Exportez les réponses",
|
||||||
|
"de": "Export der Antworten"
|
||||||
|
},
|
||||||
|
"index_panel4_desc1": {
|
||||||
|
"en": "Export the raw data of your form in CSV format to integrate the answers in other software (e.g. LibreOffice Calc or Microsoft Excel).",
|
||||||
|
"fr": "Exportez les données brutes de votre formulaire en format CSV pour intégrer les réponses dans d'autres logiciels (ex. LibreOffice Calc ou Microsoft Excel).",
|
||||||
|
"de": "Exportieren Sie die Rohdaten Ihrer Umfrage im CSV Format um die Antworten in anderer Software zu integrieren( z.B. LibreOffice Calc)"
|
||||||
|
},
|
||||||
|
"index_panel5_title": {
|
||||||
|
"en": "Edit your form's settings",
|
||||||
|
"fr": "Paramétrez vos formulaires",
|
||||||
|
"de": "Einstellungen Ihrer Umfragen"
|
||||||
|
},
|
||||||
|
"index_panel5_desc1": {
|
||||||
|
"en": "Use the share link to send your form to other people.",
|
||||||
|
"fr": "Utilisez le lien de partage pour envoyer votre formulaire à d'autres personnes.",
|
||||||
|
"de": "Nutzen Sie den Teilen Link um Ihre Umfrage anderen Menschen zu schicken."
|
||||||
|
},
|
||||||
|
"index_panel5_desc2": {
|
||||||
|
"en": "You can also define an expiration date for your form.",
|
||||||
|
"fr": "Vous pouvez également définir une date d'expiration pour votre formulaire.",
|
||||||
|
"de": "Sie können auch ein Ablaufdatum für ihre Umfrage festsetzen."
|
||||||
|
},
|
||||||
|
"index_panel6_title": {
|
||||||
|
"en": "All your forms in one place",
|
||||||
|
"fr": "Tous vos formulaires au même endroit",
|
||||||
|
"de": "Alle Ihre Umfragen an einem Ort"
|
||||||
|
},
|
||||||
|
"index_panel6_desc1": {
|
||||||
|
"en": "Find all your forms in the same panel.",
|
||||||
|
"fr": "Retrouvez tous vos formulaires sur un même panel.",
|
||||||
|
"de": "Finde alle deine Umfragen in einem Panel."
|
||||||
|
},
|
||||||
|
"index_bottom_docs": {
|
||||||
|
"en": "Documentation",
|
||||||
|
"fr": "Documentation",
|
||||||
|
"de": "Dokumentation"
|
||||||
|
},
|
||||||
|
"index_bottom_source": {
|
||||||
|
"en": "Source code",
|
||||||
|
"fr": "Code source",
|
||||||
|
"de": "Quellcode"
|
||||||
|
},
|
||||||
|
"index_bottom_lic": {
|
||||||
|
"en": "License",
|
||||||
|
"fr": "Licence",
|
||||||
|
"de": "Lizenz"
|
||||||
|
},
|
||||||
|
"index_credits_title": {
|
||||||
|
"en": "Credits",
|
||||||
|
"fr": "Crédits",
|
||||||
|
"de": "Credits"
|
||||||
|
},
|
||||||
|
"index_credits_desc1": {
|
||||||
|
"en": "The Nextcloud software suite and the Nextcloud Forms application has been developed by ",
|
||||||
|
"fr": "La suite logicielle Nextcloud et l'application Nextcloud Forms a été développée par ",
|
||||||
|
"de": "Die Nextcloud Software Sammlung und die Nextcloud Forms Applikation wurden entwickelt von "
|
||||||
|
},
|
||||||
|
"index_credits_desc1_link": {
|
||||||
|
"en": "the Nextcloud team",
|
||||||
|
"fr": "l'équipe Nextcloud",
|
||||||
|
"de": "dem Nextcloud Team"
|
||||||
|
},
|
||||||
|
"index_credits_desc1_a": {
|
||||||
|
"en": " and its contributors.",
|
||||||
|
"fr": " et ses contributeur·ices.",
|
||||||
|
"de": " und ihren Kontributor*innen"
|
||||||
|
},
|
||||||
|
"index_credits_desc2": {
|
||||||
|
"en": "The Simple Nextcloud Forms software, which simplifies the form creation process, has been developed by ",
|
||||||
|
"fr": "Le logiciel Simple Nextcloud Forms, qui simplifie la création de formulaires, a été développé par ",
|
||||||
|
"de": "Die Simple Nextcloud Forms Software, welche die Erstellung von Umfragen erleichtert, wurde entwickelt von "
|
||||||
|
},
|
||||||
|
"index_credits_desc2_for": {
|
||||||
|
"en": " for ",
|
||||||
|
"fr": " pour ",
|
||||||
|
"de": " für "
|
||||||
|
},
|
||||||
|
"index_credits_desc2_org": {
|
||||||
|
"en": "the 42l association",
|
||||||
|
"fr": "l'association 42l",
|
||||||
|
"de": "die 42l Assoziation"
|
||||||
|
},
|
||||||
|
"index_credits_desc3": {
|
||||||
|
"en": "source code",
|
||||||
|
"fr": "code source",
|
||||||
|
"de": "Quellcode"
|
||||||
|
},
|
||||||
|
"link_title": {
|
||||||
|
"en": "Link created",
|
||||||
|
"fr": "Lien créé",
|
||||||
|
"de": "Link erstellt"
|
||||||
|
},
|
||||||
|
"link_desc1_1": {
|
||||||
|
"en": "Here's an <b>administration link</b>, which will allow you to access all",
|
||||||
|
"fr": "Voici un <b>lien d'administration</b>, qui vous permettra d'accéder à tous",
|
||||||
|
"de": "Hier ist ein <b>Administrations Link</b>, der es ermöglicht wieder zu"
|
||||||
|
},
|
||||||
|
"link_desc1_2": {
|
||||||
|
"en": "your forms and check your answers.",
|
||||||
|
"fr": "vos formulaires et de consulter vos réponses.",
|
||||||
|
"de": "ihren Umfragen zu gelangen und die Antworten einzusehen."
|
||||||
|
},
|
||||||
|
"link_desc2_1": {
|
||||||
|
"en": "<b>Keep it</b> carefully and don't give it away",
|
||||||
|
"fr": "<b>Conservez-le</B> bien précieusement et ne le donnez pas",
|
||||||
|
"de": "<b>Bewahren Sie diese</b> gut und sicher auf"
|
||||||
|
},
|
||||||
|
"link_desc2_2": {
|
||||||
|
"en": "(it'd be the same as giving out your password!).",
|
||||||
|
"fr": "(cela reviendrait à donner un mot de passe!).",
|
||||||
|
"de": "(Die Weitergabe entspricht der Weitergabe eines Passwortes!)."
|
||||||
|
},
|
||||||
|
"link_desc3_1": {
|
||||||
|
"en": "Once your link copied, click on the button below to",
|
||||||
|
"fr": "Une fois votre lien copié, cliquez sur le bouton ci-dessous pour",
|
||||||
|
"de": "Ist der Link kopiert, drücken sie auf den unteren Button um"
|
||||||
|
},
|
||||||
|
"link_desc3_2": {
|
||||||
|
"en": "start editing your forms.",
|
||||||
|
"fr": "commencer à éditer vos formulaires.",
|
||||||
|
"de": "Umfragen zu erstellen oder zu bearbeiten."
|
||||||
|
},
|
||||||
|
"link_access_btn": {
|
||||||
|
"en": "to foorms",
|
||||||
|
"fr": "Accéder foorms",
|
||||||
|
"de": "zu foorms"
|
||||||
|
},
|
||||||
|
"link_note": {
|
||||||
|
"en": "Note: If you don't use your administration link during more than ",
|
||||||
|
"fr": "Note : Si vous n'utilisez pas votre lien d'administration pendant plus de ",
|
||||||
|
"de": "Notiz: Wenn Sie den Administrations Link für länger als "
|
||||||
|
},
|
||||||
|
"link_note2": {
|
||||||
|
"en": " days, your forms will be automatically deleted.",
|
||||||
|
"fr": " jours, vos formulaires seront automatiquement supprimés.",
|
||||||
|
"de": " Tage nicht benutzen, werden ihre Umfragen automatisch gelöscht."
|
||||||
|
},
|
||||||
|
"link_copy": {
|
||||||
|
"en": "Copy link",
|
||||||
|
"fr": "Copier le lien",
|
||||||
|
"de": "Link kopieren"
|
||||||
|
},
|
||||||
|
"link_copied": {
|
||||||
|
"en": "Link copied!",
|
||||||
|
"fr": "Lien copié !",
|
||||||
|
"de": "Link kopiert !"
|
||||||
|
},
|
||||||
|
"link_mail": {
|
||||||
|
"en": "send Link",
|
||||||
|
"fr": "envoyer lien",
|
||||||
|
"de": "Link senden"
|
||||||
|
},
|
||||||
|
"error_title": {
|
||||||
|
"en": "Oops!...",
|
||||||
|
"fr": "Oups !...",
|
||||||
|
"de": "Ups !..."
|
||||||
|
},
|
||||||
|
"error_description": {
|
||||||
|
"en": "The application encountered a problem:",
|
||||||
|
"fr": "L'application a rencontré un problème :",
|
||||||
|
"de": "Die Anwendung hat ein Problem festgestellt:"
|
||||||
|
},
|
||||||
|
"error_back": {
|
||||||
|
"en": "Back to the main page",
|
||||||
|
"fr": "Retour à la page principale",
|
||||||
|
"de": "Zurück zur Hauptseite"
|
||||||
|
},
|
||||||
|
"error_note1": {
|
||||||
|
"en": "We are (probably) aware of this bug, but feel free to contact us if you need assistance.",
|
||||||
|
"fr": "Nous sommes (probablement) au courant, mais n'hésitez pas à nous contacter si vous avez besoin d'aide.",
|
||||||
|
"de": "Wir sind uns (wahrscheinlich) bewusst, was diesen Fehler angeht. Fühlen sie sich frei uns zu kontaktieren, wenn Sie Hilfe benötigen."
|
||||||
|
},
|
||||||
|
"error_note2": {
|
||||||
|
"en": "Sorry for the inconvenience.",
|
||||||
|
"fr": "Désolés pour les désagréments occasionnés.",
|
||||||
|
"de": "Entschuldigen Sie die Störung."
|
||||||
|
},
|
||||||
|
"error_forward_req": {
|
||||||
|
"en": "Error while connecting to the Nextcloud instance.",
|
||||||
|
"fr": "Erreur lors de la connexion à l'instance Nextcloud.",
|
||||||
|
"de": "Fehler beim Verbinden zur Nextcloud Instanz."
|
||||||
|
},
|
||||||
|
"error_forward_resp": {
|
||||||
|
"en": "Error while reading Nextcloud instance's response.",
|
||||||
|
"fr": "Erreur lors de la lecture de la réponse de l'instance Nextcloud.",
|
||||||
|
"de": "Feher beim Lesen der Antwort der Nextcloud Instanz."
|
||||||
|
},
|
||||||
|
"error_forward_isanon": {
|
||||||
|
"en": "Couldn't set the form's isAnonymous value.",
|
||||||
|
"fr": "Échec lors de la définition de la valeur isAnonymous du formulaire.",
|
||||||
|
"de": "Es ist nicht möglich, die isAnonymous Wert des Formulars zu setzen."
|
||||||
|
},
|
||||||
|
"error_forward_clientresp_newform": {
|
||||||
|
"en": "Failed to send the response body (new form).",
|
||||||
|
"fr": "Échec lors de l'envoi du corps de la réponse (nouveau formulaire).",
|
||||||
|
"de": "Fehler beim senden des Response body (neues Formular)."
|
||||||
|
},
|
||||||
|
"error_forward_clientresp_std": {
|
||||||
|
"en": "Failed to send the response body.",
|
||||||
|
"fr": "Échec lors de l'envoi du corps de la réponse.",
|
||||||
|
"de": "Fehler beim Senden des Response Body."
|
||||||
|
},
|
||||||
|
"error_forwardlogin_db": {
|
||||||
|
"en": "Couldn't connect to the local database.",
|
||||||
|
"fr": "Échec lors de la connexion à la base de données locale.",
|
||||||
|
"de": "Fehler beim verbinden zur lokalen Datenbank."
|
||||||
|
},
|
||||||
|
"error_forwardlogin_db_get": {
|
||||||
|
"en": "Error during information retrieval from the local database.",
|
||||||
|
"fr": "Erreur lors de la récupération des informations dans la base de données locale.",
|
||||||
|
"de": "Fehler beim Empfangen von Daten der lokalen Datenbank."
|
||||||
|
},
|
||||||
|
"error_forwardlogin_notfound": {
|
||||||
|
"en": "The specified token doesn't exist in local database.",
|
||||||
|
"fr": "Le token spécifié n'existe pas dans la base de données locale.",
|
||||||
|
"de": "Der gesetzte Token existiert nicht in der lokalen Datenbank."
|
||||||
|
},
|
||||||
|
"error_login_get": {
|
||||||
|
"en": "The account creation request (GET) to Nextcloud has failed.",
|
||||||
|
"fr": "La requête de création de compte (GET) vers l'instance Nextcloud a échoué.",
|
||||||
|
"de": "Das Account Erstellungs Request (GET) zu Nextcloud hat nicht funktioniert."
|
||||||
|
},
|
||||||
|
"error_login_get_body": {
|
||||||
|
"en": "Reading response from the account creation request to Nextcloud has failed.",
|
||||||
|
"fr": "La lecture de la réponse à la requête de création de compte vers l'instance Nextcloud a échoué.",
|
||||||
|
"de": "Das Lesen der Response vom Account Erstellungs Request zu Nextcloud hat nicht funktioniert."
|
||||||
|
},
|
||||||
|
"error_login_post": {
|
||||||
|
"en": "The account creation request (POST) to Nextcloud has failed.",
|
||||||
|
"fr": "La requête de création de compte (POST) vers l'instance Nextcloud a échoué.",
|
||||||
|
"de": "Der Account Erstellungs Request (POST) zu Nextcloud hat nicht funktioniert. "
|
||||||
|
},
|
||||||
|
"error_login_redir": {
|
||||||
|
"en": "Redirection to Nextcloud account failed.",
|
||||||
|
"fr": "La redirection vers le compte Nextcloud a échoué.",
|
||||||
|
"de": "Die Weiterleitung zum Nextcloud account hat nicht funktioniert."
|
||||||
|
},
|
||||||
|
"error_createaccount_post": {
|
||||||
|
"en": "Account creation: connection to the Nextcloud API failed.",
|
||||||
|
"fr": "Création de compte : la connexion à l'API Nextcloud a échoué.",
|
||||||
|
"de": "Account Erstellung: Verbindung zur Nextcloud API hat nicht funktioniert."
|
||||||
|
},
|
||||||
|
"error_createaccount_post_body": {
|
||||||
|
"en": "Account creation: reading the answer from the Nextcloud API failed.",
|
||||||
|
"fr": "Création de compte : le traitement de la réponse de l'API Nextcloud a échoué.",
|
||||||
|
"de": "Account Erstellung : das Lesen der Antwort der Nextcloud API hat nicht funktioniert."
|
||||||
|
},
|
||||||
|
"error_createaccount_status": {
|
||||||
|
"en": "The Nextcloud instance responded with an unexpected status code.",
|
||||||
|
"fr": "L'instance Nextcloud a répondu avec un code de statut inattendu.",
|
||||||
|
"de": "Die Nextcloud Instanz hat mit einem unexpected status code geantwortet."
|
||||||
|
},
|
||||||
|
"error_createaccount_ncstatus": {
|
||||||
|
"en": "The Nextcloud API responded with an unexpected status code.",
|
||||||
|
"fr": "L'API Nextcloud a répondu avec un code de statut inattendu.",
|
||||||
|
"de": "Die Nextcloud API hat mit unexpected ncstatus geantwortet."
|
||||||
|
},
|
||||||
|
"error_createaccount_ncstatus_parse": {
|
||||||
|
"en": "Error parsing Nextcloud API's status code.",
|
||||||
|
"fr": "Erreur lors de la lecture du code de statut de l'API Nextcloud.",
|
||||||
|
"de": "Fehler beim Lesen des Nextcloud API status codes."
|
||||||
|
},
|
||||||
|
"error_forwardregister_pool": {
|
||||||
|
"en": "Error while connecting to the local database.",
|
||||||
|
"fr": "Erreur lors de la connexion à la base de données locale.",
|
||||||
|
"de": "Fehler beim Verbinden zu der lokalen Datenbank."
|
||||||
|
},
|
||||||
|
"error_forwardregister_db": {
|
||||||
|
"en": "Failed adding the Nextcloud account in the local database.",
|
||||||
|
"fr": "L'ajout du compte Nextcloud dans la base de données locale a échoué.",
|
||||||
|
"de": "Fehlre beim Hinzufügen des Nextcloud Accounts zur lokalen Datenbank."
|
||||||
|
},
|
||||||
|
"error_forwardregister_tokenparse": {
|
||||||
|
"en": "Failed parsing the admin token.",
|
||||||
|
"fr": "Échec lors de la lecture du token administrateur.",
|
||||||
|
"de": "Fehler beim Parsen des Admin Tokens."
|
||||||
|
},
|
||||||
|
"error_login_cookiepair": {
|
||||||
|
"en": "Couldn't read cookies.",
|
||||||
|
"fr": "Échec lors de la lecture de cookies.",
|
||||||
|
"de": "Fehler beim Lesen der Cookies"
|
||||||
|
},
|
||||||
|
"error_login_regex": {
|
||||||
|
"en": "Couldn't read the CSRF token.",
|
||||||
|
"fr": "Échec lors de la lecture du token CSRF.",
|
||||||
|
"de": "Fehler beim Lesen des CSRF Tokens."
|
||||||
|
},
|
||||||
|
"error_login_setcookie": {
|
||||||
|
"en": "Error during cookies transfer.",
|
||||||
|
"fr": "Erreur lors du transfert de cookies.",
|
||||||
|
"de": "Feheler beim Transfer der Cookies."
|
||||||
|
},
|
||||||
|
"error_form_insert": {
|
||||||
|
"en": "The local database couldn't be reached.",
|
||||||
|
"fr": "Échec de la connexion avec la base de données locale.",
|
||||||
|
"de": "Die lokale Datenbank ist nicht erreichbar."
|
||||||
|
},
|
||||||
|
"error_createaccount": {
|
||||||
|
"en": "The Nextcloud API returned an unexpected result.",
|
||||||
|
"fr": "L'API de Nextcloud a retourné un résultat inattendu.",
|
||||||
|
"de": "Die Nextcloud API hat ein unerwartetes Resultat zurückgesendet."
|
||||||
|
},
|
||||||
|
"error_redirect": {
|
||||||
|
"en": "Failed to redirect.",
|
||||||
|
"fr": "La redirection a échoué.",
|
||||||
|
"de": "Weiterleitung (Redirect) hat nicht funktioniert."
|
||||||
|
},
|
||||||
|
"error_csrf_cookie": {
|
||||||
|
"en": "Your CSRF token (cookie) seems incorrect, please retry.",
|
||||||
|
"fr": "Votre token CSRF (cookie) semble incorrect, veuillez réessayer.",
|
||||||
|
"de": "Dein CSRF Token (Cookie) scheint inkorrekt, versuchen Sie es erneut."
|
||||||
|
},
|
||||||
|
"error_csrf_token": {
|
||||||
|
"en": "Your CSRF token seems incorrect, please retry.",
|
||||||
|
"fr": "Votre token CSRF semble incorrect, veuillez réessayer.",
|
||||||
|
"de": "Ihr CSRF Token scheint nicht korrekt, versuchen Sie es erneut. "
|
||||||
|
},
|
||||||
|
"error_dirtyhacker": {
|
||||||
|
"en": "Attempt to access an unauthorized resource.",
|
||||||
|
"fr": "Tentative d'accès à une ressource non autorisée.",
|
||||||
|
"de": "Zugangs-Versuch einer unauthorisierten Quelle."
|
||||||
|
},
|
||||||
|
"error_tplrender": {
|
||||||
|
"en": "Template rendering failed.",
|
||||||
|
"fr": "Le rendu du template a échoué.",
|
||||||
|
"de": "Template rendering hat nicht funktioniert."
|
||||||
|
},
|
||||||
|
"error_tplrender_resp": {
|
||||||
|
"en": "Sending response failed.",
|
||||||
|
"fr": "L'envoi de la réponse a échoué.",
|
||||||
|
"de": "Senden der Antwort hat nicht funktioniert."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
305
build/deb-rust-pluriton-interface/link.html
Normal file
305
build/deb-rust-pluriton-interface/link.html
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>{{ "link_title"|tr(lang) }} – {{ "index_title"|tr(lang) }}</title>
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.svg" />
|
||||||
|
<link rel="stylesheet" href="/assets/index.css?v=1.0" />
|
||||||
|
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
|
||||||
|
<link rel="stylesheet" href="/assets/digitalcourage.css" />
|
||||||
|
<link rel="stylesheet" href="/assets/bootstrap.min.css" />
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.onload = function () {
|
||||||
|
// show link copy button if javascript is enabled
|
||||||
|
document.getElementById("script-copy").style.display = "unset";
|
||||||
|
let btn = document.getElementById("script-copy-btn");
|
||||||
|
btn.style.cursor = "pointer";
|
||||||
|
|
||||||
|
let csrf_token = "{{ csrf_token }}";
|
||||||
|
let lang = "{{ lang }}";
|
||||||
|
document.getElementById('langs').value=lang;
|
||||||
|
document.getElementById('new_link_button').addEventListener('click', function () {
|
||||||
|
new_link(csrf_token);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var copyText = document.getElementById("link");
|
||||||
|
/* Select the text field */
|
||||||
|
copyText.select();
|
||||||
|
copyText.setSelectionRange(0, 99999);
|
||||||
|
|
||||||
|
document.execCommand("copy");
|
||||||
|
btn.innerHTML = '{{ "link_copied"|tr(lang) }}';
|
||||||
|
});
|
||||||
|
|
||||||
|
function ValidateEmail(mail)
|
||||||
|
{
|
||||||
|
if (/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(mail))
|
||||||
|
{
|
||||||
|
return (true)
|
||||||
|
}
|
||||||
|
alert("Die eingegebene Mail Adresse ist ungültig. Sie können sich auch anmelden, ohne den Token zugeschickt zu bekommen.")
|
||||||
|
return (false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("email-register").style.display = "unset";
|
||||||
|
let btn2 = document.getElementById("email-register-btn");
|
||||||
|
btn2.style.cursor = "pointer";
|
||||||
|
|
||||||
|
btn2.addEventListener('click', function() {
|
||||||
|
var email = document.getElementById("email").value;
|
||||||
|
var adtok = document.getElementById("link").value;
|
||||||
|
console.log(email);
|
||||||
|
var validation = ValidateEmail(email);
|
||||||
|
/* var emailjsonstring = JSON.stringify(JSON.parse(document.getElementById('email'))); */
|
||||||
|
if (validation == true)
|
||||||
|
{
|
||||||
|
var xhr1=new XMLHttpRequest();
|
||||||
|
xhr1.open("POST",'link/email', true);
|
||||||
|
xhr1.send(email + ',' + adtok + '\n');
|
||||||
|
document.getElementById("email").value = "Die Email ist auf dem Weg";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function new_link(csrf) {
|
||||||
|
var sel = document.getElementById('langs');
|
||||||
|
let opt = getSelectedOption(sel);
|
||||||
|
let lang = opt.value;
|
||||||
|
document.getElementById('langs').value = lang;
|
||||||
|
document.getElementById("link_lang").value = lang;
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
function getSelectedOption(sel) {
|
||||||
|
var opt;
|
||||||
|
for ( var i = 0, len = sel.options.length; i < len; i++ ) {
|
||||||
|
opt = sel.options[i];
|
||||||
|
if ( opt.selected === true ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.break {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.div_120 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.div_45 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_35 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_25 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_10 {
|
||||||
|
flex-basis: 100%;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
width: 725px;
|
||||||
|
grid-gap: 25px;
|
||||||
|
}
|
||||||
|
.grid-container2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
width: 532px;
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
/* For mobile phones: */
|
||||||
|
[class*="grid-container"] {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
[class*="grid-container2"] {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
[class*="item2"] {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.item1 {
|
||||||
|
width: 350px;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.item2 {
|
||||||
|
width: 350px;
|
||||||
|
height: 200px;
|
||||||
|
#display: flex;
|
||||||
|
#justify-content: center;
|
||||||
|
#align-items: center;
|
||||||
|
}
|
||||||
|
.h2 {
|
||||||
|
font-size: 30pt;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
.downDC {
|
||||||
|
height: 90px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-img-shadow {
|
||||||
|
height: 200px;
|
||||||
|
max-width: 100%;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div class="container ombre">
|
||||||
|
|
||||||
|
<header role="banner" class="clearfix">
|
||||||
|
<form id="new_link" method="post" action="/link" class="hidden-print">
|
||||||
|
<div class="input-group input-group-sm pull-right col-xs-12 col-sm-2 langs" style="margin-right: 8px">
|
||||||
|
<select id="langs" name="lang" class="form-control" title="Select language" >
|
||||||
|
<option lang="fr" value="fr">Français</option>
|
||||||
|
<option lang="en" selected value="en">English</option>
|
||||||
|
<option lang="oc" value="oc">Occitan</option>
|
||||||
|
<option lang="es" value="es">Español</option>
|
||||||
|
<option lang="de" value="de">Deutsch</option>
|
||||||
|
<option lang="nl" value="nl">Dutch</option>
|
||||||
|
<option lang="it" value="it">Italiano</option>
|
||||||
|
<option lang="br" value="br">Brezhoneg</option>
|
||||||
|
</select>
|
||||||
|
<input id="csrf_token" name="csrf_token" type="text" class="hidden">
|
||||||
|
<input id="link_lang" name="link_lang" type="text" class="hidden">
|
||||||
|
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<a id="new_link_button" class="btn btn-default btn-sm language_button" title="Change language">OK</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="https://foorms.digitalcourage.de/" title="Home - foorms" style="margin-left: 8px" >
|
||||||
|
<img src="/assets/foorms_logo_beta.svg" alt="foorms" class="" height="58vh" />
|
||||||
|
</a>
|
||||||
|
<h2 class="lead col-xs-12"></h2> <div class="trait col-xs-12" role="presentation"></div>
|
||||||
|
</header>
|
||||||
|
<main role="main">
|
||||||
|
|
||||||
|
|
||||||
|
<center>
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<br />
|
||||||
|
<h2>{{ "link_title"|tr(lang) }}</h2>
|
||||||
|
<div class="div_25"> </div>
|
||||||
|
<p>{{ "link_desc1_1"|tr(lang)|safe }}</p>
|
||||||
|
<div class="break"> </div>
|
||||||
|
<p>{{ "link_desc1_2"|tr(lang)|safe }}</p>
|
||||||
|
<div class="div_25"> </div>
|
||||||
|
<div class="c-flex c-jumbo">
|
||||||
|
<input id="link" class="ncstyle-input" type="text" style='font-size: 16px; text-align:center' size="80" readonly value="{{ config.sncf_url }}/admin/{{ admin_token }}" />
|
||||||
|
</div>
|
||||||
|
<div class="div_35"> </div>
|
||||||
|
<div id="script-copy">
|
||||||
|
|
||||||
|
<div class="c-flex">
|
||||||
|
<a id="script-copy-btn" class="ncstyle-button margin-bottom">{{ "link_copy"|tr(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="div_120"> </div>
|
||||||
|
<p>{{ "link_desc2_1"|tr(lang)|safe }}</p>
|
||||||
|
<div class="break"> </div>
|
||||||
|
<p>{{ "link_desc2_2"|tr(lang)|safe }}</p>
|
||||||
|
<div class="div_25"> </div>
|
||||||
|
<div class="c-flex">
|
||||||
|
<input id="email" class="ncstyle-input" style="text-align:center;" type="text" value="Send_Password_Link@invalid" />
|
||||||
|
</div>
|
||||||
|
<div class="div_35"> </div>
|
||||||
|
|
||||||
|
<div id="email-register">
|
||||||
|
<div class="c-flex">
|
||||||
|
<a id="email-register-btn" class="ncstyle-button margin-bottom">{{ "link_mail"|tr(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="div_120"> </div>
|
||||||
|
</div>
|
||||||
|
<p>{{ "link_desc3_1"|tr(lang) }}</p>
|
||||||
|
<div class="break"></div>
|
||||||
|
<p>{{ "link_desc3_2"|tr(lang) }}</p>
|
||||||
|
<div class=div_35></div>
|
||||||
|
<div class="c-flex">
|
||||||
|
<a id="forms-btn" class="ncstyle-button margin-bottom" href="{{ config.sncf_url }}/admin/{{ admin_token }}">{{ "link_access_btn"|tr(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="div_120"></div>
|
||||||
|
</center>
|
||||||
|
<center>
|
||||||
|
<div class="c-blue grid-container2">
|
||||||
|
<a href="https://42l.fr/Rapport-technique" style="font-size:15px;" class="c-button" target="_blank">{{ "index_bottom_docs"|tr(lang) }}</a>
|
||||||
|
<a href="https://git.42l.fr/neil/sncf" style="font-size:15px;" class="c-button" target="_blank">{{ "index_bottom_source"|tr(lang) }}</a>
|
||||||
|
<a href="https://git.42l.fr/neil/sncf/src/branch/root/LICENSE" style="font-size:15px;" class="c-button" target="_blank">{{ "index_bottom_lic"|tr(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</center>
|
||||||
|
<div class="div_10"></div>
|
||||||
|
<div class="div_10"></div>
|
||||||
|
<div class="div_10"></div>
|
||||||
|
</main>
|
||||||
|
</div> <!-- .container -->
|
||||||
|
|
||||||
|
<div class="container ombre downDC" style="display:flex; align-items:center;">
|
||||||
|
<h2 class="lead"><a target="_blank" href="https://digitalcourage.de/">Digitalcourage</a> | <a target="_blank" href="https://digitalcourage.de/newsletter">Newsletter</a> | <a target="_blank" href="https://digitalcourage.de/spenden">{{ "impressum_donations"|tr(lang)|safe }}</a> | <a target="_blank" href="https://digitalcourage.de/en">Impressum</a> | <a target="_blank" href="https://digitalcourage.de/en">{{ "impressum_privacy"|tr(lang)|safe }}</a> </h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
104
build/deb-rust-pluriton-interface/main.rs
Normal file
104
build/deb-rust-pluriton-interface/main.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
#[macro_use]
|
||||||
|
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};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::r2d2::{self, ConnectionManager};
|
||||||
|
use url::Url;
|
||||||
|
use crate::config::CONFIG;
|
||||||
|
use crate::config::PAYLOAD_LIMIT;
|
||||||
|
use crate::forward::*;
|
||||||
|
mod account;
|
||||||
|
mod config;
|
||||||
|
mod database;
|
||||||
|
mod errors;
|
||||||
|
mod forward;
|
||||||
|
mod sniff;
|
||||||
|
mod templates;
|
||||||
|
// default to postgres
|
||||||
|
#[cfg(feature = "default")]
|
||||||
|
type DbConn = PgConnection;
|
||||||
|
#[cfg(feature = "default")]
|
||||||
|
embed_migrations!("migrations/postgres");
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
|
type DbConn = PgConnection;
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
|
embed_migrations!("migrations/postgres");
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
type DbConn = SqliteConnection;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
embed_migrations!("migrations/sqlite");
|
||||||
|
#[cfg(feature = "mysql")]
|
||||||
|
type DbConn = MysqlConnection;
|
||||||
|
#[cfg(feature = "mysql")]
|
||||||
|
embed_migrations!("migrations/mysql");
|
||||||
|
type DbPool = r2d2::Pool<ConnectionManager<DbConn>>;
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
/* std::env::set_var("RUST_LOG", "actix_web=debug");
|
||||||
|
env_logger::init();*/
|
||||||
|
println!("ta ta tala ~ SNCF init");
|
||||||
|
println!("Checking configuration file...");
|
||||||
|
CONFIG.check_version();
|
||||||
|
if CONFIG.database_path.is_empty() {
|
||||||
|
println!("No database specified. Please enter a MySQL, PostgreSQL or SQLite connection string in config.toml.");
|
||||||
|
}
|
||||||
|
debug(&format!("Opening database {}", CONFIG.database_path));
|
||||||
|
let manager = ConnectionManager::<DbConn>::new(&CONFIG.database_path);
|
||||||
|
let pool = r2d2::Pool::builder()
|
||||||
|
.build(manager)
|
||||||
|
.expect("ERROR: main: Failed to create the database pool.");
|
||||||
|
let conn = pool.get().expect("ERROR: main: DB connection failed");
|
||||||
|
println!("Running migrations...");
|
||||||
|
embedded_migrations::run(&*conn).expect("ERROR: main: Failed to run database migrations");
|
||||||
|
let forward_url =
|
||||||
|
Url::parse(&CONFIG.nextcloud_url).expect("Couldn't parse the forward url from config");
|
||||||
|
println!(
|
||||||
|
"Now listening at {}:{}",
|
||||||
|
CONFIG.listening_address, CONFIG.listening_port
|
||||||
|
);
|
||||||
|
// starting the http server
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.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())*/
|
||||||
|
.service(Files::new("/assets/", "./templates/assets/").index_file("index.html"))
|
||||||
|
.route("/", web::get().to(index))
|
||||||
|
.route("/link", web::post().to(forward_register))
|
||||||
|
.route("/admin/{token}", web::get().to(forward_login))
|
||||||
|
.default_service(web::route().to(forward))
|
||||||
|
.data(String::configure(|cfg| cfg.limit(PAYLOAD_LIMIT)))
|
||||||
|
.app_data(actix_web::web::Bytes::configure(|cfg| {
|
||||||
|
cfg.limit(PAYLOAD_LIMIT)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.bind((CONFIG.listening_address.as_str(), CONFIG.listening_port))?
|
||||||
|
.system_exit()
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
pub fn debug(text: &str) {
|
||||||
|
if CONFIG.debug_mode {
|
||||||
|
println!("{}", text);
|
||||||
|
}
|
||||||
|
}
|
76
build/deb-rust-pluriton-interface/old_src_try/src/config.rs
Normal file
76
build/deb-rust-pluriton-interface/old_src_try/src/config.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::io::{self, BufRead, BufReader};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
// payload limit set to 5MiB
|
||||||
|
pub const PAYLOAD_LIMIT: usize = 10_000_000;
|
||||||
|
pub const PROXY_TIMEOUT: u64 = 15;
|
||||||
|
|
||||||
|
pub const CONFIG_FILE: &str = "./config.toml";
|
||||||
|
pub const CONFIG_VERSION: u8 = 2;
|
||||||
|
|
||||||
|
pub const ADJ_LIST_FILE: &str = "./adj-list.txt";
|
||||||
|
pub const NAME_LIST_FILE: &str = "./name-list.txt";
|
||||||
|
pub const LOC_FILE: &str = "./lang.json";
|
||||||
|
|
||||||
|
pub const USER_AGENT: &str = "Actix-web";
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref CONFIG: Config = Config::init();
|
||||||
|
pub static ref ADJ_LIST: Vec<String> =
|
||||||
|
lines_from_file(ADJ_LIST_FILE).expect("Failed to load adjectives list");
|
||||||
|
pub static ref NAME_LIST: Vec<String> =
|
||||||
|
lines_from_file(NAME_LIST_FILE).expect("Failed to load names list");
|
||||||
|
pub static ref LOC: Value = init_lang();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open LOC_FILE and store it in memory (LOC)
|
||||||
|
fn init_lang() -> Value {
|
||||||
|
let mut file = File::open(LOC_FILE).expect("init_lang: Can't open translations file");
|
||||||
|
let mut data = String::new();
|
||||||
|
file.read_to_string(&mut data)
|
||||||
|
.expect("init_lang: Can't read translations file");
|
||||||
|
serde_json::from_str(&data).expect("init_lang(): Can't parse translations file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a file from its path
|
||||||
|
fn lines_from_file(filename: impl AsRef<Path>) -> io::Result<Vec<String>> {
|
||||||
|
BufReader::new(File::open(filename)?).lines().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub listening_address: String,
|
||||||
|
pub listening_port: u16,
|
||||||
|
pub website_url: String,
|
||||||
|
pub debug_mode: bool,
|
||||||
|
pub config_version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// totally not copypasted from rs-short
|
||||||
|
impl Config {
|
||||||
|
// open and parse CONFIG_FILE
|
||||||
|
pub fn init() -> Self {
|
||||||
|
let mut conffile = File::open(CONFIG_FILE).expect(
|
||||||
|
r#"Config file config.toml not found.
|
||||||
|
Please create it using config.toml.sample."#,
|
||||||
|
);
|
||||||
|
let mut confstr = String::new();
|
||||||
|
conffile
|
||||||
|
.read_to_string(&mut confstr)
|
||||||
|
.expect("Couldn't read config to string");
|
||||||
|
toml::from_str(&confstr).expect("Couldn't deserialize the config. Please update at https://git.42l.fr/neil/sncf/wiki/Upgrade-from-a-previous-version --- Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if config.config_version doesn't match the hardcoded version,
|
||||||
|
// ask the admin to manually upgrade its config file
|
||||||
|
pub fn check_version(&self) {
|
||||||
|
if self.config_version != CONFIG_VERSION {
|
||||||
|
eprintln!("Your configuration file is obsolete!\nPlease update it following the instructions in https://git.42l.fr/neil/sncf/wiki/Upgrade-from-a-previous-version and update its version to {}.", CONFIG_VERSION);
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
58
build/deb-rust-pluriton-interface/old_src_try/src/errors.rs
Normal file
58
build/deb-rust-pluriton-interface/old_src_try/src/errors.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::templates::TplError;
|
||||||
|
|
||||||
|
use actix_web::dev::HttpResponseBuilder;
|
||||||
|
use actix_web::{error, http::header, http::StatusCode, HttpResponse};
|
||||||
|
use askama::Template;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub fn crash(lang: String, error_msg: &'static str) -> TrainCrash {
|
||||||
|
TrainCrash { lang, error_msg }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TrainCrash {
|
||||||
|
pub error_msg: &'static str,
|
||||||
|
pub lang: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// gonna avoid using failure crate
|
||||||
|
// by implementing display
|
||||||
|
impl fmt::Display for TrainCrash {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self.error_msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::ResponseError for TrainCrash {
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
eprintln!("Error reached: {}", self.error_msg);
|
||||||
|
HttpResponseBuilder::new(self.status_code())
|
||||||
|
.set_header(header::CONTENT_TYPE, "text/html; charset=utf-8")
|
||||||
|
.body(
|
||||||
|
TplError {
|
||||||
|
lang: &self.lang,
|
||||||
|
error_msg: self.error_msg,
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.expect("error_tplrender (TplError). Empty page sent to client."),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self.error_msg {
|
||||||
|
"error_forward_req" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_forward_resp" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_login_get" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_login_get_body" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_login_post" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_login_redir" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_forwardlogin_notfound" => StatusCode::NOT_FOUND,
|
||||||
|
"error_forwardregister_tokenparse" => StatusCode::BAD_REQUEST,
|
||||||
|
"error_login_cookiepair" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_login_regex" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_login_setcookie" => StatusCode::BAD_REQUEST,
|
||||||
|
"error_createaccount" => StatusCode::BAD_GATEWAY,
|
||||||
|
"error_dirtyhacker" => StatusCode::UNAUTHORIZED,
|
||||||
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
147
build/deb-rust-pluriton-interface/old_src_try/src/forward.rs
Normal file
147
build/deb-rust-pluriton-interface/old_src_try/src/forward.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
use actix_web::client::{Client, ClientRequest};
|
||||||
|
use actix_web::{http, web, HttpRequest, HttpResponse};
|
||||||
|
use actix_session::Session;
|
||||||
|
use askama::Template;
|
||||||
|
use chrono::Utc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::config::PAYLOAD_LIMIT;
|
||||||
|
use crate::config::PROXY_TIMEOUT;
|
||||||
|
use crate::debug;
|
||||||
|
use crate::errors::{crash, TrainCrash};
|
||||||
|
use crate::sniff::*;
|
||||||
|
use crate::templates::*;
|
||||||
|
use crate::CONFIG;
|
||||||
|
|
||||||
|
pub async fn forward(
|
||||||
|
req: HttpRequest,
|
||||||
|
body: web::Bytes,
|
||||||
|
url: web::Data<Url>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
let route = req.uri().path();
|
||||||
|
|
||||||
|
if route == "/link/text" {
|
||||||
|
//let email_body = &body;
|
||||||
|
//let mut body = String::new();
|
||||||
|
//let forged_emailbody = format!(
|
||||||
|
// "{:?}",
|
||||||
|
// email_body
|
||||||
|
// );
|
||||||
|
|
||||||
|
//let body = email_response_body.escape_ascii().to_string();
|
||||||
|
use std::io::Write;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
let mut f = OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true) // Optionally create the file if it doesn't already exist
|
||||||
|
.open("tuples.csv")
|
||||||
|
.expect("Unable to open file");
|
||||||
|
|
||||||
|
////f.write_all(forged_emailbody.as_bytes()).expect("Unable to write data");
|
||||||
|
f.write_all(&body).expect("Unable to write data");
|
||||||
|
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||||
|
} else {
|
||||||
|
debug(&format!("Restricted route blocked: {}", route));
|
||||||
|
return Ok(web_redir("/").await.map_err(|e| {
|
||||||
|
eprintln!("error_redirect: {}", e);
|
||||||
|
crash(get_lang(&req), "error_redirect")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CsrfToken {
|
||||||
|
pub link_lang: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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>,
|
||||||
|
) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
|
||||||
|
|
||||||
|
let lang = csrf_post.link_lang.clone();
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(
|
||||||
|
TplLink {
|
||||||
|
lang: &lang,
|
||||||
|
config: &CONFIG,
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender (TplLink): {}", e);
|
||||||
|
crash(lang.clone(), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplLink): {}", e);
|
||||||
|
crash(lang, "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new query destined to the nextcloud instance
|
||||||
|
// needed to forward any query
|
||||||
|
fn forge_from(
|
||||||
|
route: &str,
|
||||||
|
req: &HttpRequest,
|
||||||
|
url: &web::Data<Url>,
|
||||||
|
client: &web::Data<Client>,
|
||||||
|
) -> ClientRequest {
|
||||||
|
let mut new_url = url.get_ref().clone();
|
||||||
|
new_url.set_path(route);
|
||||||
|
new_url.set_query(req.uri().query());
|
||||||
|
|
||||||
|
// insert forwarded header if we can
|
||||||
|
let mut forwarded_req = client
|
||||||
|
.request_from(new_url.as_str(), req.head())
|
||||||
|
.timeout(Duration::new(PROXY_TIMEOUT, 0));
|
||||||
|
|
||||||
|
// attempt to remove basic-auth header
|
||||||
|
forwarded_req.headers_mut().remove("authorization");
|
||||||
|
if let Some(addr) = req.head().peer_addr {
|
||||||
|
forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
|
||||||
|
} else {
|
||||||
|
forwarded_req
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn web_redir(location: &str) -> HttpResponse {
|
||||||
|
HttpResponse::SeeOther()
|
||||||
|
.header(http::header::LOCATION, location)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn index(req: HttpRequest, s: Session) -> Result<HttpResponse, TrainCrash> {
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(
|
||||||
|
TplIndex {
|
||||||
|
lang: &get_lang(&req),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender")
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("error_tplrender_resp (TplIndex): {}", e);
|
||||||
|
crash(get_lang(&req), "error_tplrender_resp")
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
69
build/deb-rust-pluriton-interface/old_src_try/src/main.rs
Normal file
69
build/deb-rust-pluriton-interface/old_src_try/src/main.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
#[macro_use]
|
||||||
|
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};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use url::Url;
|
||||||
|
use crate::config::CONFIG;
|
||||||
|
use crate::config::PAYLOAD_LIMIT;
|
||||||
|
use crate::forward::*;
|
||||||
|
mod config;
|
||||||
|
mod errors;
|
||||||
|
mod forward;
|
||||||
|
mod sniff;
|
||||||
|
mod templates;
|
||||||
|
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
/* std::env::set_var("RUST_LOG", "actix_web=debug");
|
||||||
|
env_logger::init();*/
|
||||||
|
println!("ta ta tala ~ SNCF init");
|
||||||
|
println!("Checking configuration file...");
|
||||||
|
CONFIG.check_version();
|
||||||
|
println!(
|
||||||
|
"Now listening at {}:{}",
|
||||||
|
CONFIG.listening_address, CONFIG.listening_port
|
||||||
|
);
|
||||||
|
|
||||||
|
// starting the http server
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.data(Client::new())
|
||||||
|
.data(forward_url.clone())
|
||||||
|
//.wrap(
|
||||||
|
// CookieSession::signed(&[0; 32])
|
||||||
|
// .secure(true)
|
||||||
|
// .same_site(SameSite::Strict)
|
||||||
|
// .http_only(true)
|
||||||
|
// .name("pluriton_cookies")
|
||||||
|
// )
|
||||||
|
.service(Files::new("/assets/", "./templates/assets/").index_file("index.html"))
|
||||||
|
.route("/", web::get().to(index))
|
||||||
|
.route("/link/text", web::post().to(forward_register))
|
||||||
|
.default_service(web::route().to(forward))
|
||||||
|
.data(String::configure(|cfg| cfg.limit(PAYLOAD_LIMIT)))
|
||||||
|
.app_data(actix_web::web::Bytes::configure(|cfg| {
|
||||||
|
cfg.limit(PAYLOAD_LIMIT)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.bind((CONFIG.listening_address.as_str(), CONFIG.listening_port))?
|
||||||
|
.system_exit()
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug(text: &str) {
|
||||||
|
if CONFIG.debug_mode {
|
||||||
|
println!("{}", text);
|
||||||
|
}
|
||||||
|
}
|
101
build/deb-rust-pluriton-interface/old_src_try/src/sniff.rs
Normal file
101
build/deb-rust-pluriton-interface/old_src_try/src/sniff.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
use actix_web::web;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::debug;
|
||||||
|
|
||||||
|
// checks to be done on user requests
|
||||||
|
// 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),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevents the user from doing anything other than link sharing.
|
||||||
|
fn rq_form_update(body: &web::Bytes) -> bool {
|
||||||
|
let req = String::from_utf8_lossy(body);
|
||||||
|
|
||||||
|
// try to serialize the body.
|
||||||
|
// If the parsing fails, drop the request
|
||||||
|
let v: Value = serde_json::from_str(&req).unwrap_or_else(|e| {
|
||||||
|
eprintln!("check_request: failed to parse JSON: {}", e);
|
||||||
|
Value::Null
|
||||||
|
});
|
||||||
|
// if the type or isAnonymous is set (isn't null),
|
||||||
|
// drop the request.
|
||||||
|
// Also drop if v is null because of parsing fail.
|
||||||
|
v == Value::Null
|
||||||
|
|| v["keyValuePairs"]["isAnonymous"] != Value::Null
|
||||||
|
|| v["keyValuePairs"]["access"]["type"] != Value::Null
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks to be done on responses from the Nextcloud instance
|
||||||
|
// if it returns true, cancels the request
|
||||||
|
// NOTE: unused for now
|
||||||
|
/*pub fn check_response(_route: &str, _body: &web::Bytes) -> bool {
|
||||||
|
false
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// checks if a form has been created.
|
||||||
|
// if it's the case, sets some parameters.
|
||||||
|
// this part may need code quality improvements
|
||||||
|
// the body MUST come from the "create new form" route
|
||||||
|
// (this is checked upstream)
|
||||||
|
// returns the form UID and the request body
|
||||||
|
pub fn check_new_form(body: &web::Bytes) -> u64 {
|
||||||
|
let req = String::from_utf8_lossy(body);
|
||||||
|
|
||||||
|
// finds the form ID
|
||||||
|
let v: Value = serde_json::from_str(&req).unwrap_or_else(|e| {
|
||||||
|
eprintln!("check_new_form: failed to parse JSON: {}", e);
|
||||||
|
Value::Null
|
||||||
|
});
|
||||||
|
|
||||||
|
if v != Value::Null
|
||||||
|
&& v["ocs"].is_object()
|
||||||
|
&& v["ocs"]["data"].is_object()
|
||||||
|
&& v["ocs"]["data"]["id"] != Value::Null
|
||||||
|
&& v["ocs"]["data"]["isAnonymous"] == Value::Null
|
||||||
|
{
|
||||||
|
//getting form id
|
||||||
|
v["ocs"]["data"]["id"].as_u64().unwrap_or_else(|| {
|
||||||
|
eprintln!("check_new_form: failed to parse formid: {}", v);
|
||||||
|
0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
eprintln!("error: check_new_form: can't find formid: {}", v);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// those routes won't be redirected
|
||||||
|
const BLOCKED_ROUTES: &[&str] = &[
|
||||||
|
"/apps/settings",
|
||||||
|
"/login",
|
||||||
|
"/settings",
|
||||||
|
"/ocs/v",
|
||||||
|
"/remote.php",
|
||||||
|
"/core/templates/filepicker.html",
|
||||||
|
];
|
||||||
|
|
||||||
|
// ...except if they are in this list
|
||||||
|
const ALLOWED_ROUTES: &[&str] = &["/ocs/v2.php/apps/forms/", "/status.php"];
|
||||||
|
|
||||||
|
// checks if the accessed route is allowed for the user.
|
||||||
|
// if it returns true, redirects elsewhere
|
||||||
|
pub fn check_route(route: &str) -> bool {
|
||||||
|
debug(route);
|
||||||
|
|
||||||
|
for r in BLOCKED_ROUTES {
|
||||||
|
if route.starts_with(r) {
|
||||||
|
for s in ALLOWED_ROUTES {
|
||||||
|
if route.starts_with(s) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
use actix_web::HttpRequest;
|
||||||
|
use askama::Template;
|
||||||
|
use crate::config::Config;
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
pub struct TplIndex<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
}
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "error.html")]
|
||||||
|
pub struct TplError<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub error_msg: &'a str,
|
||||||
|
}
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "link.html")]
|
||||||
|
pub struct TplLink<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub config: &'a Config,
|
||||||
|
}
|
||||||
|
pub fn get_lang(req: &HttpRequest) -> String {
|
||||||
|
// getting language from client header
|
||||||
|
// taking the two first characters of the Accept-Language header,
|
||||||
|
// in lowercase, then parsing it.
|
||||||
|
// if it fails, returns "en"
|
||||||
|
if let Some(la) = req.uri().query() {
|
||||||
|
|
||||||
|
return la[5..].to_string();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if let Some(l) = req.headers().get("Accept-Language") {
|
||||||
|
if let Ok(s) = l.to_str() {
|
||||||
|
return s.to_lowercase()[..2].to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::from("en")
|
||||||
|
}
|
||||||
|
mod filters {
|
||||||
|
use crate::config::LOC;
|
||||||
|
pub fn tr(key: &str, lang: &str) -> askama::Result<String> {
|
||||||
|
let translation = LOC.get(key).ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: couldn't find the key {}", key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?;
|
||||||
|
Ok(String::from(
|
||||||
|
translation
|
||||||
|
.get(lang)
|
||||||
|
.unwrap_or(translation.get("en").ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: couldn't find the lang {} in key {}", lang, key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?)
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: lang {} in key {} is not str", lang, key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b1fd3fccaeb98678c6a36973bac2666def4b3da1
|
65
build/deb-rust-pluriton-interface/templates.rs
Normal file
65
build/deb-rust-pluriton-interface/templates.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use actix_web::HttpRequest;
|
||||||
|
use askama::Template;
|
||||||
|
use crate::config::Config;
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
pub struct TplIndex<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub csrf_token: &'a str,
|
||||||
|
pub sncf_admin_token: Option<String>,
|
||||||
|
}
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "error.html")]
|
||||||
|
pub struct TplError<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub error_msg: &'a str,
|
||||||
|
}
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "link.html")]
|
||||||
|
pub struct TplLink<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub admin_token: &'a str,
|
||||||
|
pub csrf_token: &'a str,
|
||||||
|
pub config: &'a Config,
|
||||||
|
}
|
||||||
|
pub fn get_lang(req: &HttpRequest) -> String {
|
||||||
|
// getting language from client header
|
||||||
|
// taking the two first characters of the Accept-Language header,
|
||||||
|
// in lowercase, then parsing it.
|
||||||
|
// if it fails, returns "en"
|
||||||
|
if let Some(la) = req.uri().query() {
|
||||||
|
|
||||||
|
return la[5..].to_string();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if let Some(l) = req.headers().get("Accept-Language") {
|
||||||
|
if let Ok(s) = l.to_str() {
|
||||||
|
return s.to_lowercase()[..2].to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::from("en")
|
||||||
|
}
|
||||||
|
mod filters {
|
||||||
|
use crate::config::LOC;
|
||||||
|
pub fn tr(key: &str, lang: &str) -> askama::Result<String> {
|
||||||
|
let translation = LOC.get(key).ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: couldn't find the key {}", key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?;
|
||||||
|
Ok(String::from(
|
||||||
|
translation
|
||||||
|
.get(lang)
|
||||||
|
.unwrap_or(translation.get("en").ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: couldn't find the lang {} in key {}", lang, key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?)
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: lang {} in key {} is not str", lang, key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
65
build/deb-rust-pluriton-interface/templates.rs.save
Normal file
65
build/deb-rust-pluriton-interface/templates.rs.save
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use actix_web::HttpRequest;
|
||||||
|
use askama::Template;
|
||||||
|
use crate::config::Config;
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
pub struct TplIndex<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub csrf_token: &'a str,
|
||||||
|
}
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "error.html")]
|
||||||
|
pub struct TplError<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub error_msg: &'a str,
|
||||||
|
}
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "link.html")]
|
||||||
|
pub struct TplLink<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub admin_token: &'a str,
|
||||||
|
pub config: &'a Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_lang(req: &HttpRequest) -> String {
|
||||||
|
// getting language from client header
|
||||||
|
// taking the two first characters of the Accept-Language header,
|
||||||
|
// in lowercase, then parsing it.
|
||||||
|
// if it fails, returns "en"
|
||||||
|
if let Some(l) = req.headers().get("Accept-Language") {
|
||||||
|
if let Ok(s) = l.to_str() {
|
||||||
|
return s.to_lowercase()[..2].to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(l) = req.headers().get("lang") {
|
||||||
|
if let Ok(s) = l.to_str() {
|
||||||
|
return s.to_lowercase()[..2].to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from("en")
|
||||||
|
}
|
||||||
|
mod filters {
|
||||||
|
use crate::config::LOC;
|
||||||
|
pub fn tr(key: &str, lang: &str) -> askama::Result<String> {
|
||||||
|
let translation = LOC.get(key).ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: couldn't find the key {}", key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?;
|
||||||
|
Ok(String::from(
|
||||||
|
translation
|
||||||
|
.get(lang)
|
||||||
|
.unwrap_or(translation.get("en").ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: couldn't find the lang {} in key {}", lang, key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?)
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
eprintln!("tr filter: lang {} in key {} is not str", lang, key);
|
||||||
|
askama::Error::from(std::fmt::Error)
|
||||||
|
})?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
build/deb-rust-pluriton-interface/white-background.png
Normal file
BIN
build/deb-rust-pluriton-interface/white-background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
|
@ -1,46 +0,0 @@
|
||||||
FROM tensorflow/tensorflow:1.12.0-gpu
|
|
||||||
|
|
||||||
COPY Prototyp /home/Prototyp
|
|
||||||
|
|
||||||
COPY requis.txt /home/requis.txt
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y wget libssl-dev openssl
|
|
||||||
#RUN wget https://www.python.org/ftp/python/3.5.3/Python-3.5.3.tgz
|
|
||||||
#RUN tar -xzvf Python-3.5.3.tgz
|
|
||||||
#RUN cd Python-3.5.3 && ./configure && make && make install
|
|
||||||
|
|
||||||
RUN python --version
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y virtualenv python-dev python-pip build-essential
|
|
||||||
|
|
||||||
#RUN python3.5 -m venv /home/venv
|
|
||||||
|
|
||||||
#ENV PATH="home/venv/bin:$PATH"
|
|
||||||
|
|
||||||
RUN python --version
|
|
||||||
|
|
||||||
#RUN pip3 install --upgrade pip
|
|
||||||
|
|
||||||
RUN pip install -r /home/requis.txt && python -m spacy download de
|
|
||||||
|
|
||||||
RUN pip install hickle==3.4.9 Twisted joblib
|
|
||||||
#nodejs npm
|
|
||||||
|
|
||||||
#RUN python -m pip install incremental
|
|
||||||
|
|
||||||
#RUN python -m pip install cffi
|
|
||||||
|
|
||||||
#RUN python -m pip install -r /home/requis.txt
|
|
||||||
|
|
||||||
#RUN python3 -m spacy download de
|
|
||||||
|
|
||||||
#RUN pip3 install pandas bs4
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y nodejs
|
|
||||||
|
|
||||||
#ENTRYPOINT ["tail"]
|
|
||||||
#CMD ["-f","/dev/null"]
|
|
||||||
|
|
||||||
CMD /bin/sh -c "cd /home/Prototyp && nodejs server.js"
|
|
44
build/tfgpu-pluriton/Dockerfile
Normal file
44
build/tfgpu-pluriton/Dockerfile
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
FROM tensorflow/tensorflow:2.3.0-gpu
|
||||||
|
|
||||||
|
# why 2.3 ? I looked it up on stack overflow
|
||||||
|
# https://stackoverflow.com/questions/50622525/which-tensorflow-and-cuda-version-combinations-are-compatible
|
||||||
|
# here is a nice list, which tf version is compatible with which cuda
|
||||||
|
# from the cmmand docker run --runtime=nvidia --rm nvidia/cuda:9.0-base nvidia-smi
|
||||||
|
# you get your installed cuda version running
|
||||||
|
|
||||||
|
RUN useradd -ms /bin/bash pluritonian
|
||||||
|
|
||||||
|
COPY Translations.txt /home/pluritonian/Translations.txt
|
||||||
|
|
||||||
|
COPY test_runwithgen.py /home/pluritonian/test_runwithgen.py
|
||||||
|
COPY test_runwithload.py /home/pluritonian/test_runwithload.py
|
||||||
|
COPY generateModels.py /home/pluritonian/generateModels.py
|
||||||
|
|
||||||
|
COPY req.js /home/pluritonian/req.js
|
||||||
|
|
||||||
|
COPY postcommand /home/pluritonian/postcommand
|
||||||
|
|
||||||
|
COPY updateDatabase.py /home/pluritonian/updateDatabase.py
|
||||||
|
|
||||||
|
COPY FASTsearch.py /home/pluritonian/FASTsearch.py
|
||||||
|
|
||||||
|
COPY fastapi_server.py /home/pluritonian/fastapi_server.py
|
||||||
|
|
||||||
|
#USER pluritonian
|
||||||
|
|
||||||
|
WORKDIR /home/pluritonian
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install nano
|
||||||
|
|
||||||
|
RUN pip install joblib scikit-learn hickle==3.4.9 fastapi uvicorn[standard]
|
||||||
|
|
||||||
|
RUN pip install idna==2.9 python-multipart==0.0.5
|
||||||
|
|
||||||
|
RUN python generateModels.py
|
||||||
|
|
||||||
|
# to let the container running:
|
||||||
|
|
||||||
|
CMD uvicorn --host 0.0.0.0 fastapi_server:app
|
||||||
|
|
||||||
|
#ENTRYPOINT ["tail"]
|
||||||
|
#CMD ["-f","/dev/null"]
|
|
@ -13,8 +13,9 @@ from sklearn.feature_extraction.text import CountVectorizer
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy as sc
|
import scipy as sc
|
||||||
|
|
||||||
import tensorflow as tf
|
import tensorflow.compat.v1 as tf
|
||||||
|
|
||||||
|
tf.compat.v1.disable_eager_execution()
|
||||||
|
|
||||||
import _pickle as cPickle
|
import _pickle as cPickle
|
||||||
|
|
2
build/tfgpu-pluriton/Translations.txt
Normal file
2
build/tfgpu-pluriton/Translations.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[['Ich gehe nach Hause, weil es regnet.'], ['Ich gehe nach Hause. Weil es regnet.']]
|
||||||
|
[['Es wäre sinnvoller, wenn die Maschinen aufhören zu regieren.'], ['Wenn die Maschinen aufhören zu regieren. Das ist sinnvoller.']]
|
37
build/tfgpu-pluriton/fastapi_server.py
Normal file
37
build/tfgpu-pluriton/fastapi_server.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from fastapi import FastAPI, Response, Request
|
||||||
|
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
from updateDatabase import *
|
||||||
|
|
||||||
|
pluriDBupdater = PluritonUpdater()
|
||||||
|
|
||||||
|
pluriDBupdater.loadModels()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/datext", response_class=JSONResponse)
|
||||||
|
async def root(data: Request):
|
||||||
|
|
||||||
|
text_bytes = await data.body()
|
||||||
|
|
||||||
|
text = str(text_bytes)
|
||||||
|
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
einfach, schwer = pluriDBupdater.searchNearest2Translate(text)
|
||||||
|
|
||||||
|
einfachstr = ''
|
||||||
|
schwerstr = ''
|
||||||
|
|
||||||
|
for word in einfach:
|
||||||
|
einfachstr += word + ' '
|
||||||
|
for word in schwer:
|
||||||
|
schwerstr += word + ' '
|
||||||
|
|
||||||
|
daresponse = einfachstr + '?&?&' + schwerstr
|
||||||
|
|
||||||
|
|
||||||
|
return JSONResponse(content=daresponse)
|
||||||
|
|
18
build/tfgpu-pluriton/generateModels.py
Normal file
18
build/tfgpu-pluriton/generateModels.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from updateDatabase import *
|
||||||
|
|
||||||
|
print('Init Pluriton..')
|
||||||
|
pluriDBupdater = PluritonUpdater()
|
||||||
|
print('done')
|
||||||
|
print('creaing hklDB from the Translations..')
|
||||||
|
pluriDBupdater.create_hklDB_from_csv('Translations.txt')
|
||||||
|
print('done')
|
||||||
|
print('generating BOW models..')
|
||||||
|
|
||||||
|
pluriDBupdater.load_DB_into_FASTsearch_and_generate_BOW()
|
||||||
|
print('done')
|
||||||
|
#pluriDBupdater.loadModels()
|
||||||
|
|
||||||
|
#einfach, schwer = pluriDBupdater.searchNearest2Translate('Die Maschinen besser')
|
||||||
|
|
||||||
|
#print('Schwer', schwer)
|
||||||
|
|
1
build/tfgpu-pluriton/postcommand
Normal file
1
build/tfgpu-pluriton/postcommand
Normal file
|
@ -0,0 +1 @@
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d @req.json http://localhost:8000/datext
|
3
build/tfgpu-pluriton/req.js
Normal file
3
build/tfgpu-pluriton/req.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"Text": "Die Maschinen werrden immer besser"
|
||||||
|
}
|
16
build/tfgpu-pluriton/test_runwithgen.py
Normal file
16
build/tfgpu-pluriton/test_runwithgen.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from updateDatabase import *
|
||||||
|
|
||||||
|
|
||||||
|
pluriDBupdater = PluritonUpdater()
|
||||||
|
|
||||||
|
|
||||||
|
pluriDBupdater.create_hklDB_from_csv('Translations.txt')
|
||||||
|
|
||||||
|
pluriDBupdater.load_DB_into_FASTsearch_and_generate_BOW()
|
||||||
|
|
||||||
|
#pluriDBupdater.loadModels()
|
||||||
|
|
||||||
|
einfach, schwer = pluriDBupdater.searchNearest2Translate('Die Maschinen besser')
|
||||||
|
|
||||||
|
print('Schwer', schwer)
|
||||||
|
|
11
build/tfgpu-pluriton/test_runwithload.py
Normal file
11
build/tfgpu-pluriton/test_runwithload.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from updateDatabase import *
|
||||||
|
|
||||||
|
|
||||||
|
pluriDBupdater = PluritonUpdater()
|
||||||
|
|
||||||
|
pluriDBupdater.loadModels()
|
||||||
|
|
||||||
|
einfach, schwer = pluriDBupdater.searchNearest2Translate('Die Maschinen besser')
|
||||||
|
|
||||||
|
print('Schwer', schwer)
|
||||||
|
|
126
build/tfgpu-pluriton/updateDatabase.py
Normal file
126
build/tfgpu-pluriton/updateDatabase.py
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import hickle as hkl
|
||||||
|
|
||||||
|
import FASTsearch
|
||||||
|
|
||||||
|
|
||||||
|
class PluritonUpdater(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.ole = 1
|
||||||
|
|
||||||
|
# Input: csv file with the form ['eine', 'schwere', 'Sprache'] , ['in', 'leicht'] for each line
|
||||||
|
# Output: hkl dump of array in form [[['eine', 'schwere', 'Sprache'],['in', 'leicht']],[..]]
|
||||||
|
|
||||||
|
def create_hklDB_from_csv(self, csvDbDir):
|
||||||
|
|
||||||
|
with open(csvDbDir) as lines:
|
||||||
|
|
||||||
|
TranslationsDB_All = []
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
|
||||||
|
TranslationsDB_All.append(list(eval(line)))
|
||||||
|
|
||||||
|
|
||||||
|
#print(ShortsDB_All)
|
||||||
|
|
||||||
|
#print(ShortsDB_All[0][0])
|
||||||
|
|
||||||
|
|
||||||
|
hkldbTranslations1 = []
|
||||||
|
hkldbTranslations2 = []
|
||||||
|
counter = 0
|
||||||
|
for n in range(len(TranslationsDB_All)):
|
||||||
|
|
||||||
|
counter += 1
|
||||||
|
#if counter % 1000 == 0:
|
||||||
|
#print(counter)
|
||||||
|
|
||||||
|
hkldbTranslations1.append([TranslationsDB_All[n][0][0]])
|
||||||
|
hkldbTranslations2.append([TranslationsDB_All[n][1][0]])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#print(hkldbTranslations1, TranslationsDB_All)
|
||||||
|
#print('creating the hkl dump of TranslationsDBAll')
|
||||||
|
hkl.dump(TranslationsDB_All, 'hkldbTranslations_All.hkl', mode='w', compression='gzip')
|
||||||
|
#print('done..')
|
||||||
|
|
||||||
|
#print('Creating the hkl dump of TranslationsDB')
|
||||||
|
hkl.dump(hkldbTranslations1, 'hkldbTranslations1.hkl', mode='w', compression='gzip')
|
||||||
|
hkl.dump(hkldbTranslations2, 'hkldbTranslations2.hkl', mode='w', compression='gzip')
|
||||||
|
#print('done..')
|
||||||
|
|
||||||
|
return 'done'
|
||||||
|
|
||||||
|
|
||||||
|
def load_DB_into_FASTsearch_and_generate_BOW(self):
|
||||||
|
|
||||||
|
print('loading the hkldbTranslations1...')
|
||||||
|
self.hkldbTranslations1 = hkl.load('hkldbTranslations1.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('loading the hkldbTranslations2...')
|
||||||
|
self.hkldbTranslations2 = hkl.load('hkldbTranslations2.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('loading hkldbTranslations 1 into FASTsearch..')
|
||||||
|
self.fsearch1 = FASTsearch.FASTsearch('hkldbTranslations1.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('loading hkldbTranslations 2 into FASTsearch..')
|
||||||
|
self.fsearch2 = FASTsearch.FASTsearch('hkldbTranslations2.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('generating BoW Model 1..')
|
||||||
|
self.fsearch1.Gen_BoW_Model(50000, "word", punctuation = False)
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('generating BoW Model 2..')
|
||||||
|
self.fsearch2.Gen_BoW_Model(50000, "word", punctuation = False)
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
return 'done'
|
||||||
|
|
||||||
|
def loadModels(self):
|
||||||
|
|
||||||
|
print('loading the hkldbTranslations1...')
|
||||||
|
self.hkldbTranslations1 = hkl.load('hkldbTranslations1.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('loading the hkldbTranslations2...')
|
||||||
|
self.hkldbTranslations2 = hkl.load('hkldbTranslations2.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('loading hkldbTranslations 1 into FASTsearch..')
|
||||||
|
self.fsearch1 = FASTsearch.FASTsearch('hkldbTranslations1.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('loading hkldbTranslations 2 into FASTsearch..')
|
||||||
|
self.fsearch2 = FASTsearch.FASTsearch('hkldbTranslations2.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('loading the bow model 1')
|
||||||
|
self.fsearch1.Load_BoW_Model('bagofwordshkldbTranslations1.pkl', 'DataBaseOneZeroshkldbTranslations1.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
print('loading the bow model 2')
|
||||||
|
self.fsearch2.Load_BoW_Model('bagofwordshkldbTranslations2.pkl', 'DataBaseOneZeroshkldbTranslations2.hkl')
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
|
||||||
|
return 'done'
|
||||||
|
|
||||||
|
def searchNearest2Translate(self, text):
|
||||||
|
|
||||||
|
|
||||||
|
bestmatches2, matchindex2 = self.fsearch1.search_with_highest_multiplikation_Output(text, 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DifficultText = self.hkldbTranslations1[matchindex2[0]][0].split()
|
||||||
|
LeichterText = self.hkldbTranslations2[matchindex2[0]][0].split()
|
||||||
|
|
||||||
|
return DifficultText, LeichterText
|
||||||
|
|
|
@ -1,12 +1,33 @@
|
||||||
version: '2.3'
|
version: '3.1'
|
||||||
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
prototype:
|
pluriton:
|
||||||
|
|
||||||
build: ../build/tf-gpu-Prototyp
|
build: ../build/tfgpu-pluriton
|
||||||
container_name: prototype
|
container_name: pluriton_python_app
|
||||||
restart: always
|
restart: always
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- capabilities: [gpu]
|
||||||
|
networks:
|
||||||
|
- pluritonNet
|
||||||
|
|
||||||
|
deb-rust-pluriton-interface:
|
||||||
|
build: ../build/deb-rust-pluriton-interface
|
||||||
|
container_name: deb-rust-pluriton-interface
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- RUST_BACKTRACE=full
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:7000:7000"
|
- "127.0.0.1:1020:7050"
|
||||||
|
networks:
|
||||||
|
- pluritonNet
|
||||||
|
|
||||||
|
|
||||||
|
networks:
|
||||||
|
pluritonNet:
|
||||||
|
driver: bridge
|
||||||
|
|
0
in
Normal file
0
in
Normal file
0
out.html
Normal file
0
out.html
Normal file
Loading…
Reference in a new issue