Added a user system with no proper user validation but working authorisation. #1

Merged
Roflin merged 6 commits from user-system into main 2022-04-23 13:17:32 +02:00
4 changed files with 281 additions and 251 deletions
Showing only changes of commit 81e65b1619 - Show all commits

View File

@ -1,36 +1,36 @@
use validator::ValidateArgs;
use crate::AppConfig;
use rocket::request::Outcome;
use jsonwebtoken::decode;
use crate::schema;
use crate::schema::DbConn;
use crate::AppConfig;
use chrono::Utc;
use jsonwebtoken::decode;
use jsonwebtoken::encode;
use jsonwebtoken::DecodingKey;
use jsonwebtoken::Validation;
use rocket::State;
use chrono::Utc;
use jsonwebtoken::{Header, EncodingKey};
use crate::schema;
use std::borrow::Cow;
use jsonwebtoken::encode;
use rocket::serde::json::{Json, json, Value};
use jsonwebtoken::{EncodingKey, Header};
use rocket::http::Status;
use rocket::request::{self, Request, FromRequest};
use rocket::outcome::Outcome::{Success, Failure};
use serde::{Serialize, Deserialize};
use rocket::outcome::Outcome::{Failure, Success};
use rocket::request::Outcome;
use rocket::request::{self, FromRequest, Request};
use rocket::serde::json::{json, Json, Value};
use rocket::State;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use validator::ValidateArgs;
pub struct Referer(String);
Review

You can probably use a Result<Value, Status> for most endpoints and avoid a custom enum. I also recommend using json::Value qualified like that because Value by itself is not very descriptive.

You can probably use a `Result<Value, Status>` for most endpoints and avoid a custom enum. I also recommend using `json::Value` qualified like that because `Value` by itself is not very descriptive.
Review

True, but in the future we might want to return a status on a non error condition, or return a Redirect, I understand it is a bit overkill now, but in a previous iteration I was also returning Redirects and then this becomes a nice solution imho.

True, but in the future we might want to return a status on a non error condition, or return a Redirect, I understand it is a bit overkill now, but in a previous iteration I was also returning Redirects and then this becomes a nice solution imho.
#[derive(Debug)]
pub enum ReferrerError {
Missing,
MoreThanOne
MoreThanOne,
}
#[derive(Debug, Responder)]
pub enum ApiResponseVariant {
Status(Status),
// Redirect(Redirect),
// Redirect(Redirect),
Value(Value),
// Flash(Flash<Redirect>)
// Flash(Flash<Redirect>)
}
#[rocket::async_trait]
@ -38,7 +38,7 @@ impl<'r> FromRequest<'r> for Referer {
type Error = ReferrerError;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let referers : Vec<_> = req.headers().get("Referer").collect();
let referers: Vec<_> = req.headers().get("Referer").collect();
match referers.len() {
0 => Failure((Status::BadRequest, ReferrerError::Missing)),
1 => Success(Referer(referers[0].to_string())),
@ -51,9 +51,9 @@ impl<'r> FromRequest<'r> for Referer {
struct ApiResponse {
result: Cow<'static, str>,
#[serde(skip_serializing_if = "Option::is_none")]
message: Option::<Cow<'static, str>>,
message: Option<Cow<'static, str>>,
#[serde(skip_serializing_if = "Option::is_none")]
jwt: Option::<Cow<'static, str>>
jwt: Option<Cow<'static, str>>,
}
impl ApiResponse {
@ -63,14 +63,14 @@ impl ApiResponse {
const SUCCES: Self = Self {
result: Self::SUCCES_RESULT,
message: None,
jwt: None
jwt: None,
};
fn error(message: String) -> Self {
Self {
Self {
result: Self::FAILURE_RESULT,
message: Some(Cow::Owned(message)),
jwt: None
jwt: None,
}
}
@ -78,7 +78,7 @@ impl ApiResponse {
Self {
result: Self::SUCCES_RESULT,
message: None,
jwt: Some(Cow::Owned(jwt))
jwt: Some(Cow::Owned(jwt)),
}
}
}
@ -98,23 +98,40 @@ impl<'r> FromRequest<'r> for schema::User {
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let header = match req.headers().get_one(AUTH_HEADER) {
Some(header) => header,
None => return Outcome::Failure((Status::BadRequest, ApiError::RequestError("No authorization header found".to_string())))
None => {
return Outcome::Failure((
Status::BadRequest,
ApiError::RequestError("No authorization header found".to_string()),
))
}
};
if !header.starts_with(BEARER) {
return Outcome::Failure((Status::BadRequest, ApiError::RequestError("Invalid Authorization header.".to_string())))
return Outcome::Failure((
Status::BadRequest,
ApiError::RequestError("Invalid Authorization header.".to_string()),
));
};
let app_config = req.guard::<&State<AppConfig>>().await.unwrap().inner();
Roflin marked this conversation as resolved
Review

I think you can use a Request Guard (see https://api.rocket.rs/v0.5-rc/rocket/request/trait.FromRequest.html) to authenticate the user and role: For example, endpoints that require admin privileges could accept a non-optional Admin struct containing a user id and the request guard that generates it would only return Success if the user is logged and has the admin role.

I think you can use a Request Guard (see https://api.rocket.rs/v0.5-rc/rocket/request/trait.FromRequest.html) to authenticate the user and role: For example, endpoints that require admin privileges could accept a non-optional `Admin` struct containing a user id and the request guard that generates it would only return `Success` if the user is logged and has the admin role.
Review

See also the examples under the header "Request-Local State" in the above link.

See also the examples under the header "Request-Local State" in the above link.
Review

Reading more carefully I see you're already doing this, just that you're accepting an Option<User> and then checking it's not None while you could accept a User and be sure.

Reading more carefully I see you're already doing this, just that you're accepting an `Option<User>` and then checking it's not `None` while you could accept a `User` and be sure.
let jwt = header.trim_start_matches(BEARER).to_owned();
let token = match decode::<Claims>(&jwt, &DecodingKey::from_secret(app_config.jwt_secret.as_bytes()), &Validation::default()) {
let token = match decode::<Claims>(
&jwt,
&DecodingKey::from_secret(app_config.jwt_secret.as_bytes()),
&Validation::default(),
) {
Ok(token) => token,
Err(error) => return Outcome::Failure((Status::BadRequest, ApiError::RequestError(error.to_string())))
Err(error) => {
return Outcome::Failure((
Status::BadRequest,
ApiError::RequestError(error.to_string()),
))
}
};
let id = token.claims.uid;
let conn = req.guard::<DbConn>().await.unwrap();
return Outcome::Success(schema::get_user(conn, id).await)
return Outcome::Success(schema::get_user(conn, id).await);
}
}
@ -123,38 +140,45 @@ pub async fn gamenights(conn: DbConn, user: Option<schema::User>) -> ApiResponse
if user.is_some() {
let gamenights = schema::get_all_gamenights(conn).await;
ApiResponseVariant::Value(json!(gamenights))
}
else {
} else {
ApiResponseVariant::Status(Status::Unauthorized)
}
}
#[post("/gamenight", format = "application/json", data = "<gamenight_json>")]
pub async fn gamenight_post_json(conn: DbConn, user: Option<schema::User>, gamenight_json: Json<schema::GameNightNoId>) -> ApiResponseVariant {
pub async fn gamenight_post_json(
conn: DbConn,
user: Option<schema::User>,
gamenight_json: Json<schema::GameNightNoId>,

Value is not an error response

Value is not an error response

We'll it is, it's an application level error, so it's a valid request and you will get a valid http response with an "Failure" result. So that's why it returns an actual Json value

We'll it is, it's an application level error, so it's a valid request and you will get a valid http response with an "Failure" result. So that's why it returns an actual Json value
) -> ApiResponseVariant {
if user.is_some() {
schema::insert_gamenight(conn, gamenight_json.into_inner()).await;
ApiResponseVariant::Value(json!(ApiResponse::SUCCES))
}
else {
} else {
ApiResponseVariant::Status(Status::Unauthorized)

Value is not an error response

Value is not an error response
}
}
#[post("/register", format = "application/json", data = "<register_json>")]
pub async fn register_post_json(conn: DbConn, register_json: Json<schema::Register>) -> ApiResponseVariant {
pub async fn register_post_json(
conn: DbConn,
register_json: Json<schema::Register>,
) -> ApiResponseVariant {
let register = register_json.into_inner();
let register_clone = register.clone();
match conn.run(move |c| {
register_clone.validate_args((c,c))
}).await {
match conn
.run(move |c| register_clone.validate_args((c, c)))
.await
{
Ok(()) => (),
Err(error) => return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string())))
Err(error) => {
return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string())))
}
}
match schema::insert_user(conn, register).await {
Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)),
Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string())))
Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))),
}
}
@ -166,24 +190,37 @@ struct Claims {
}
#[post("/login", format = "application/json", data = "<login_json>")]
pub async fn login_post_json(conn: DbConn, config: &State<AppConfig>, login_json: Json<schema::Login>) -> ApiResponseVariant {
pub async fn login_post_json(
conn: DbConn,
config: &State<AppConfig>,
login_json: Json<schema::Login>,
) -> ApiResponseVariant {
match schema::login(conn, login_json.into_inner()).await {
Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))),
Ok(login_result) => {
if !login_result.result {
return ApiResponseVariant::Value(json!(ApiResponse::error(String::from(
"username and password didn't match"
))));
}
let my_claims = Claims {
exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(),
uid: login_result.id.unwrap(),
role: login_result.role.unwrap()
role: login_result.role.unwrap(),
};
let secret = &config.inner().jwt_secret;
match encode(&Header::default(), &my_claims, &EncodingKey::from_secret(secret.as_bytes()))
{
match encode(
&Header::default(),
&my_claims,
&EncodingKey::from_secret(secret.as_bytes()),
) {
Ok(token) => ApiResponseVariant::Value(json!(ApiResponse::login_response(token))),
Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string())))
Err(error) => {
ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string())))
}
}
}
}
}
}

View File

@ -1,8 +1,17 @@
#[macro_use] extern crate rocket;
#[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate diesel;
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate diesel_migrations;
#[macro_use]
extern crate diesel;
use rocket::{fairing::AdHoc, figment::{Figment, providers::{Serialized, Toml, Env, Format}, Profile}};
use rocket::{
fairing::AdHoc,
figment::{
providers::{Env, Format, Serialized, Toml},
Figment, Profile,
},
};
use rocket_dyn_templates::Template;
use serde::{Deserialize, Serialize};
@ -12,12 +21,14 @@ mod site;
#[derive(Debug, Deserialize, Serialize)]
pub struct AppConfig {
jwt_secret: String
jwt_secret: String,
}
impl Default for AppConfig {
fn default() -> AppConfig {
AppConfig { jwt_secret: String::from("secret") }
AppConfig {
jwt_secret: String::from("secret"),
}
}
}
@ -34,13 +45,17 @@ fn rocket() -> _ {
.attach(Template::fairing())
.attach(AdHoc::on_ignite("Run Migrations", schema::run_migrations))
.attach(AdHoc::config::<AppConfig>())
.mount("/", routes![site::index, site::gamenights,
site::add_game_night, site::register])
.mount("/api", routes![
api::gamenights, api::gamenight_post_json,
api::register_post_json,
api::login_post_json
]);
.attach(site::make_cors())
.mount("/", routes![site::index, site::files])
.mount(
"/api",
routes![
api::gamenights,
api::gamenight_post_json,
api::register_post_json,
api::login_post_json
],
);
rocket
}

View File

@ -1,24 +1,21 @@
use diesel::dsl::count;
use std::ops::Deref;
use argon2::PasswordVerifier;
use argon2::PasswordHash;
use diesel_derive_enum::DbEnum;
use crate::diesel::QueryDsl;
use crate::diesel::BoolExpressionMethods;
use crate::diesel::ExpressionMethods;
use crate::diesel::Connection;
use rocket_sync_db_pools::database;
use serde::{Serialize, Deserialize};
use rocket::{Rocket, Build};
use diesel::RunQueryDsl;
use argon2::{
password_hash::{
rand_core::OsRng,
PasswordHasher
},
Argon2
};
use crate::diesel::ExpressionMethods;
use crate::diesel::QueryDsl;
use argon2::password_hash::SaltString;
use argon2::PasswordHash;
use argon2::PasswordVerifier;
use argon2::{
password_hash::{rand_core::OsRng, PasswordHasher},
Argon2,
};
use diesel::dsl::count;
use diesel::RunQueryDsl;
use diesel_derive_enum::DbEnum;
use rocket::{Build, Rocket};
use rocket_sync_db_pools::database;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use validator::{Validate, ValidationError};
#[database("gamenight_database")]
@ -66,30 +63,25 @@ table! {
}
}
allow_tables_to_appear_in_same_query!(
gamenight,
known_games,
);
allow_tables_to_appear_in_same_query!(gamenight, known_games,);
pub enum DatabaseError {
Hash(password_hash::Error),
Query(String)
Query(String),
}
impl std::fmt::Display for DatabaseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match self {
DatabaseError::Hash(err) => write!(f, "{}", err),
DatabaseError::Query(err) => write!(f, "{}", err)
}
DatabaseError::Query(err) => write!(f, "{}", err),
}
}
}
pub async fn get_all_gamenights(conn: DbConn) -> Vec::<GameNight> {
conn.run(|c| {
gamenight::table.load::<GameNight>(c).unwrap()
}).await
pub async fn get_all_gamenights(conn: DbConn) -> Vec<GameNight> {
conn.run(|c| gamenight::table.load::<GameNight>(c).unwrap())
.await
}
pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNightNoId) -> () {
@ -98,7 +90,8 @@ pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNightNoId) -> ()
.values(new_gamenight)
.execute(c)
.unwrap()
}).await;
})
.await;
}
pub async fn insert_user(conn: DbConn, new_user: Register) -> Result<(), DatabaseError> {
@ -107,29 +100,41 @@ pub async fn insert_user(conn: DbConn, new_user: Register) -> Result<(), Databas
let argon2 = Argon2::default();
let password_hash = match argon2.hash_password(new_user.password.as_bytes(), &salt) {
Ok(hash) => hash.to_string(),
Err(error) => return Err(DatabaseError::Hash(error))
Ok(hash) => hash.to_string(),
Err(error) => return Err(DatabaseError::Hash(error)),
};
match conn.run(move |c| {
c.transaction(|| {
diesel::insert_into(user::table)
.values((user::username.eq(&new_user.username), user::email.eq(&new_user.email), user::role.eq(Role::User)))
.execute(c)?;
let ids : Vec::<i32> = match user::table
.filter(user::username.eq(&new_user.username).and(user::email.eq(&new_user.email)))
.select(user::id)
.get_results(c) {
match conn
.run(move |c| {
c.transaction(|| {
diesel::insert_into(user::table)
.values((
user::username.eq(&new_user.username),
user::email.eq(&new_user.email),
user::role.eq(Role::User),
))
.execute(c)?;
let ids: Vec<i32> = match user::table
.filter(
user::username
.eq(&new_user.username)
.and(user::email.eq(&new_user.email)),
)
.select(user::id)
Roflin marked this conversation as resolved
Review

called user_id now

called `user_id` now
.get_results(c)
{
Ok(id) => id,
Err(e) => return Err(e)
Err(e) => return Err(e),
};
diesel::insert_into(pwd::table)
.values((pwd::id.eq(ids[0]), pwd::password.eq(&password_hash)))
.execute(c)
diesel::insert_into(pwd::table)
.values((pwd::id.eq(ids[0]), pwd::password.eq(&password_hash)))
Roflin marked this conversation as resolved Outdated
Outdated
Review

Wel mooi om de grote expression waar je hier op matcht even een naam te geven zodat de match leesbaar blijft.

Wel mooi om de grote expression waar je hier op matcht even een naam te geven zodat de match leesbaar blijft.
.execute(c)
})
})
}).await {
.await
{
Err(e) => Err(DatabaseError::Query(e.to_string())),
_ => Ok(()),
}
@ -137,83 +142,94 @@ pub async fn insert_user(conn: DbConn, new_user: Register) -> Result<(), Databas
pub async fn login(conn: DbConn, login: Login) -> Result<LoginResult, DatabaseError> {
conn.run(move |c| -> Result<LoginResult, DatabaseError> {
let id : i32 = match user::table
let id: i32 = match user::table
.filter(user::username.eq(&login.username))
.or_filter(user::email.eq(&login.username))
.select(user::id)
.get_results(c) {
Ok(id) => id[0],
Err(error) => return Err(DatabaseError::Query(error.to_string()))
};
let pwd : String = match pwd::table
.get_results(c)
{
Ok(id) => id[0],
Err(error) => return Err(DatabaseError::Query(error.to_string())),
Roflin marked this conversation as resolved Outdated

generates a panic if the user does not exist

generates a panic if the user does not exist
};
let pwd: String = match pwd::table
.filter(pwd::id.eq(id))
.select(pwd::password)
.get_results::<String>(c) {
Ok(pwd) => pwd[0].clone(),
Err(error) => return Err(DatabaseError::Query(error.to_string()))
};
.get_results::<String>(c)
{
Ok(pwd) => pwd[0].clone(),
Err(error) => return Err(DatabaseError::Query(error.to_string())),
};
let parsed_hash = match PasswordHash::new(&pwd) {
Ok(hash) => hash,
Err(error) => return Err(DatabaseError::Hash(error))
Err(error) => return Err(DatabaseError::Hash(error)),
};
if Argon2::default().verify_password(&login.password.as_bytes(), &parsed_hash).is_ok() {
let role : Role = match user::table
if Argon2::default()
.verify_password(&login.password.as_bytes(), &parsed_hash)
.is_ok()
{
let role: Role = match user::table
.filter(user::id.eq(id))
.select(user::role)
.get_results::<Role>(c) {
Ok(role) => role[0].clone(),
Err(error) => return Err(DatabaseError::Query(error.to_string()))
};
.get_results::<Role>(c)
{
Ok(role) => role[0].clone(),
Err(error) => return Err(DatabaseError::Query(error.to_string())),
};
Ok(LoginResult {
result: true,
id: Some(id),
role: Some(role)
role: Some(role),
})
}
else {
} else {
Ok(LoginResult {
result: false,
id: None,
role: None,
})
}
}).await
})
.await
}
pub async fn get_user(conn: DbConn, id: i32) -> User {
conn.run(move |c| {
user::table
.filter(user::id.eq(id))
.first(c)
.unwrap()
}).await
conn.run(move |c| user::table.filter(user::id.eq(id)).first(c).unwrap())
.await
}
pub fn unique_username(username: &String, conn: &diesel::SqliteConnection) -> Result<(), ValidationError> {
pub fn unique_username(
username: &String,
conn: &diesel::SqliteConnection,
) -> Result<(), ValidationError> {
match user::table
.select(count(user::username))
.filter(user::username.eq(username))
.execute(conn) {
Ok(0) => Ok(()),
Ok(_) => Err(ValidationError::new("User already exists")),
Err(_) => Err(ValidationError::new("Database error while validating user"))
}
.execute(conn)
{
Ok(0) => Ok(()),
Ok(_) => Err(ValidationError::new("User already exists")),
Err(_) => Err(ValidationError::new("Database error while validating user")),
}
}
pub fn unique_email(email: &String, conn: &diesel::SqliteConnection) -> Result<(), ValidationError> {
pub fn unique_email(
email: &String,
conn: &diesel::SqliteConnection,
) -> Result<(), ValidationError> {
match user::table
.select(count(user::email))
.filter(user::email.eq(email))
.execute(conn) {
Ok(0) => Ok(()),
Ok(_) => Err(ValidationError::new("email already exists")),
Err(_) => Err(ValidationError::new("Database error while validating email"))
}
.execute(conn)
{
Ok(0) => Ok(()),
Ok(_) => Err(ValidationError::new("email already exists")),
Err(_) => Err(ValidationError::new(
"Database error while validating email",
)),
}
}
pub async fn run_migrations(rocket: Rocket<Build>) -> Rocket<Build> {
@ -223,7 +239,9 @@ pub async fn run_migrations(rocket: Rocket<Build>) -> Rocket<Build> {
embed_migrations!();
let conn = DbConn::get_one(&rocket).await.expect("database connection");
conn.run(|c| embedded_migrations::run(c)).await.expect("can run migrations");
conn.run(|c| embedded_migrations::run(c))
.await
.expect("can run migrations");
rocket
}
@ -235,45 +253,51 @@ pub enum Role {
}
#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)]
#[table_name="user"]
#[table_name = "user"]
pub struct User {
pub id: i32,
pub username: String,
pub email: String,
pub role: Role
pub role: Role,
}
#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)]
#[table_name="known_games"]
#[table_name = "known_games"]
pub struct GameNoId {
pub game : String,
pub game: String,
}
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
pub struct Game {
pub id: i32,
pub game : String,
pub game: String,
}
#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)]
#[table_name="gamenight"]
#[table_name = "gamenight"]
pub struct GameNightNoId {
pub game : String,
pub datetime : String,
pub game: String,
pub datetime: String,
}
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
pub struct GameNight {
pub id: i32,
pub game : String,
pub datetime : String,
pub game: String,
pub datetime: String,
}
#[derive(Serialize, Deserialize, Debug, Validate, Clone)]
pub struct Register {
#[validate(length(min = 1), custom( function = "unique_username", arg = "&'v_a diesel::SqliteConnection"))]
#[validate(
length(min = 1),
custom(function = "unique_username", arg = "&'v_a diesel::SqliteConnection")
)]
pub username: String,
#[validate(email, custom( function = "unique_email", arg = "&'v_a diesel::SqliteConnection"))]
#[validate(
email,
custom(function = "unique_email", arg = "&'v_a diesel::SqliteConnection")
)]
pub email: String,
#[validate(length(min = 10), must_match = "password_repeat")]
pub password: String,
@ -283,12 +307,12 @@ pub struct Register {
#[derive(Serialize, Deserialize, Debug)]
pub struct Login {
pub username: String,
pub password: String
pub password: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LoginResult {
pub result: bool,
pub id: Option<i32>,
pub role: Option<Role>
pub role: Option<Role>,
}

View File

@ -1,88 +1,42 @@
use std::borrow::Cow;
use serde::{Serialize, Deserialize};
use rocket_dyn_templates::Template;
use rocket::response::{Redirect};
use rocket::request::{FlashMessage};
use crate::schema;
use rocket::fs::NamedFile;
use rocket::http::Method;
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors, CorsOptions};
Roflin marked this conversation as resolved Outdated

| ^^^^^^^^^^^ use of undeclared crate or module rocket_cors

| ^^^^^^^^^^^ use of undeclared crate or module `rocket_cors`
use std::io;
use std::path::{Path, PathBuf};
#[derive(Serialize, Deserialize, Debug)]
struct FlashData {
has_data: bool,
kind: Cow<'static, str>,
message: Cow<'static, str>
pub fn make_cors() -> Cors {
let allowed_origins = AllowedOrigins::some_exact(&[
// 4.
//CHANGE THESE TO MATCH YOUR PORTS
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:8000",
"http://0.0.0.0:8000",
]);
CorsOptions {
// 5.
allowed_origins,
allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), // 1.
allowed_headers: AllowedHeaders::some(&[
"Authorization",
"Accept",
"Access-Control-Allow-Origin", // 6.
]),
allow_credentials: true,
..Default::default()
}
.to_cors()
.expect("error while building CORS")
}
impl FlashData {
const EMPTY: Self = Self { has_data: false, message: Cow::Borrowed(""), kind: Cow::Borrowed("") };
}
#[derive(Serialize, Deserialize, Debug)]
struct GameNightsData {
gamenights: Vec::<schema::GameNight>,
flash: FlashData
}
#[get("/gamenights")]
pub async fn gamenights(conn: schema::DbConn) -> Template {
let gamenights = schema::get_all_gamenights(conn).await;
let data = GameNightsData {
gamenights: gamenights,
flash: FlashData::EMPTY
};
Template::render("gamenights", &data)
#[get("/<file..>")]
pub async fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("../frontend/build/").join(file))
.await
.ok()
}
#[get("/")]
pub async fn index() -> Redirect {
Redirect::to(uri!(gamenights))
pub async fn index() -> io::Result<NamedFile> {
NamedFile::open("../frontend/build/index.html").await
}
#[derive(Serialize, Deserialize, Debug)]
struct GameNightAddData {
post_url: String,
flash : FlashData
}
#[get("/gamenight/add")]
pub async fn add_game_night(flash: Option<FlashMessage<'_>>) -> Template {
let flash_data = match flash {
None => FlashData::EMPTY,
Some(flash) => FlashData {
has_data: true,
message: Cow::Owned(flash.message().to_string()),
kind: Cow::Owned(flash.kind().to_string())
}
};
let data = GameNightAddData {
post_url: "/api/gamenight".to_string(),
flash: flash_data
};
Template::render("gamenight_add", &data)
}
#[derive(Serialize, Deserialize, Debug)]
struct RegisterData {
flash : FlashData
}
#[get("/register")]
pub async fn register(flash: Option<FlashMessage<'_>>) -> Template {
let flash_data = match flash {
None => FlashData::EMPTY,
Some(flash) => FlashData {
has_data: true,
message: Cow::Owned(flash.message().to_string()),
kind: Cow::Owned(flash.kind().to_string())
}
};
let data = RegisterData {
flash: flash_data
};
Template::render("register", &data)
}