#![warn(clippy::too_many_arguments)] use rocket::{ request::{Form, Request}, response::{self, Responder}, }; use rocket_contrib::json::Json; use plume_common::utils::random_hex; use plume_models::{api_tokens::*, apps::App, users::User, Error, PlumeRocket}; type Api = Result, ApiError>; #[derive(Debug)] pub struct ApiError(Error); impl From for ApiError { fn from(err: Error) -> ApiError { ApiError(err) } } impl From for ApiError { fn from(err: std::option::NoneError) -> ApiError { ApiError(err.into()) } } #[rocket::async_trait] impl<'r> Responder<'r> for ApiError { async fn respond_to(self, req: &'r Request<'_>) -> response::Result<'r> { match self.0 { Error::NotFound => { Json(json!({ "error": "Not found" })) .respond_to(req) .await } Error::Unauthorized => { Json(json!({ "error": "You are not authorized to access this resource" })) .respond_to(req) .await } _ => { Json(json!({ "error": "Server error" })) .respond_to(req) .await } } } } #[derive(FromForm)] pub struct OAuthRequest { client_id: String, client_secret: String, password: String, username: String, scopes: String, } #[get("/oauth2?")] pub async fn oauth( query: Form, rockets: PlumeRocket, ) -> Result, ApiError> { let conn = &*rockets.conn; let app = App::find_by_client_id(conn, &query.client_id)?; if app.client_secret == query.client_secret { if let Ok(user) = User::find_by_fqn(&rockets, &query.username).await { if user.auth(&query.password) { let token = ApiToken::insert( conn, NewApiToken { app_id: app.id, user_id: user.id, value: random_hex(), scopes: query.scopes.clone(), }, )?; Ok(Json(json!({ "token": token.value }))) } else { Ok(Json(json!({ "error": "Invalid credentials" }))) } } else { // Making fake password verification to avoid different // response times that would make it possible to know // if a username is registered or not. User::get(conn, 1)?.auth(&query.password); Ok(Json(json!({ "error": "Invalid credentials" }))) } } else { Ok(Json(json!({ "error": "Invalid client_secret" }))) } } pub mod apps; pub mod authorization; pub mod posts;