working login and logout

This commit is contained in:
Felix Ableitner 2024-01-18 15:31:32 +01:00
parent f41cc01ad3
commit 469ca6f718
9 changed files with 35 additions and 55 deletions

View File

@ -57,7 +57,7 @@ uuid = { version = "1.6.1", features = ["serde"] }
tower-http = { version = "0.4.0", features = ["cors", "fs"], optional = true }
serde = { version = "1.0.192", features = ["derive"] }
url = { version = "2.4.1", features = ["serde"] }
reqwest = { version = "0.11.22", features = ["json"] }
reqwest = { version = "0.11.22", features = ["json", "cookies"] }
log = "0.4"
tracing = "0.1.40"
once_cell = "1.18.0"
@ -68,7 +68,6 @@ time = "0.3.31"
[dev-dependencies]
pretty_assertions = "1.4.0"
reqwest = { version = "0.11.22", features = ["cookies"] }
[package.metadata.leptos]
output-name = "ibis"

View File

@ -3,4 +3,4 @@ filehash = false
target = "assets/index.html"
[[proxy]]
backend = "http://[::1]:8131"
backend = "http://127.0.0.1:8131"

View File

@ -91,6 +91,7 @@ fn create_cookie(jwt: String, data: &Data<MyDataHandle>) -> Cookie<'static> {
.same_site(SameSite::Strict)
.path("/")
.http_only(true)
.secure(true)
.expires(Expiration::DateTime(
OffsetDateTime::now_utc() + Duration::weeks(52),
))

View File

@ -6,13 +6,10 @@ use crate::common::{DbArticle, GetArticleData};
use crate::common::{DbInstance, FollowInstance, InstanceView, SearchArticleData};
use crate::frontend::error::MyResult;
use anyhow::anyhow;
use once_cell::sync::Lazy;
use reqwest::{Client, RequestBuilder, StatusCode};
use serde::{Deserialize, Serialize};
use url::Url;
pub static CLIENT: Lazy<Client> = Lazy::new(Client::new);
#[derive(Clone)]
pub struct ApiClient {
client: Client,
@ -116,8 +113,9 @@ impl ApiClient {
let resolve_form = ResolveObject {
id: Url::parse(&format!("http://{}", follow_instance))?,
};
let instance_resolved: DbInstance =
get_query(&self.hostname, "instance/resolve", Some(resolve_form)).await?;
let instance_resolved: DbInstance = self
.get_query("instance/resolve", Some(resolve_form))
.await?;
// send follow
let follow_form = FollowInstance {
@ -170,35 +168,29 @@ impl ApiClient {
pub async fn resolve_article(&self, id: Url) -> MyResult<ArticleView> {
let resolve_object = ResolveObject { id };
get_query(&self.hostname, "article/resolve", Some(resolve_object)).await
self.get_query("article/resolve", Some(resolve_object))
.await
}
pub async fn resolve_instance(&self, id: Url) -> MyResult<DbInstance> {
let resolve_object = ResolveObject { id };
get_query(&self.hostname, "instance/resolve", Some(resolve_object)).await
self.get_query("instance/resolve", Some(resolve_object))
.await
}
}
async fn get_query<T, R>(hostname: &str, endpoint: &str, query: Option<R>) -> MyResult<T>
async fn handle_json_res<T>(#[allow(unused_mut)] mut req: RequestBuilder) -> MyResult<T>
where
T: for<'de> Deserialize<'de>,
R: Serialize,
{
let mut req = CLIENT.get(format!("http://{}/api/v1/{}", hostname, endpoint));
if let Some(query) = query {
req = req.query(&query);
#[cfg(not(feature = "ssr"))]
{
req = req.fetch_credentials_include();
}
handle_json_res::<T>(req).await
}
async fn handle_json_res<T>(req: RequestBuilder) -> MyResult<T>
where
T: for<'de> Deserialize<'de>,
{
let res = req.send().await?;
let status = res.status();
let text = res.text().await?;
if status == reqwest::StatusCode::OK {
if status == StatusCode::OK {
Ok(serde_json::from_str(&text).map_err(|e| anyhow!("Json error on {text}: {e}"))?)
} else {
Err(anyhow!("API error: {text}").into())

View File

@ -43,10 +43,9 @@ impl GlobalState {
create_local_resource(
move || (),
|_| async move {
if let Ok(my_profile) = GlobalState::api_client().my_profile().await {
expect_context::<RwSignal<GlobalState>>()
.update(|state| state.my_profile = Some(my_profile.clone()))
};
let my_profile = GlobalState::api_client().my_profile().await.ok();
expect_context::<RwSignal<GlobalState>>()
.update(|state| state.my_profile = my_profile.clone());
},
);
}
@ -54,7 +53,7 @@ impl GlobalState {
#[component]
pub fn App() -> impl IntoView {
let backend_hostname = "localhost:8080".to_string();
let backend_hostname = "127.0.0.1:8080".to_string();
provide_meta_context();
let backend_hostname = GlobalState {

View File

@ -6,6 +6,12 @@ use leptos_router::*;
#[component]
pub fn Nav() -> impl IntoView {
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
let logout_action = create_action(move |_| async move {
GlobalState::api_client().logout().await.unwrap();
expect_context::<RwSignal<GlobalState>>()
.get_untracked()
.update_my_profile();
});
view! {
<nav class="inner">
<li>
@ -19,11 +25,7 @@ pub fn Nav() -> impl IntoView {
{
move || global_state.with(|state| state.my_profile.clone().unwrap().person.username)
}
<button on:click=move |_| {
// TODO: not executed
dbg!(1);
do_logout()
}>
<button on:click=move |_| logout_action.dispatch(())>
Logout
</button>
</p>
@ -40,14 +42,3 @@ pub fn Nav() -> impl IntoView {
</nav>
}
}
fn do_logout() {
dbg!("do logout");
create_action(move |()| async move {
dbg!("run logout action");
GlobalState::api_client().logout().await.unwrap();
expect_context::<RwSignal<GlobalState>>()
.get()
.update_my_profile();
});
}

View File

@ -26,12 +26,19 @@ pub fn Article() -> impl IntoView {
},
);
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
let (count, set_count) = create_signal(0);
view! {
<Suspense fallback=|| view! { "Loading..." }>
{move || article.get().map(|article|
view! {
<div class="item-view">
<h1>{article.article.title}</h1>
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
<button on:click=move |_| {
set_count.update(|n| *n += 1);
}>Edit {move || count.get()}</button>
</Show>
<div>{article.article.text}</div>
</div>
})}

View File

@ -8,7 +8,7 @@ pub async fn main() -> ibis_lib::backend::error::MyResult<()> {
.filter_module("ibis", LevelFilter::Info)
.init();
let database_url = "postgres://ibis:password@localhost:5432/ibis";
ibis_lib::backend::start("localhost:8131", database_url).await?;
ibis_lib::backend::start("127.0.0.1:8131", database_url).await?;
Ok(())
}

View File

@ -115,16 +115,7 @@ impl IbisInstance {
username: username.to_string(),
password: "hunter2".to_string(),
};
// use a separate http client for each backend instance, with cookie store for auth
// how to pass the client/hostname to api client methods?
// probably create a struct ApiClient(hostname, client) with all api methods in impl
// TODO: seems that cookie isnt being stored? or maybe wrong hostname?
let jar = Arc::new(Jar::default());
let client = ClientBuilder::new()
.cookie_store(true)
.cookie_provider(jar.clone())
.build()
.unwrap();
let client = ClientBuilder::new().cookie_store(true).build().unwrap();
let api_client = ApiClient::new(client, hostname.clone());
api_client.register(form).await.unwrap();
Self {