exchange contact with QRcode

Niko PLP 1 day ago
parent 6fe3624ba0
commit a9ddf9ad32
  1. 15
      ng-app/src/apps/ContactEditor.svelte
  2. 103
      ng-app/src/apps/ProfileEditor.svelte
  3. 28
      ng-app/src/apps/ProfileQrCode.svelte
  4. 1
      ng-app/src/locales/en.json
  5. 45
      ng-net/src/types.rs
  6. 1
      ng-repo/src/errors.rs
  7. 63
      ng-verifier/src/inbox_processor.rs
  8. 117
      ng-verifier/src/request_processor.rs
  9. 6
      ng-verifier/src/verifier.rs

@ -30,6 +30,7 @@
check_has_camera, check_has_camera,
toast_error, toast_error,
display_error, display_error,
online
} from "../store"; } from "../store";
import { onDestroy, onMount, tick } from "svelte"; import { onDestroy, onMount, tick } from "svelte";
@ -95,21 +96,25 @@
} }
</script> </script>
<div class="flex-col p-5" bind:this={container}> <div class="flex-col p-5"bind:this={container}>
<h1 class="font-bold text-xl text-blue-700">Contact</h1>
{#if !has_camera && !has_name} {#if !has_camera && !has_name}
<Alert class="m-2" color="red">No camera available. You cannot import with QR-code</Alert> <Alert class="m-2" color="red" style="word-break: break-word;" >No camera available. You cannot import with QR-code</Alert>
{/if} {/if}
{#if !has_name} {#if !has_name && has_camera}
<Button <Button
on:click={test} on:click={open_scanner}
on:keypress={open_scanner} on:keypress={open_scanner}
disabled={!$online}
class="select-none ml-2 mt-2 mb-2 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55" class="select-none ml-2 mt-2 mb-2 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
> >
<QrCode tabindex="-1" class="mr-2 focus:outline-none" /> <QrCode tabindex="-1" class="mr-2 focus:outline-none" />
Import with QR-code Import with QR-code
</Button><br/> </Button><br/>
{/if} {/if}
Name: {has_name || ""}<br/> {#if has_name}
Name: {has_name}<br/>
{/if}
{#if has_email} {#if has_email}
Email: {has_email}<br/> Email: {has_email}<br/>
{/if} {/if}

@ -23,66 +23,87 @@
import { import {
openModalCreate, openModalCreate,
sparql_query, sparql_query,
active_session active_session,
toast_error,
display_error,
toast_success,
} from "../store"; } from "../store";
import { import {
Clipboard CheckCircle,
ArrowLeft
} from "svelte-heros-v2"; } from "svelte-heros-v2";
export let commits; export let commits;
let name = "";
let email = "";
let readonly = false;
$: valid = name.trim().length > 1 && email.trim().length > 6 && email.indexOf("@") >= 0 && email.indexOf("\"") < 0;
function contained(graph) { function contained(graph) {
let ret = [];
for (const g of graph) { for (const g of graph) {
if (g.substring(57,90) === "http://www.w3.org/ns/ldp#contains") { if (g.substring(57,91) === "http://www.w3.org/2006/vcard/ns#fn") {
let nuri = g.substring(93,146); name = g.substring(94, g.length-1);
let repo = nuri; readonly = true;
nuri = nuri + ":" + $cur_tab.store.overlay; } else if (g.substring(57,97) === "http://www.w3.org/2006/vcard/ns#hasEmail") {
let hash = nuri.substring(9,16); email = g.substring(100, g.length-1);
ret.push({nuri,hash,repo}); readonly = true;
} }
} }
ret.sort((a, b) => a.hash.localeCompare(b.hash));
return ret;
} }
async function fetch_header(repo) { $: if (commits) { contained(commits.graph) }
async function save() {
try { try {
let res = await ng.fetch_header($active_session.session_id, repo); console.log($cur_tab.doc.nuri);
return res; //TODO: more sanitation on the input here!
}catch(e){ await ng.sparql_update($active_session.session_id, "PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>"
console.error(e); +"INSERT DATA { <> a vcard:Individual . <> vcard:fn \""+name.replace('"',"\\\"")+"\". <> vcard:hasEmail \""+email+"\" }", "did:ng:"+$cur_tab.doc.nuri );
return {}; toast_success("Your profile was edited successfully!");
set_view_or_edit(true);
} catch (e) {
toast_error(display_error(e));
} }
} }
const create = () => { function cancel() {
openModalCreate(); set_view_or_edit(true);
} }
const config = {
class: "mr-2 w-6 h-6 shrink-0 focus:outline-none"
}
</script> </script>
<div class="flex-col p-5"> <div class="flex-col p-5">
{#each contained(commits.graph) as doc} <h2>Editing your profile</h2>
{#await fetch_header(doc.repo)} <input
<div class="flex"> <Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/><div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{doc.hash}</a> </div> </div> class="mt-5"
{:then header} id="name"
<div class="flex" title="{header.about || ''}"> {#if header.class}<DataClassIcon {config} dataClass={header.class}/>{:else}<Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/>{/if}<div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{header.title || doc.hash}</a> </div></div> placeholder="Enter your name"
{/await} bind:value={name}
{/each} disabled={readonly}
{#if commits.graph.length == 0 || contained(commits.graph).length == 0} />
<p>{$t("doc.empty_container")}</p> <br/>
{#if $cur_tab_doc_can_edit} <input
class="mt-5"
id="name"
placeholder="Enter your email address"
bind:value={email}
disabled={readonly}
/>
<br/>
<Button
on:click={save}
disabled={!valid || readonly}
class="select-none mt-5 mb-2 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<CheckCircle tabindex="-1" class="mr-2 focus:outline-none" />
Save
</Button>
<button <button
on:click={create} on:click={cancel}
on:keypress={create} class="mt-5 mb-2 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 ounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
class="select-none ml-0 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55" ><ArrowLeft
tabindex="-1"
class="mr-2 focus:outline-none"
/>Cancel</button
> >
<PlusCircle tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.create")}
</button>
{/if}
{/if}
</div> </div>

@ -15,7 +15,7 @@
import { link, push } from "svelte-spa-router"; import { link, push } from "svelte-spa-router";
import { onDestroy, onMount, tick } from "svelte"; import { onDestroy, onMount, tick } from "svelte";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte"; import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import{ PlusCircle, ArrowLeft } from "svelte-heros-v2"; import{ PlusCircle, ArrowLeft, PencilSquare } from "svelte-heros-v2";
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
import { import {
@ -24,7 +24,8 @@
import { import {
openModalCreate, openModalCreate,
sparql_query, sparql_query,
active_session active_session,
display_error,
} from "../store"; } from "../store";
@ -34,6 +35,7 @@
let generation_state: "before_start" | "loading" | "generated" = let generation_state: "before_start" | "loading" | "generated" =
"before_start"; "before_start";
let generated_qr: string | undefined = undefined; let generated_qr: string | undefined = undefined;
let error = undefined;
async function scrollToTop() { async function scrollToTop() {
await tick(); await tick();
@ -51,21 +53,41 @@
async function generate_qr_code() { async function generate_qr_code() {
generation_state = "loading"; generation_state = "loading";
console.log(container.clientWidth); try {
generated_qr = await ng.get_qrcode_for_profile( generated_qr = await ng.get_qrcode_for_profile(
$active_session.session_id, $active_session.session_id,
$cur_tab.store.store_type == "public", // are we public or protected? $cur_tab.store.store_type == "public", // are we public or protected?
Math.min(container.clientWidth, 800) Math.min(container.clientWidth, 800)
); );
generation_state = "generated"; generation_state = "generated";
} catch (e) {
error = e;
}
} }
function back_to_profile_viewer() { function back_to_profile_viewer() {
set_viewer("n:g:z:profile"); set_viewer("n:g:z:profile");
} }
function edit() {
set_editor("n:g:z:profile_editor");
set_view_or_edit(false);
}
</script> </script>
<div class="flex-col" bind:this={container}> <div class="flex-col" bind:this={container}>
{#if error}
<Alert class="m-2" color="red" style="word-break: break-word;">{display_error(error)}</Alert>
<button
on:click={edit}
on:keypress={edit}
class="select-none mx-6 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><PencilSquare
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>Edit profile</button
>
{/if}
{#if generation_state == "generated"} {#if generation_state == "generated"}
<div class="mx-auto"> <div class="mx-auto">
{@html generated_qr} {@html generated_qr}

@ -658,6 +658,7 @@
"SocialQueryAlreadyStarted": "Social Query already started", "SocialQueryAlreadyStarted": "Social Query already started",
"ContactAlreadyExists": "Contact already added to your account", "ContactAlreadyExists": "Contact already added to your account",
"ContactNotFound": "You don't have any contact. We cannot start the Social Query", "ContactNotFound": "You don't have any contact. We cannot start the Social Query",
"InvalidProfile": "Your profile is incomplete. You should add a name before you can share your profile with others",
"no_wasm_on_old_safari": "Your Safari browser is too old (version before 14.1). As a result we cannot load Automerge, needed for this document. Please upgrade your macOS or iOS system", "no_wasm_on_old_safari": "Your Safari browser is too old (version before 14.1). As a result we cannot load Automerge, needed for this document. Please upgrade your macOS or iOS system",
"BrowserTooOld": "Your browser is too old. Please upgrade it, use another browser, or install our native app. If you are using jshelter or another javascript protection mechanism, please deactivate it as we need access to the WebWorker facility of your browser.", "BrowserTooOld": "Your browser is too old. Please upgrade it, use another browser, or install our native app. If you are using jshelter or another javascript protection mechanism, please deactivate it as we need access to the WebWorker facility of your browser.",
"NoLocalStorage": "You have disabled local storage in your browser. Please allow the current website (and https://nextgraph.net website) to store data in this browser as otherwise we cannot proceed with Wallet creation. After allowing storage, please refresh the current page. You might need to all third-party cookies too." "NoLocalStorage": "You have disabled local storage in your browser. Please allow the current website (and https://nextgraph.net website) to store data in this browser as otherwise we cannot proceed with Wallet creation. After allowing storage, please refresh the current page. You might need to all third-party cookies too."

@ -3763,6 +3763,35 @@ impl InboxPost {
Err(NgError::InvalidNuri) Err(NgError::InvalidNuri)
} }
pub fn new_contact_details(
from_profile_store_repo: StoreRepo,
from_inbox: PrivKey,
to_overlay: OverlayId,
to_inbox: PubKey,
to_broker: Option<Locator>,
with_readcap: bool,
name: String,
email: Option<String>
) -> Result<Self, NgError> {
let from_overlay = from_profile_store_repo.outer_overlay();
let content = InboxMsgContent::ContactDetails(ContactDetails{
profile: from_profile_store_repo,
read_cap: if with_readcap {unimplemented!();} else {None},
name,
email
});
return Ok(InboxPost::new(
to_overlay,
to_inbox,
Some((from_overlay,from_inbox)),
&content,
vec![],
to_broker
)?);
}
} }
/// Request to publish an event in pubsub /// Request to publish an event in pubsub
@ -4186,6 +4215,20 @@ pub struct SocialQueryResponse {
pub content: SocialQueryResponseContent, pub content: SocialQueryResponseContent,
} }
/// ContactDetails sent in reply to scanning a QRcode of a profile
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ContactDetails {
/// Profile Nuri
pub profile: StoreRepo,
/// optional readcap on the profile, if user wants to share the content of profile
pub read_cap: Option<ReadCap>,
pub name: String,
pub email: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SocialQuery { pub enum SocialQuery {
Request(SocialQueryRequest), Request(SocialQueryRequest),
@ -4196,7 +4239,7 @@ pub enum SocialQuery {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InboxMsgContent { pub enum InboxMsgContent {
ContactDetails, ContactDetails(ContactDetails),
DialogRequest, DialogRequest,
Link, Link,
Patch, Patch,

@ -396,6 +396,7 @@ pub enum VerifierError {
InvalidProfile, InvalidProfile,
ContactAlreadyExists, ContactAlreadyExists,
InternalError, InternalError,
InvalidInboxPost,
} }
impl Error for VerifierError {} impl Error for VerifierError {}

@ -24,7 +24,7 @@ use crate::verifier::*;
impl Verifier { impl Verifier {
async fn post_to_inbox(&self, post: InboxPost) -> Result<(), VerifierError> { pub(crate) async fn post_to_inbox(&self, post: InboxPost) -> Result<(), VerifierError> {
match self.client_request::<_,()>(post).await match self.client_request::<_,()>(post).await
{ {
Err(e) => Err(VerifierError::InboxError(e.to_string())), Err(e) => Err(VerifierError::InboxError(e.to_string())),
@ -79,17 +79,16 @@ impl Verifier {
Ok(()) Ok(())
} }
fn get_privkey_of_inbox(&self, this_overlay: &OverlayId) -> Result<PrivKey, VerifierError> { pub(crate) fn get_privkey_of_inbox(&self, this_overlay: &OverlayId) -> Result<PrivKey, VerifierError> {
let store = self.get_store_by_overlay_id(this_overlay)?; let store = self.get_store_by_overlay_id(this_overlay)?;
let repo = self.repos.get(&store.id()).ok_or(NgError::RepoNotFound)?; let repo = self.repos.get(&store.id()).ok_or(NgError::RepoNotFound)?;
let from_inbox = repo.inbox.to_owned().ok_or(NgError::InboxNotFound)?; let from_inbox = repo.inbox.to_owned().ok_or(NgError::InboxNotFound)?;
Ok(from_inbox) Ok(from_inbox)
} }
pub(crate) fn get_profile_replying_to(&self, forwarded_from_profile: &String) -> Result< fn get_profile_replying_to(&self, from_profile: &String) -> Result<(OverlayId, PrivKey) ,NgError> {
(OverlayId, PrivKey) ,NgError> {
let from_profile_id = if forwarded_from_profile.starts_with("did:ng:b") { let from_profile_id = if from_profile.starts_with("did:ng:b") {
self.config.protected_store_id.unwrap() self.config.protected_store_id.unwrap()
} else { } else {
self.config.public_store_id.unwrap() self.config.public_store_id.unwrap()
@ -571,12 +570,62 @@ impl Verifier {
} }
SocialQueryResponseContent::QueryResult(_) | SocialQueryResponseContent::False | SocialQueryResponseContent::True => { SocialQueryResponseContent::QueryResult(_) | SocialQueryResponseContent::False | SocialQueryResponseContent::True => {
// not implemented yet // not implemented yet
unimplemented!(); return Err(VerifierError::NotImplemented)
} }
} }
} }
_ => unimplemented!() InboxMsgContent::ContactDetails(details) => {
if msg.body.from_inbox.is_none() {
// TODO log error
// we do nothing as this is invalid msg. it must have a from.
return Err(VerifierError::InvalidInboxPost);
}
let inbox_nuri_string: String = NuriV0::inbox(&msg.body.from_inbox.unwrap());
let profile_nuri_string: String = NuriV0::from_store_repo_string(&details.profile);
let a_or_b = if details.profile.is_public() { "site" } else { "protected" };
// checking if this contact has already been added
match self.sparql_query(
&NuriV0::new_entire_user_site(),
format!("ASK {{ ?s <did:ng:x:ng#{a_or_b}_inbox> <{inbox_nuri_string}> . ?s <did:ng:x:ng#{a_or_b}> <{profile_nuri_string}> }}"), None).await?
{
QueryResults::Boolean(true) => {
return Err(VerifierError::ContactAlreadyExists);
}
_ => {}
}
let contact = self.doc_create_with_store_repo(
"Graph".to_string(), "social:contact".to_string(),
"store".to_string(), None // meaning in private store
).await?;
let contact_nuri = NuriV0::new_from_repo_graph(&contact)?;
let contact_id = contact_nuri.target.repo_id().clone();
let contact_nuri_string = NuriV0::repo_id(&contact_id);
let has_email = details.email.map_or("".to_string(), |email| format!("<> vcard:hasEmail \"{email}\"."));
// adding triples in contact doc
let sparql_update = format!(" PREFIX ng: <did:ng:x:ng#>
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
INSERT DATA {{ <> ng:{a_or_b} <{profile_nuri_string}>.
<> ng:{a_or_b}_inbox <{inbox_nuri_string}>.
<> a vcard:Individual .
<> vcard:fn \"{}\".
{has_email} }}", details.name);
let ret = self
.process_sparql_update(&contact_nuri, &sparql_update, &Some(contact_nuri_string), vec![])
.await;
if let Err(e) = ret {
return Err(VerifierError::SparqlError(e));
}
self.update_header(&contact_nuri.target, Some(details.name), None).await?;
}
_ => return Err(VerifierError::NotImplemented)
} }
Ok(()) Ok(())
} }

@ -153,7 +153,7 @@ impl Verifier {
} }
} }
async fn update_header(&mut self, target: &NuriTargetV0, title: Option<String>, about: Option<String>) -> Result<(), VerifierError> { pub(crate) async fn update_header(&mut self, target: &NuriTargetV0, title: Option<String>, about: Option<String>) -> Result<(), VerifierError> {
let (repo_id, branch_id, store_repo) = self.resolve_header_branch(target)?; let (repo_id, branch_id, store_repo) = self.resolve_header_branch(target)?;
let graph_name = NuriV0::branch_repo_graph_name( let graph_name = NuriV0::branch_repo_graph_name(
@ -713,18 +713,31 @@ impl Verifier {
Ok(nuri_result) Ok(nuri_result)
} }
fn get_profile_for_inbox_post(&self, public: bool) -> Result<(StoreRepo, PrivKey),NgError> {
let from_profile_id = if !public {
self.config.protected_store_id.unwrap()
} else {
self.config.public_store_id.unwrap()
};
let repo = self.repos.get(&from_profile_id).ok_or(NgError::RepoNotFound)?;
let inbox = repo.inbox.to_owned().ok_or(NgError::InboxNotFound)?;
let store_repo = repo.store.get_store_repo();
Ok( (store_repo.clone(), inbox.clone()) )
}
async fn import_contact_from_qrcode(&mut self, repo_id: RepoId, contact: NgQRCodeProfileSharingV0) -> Result<(), VerifierError> { async fn import_contact_from_qrcode(&mut self, repo_id: RepoId, contact: NgQRCodeProfileSharingV0) -> Result<(), VerifierError> {
let inbox_nuri_string: String = NuriV0::inbox(&contact.inbox); let inbox_nuri_string: String = NuriV0::inbox(&contact.inbox);
let profile_nuri_string: String = NuriV0::from_store_repo_string(&contact.profile); let profile_nuri_string: String = NuriV0::from_store_repo_string(&contact.profile);
let a_or_b = if contact.profile.is_public() { "a" } else { "b" }; let a_or_b = if contact.profile.is_public() { "site" } else { "protected" };
// checking if this contact has already been added // checking if this contact has already been added
match self.sparql_query( match self.sparql_query(
&NuriV0::new_entire_user_site(), &NuriV0::new_entire_user_site(),
format!("ASK {{ ?s <did:ng:x:ng#d> <{inbox_nuri_string}> . ?s <did:ng:x:ng#{}> <{profile_nuri_string}> }}", format!("ASK {{ ?s <did:ng:x:ng#{a_or_b}_inbox> <{inbox_nuri_string}> . ?s <did:ng:x:ng#{a_or_b}> <{profile_nuri_string}> }}"), None).await?
a_or_b ), None).await?
{ {
QueryResults::Boolean(true) => { QueryResults::Boolean(true) => {
return Err(VerifierError::ContactAlreadyExists); return Err(VerifierError::ContactAlreadyExists);
@ -732,6 +745,37 @@ impl Verifier {
_ => {} _ => {}
} }
// getting the privkey of the inbox and ovelray because we will need it here below to send responses.
let (from_profile, from_inbox) = self.get_profile_for_inbox_post(contact.profile.is_public())?;
// get the name and optional email address of the profile we will respond with.
// if we don't have a name, we fail
let from_profile_nuri = NuriV0::repo_id(from_profile.repo_id());
let (name,email) = match self.sparql_query(
&NuriV0::from_store_repo(&from_profile),
format!("PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
SELECT ?name ?email WHERE {{ <> vcard:fn ?name . <> vcard:hasEmail ?email }}"), Some(from_profile_nuri)).await?
{
QueryResults::Solutions(mut sol) => {
let mut name = None;
let mut email = None;
if let Some(Ok(s)) = sol.next() {
if let Some(Term::Literal(l)) = s.get("name") {
name = Some(l.value().to_string());
}
if let Some(Term::Literal(l)) = s.get("email") {
email = Some(l.value().to_string());
}
}
if name.is_none() {
return Err(VerifierError::InvalidProfile)
}
(name.unwrap(),email)
}
_ => return Err(VerifierError::InvalidResponse),
};
let contact_doc_nuri_string = NuriV0::repo_id(&repo_id); let contact_doc_nuri_string = NuriV0::repo_id(&repo_id);
let contact_doc_nuri = NuriV0::new_repo_target_from_id(&repo_id); let contact_doc_nuri = NuriV0::new_repo_target_from_id(&repo_id);
let has_email = contact.email.map_or("".to_string(), |email| format!("<> vcard:hasEmail \"{email}\".")); let has_email = contact.email.map_or("".to_string(), |email| format!("<> vcard:hasEmail \"{email}\"."));
@ -739,7 +783,7 @@ impl Verifier {
let sparql_update = format!(" PREFIX ng: <did:ng:x:ng#> let sparql_update = format!(" PREFIX ng: <did:ng:x:ng#>
PREFIX vcard: <http://www.w3.org/2006/vcard/ns#> PREFIX vcard: <http://www.w3.org/2006/vcard/ns#>
INSERT DATA {{ <> ng:{a_or_b} <{profile_nuri_string}>. INSERT DATA {{ <> ng:{a_or_b} <{profile_nuri_string}>.
<> ng:d <{inbox_nuri_string}>. <> ng:{a_or_b}_inbox <{inbox_nuri_string}>.
<> a vcard:Individual . <> a vcard:Individual .
<> vcard:fn \"{}\". <> vcard:fn \"{}\".
{has_email} }}", contact.name); {has_email} }}", contact.name);
@ -752,9 +796,61 @@ impl Verifier {
self.update_header(&contact_doc_nuri.target, Some(contact.name), None).await?; self.update_header(&contact_doc_nuri.target, Some(contact.name), None).await?;
self.post_to_inbox(InboxPost::new_contact_details(
from_profile,
from_inbox,
contact.profile.outer_overlay(),
contact.inbox,
None,
false,
name,
email,
)?).await?;
Ok(()) Ok(())
} }
pub(crate) async fn search_for_contacts(&self, excluding_profile_id_nuri: Option<String>) -> Result<Vec<(String,String)>, VerifierError> {
let extra_conditions = if let Some(s) = excluding_profile_id_nuri {
format!("&& NOT EXISTS {{ ?c ng:site <{s}> }} && NOT EXISTS {{ ?c ng:protected <{s}> }}")
} else {
String::new()
};
let sparql = format!("PREFIX ng: <did:ng:x:ng#>
SELECT ?profile_id ?inbox_id WHERE
{{ ?c a <http://www.w3.org/2006/vcard/ns#Individual> .
OPTIONAL {{ ?c ng:site ?profile_id . ?c ng:site_inbox ?inbox_id }}
OPTIONAL {{ ?c ng:protected ?profile_id . ?c ng:protected_inbox ?inbox_id }}
FILTER ( bound(?profile_id) {extra_conditions} )
}}");
log_info!("{sparql}");
let sols = match self.sparql_query(
&NuriV0::new_entire_user_site(),
sparql, None).await?
{
QueryResults::Solutions(sols) => { sols }
_ => return Err(VerifierError::SparqlError(NgError::InvalidResponse.to_string())),
};
let mut res = vec![];
for sol in sols {
match sol {
Err(e) => return Err(VerifierError::SparqlError(e.to_string())),
Ok(s) => {
if let Some(Term::NamedNode(profile_id)) = s.get("profile_id") {
let profile_nuri = profile_id.as_string();
if let Some(Term::NamedNode(inbox_id)) = s.get("inbox_id") {
let inbox_nuri = inbox_id.as_string();
res.push((profile_nuri.clone(), inbox_nuri.clone()));
}
}
}
}
}
Ok(res)
}
pub(crate) async fn process( pub(crate) async fn process(
&mut self, &mut self,
command: &AppRequestCommandV0, command: &AppRequestCommandV0,
@ -790,12 +886,13 @@ impl Verifier {
return Err(NgError::NotConnected); return Err(NgError::NotConnected);
} }
// TODO: search for contacts (all stores, one store, a sparql query, etc..) // searching for contacts (all stores, one store, a sparql query, etc..)
// (profile_nuri, inbox_nuri) // (profile_nuri, inbox_nuri)
let contacts = if contacts_string.as_str() == "did:ng:d:c" { let contacts = if contacts_string.as_str() == "did:ng:d:c" {
let mut res = vec![]; self.search_for_contacts(None).await?
res.push(("did:ng:a:rjoQTS4LMBDcuh8CEjmTYrgALeApBg2cgKqyPEuQDUgA".to_string(),"did:ng:d:KMFdOcGjdFBQgA9QNEDWcgEErQ1isbvDe7d_xndNOUMA".to_string())); // let mut res = vec![];
res // res.push(("did:ng:a:rjoQTS4LMBDcuh8CEjmTYrgALeApBg2cgKqyPEuQDUgA".to_string(),"did:ng:d:KMFdOcGjdFBQgA9QNEDWcgEErQ1isbvDe7d_xndNOUMA".to_string()));
// res
} else { } else {
return Ok(AppResponse::error(NgError::NotImplemented.to_string())); return Ok(AppResponse::error(NgError::NotImplemented.to_string()));
}; };

@ -1585,7 +1585,7 @@ impl Verifier {
<> vcard:hasEmail ?email . <> vcard:hasEmail ?email .
}}"); }}");
//log_info!("{sparql}"); //log_info!("{sparql}");
let (mut name, mut email) = match self.sparql_query( let (name, email) = match self.sparql_query(
&NuriV0::new_repo_target_from_id(profile_id), &NuriV0::new_repo_target_from_id(profile_id),
sparql, Some(NuriV0::repo_id(profile_id))).await? sparql, Some(NuriV0::repo_id(profile_id))).await?
{ {
@ -1616,9 +1616,7 @@ impl Verifier {
_ => return Err(VerifierError::SparqlError(NgError::InvalidResponse.to_string())), _ => return Err(VerifierError::SparqlError(NgError::InvalidResponse.to_string())),
}; };
if name.is_none() { if name.is_none() {
//return Err(VerifierError::InvalidProfile); return Err(VerifierError::InvalidProfile);
name = Some("no name".to_string());
email = Some("fake@email.com".to_string());
} }
let profile_sharing = NgQRCode::ProfileSharingV0(NgQRCodeProfileSharingV0 { let profile_sharing = NgQRCode::ProfileSharingV0(NgQRCodeProfileSharingV0 {
inbox, inbox,

Loading…
Cancel
Save