add initial jwt code
This commit is contained in:
parent
dea8968f6b
commit
99246773ef
8 changed files with 218 additions and 20 deletions
124
Cargo.lock
generated
124
Cargo.lock
generated
|
@ -462,6 +462,15 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
@ -897,6 +906,7 @@ dependencies = [
|
|||
"eyre",
|
||||
"handlebars",
|
||||
"headers",
|
||||
"jsonwebtoken",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"sqlx",
|
||||
|
@ -955,6 +965,20 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "9.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "155c4d7e39ad04c172c5e3a99c434ea3b4a7ba7960b38ecd562b270b097cce09"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"pem",
|
||||
"ring 0.17.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -1083,6 +1107,17 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.4"
|
||||
|
@ -1191,6 +1226,16 @@ version = "1.0.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
|
@ -1310,6 +1355,12 @@ version = "0.3.27"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
|
@ -1421,11 +1472,25 @@ dependencies = [
|
|||
"libc",
|
||||
"once_cell",
|
||||
"spin 0.5.2",
|
||||
"untrusted",
|
||||
"untrusted 0.7.1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin 0.9.8",
|
||||
"untrusted 0.9.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.2"
|
||||
|
@ -1508,7 +1573,7 @@ version = "0.21.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"ring 0.16.20",
|
||||
"rustls-webpki",
|
||||
"sct",
|
||||
]
|
||||
|
@ -1528,8 +1593,8 @@ version = "0.101.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.16.20",
|
||||
"untrusted 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1565,8 +1630,8 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.16.20",
|
||||
"untrusted 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1663,6 +1728,18 @@ dependencies = [
|
|||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
@ -2030,6 +2107,35 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
@ -2246,6 +2352,12 @@ version = "0.7.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
|
|
|
@ -14,6 +14,7 @@ dotenvy = "0.15.7"
|
|||
eyre = "0.6.8"
|
||||
handlebars = { version = "4.4.0", features = ["rust-embed", "dir_source"] }
|
||||
headers = "0.3.9"
|
||||
jsonwebtoken = "9.1.0"
|
||||
rust-embed = "8.0.0"
|
||||
serde = { version = "1.0.189", features = ["derive"] }
|
||||
sqlx = { version = "0.7.2", features = ["runtime-tokio", "tls-rustls", "postgres", "macros", "migrate", "chrono"] }
|
||||
|
|
|
@ -8,4 +8,6 @@ pub struct Config {
|
|||
pub port: u16,
|
||||
#[arg(long, env, default_value_t = false)]
|
||||
pub dev_mode: bool,
|
||||
#[arg(env)]
|
||||
pub secret_key: String,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use axum::{
|
|||
use headers::Cookie;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{model::user::User, state::AppState};
|
||||
use crate::{model::user::User, state::AppState, token::Claims};
|
||||
|
||||
pub struct ExtractUserToken(pub Option<User>);
|
||||
|
||||
|
@ -33,17 +33,28 @@ where
|
|||
Err(err) => {
|
||||
error!("Getting cookie header: {}", err);
|
||||
return Ok(ExtractUserToken(None));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let user = match cookie.get("imgboard-token") {
|
||||
Some(_token) => {
|
||||
// TODO: get from token
|
||||
None
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
match cookie.get("imgboard-token") {
|
||||
Some(token) => {
|
||||
let claims = Claims::decode(token, &state.decoding_key).map_err(|e| {
|
||||
error!("Decoding token claims: {}", e);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
|
||||
})?;
|
||||
|
||||
Ok(ExtractUserToken(user))
|
||||
match sqlx::query_as!(User, r#"SELECT * FROM users WHERE id = $1"#, claims.uid)
|
||||
.fetch_one(&state.pool)
|
||||
.await
|
||||
{
|
||||
Ok(u) => Ok(ExtractUserToken(Some(u))),
|
||||
Err(err) => {
|
||||
error!("Getting user from database: {}", err);
|
||||
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Ok(ExtractUserToken(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -4,6 +4,7 @@ pub mod model;
|
|||
pub mod pages;
|
||||
pub mod state;
|
||||
pub mod templates;
|
||||
pub mod token;
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
|
@ -11,6 +12,7 @@ use std::sync::Arc;
|
|||
use axum::{routing::get, Router};
|
||||
use clap::Parser;
|
||||
use eyre::{Result, WrapErr};
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||
use sqlx::migrate;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
@ -44,14 +46,24 @@ async fn main() -> Result<()> {
|
|||
|
||||
migrate!().run(&pool).await.wrap_err("running migrations")?;
|
||||
|
||||
let state = Arc::new(AppState { pool, hbs });
|
||||
let state = Arc::new(AppState {
|
||||
pool,
|
||||
hbs,
|
||||
encoding_key: EncodingKey::from_base64_secret(&config.secret_key)
|
||||
.wrap_err("parsing $SECRET_KEY")?,
|
||||
decoding_key: DecodingKey::from_base64_secret(&config.secret_key)
|
||||
.wrap_err("parsing $SECRET_KEY")?,
|
||||
});
|
||||
|
||||
debug!("Building router");
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(pages::index))
|
||||
.route("/users/:id", get(pages::get_user))
|
||||
.route("/users/new", get(pages::get_user_new).post(pages::post_user_new))
|
||||
.route(
|
||||
"/users/new",
|
||||
get(pages::get_user_new).post(pages::post_user_new),
|
||||
)
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ pub struct User {
|
|||
pub last_active: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(sqlx::Type, Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum Role {
|
||||
Viewer = 1,
|
||||
|
@ -62,3 +62,25 @@ impl TryFrom<&str> for Role {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<&str> for Role {
|
||||
fn into(self) -> &'static str {
|
||||
match self {
|
||||
Self::Viewer => "viewer",
|
||||
Self::Editor => "editor",
|
||||
Self::Manager => "manager",
|
||||
Self::Admin => "admin",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Role {
|
||||
fn into(self) -> String {
|
||||
match self {
|
||||
Self::Viewer => "viewer".into(),
|
||||
Self::Editor => "editor".into(),
|
||||
Self::Manager => "manager".into(),
|
||||
Self::Admin => "admin".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{extract::{FromRef, FromRequestParts}, async_trait, http::request::Parts};
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRef, FromRequestParts},
|
||||
http::request::Parts,
|
||||
};
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||
use sqlx::{postgres::Postgres, Pool};
|
||||
|
||||
#[derive(Clone, FromRef)]
|
||||
pub struct AppState {
|
||||
pub pool: Pool<Postgres>,
|
||||
pub hbs: handlebars::Handlebars<'static>,
|
||||
pub encoding_key: EncodingKey,
|
||||
pub decoding_key: DecodingKey,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
|
31
src/token.rs
Normal file
31
src/token.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use jsonwebtoken::{errors::Error, DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::user::User;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub exp: usize,
|
||||
pub uid: i32,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
impl Claims {
|
||||
pub fn new(user: &User) -> Self {
|
||||
Self {
|
||||
exp: 0,
|
||||
uid: user.id,
|
||||
role: user.role.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(self, key: &EncodingKey) -> Result<String, Error> {
|
||||
jsonwebtoken::encode(&Header::default(), &self, key)
|
||||
}
|
||||
|
||||
pub fn decode(token: &str, key: &DecodingKey) -> Result<Self, Error> {
|
||||
let token = jsonwebtoken::decode::<Claims>(token, key, &Validation::default())?;
|
||||
|
||||
Ok(token.claims)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue