mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-21 19:29:49 -04:00
User user_data response to show the user id in the header
This commit is contained in:
parent
997d95e201
commit
7343c77a04
@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use recipes::{IngredientKey, RecipeEntry};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum Response<T> {
|
||||
Success(T),
|
||||
Err { status: u16, message: String },
|
||||
@ -103,7 +103,7 @@ pub type CategoryResponse = Response<String>;
|
||||
|
||||
pub type EmptyResponse = Response<()>;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UserData {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ edition = "2021"
|
||||
tracing = "0.1.35"
|
||||
tracing-subscriber = "0.3.14"
|
||||
recipes = { path = "../recipes" }
|
||||
api = { path = "../api" }
|
||||
client-api = { path = "../api", features = ["server"], package = "api" }
|
||||
csv = "1.1.1"
|
||||
rust-embed="6.4.0"
|
||||
mime_guess = "2.0.4"
|
||||
|
@ -14,13 +14,13 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use api;
|
||||
use async_session::{Session, SessionStore};
|
||||
use axum::{
|
||||
extract::Extension,
|
||||
http::{header, HeaderMap, StatusCode},
|
||||
};
|
||||
use axum_auth::AuthBasic;
|
||||
use client_api as api;
|
||||
use cookie::{Cookie, SameSite};
|
||||
use secrecy::Secret;
|
||||
use tracing::{debug, error, info, instrument};
|
||||
|
@ -30,7 +30,7 @@ use tower::ServiceBuilder;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{debug, info, instrument};
|
||||
|
||||
use api;
|
||||
use client_api as api;
|
||||
use storage::{APIStore, AuthStore};
|
||||
|
||||
mod auth;
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use base64;
|
||||
use reqwasm;
|
||||
use serde_json::{from_str, to_string};
|
||||
use sycamore::prelude::*;
|
||||
@ -79,6 +80,16 @@ pub async fn init_page_state(store: &HttpStore, state: &app_state::State) -> Res
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Checking for user_data in local storage");
|
||||
let storage = js_lib::get_storage();
|
||||
let user_data = storage
|
||||
.get("user_data")
|
||||
.expect("Couldn't read from storage");
|
||||
if let Some(data) = user_data {
|
||||
if let Ok(user_data) = from_str(&data) {
|
||||
state.auth.set(user_data)
|
||||
}
|
||||
}
|
||||
info!("Synchronizing categories");
|
||||
match store.get_categories().await {
|
||||
Ok(Some(categories_content)) => {
|
||||
@ -164,6 +175,10 @@ fn recipe_key<S: std::fmt::Display>(id: S) -> String {
|
||||
format!("recipe:{}", id)
|
||||
}
|
||||
|
||||
fn token68(user: String, pass: String) -> String {
|
||||
base64::encode(format!("{}:{}", user, pass))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HttpStore {
|
||||
root: String,
|
||||
@ -194,6 +209,42 @@ impl HttpStore {
|
||||
use_context::<std::rc::Rc<Self>>(cx).clone()
|
||||
}
|
||||
|
||||
// NOTE(jwall): We do **not** want to record the password in our logs.
|
||||
#[instrument(skip_all, fields(?self, user))]
|
||||
pub async fn authenticate(&self, user: String, pass: String) -> Option<UserData> {
|
||||
debug!("attempting login request against api.");
|
||||
let mut path = self.v1_path();
|
||||
path.push_str("/auth");
|
||||
let storage = js_lib::get_storage();
|
||||
let result = reqwasm::http::Request::get(&path)
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("Basic {}", token68(user, pass)).as_str(),
|
||||
)
|
||||
.send()
|
||||
.await;
|
||||
if let Ok(resp) = &result {
|
||||
if resp.status() == 200 {
|
||||
let user_data = resp
|
||||
.json::<AccountResponse>()
|
||||
.await
|
||||
.expect("Unparseable authentication response")
|
||||
.as_success();
|
||||
storage
|
||||
.set(
|
||||
"user_data",
|
||||
&to_string(&user_data).expect("Unable to serialize user_data"),
|
||||
)
|
||||
.unwrap();
|
||||
return user_data;
|
||||
}
|
||||
error!(status = resp.status(), "Login was unsuccessful")
|
||||
} else {
|
||||
error!(err=?result.unwrap_err(), "Failed to send auth request");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
//#[instrument]
|
||||
pub async fn get_categories(&self) -> Result<Option<String>, Error> {
|
||||
let mut path = self.v1_path();
|
||||
|
@ -16,6 +16,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use sycamore::prelude::*;
|
||||
use tracing::{debug, instrument, warn};
|
||||
|
||||
use client_api::UserData;
|
||||
use recipes::{Ingredient, IngredientAccumulator, IngredientKey, Recipe};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -27,6 +28,7 @@ pub struct State {
|
||||
pub category_map: RcSignal<BTreeMap<String, String>>,
|
||||
pub filtered_ingredients: RcSignal<BTreeSet<IngredientKey>>,
|
||||
pub modified_amts: RcSignal<BTreeMap<IngredientKey, RcSignal<String>>>,
|
||||
pub auth: RcSignal<Option<UserData>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@ -39,6 +41,7 @@ impl State {
|
||||
category_map: create_rc_signal(BTreeMap::new()),
|
||||
filtered_ingredients: create_rc_signal(BTreeSet::new()),
|
||||
modified_amts: create_rc_signal(BTreeMap::new()),
|
||||
auth: create_rc_signal(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,15 +14,26 @@
|
||||
|
||||
use sycamore::prelude::*;
|
||||
|
||||
use crate::app_state;
|
||||
|
||||
#[component]
|
||||
pub fn Header<G: Html>(cx: Scope) -> View<G> {
|
||||
let state = app_state::State::get_from_context(cx);
|
||||
let login = create_memo(cx, move || {
|
||||
let user_id = state.auth.get();
|
||||
match user_id.as_ref() {
|
||||
Some(user_data) => format!("{}", user_data.user_id),
|
||||
None => "Login".to_owned(),
|
||||
}
|
||||
});
|
||||
view! {cx,
|
||||
nav(class="no-print") {
|
||||
h1(class="title") { "Kitchen" }
|
||||
ul {
|
||||
li { a(href="/ui/planning/plan") { "MealPlan" } }
|
||||
li { a(href="/ui/manage/categories") { "Manage" } }
|
||||
li { a(href="/ui/login") { "Login" } }
|
||||
li { a(href="/ui/login") { (login.get()) } }
|
||||
// TODO(jwall): Move to footer?
|
||||
li { a(href="https://github.com/zaphar/kitchen") { "Github" } }
|
||||
}
|
||||
}
|
||||
|
@ -11,42 +11,10 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use base64;
|
||||
use client_api::AccountResponse;
|
||||
use reqwasm::http;
|
||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
||||
use tracing::{debug, error, info};
|
||||
use tracing::{debug, info};
|
||||
|
||||
fn token68(user: String, pass: String) -> String {
|
||||
base64::encode(format!("{}:{}", user, pass))
|
||||
}
|
||||
|
||||
async fn authenticate(user: String, pass: String) -> Option<AccountResponse> {
|
||||
debug!(
|
||||
username = user,
|
||||
password = pass,
|
||||
"attempting login request against api."
|
||||
);
|
||||
let result = http::Request::get("/api/v1/auth")
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("Basic {}", token68(user, pass)).as_str(),
|
||||
)
|
||||
.send()
|
||||
.await;
|
||||
if let Ok(resp) = &result {
|
||||
if resp.status() == 200 {
|
||||
return resp
|
||||
.json()
|
||||
.await
|
||||
.expect("Unparseable authentication response");
|
||||
}
|
||||
error!(status = resp.status(), "Login was unsuccessful")
|
||||
} else {
|
||||
error!(err=?result.unwrap_err(), "Failed to send auth request");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
use crate::app_state;
|
||||
|
||||
#[component]
|
||||
pub fn LoginForm<G: Html>(cx: Scope) -> View<G> {
|
||||
@ -57,10 +25,11 @@ pub fn LoginForm<G: Html>(cx: Scope) -> View<G> {
|
||||
let (username, password) = (*clicked.get()).clone();
|
||||
if username != "" && password != "" {
|
||||
spawn_local_scoped(cx, async move {
|
||||
let state = app_state::State::get_from_context(cx);
|
||||
let store = crate::api::HttpStore::get_from_context(cx);
|
||||
debug!("authenticating against ui");
|
||||
// TODO(jwall): Navigate to plan if the below is successful.
|
||||
// TODO(jwall): Store account data in our app_state.
|
||||
authenticate(username, password).await;
|
||||
state.auth.set(store.authenticate(username, password).await);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user