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
9 changed files with 104 additions and 70 deletions
Showing only changes of commit 5f73d556c6 - Show all commits

View File

@ -1,4 +1,4 @@
-- This file should undo anything in `up.sql` -- This file should undo anything in `up.sql`
drop table user; drop table pwd;
drop table pwd; drop table user;

View File

@ -6,6 +6,7 @@ CREATE TABLE user (
); );
CREATE TABLE pwd ( CREATE TABLE pwd (
id INTEGER NOT NULL PRIMARY KEY, user_id INTEGER NOT NULL PRIMARY KEY,
Roflin marked this conversation as resolved Outdated
Outdated
Review

I recommend calling the column user_id in both tables. The column in pwd should also have a foreign key constraint like REFERENCES user ON DELETE CASCADE. Or it could be in the same table: Using a separate table is usually only worth it if the rows are big or the relation is not one on one.

I recommend calling the column `user_id` in both tables. The column in `pwd` should also have a foreign key constraint like `REFERENCES user ON DELETE CASCADE`. Or it could be in the same table: Using a separate table is usually only worth it if the rows are big or the relation is not one on one.
password TEXT NOT NULL password TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
); );

View File

@ -8,23 +8,14 @@ use jsonwebtoken::DecodingKey;
use jsonwebtoken::Validation; use jsonwebtoken::Validation;
use jsonwebtoken::{EncodingKey, Header}; use jsonwebtoken::{EncodingKey, Header};
use rocket::http::Status; use rocket::http::Status;
use rocket::outcome::Outcome::{Failure, Success};
use rocket::request::Outcome; use rocket::request::Outcome;
use rocket::request::{self, FromRequest, Request}; use rocket::request::{FromRequest, Request};
use rocket::serde::json::{json, Json, Value}; use rocket::serde::json::{json, Json, Value};
use rocket::State; use rocket::State;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Cow; use std::borrow::Cow;
use validator::ValidateArgs; use validator::ValidateArgs;
pub struct Referer(String);
#[derive(Debug)]
pub enum ReferrerError {
Missing,
MoreThanOne,
}
#[derive(Debug, Responder)] #[derive(Debug, Responder)]
pub enum ApiResponseVariant { pub enum ApiResponseVariant {
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.
Status(Status), Status(Status),
@ -33,20 +24,6 @@ pub enum ApiResponseVariant {
// Flash(Flash<Redirect>) // Flash(Flash<Redirect>)
} }
#[rocket::async_trait]
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();
match referers.len() {
0 => Failure((Status::BadRequest, ReferrerError::Missing)),
1 => Success(Referer(referers[0].to_string())),
_ => Failure((Status::BadRequest, ReferrerError::MoreThanOne)),
}
}
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct ApiResponse { struct ApiResponse {
result: Cow<'static, str>, result: Cow<'static, str>,

View File

@ -45,8 +45,15 @@ fn rocket() -> _ {
.attach(Template::fairing()) .attach(Template::fairing())
.attach(AdHoc::on_ignite("Run Migrations", schema::run_migrations)) .attach(AdHoc::on_ignite("Run Migrations", schema::run_migrations))
.attach(AdHoc::config::<AppConfig>()) .attach(AdHoc::config::<AppConfig>())
.attach(site::make_cors()) .mount(
.mount("/", routes![site::index, site::files]) "/",
routes![
site::index,
site::gamenights,
site::add_game_night,
site::register
],
)
.mount( .mount(
"/api", "/api",
routes![ routes![

View File

@ -104,7 +104,7 @@ pub async fn insert_user(conn: DbConn, new_user: Register) -> Result<(), Databas
Err(error) => return Err(DatabaseError::Hash(error)), Err(error) => return Err(DatabaseError::Hash(error)),
}; };
match conn let user_insert_result = conn
.run(move |c| { .run(move |c| {
c.transaction(|| { c.transaction(|| {
diesel::insert_into(user::table) diesel::insert_into(user::table)
@ -133,8 +133,9 @@ pub async fn insert_user(conn: DbConn, new_user: Register) -> Result<(), Databas
.execute(c) .execute(c)
}) })
}) })
.await .await;
{
match user_insert_result {
Err(e) => Err(DatabaseError::Query(e.to_string())), Err(e) => Err(DatabaseError::Query(e.to_string())),
_ => Ok(()), _ => Ok(()),
} }

View File

@ -1,42 +1,90 @@
use rocket::fs::NamedFile; use crate::schema;
use rocket::http::Method; use rocket::request::FlashMessage;
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors, CorsOptions}; use rocket::response::Redirect;
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 rocket_dyn_templates::Template;
use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize};
use std::borrow::Cow;
pub fn make_cors() -> Cors { #[derive(Serialize, Deserialize, Debug)]
let allowed_origins = AllowedOrigins::some_exact(&[ struct FlashData {
// 4. has_data: bool,
//CHANGE THESE TO MATCH YOUR PORTS kind: Cow<'static, str>,
"http://localhost:3000", message: Cow<'static, str>,
"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")
} }
#[get("/<file..>")] impl FlashData {
pub async fn files(file: PathBuf) -> Option<NamedFile> { const EMPTY: Self = Self {
NamedFile::open(Path::new("../frontend/build/").join(file)) has_data: false,
.await message: Cow::Borrowed(""),
.ok() 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("/")] #[get("/")]
pub async fn index() -> io::Result<NamedFile> { pub async fn index() -> Redirect {
NamedFile::open("../frontend/build/index.html").await Redirect::to(uri!(gamenights))
}
#[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)
} }

View File

@ -16,4 +16,4 @@
<input type="submit"> <input type="submit">
</form> </form>
</body> </body>
</html>1 </html>
Roflin marked this conversation as resolved Outdated
Outdated
Review

1

1

View File

@ -1,4 +1,4 @@
ex<!DOCTYPE html> <!DOCTYPE html>
Roflin marked this conversation as resolved Outdated

ex

ex
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />