added auth to create_event endpoint
This commit is contained in:
parent
912e20399f
commit
711c5a88fb
@ -8,6 +8,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.27", features = ["full"] }
|
tokio = { version = "1.27", features = ["full"] }
|
||||||
axum = "0.6"
|
axum = "0.6"
|
||||||
|
axum-auth = { version = "0.4", features = ["auth-bearer"]}
|
||||||
sqlx = { version = "0.6", features = ["postgres", "runtime-tokio-rustls", "all-types"] }
|
sqlx = { version = "0.6", features = ["postgres", "runtime-tokio-rustls", "all-types"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|||||||
@ -8,5 +8,6 @@ RUN cargo install --path .
|
|||||||
|
|
||||||
FROM debian:buster-slim
|
FROM debian:buster-slim
|
||||||
COPY --from=build /usr/local/cargo/bin/school_app_api /usr/local/bin/school_app_api
|
COPY --from=build /usr/local/cargo/bin/school_app_api /usr/local/bin/school_app_api
|
||||||
|
ENV DATABASE_URL "postgres://school_app_api_user:school_app_api_pass@school_app_db/school_app_api"
|
||||||
CMD ["school_app_api"]
|
CMD ["school_app_api"]
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
use axum_auth::AuthBearer;
|
||||||
|
use jsonwebtoken::{Algorithm, Validation};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
@ -6,7 +13,7 @@ use sqlx::{
|
|||||||
types::{chrono::NaiveDateTime, BigDecimal},
|
types::{chrono::NaiveDateTime, BigDecimal},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::{jwt::Claims, user::Role, AppState};
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
sqlx::Type, Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
|
sqlx::Type, Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
|
||||||
@ -38,7 +45,6 @@ pub struct NewEventRequestEntry {
|
|||||||
place: Option<String>,
|
place: Option<String>,
|
||||||
#[serde(with = "serialize_big_decimal")]
|
#[serde(with = "serialize_big_decimal")]
|
||||||
price: BigDecimal,
|
price: BigDecimal,
|
||||||
created_by: Option<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Debug)]
|
#[derive(Clone, Serialize, Debug)]
|
||||||
@ -136,13 +142,20 @@ LIMIT
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_event(
|
pub async fn create_event(
|
||||||
|
AuthBearer(token): AuthBearer,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
Json(event_post_query): Json<NewEventRequestEntry>,
|
Json(event_post_query): Json<NewEventRequestEntry>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
let token_data = match handle_token(token, &app_state, Role::Teacher) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(value) => return value,
|
||||||
|
};
|
||||||
|
|
||||||
let result = query!(
|
let result = query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO events (title, description, time_start, time_end, event_type, points, place, price, created_by)
|
INSERT INTO events (title, description, time_start, time_end, event_type, points, place, price, created_by)
|
||||||
VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9 )
|
VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9 )
|
||||||
|
RETURNING id
|
||||||
"#,
|
"#,
|
||||||
event_post_query.title,
|
event_post_query.title,
|
||||||
event_post_query.description,
|
event_post_query.description,
|
||||||
@ -152,11 +165,11 @@ pub async fn create_event(
|
|||||||
event_post_query.points,
|
event_post_query.points,
|
||||||
event_post_query.place,
|
event_post_query.place,
|
||||||
event_post_query.price,
|
event_post_query.price,
|
||||||
event_post_query.created_by,
|
token_data.id,
|
||||||
).execute(&app_state.db_pool).await;
|
).fetch_one(&app_state.db_pool).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => (StatusCode::OK, "").into_response(),
|
Ok(record) => (StatusCode::OK, Json(json!({ "data": record.id }))).into_response(),
|
||||||
Err(err) => (
|
Err(err) => (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(json!({
|
Json(json!({
|
||||||
@ -166,3 +179,35 @@ pub async fn create_event(
|
|||||||
.into_response(),
|
.into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_token(
|
||||||
|
token: String,
|
||||||
|
app_state: &AppState,
|
||||||
|
minimum_role: Role,
|
||||||
|
) -> Result<Claims, Response> {
|
||||||
|
let token_result = jsonwebtoken::decode::<Claims>(
|
||||||
|
&token,
|
||||||
|
&app_state.jwt_decode,
|
||||||
|
&Validation::new(Algorithm::HS256),
|
||||||
|
);
|
||||||
|
let token_data = match token_result {
|
||||||
|
Ok(token_data) => token_data.claims,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(json!({ "error": format!("Failed to parse JWT: {}", err) })),
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if token_data.role < minimum_role {
|
||||||
|
return Err((
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
Json(json!({
|
||||||
|
"error": format!("You must be at least a {:?} to do this", minimum_role)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.into_response());
|
||||||
|
}
|
||||||
|
Ok(token_data)
|
||||||
|
}
|
||||||
|
|||||||
43
src/jwt.rs
43
src/jwt.rs
@ -1,13 +1,50 @@
|
|||||||
use jsonwebtoken::EncodingKey;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::types::time::OffsetDateTime;
|
||||||
|
|
||||||
|
use crate::user::Role;
|
||||||
|
|
||||||
|
pub const JWT_LIFETIME: Duration = Duration::from_secs(60 * 60 * 24 * 7);
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, Hash)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Hash)]
|
||||||
pub struct Claims {
|
pub struct Claims {
|
||||||
|
#[serde(with = "jwt_numeric_date")]
|
||||||
|
pub exp: OffsetDateTime,
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
pub role: Role,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_jwt_secret() -> EncodingKey {
|
pub fn get_jwt_keys() -> (EncodingKey, DecodingKey) {
|
||||||
let secret = std::env::var("SAAPI_JWT_SECRET").unwrap_or("Sdb\\y5PP`,hmG+98".into());
|
let secret = std::env::var("SAAPI_JWT_SECRET").unwrap_or("Sdb\\y5PP`,hmG+98".into());
|
||||||
EncodingKey::from_secret(secret.as_bytes())
|
(
|
||||||
|
EncodingKey::from_secret(secret.as_bytes()),
|
||||||
|
DecodingKey::from_secret(secret.as_bytes()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod jwt_numeric_date {
|
||||||
|
//! Custom serialization of OffsetDateTime to conform with the JWT spec (RFC 7519 section 2, "Numeric Date")
|
||||||
|
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||||
|
use sqlx::types::time::OffsetDateTime;
|
||||||
|
|
||||||
|
/// Serializes an OffsetDateTime to a Unix timestamp (milliseconds since 1970/1/1T00:00:00T)
|
||||||
|
pub fn serialize<S>(date: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let timestamp = date.unix_timestamp();
|
||||||
|
serializer.serialize_i64(timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to deserialize an i64 and use as a Unix timestamp
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
OffsetDateTime::from_unix_timestamp(i64::deserialize(deserializer)?)
|
||||||
|
.map_err(|_| serde::de::Error::custom("invalid Unix timestamp value"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/main.rs
13
src/main.rs
@ -6,7 +6,7 @@ use axum::{
|
|||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use jsonwebtoken::EncodingKey;
|
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@ -15,7 +15,8 @@ use user::{signin, signup};
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub db_pool: PgPool,
|
pub db_pool: PgPool,
|
||||||
pub jwt_key: EncodingKey,
|
pub jwt_encode: EncodingKey,
|
||||||
|
pub jwt_decode: DecodingKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -27,7 +28,7 @@ async fn main() {
|
|||||||
let db_url = std::env::var("DATABASE_URL")
|
let db_url = std::env::var("DATABASE_URL")
|
||||||
.expect("Set `DATABASE_URL` to the url of the postgres database.");
|
.expect("Set `DATABASE_URL` to the url of the postgres database.");
|
||||||
|
|
||||||
let jwt_key = jwt::get_jwt_secret();
|
let (jwt_encode, jwt_decode) = jwt::get_jwt_keys();
|
||||||
|
|
||||||
let db_pool = PgPoolOptions::new()
|
let db_pool = PgPoolOptions::new()
|
||||||
.max_connections(50)
|
.max_connections(50)
|
||||||
@ -42,7 +43,11 @@ async fn main() {
|
|||||||
.route("/user/signin", post(signin))
|
.route("/user/signin", post(signin))
|
||||||
.route("/event", post(events::create_event))
|
.route("/event", post(events::create_event))
|
||||||
.route("/event/preview", get(events::get_events_preview))
|
.route("/event/preview", get(events::get_events_preview))
|
||||||
.with_state(AppState { db_pool, jwt_key });
|
.with_state(AppState {
|
||||||
|
db_pool,
|
||||||
|
jwt_encode,
|
||||||
|
jwt_decode,
|
||||||
|
});
|
||||||
|
|
||||||
// run our app with hyper
|
// run our app with hyper
|
||||||
// `axum::Server` is a re-export of `hyper::Server`
|
// `axum::Server` is a re-export of `hyper::Server`
|
||||||
|
|||||||
20
src/user.rs
20
src/user.rs
@ -2,8 +2,12 @@ use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
|||||||
use jsonwebtoken::Header;
|
use jsonwebtoken::Header;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use sqlx::types::time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{jwt::Claims, AppState};
|
use crate::{
|
||||||
|
jwt::{Claims, JWT_LIFETIME},
|
||||||
|
AppState,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Signup {
|
pub struct Signup {
|
||||||
@ -88,7 +92,10 @@ pub async fn signin(
|
|||||||
|
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT id, username
|
SELECT
|
||||||
|
id,
|
||||||
|
username,
|
||||||
|
role AS "role!: Role"
|
||||||
FROM users
|
FROM users
|
||||||
WHERE username = $1 AND password = $2
|
WHERE username = $1 AND password = $2
|
||||||
"#,
|
"#,
|
||||||
@ -101,17 +108,20 @@ pub async fn signin(
|
|||||||
match result {
|
match result {
|
||||||
Ok(Some(user)) => {
|
Ok(Some(user)) => {
|
||||||
let claims = Claims {
|
let claims = Claims {
|
||||||
|
exp: OffsetDateTime::now_utc() + JWT_LIFETIME,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
|
role: user.role,
|
||||||
};
|
};
|
||||||
let token = match jsonwebtoken::encode(&Header::default(), &claims, &app_state.jwt_key)
|
let token =
|
||||||
{
|
match jsonwebtoken::encode(&Header::default(), &claims, &app_state.jwt_encode) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"error": format!("Unknown error signing in when creating JWT: {}", err)
|
"error":
|
||||||
|
format!("Unknown error signing in when creating JWT: {}", err)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user