Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: integrate auth metadata api #71

Merged
merged 1 commit into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions did/individual_user_template.did
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ type UserProfileUpdateDetailsFromFrontend = record {
profile_picture_url : opt text;
display_name : opt text;
};
service : {
service : (IndividualUserTemplateInitArgs) -> {
add_post_v2 : (PostDetailsFromFrontend) -> (Result);
backup_data_to_backup_canister : (principal, principal) -> ();
bet_on_currently_viewing_post : (PlaceBetArg) -> (Result_1);
Expand Down Expand Up @@ -344,4 +344,4 @@ service : {
update_profiles_that_follow_me_toggle_list_with_specified_profile : (
FollowerArg,
) -> (Result_2);
}
}
5 changes: 3 additions & 2 deletions did/platform_orchestrator.did
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ type WasmType = variant {
SubnetOrchestratorWasm;
};
service : (PlatformOrchestratorInitArgs) -> {
get_next_available_subnet : () -> (principal) query;
get_all_available_subnet_orchestrators : () -> (vec principal) query;
get_all_subnet_orchestrators : () -> (vec principal) query;
get_subnet_last_upgrade_status : () -> (CanisterUpgradeStatus) query;
get_version : () -> (text) query;
provision_subnet_orchestrator_canister : (principal) -> (Result);
subnet_orchestrator_maxed_out : () -> ();
upgrade_canister : (UpgradeCanisterArg) -> (Result_1);
upload_wasms : (WasmType, vec nat8) -> (Result_1);
}
}
2 changes: 1 addition & 1 deletion did/post_cache.did
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type TopPostsFetchError = variant {
InvalidBoundsPassed;
ExceededMaxNumberOfItemsAllowedInOneRequest;
};
service : {
service : (PostCacheInitArgs) -> {
get_cycle_balance : () -> (nat) query;
get_top_posts_aggregated_from_canisters_on_this_network_for_home_feed : (
nat64,
Expand Down
9 changes: 8 additions & 1 deletion did/user_index.did
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type CanisterStatusResponse = record {
memory_size : nat;
cycles : nat;
settings : DefiniteCanisterSettings;
query_stats : QueryStats;
idle_cycles_burned_per_day : nat;
module_hash : opt vec nat8;
};
Expand All @@ -27,6 +28,12 @@ type KnownPrincipalType = variant {
CanisterIdSnsGovernance;
UserIdGlobalSuperAdmin;
};
type QueryStats = record {
response_payload_bytes_total : nat;
num_instructions_total : nat;
num_calls_total : nat;
request_payload_bytes_total : nat;
};
type RejectionCode = variant {
NoError;
CanisterError;
Expand Down Expand Up @@ -70,7 +77,7 @@ type UserIndexInitArgs = record {
version : text;
access_control_map : opt vec record { principal; vec UserAccessRole };
};
service : {
service : (UserIndexInitArgs) -> {
are_signups_enabled : () -> (bool) query;
backup_all_individual_user_canisters : () -> ();
create_pool_of_individual_user_available_canisters : (text, vec nat8) -> (
Expand Down
3 changes: 2 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
upload::UploadPostPage,
},
state::{
auth::AuthState,
auth::{AuthClient, AuthState},
canisters::{do_canister_auth, Canisters},
},
utils::MockPartialEq,
Expand All @@ -33,6 +33,7 @@ pub fn App() -> impl IntoView {
provide_context(PostViewCtx::default());
let auth_state = AuthState::default();
provide_context(auth_state.clone());
provide_context(AuthClient::default());
provide_context(Resource::local(
move || MockPartialEq(auth_state.identity.get()),
|auth| do_canister_auth(auth.0),
Expand Down
5 changes: 4 additions & 1 deletion src/component/auth_provider.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{
consts::AUTH_URL,
state::auth::{auth_state, DelegationIdentity, SessionResponse},
state::auth::{
auth_state,
types::{DelegationIdentity, SessionResponse},
},
};
use leptos::*;
use leptos_use::{use_event_listener, use_window};
Expand Down
2 changes: 1 addition & 1 deletion src/component/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use reqwest::Url;

use crate::{
consts::{self, ACCOUNT_CONNECTED_STORE},
state::auth::{auth_state, SessionResponse},
state::auth::{auth_state, types::SessionResponse},
};

#[component]
Expand Down
26 changes: 10 additions & 16 deletions src/page/profile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use leptos_icons::*;
use leptos_router::*;

use crate::{
component::spinner::FullScreenSpinner, state::canisters::unauth_canisters,
component::spinner::FullScreenSpinner,
state::{auth::auth_client, canisters::unauth_canisters},
utils::profile::ProfileDetails,
};

Expand Down Expand Up @@ -109,28 +110,21 @@ fn ProfileViewInner(user: ProfileDetails, user_canister: Principal) -> impl Into
#[component]
pub fn ProfileView() -> impl IntoView {
let params = use_params::<ProfileParams>();
let principal_or_username = move || {
let principal = move || {
params.with(|p| {
let ProfileParams { id } = p.as_ref().ok()?;

let res = Principal::from_text(id).map_err(|_| id.clone());
Some(res)
Principal::from_text(id).ok()
})
};

let user_details = create_resource(principal_or_username, |principal_or_username| async move {
let user_details = create_resource(principal, |principal| async move {
let canisters = unauth_canisters();
let user_index = canisters.user_index();
let user_canister = match principal_or_username? {
Ok(p) => user_index
.get_user_canister_id_from_user_principal_id(p)
.await
.ok()??,
Err(u) => user_index
.get_user_canister_id_from_unique_user_name(u)
.await
.ok()??,
};
let auth = auth_client();
let user_canister = auth
.get_individual_canister_by_user_principal(principal?)
.await
.ok()??;
let user = canisters.individual_user(user_canister);
let user_details = user.get_profile_details().await.ok()?;
Some((user_details.into(), user_canister))
Expand Down
113 changes: 113 additions & 0 deletions src/state/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
pub mod types;

use std::num::ParseIntError;

use ic_agent::{export::Principal, identity::DelegatedIdentity};

use leptos::{create_effect, create_signal, expect_context, Effect, ReadSignal, RwSignal};
use leptos_use::storage::{use_local_storage, StringCodec};
use thiserror::Error;

use crate::consts::{ACCOUNT_CONNECTED_STORE, AUTH_URL};
use types::{DelegationIdentity, SessionResponse, UserDetails};

#[derive(Error, Debug, Clone)]
pub enum AuthError {
#[error("Invalid Secret Key")]
InvalidSecretKey(#[from] k256::elliptic_curve::Error),
#[error("Invalid expiry")]
InvalidExpiry(#[from] ParseIntError),
#[error("reqwest error: {0}")]
Reqwest(String),
}

impl From<reqwest::Error> for AuthError {
fn from(e: reqwest::Error) -> Self {
AuthError::Reqwest(e.to_string())
}
}

#[derive(Default, Clone)]
pub struct AuthClient {
client: reqwest::Client,
}

impl AuthClient {
pub async fn generate_session(&self) -> Result<DelegatedIdentity, AuthError> {
let resp: SessionResponse = self
.client
.post(AUTH_URL.join("api/generate_session").unwrap())
.send()
.await?
.json()
.await?;
resp.delegation_identity.try_into()
}

pub async fn update_user_metadata(
&self,
id: DelegationIdentity,
user_canister: Principal,
username: String,
) -> Result<(), AuthError> {
let details = UserDetails {
delegation_identity: id,
user_canister_id: user_canister.to_text(),
user_name: username,
};
let res = self
.client
.post(AUTH_URL.join("rest_api/update_user_metadata").unwrap())
.json(&details)
.send()
.await?;
if res.status().is_success() {
Ok(())
} else {
Err(AuthError::Reqwest(res.text().await?))
}
}

pub async fn get_individual_canister_by_user_principal(
&self,
user_principal: Principal,
) -> Result<Option<Principal>, AuthError> {
let res = self
.client
.post(AUTH_URL.join("rest_api/get_user_canister").unwrap())
.json(&user_principal.to_text())
.send()
.await?
.text()
.await?;
Ok(Principal::from_text(res).ok())
}
}

pub fn auth_client() -> AuthClient {
expect_context()
}

#[derive(Default, Clone)]
pub struct AuthState {
pub identity: RwSignal<Option<DelegationIdentity>>,
}

pub fn auth_state() -> AuthState {
expect_context()
}

/// Prevents hydration bugs if the value in store is used to conditionally show views
/// this is because the server will always get a `false` value and do rendering based on that
pub fn account_connected_reader() -> (ReadSignal<bool>, Effect<()>) {
let (read_account_connected, _, _) =
use_local_storage::<bool, StringCodec>(ACCOUNT_CONNECTED_STORE);
let (is_connected, set_is_connected) = create_signal(false);

(
is_connected,
create_effect(move |_| {
set_is_connected(read_account_connected());
}),
)
}
68 changes: 6 additions & 62 deletions src/state/auth.rs → src/state/auth/types.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
use std::num::ParseIntError;

use candid::Principal;
use ic_agent::identity::{DelegatedIdentity, Secp256k1Identity};
use k256::SecretKey;

use leptos::{create_effect, create_signal, expect_context, Effect, ReadSignal, RwSignal};
use leptos_use::storage::{use_local_storage, StringCodec};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::consts::{ACCOUNT_CONNECTED_STORE, AUTH_URL};
use super::AuthError;

#[derive(Debug, Serialize, Clone)]
struct PrincipalId {
Expand Down Expand Up @@ -90,60 +85,9 @@ pub struct SessionResponse {
pub delegation_identity: DelegationIdentity,
}

#[derive(Error, Debug, Clone)]
pub enum AuthError {
#[error("Invalid Secret Key")]
InvalidSecretKey(#[from] k256::elliptic_curve::Error),
#[error("Invalid expiry")]
InvalidExpiry(#[from] ParseIntError),
#[error("reqwest error: {0}")]
Reqwest(String),
}

impl From<reqwest::Error> for AuthError {
fn from(e: reqwest::Error) -> Self {
AuthError::Reqwest(e.to_string())
}
}

#[derive(Default, Clone)]
pub struct AuthClient {
client: reqwest::Client,
}

impl AuthClient {
pub async fn generate_session(&self) -> Result<DelegatedIdentity, AuthError> {
let resp: SessionResponse = self
.client
.post(AUTH_URL.join("api/generate_session").unwrap())
.send()
.await?
.json()
.await?;
resp.delegation_identity.try_into()
}
}

#[derive(Default, Clone)]
pub struct AuthState {
pub identity: RwSignal<Option<DelegationIdentity>>,
}

pub fn auth_state() -> AuthState {
expect_context()
}

/// Prevents hydration bugs if the value in store is used to conditionally show views
/// this is because the server will always get a `false` value and do rendering based on that
pub fn account_connected_reader() -> (ReadSignal<bool>, Effect<()>) {
let (read_account_connected, _, _) =
use_local_storage::<bool, StringCodec>(ACCOUNT_CONNECTED_STORE);
let (is_connected, set_is_connected) = create_signal(false);

(
is_connected,
create_effect(move |_| {
set_is_connected(read_account_connected());
}),
)
#[derive(Serialize)]
pub struct UserDetails {
pub delegation_identity: DelegationIdentity,
pub user_canister_id: String,
pub user_name: String,
}
Loading
Loading