Skip to content

Commit 9791414

Browse files
authored
Merge pull request #13 from QuantGeekDev/feature/users
Feature/users
2 parents c9acc68 + d7031bb commit 9791414

File tree

16 files changed

+295
-58
lines changed

16 files changed

+295
-58
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7+
bcrypt = "0.15.1"
78
chrono = { version = "0.4.38", features = ["serde"] }
89
diesel = { version = "2.2.4", features = ["postgres", "numeric", "serde_json", "chrono"] }
910
dotenv = "0.15.0"
11+
jsonwebtoken = "9.3.0"
1012
rocket = { version = "0.5.1", features = ["json"] }
1113
rust_decimal = { version = "1.36.0", features = ["db-diesel-postgres", "serde-with-str"] }
1214
serde = "1.0.213"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE IF EXISTS users CASCADE;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CREATE TABLE users
2+
(
3+
id SERIAL PRIMARY KEY,
4+
username VARCHAR NOT NULL UNIQUE,
5+
password_hash VARCHAR NOT NULL,
6+
role VARCHAR NOT NULL DEFAULT 'user',
7+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
8+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
9+
);
10+
11+
SELECT diesel_manage_updated_at('users');
12+
13+
CREATE INDEX idx_users_username ON users (username);
14+
15+
ALTER TABLE users
16+
ADD CONSTRAINT valid_role CHECK (role IN ('user', 'admin'));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- This file should undo anything in `up.sql`
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
ALTER TABLE snacks
2+
ADD COLUMN user_id INTEGER;
3+
4+
INSERT INTO users (username, password_hash, role, created_at, updated_at)
5+
SELECT 'admin',
6+
'$2a$12$K6O5bFK6.O4V6csO94BFN.yvMkv4pOXxYH.rolz4.Y4.pxpwB6fvC',
7+
'admin',
8+
CURRENT_TIMESTAMP,
9+
CURRENT_TIMESTAMP
10+
WHERE NOT EXISTS (
11+
SELECT 1 FROM users WHERE username = 'admin'
12+
) RETURNING id;
13+
14+
UPDATE snacks
15+
SET user_id = (SELECT id FROM users WHERE username = 'admin')
16+
WHERE user_id IS NULL;
17+
18+
ALTER TABLE snacks
19+
ALTER COLUMN user_id SET NOT NULL;
20+
21+
ALTER TABLE snacks
22+
ADD CONSTRAINT fk_snacks_user
23+
FOREIGN KEY (user_id)
24+
REFERENCES users(id)
25+
ON DELETE CASCADE;
26+
27+
CREATE INDEX idx_snacks_user_id ON snacks(user_id);

src/auth/mod.rs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1 @@
1-
pub struct ApiKey(pub String);
2-
use rocket::{http::Status, request::{FromRequest, Outcome}, Request};
3-
use std::env;
4-
5-
#[derive(Debug)]
6-
pub enum ApiKeyError {
7-
Missing,
8-
Invalid,
9-
}
10-
11-
#[rocket::async_trait]
12-
impl<'r> FromRequest<'r> for ApiKey {
13-
type Error = ApiKeyError;
14-
15-
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
16-
let api_key = request.headers().get_one("x-api-key");
17-
18-
match api_key {
19-
None => Outcome::Error((Status::Unauthorized, ApiKeyError::Missing)),
20-
Some(api_key) => {
21-
if api_key == env::var("AUTH_API_KEY").unwrap() {
22-
Outcome::Success(ApiKey(api_key.to_string()))
23-
} else {
24-
Outcome::Error((Status::Unauthorized, ApiKeyError::Invalid))
25-
}
26-
}
27-
}
28-
}
29-
}
1+
pub mod user;

src/auth/user.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use crate::models::user::User;
2+
use diesel::prelude::*;
3+
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
4+
use rocket::http::Status;
5+
use rocket::request::{FromRequest, Outcome};
6+
use rocket::Request;
7+
use serde::{Deserialize, Serialize};
8+
9+
pub struct AuthenticatedUser(pub User);
10+
11+
#[derive(Debug, Serialize, Deserialize)]
12+
pub(crate) struct Claims {
13+
pub(crate) sub: i32,
14+
pub(crate) role: String,
15+
pub(crate) exp: usize,
16+
}
17+
18+
#[rocket::async_trait]
19+
impl<'r> FromRequest<'r> for AuthenticatedUser {
20+
type Error = ();
21+
22+
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
23+
let token = request.headers().get_one("Authorization");
24+
25+
match token {
26+
Some(token) if token.starts_with("Bearer ") => {
27+
let token = &token[7..];
28+
let decoding_key = DecodingKey::from_secret("SECRET".as_ref());
29+
match decode::<Claims>(token, &decoding_key, &Validation::new(Algorithm::HS256)) {
30+
Ok(token_data) => {
31+
let mut conn = crate::db::establish_connection();
32+
match crate::schema::users::dsl::users
33+
.find(token_data.claims.sub)
34+
.first::<User>(&mut conn)
35+
{
36+
Ok(user) => Outcome::Success(AuthenticatedUser(user)),
37+
Err(_) => Outcome::Error((Status::Unauthorized, ())),
38+
}
39+
}
40+
Err(_) => Outcome::Error((Status::Unauthorized, ())),
41+
}
42+
}
43+
_ => Outcome::Error((Status::Unauthorized, ())),
44+
}
45+
}
46+
}

src/catchers/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ pub fn unauthorized(req: &Request) -> Json<ErrorResponse> {
1818
}
1919

2020
#[catch(404)]
21-
pub fn not_found(req: &Request) -> Json<ErrorResponse> {
21+
pub fn not_found(_req: &Request) -> Json<ErrorResponse> {
2222
Json(ErrorResponse {
2323
status: 404,
2424
message: "Page not found".to_string(),
2525
})
2626
}
2727

2828
#[catch(500)]
29-
pub fn internal_server_error(req: &Request) -> Json<ErrorResponse> {
29+
pub fn internal_server_error(_req: &Request) -> Json<ErrorResponse> {
3030
Json(ErrorResponse {
3131
status: 500,
3232
message: "Internal Server Error".to_string(),

src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod catchers;
88
mod db;
99
mod schema;
1010

11+
use crate::routes::auth::{login, register};
1112
use crate::routes::snack::{create_snack, delete_snack, list_snacks, update_snack};
1213
use dotenv::dotenv;
1314
use rocket::*;
@@ -28,7 +29,7 @@ fn index() -> &'static str {
2829
fn rocket() -> _ {
2930
dotenv().ok();
3031

31-
rocket::build().mount("/", routes![index, create_snack, list_snacks, update_snack, delete_snack]).register("/", catchers![catchers::unauthorized, catchers::not_found,
32+
rocket::build().mount("/", routes![index, create_snack, list_snacks, update_snack, delete_snack, register, login]).register("/", catchers![catchers::unauthorized, catchers::not_found,
3233
catchers::internal_server_error])
3334
}
3435

src/models/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
pub mod snack;
1+
pub mod snack;
2+
pub(crate) mod user;

0 commit comments

Comments
 (0)