2022-02-05 06:11:24 +00:00
|
|
|
use std::ops::Add;
|
|
|
|
|
|
|
|
use jsonwebtoken::{encode, EncodingKey, Header};
|
|
|
|
use tracing::instrument;
|
|
|
|
|
|
|
|
use crate::api::api_prelude::*;
|
2022-02-08 04:04:45 +00:00
|
|
|
use crate::auth::{generate_secret, get_secret, set_password, verify_password};
|
2022-02-05 06:11:24 +00:00
|
|
|
use crate::error::{Error, Result};
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct AuthRequest {
|
|
|
|
password: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct AuthResponse {
|
|
|
|
token: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument]
|
|
|
|
#[post("authorize")]
|
|
|
|
pub async fn authorize(
|
|
|
|
state: web::Data<AppState>,
|
|
|
|
req: web::Json<AuthRequest>,
|
|
|
|
) -> Result<HttpResponse> {
|
|
|
|
if verify_password(&state.db, &req.password).await {
|
|
|
|
let secret = get_secret(&state.db).await?;
|
|
|
|
let exp = chrono::Utc::now()
|
|
|
|
.add(chrono::Duration::days(30))
|
|
|
|
.timestamp() as usize;
|
|
|
|
let token = encode(
|
|
|
|
&Header::default(),
|
|
|
|
&crate::auth::AuthClaims { exp },
|
|
|
|
&EncodingKey::from_secret(&secret),
|
|
|
|
)
|
|
|
|
.map_err(|_| Error::unauthorized())?;
|
|
|
|
Ok(HttpResponse::Ok().json(AuthResponse { token }))
|
|
|
|
} else {
|
|
|
|
Err(Error::unauthorized())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UpdatePasswordRequest {
|
|
|
|
password: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument]
|
|
|
|
#[post("password")]
|
|
|
|
pub async fn update_password(
|
|
|
|
state: web::Data<AppState>,
|
|
|
|
req: web::Json<UpdatePasswordRequest>,
|
|
|
|
) -> Result<HttpResponse> {
|
|
|
|
set_password(&state.db, &req.password).await?;
|
|
|
|
Ok(HttpResponse::Ok().body(""))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct SetupRequest {
|
|
|
|
password: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[instrument]
|
|
|
|
#[post("setup")]
|
|
|
|
pub async fn initial_setup(
|
|
|
|
state: web::Data<AppState>,
|
|
|
|
req: web::Json<SetupRequest>,
|
|
|
|
) -> Result<HttpResponse> {
|
|
|
|
let count = setting::Entity::find()
|
|
|
|
.filter(setting::Column::SettingName.eq("password_hash".to_string()))
|
|
|
|
.count(&state.db)
|
|
|
|
.await?;
|
|
|
|
if count == 0 {
|
|
|
|
set_password(&state.db, &req.password).await?;
|
2022-02-08 04:04:45 +00:00
|
|
|
generate_secret(&state.db).await?;
|
2022-02-05 06:11:24 +00:00
|
|
|
Ok(HttpResponse::Ok().body(""))
|
|
|
|
} else {
|
|
|
|
Err(Error::new(
|
|
|
|
crate::error::ErrorCode::UnAuthorized,
|
|
|
|
"Setup has already been run",
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
api::test_prelude::*,
|
|
|
|
auth::{get_secret, AuthClaims},
|
|
|
|
};
|
|
|
|
use actix_web::http::Method;
|
|
|
|
|
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_authorize() -> Result<()> {
|
|
|
|
let state = setup_state().await?;
|
|
|
|
|
|
|
|
let test_password = "sshh a secret".to_string();
|
|
|
|
let test_hash = bcrypt::hash(test_password.clone(), bcrypt::DEFAULT_COST).unwrap();
|
|
|
|
|
|
|
|
setting::ActiveModel {
|
|
|
|
id: NotSet,
|
|
|
|
setting_name: Set("password_hash".into()),
|
|
|
|
setting_value: Set(test_hash),
|
|
|
|
}
|
|
|
|
.insert(&state.db)
|
|
|
|
.await?;
|
|
|
|
|
2022-02-08 04:04:45 +00:00
|
|
|
let mut req = actix_web::test::TestRequest::with_uri("/api/authorize")
|
2022-02-05 06:11:24 +00:00
|
|
|
.method(Method::POST)
|
|
|
|
.set_json(super::AuthRequest {
|
|
|
|
password: test_password,
|
|
|
|
})
|
|
|
|
.to_request();
|
|
|
|
let resp = call_endpoint!(req, state);
|
|
|
|
|
|
|
|
let jwt_secret = get_secret(&state.db).await?;
|
|
|
|
|
|
|
|
let status = resp.status();
|
|
|
|
assert_eq!(status, 200);
|
|
|
|
let data = get_response!(resp, super::AuthResponse);
|
|
|
|
assert_eq!(status, 200);
|
|
|
|
|
|
|
|
let decoded = jsonwebtoken::decode::<AuthClaims>(
|
|
|
|
&data.token,
|
|
|
|
&jsonwebtoken::DecodingKey::from_secret(&jwt_secret),
|
|
|
|
&jsonwebtoken::Validation::default(),
|
|
|
|
);
|
|
|
|
|
|
|
|
decoded.expect("Decode failure");
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|