GUIs for importing wallet with username and password

master
Niko PLP 7 days ago
parent 7331289e0f
commit 6aa87f1467
  1. 10
      ng-app/src-tauri/src/lib.rs
  2. 2
      ng-app/src/App.svelte
  3. 3
      ng-app/src/api.ts
  4. 13
      ng-app/src/lib/components/PasswordInput.svelte
  5. 21
      ng-app/src/locales/de.json
  6. 36
      ng-app/src/locales/en.json
  7. 2
      ng-app/src/routes/WalletCreate.svelte
  8. 29
      ng-app/src/routes/WalletLogin.svelte
  9. 2
      ng-app/src/routes/WalletLoginQr.svelte
  10. 3
      ng-app/src/routes/WalletLoginTextCode.svelte
  11. 319
      ng-app/src/routes/WalletLoginUsername.svelte
  12. 2
      ng-net/Cargo.toml
  13. 17
      ng-net/src/types.rs
  14. 37
      ng-net/src/utils.rs
  15. 11
      ng-sdk-js/src/lib.rs

@ -359,6 +359,15 @@ async fn decode_invitation(invite: String) -> Option<Invitation> {
decode_invitation_string(invite) decode_invitation_string(invite)
} }
#[tauri::command(rename_all = "snake_case")]
async fn retrieve_ng_bootstrap(
location: String,
) -> Result<ng_net::types::LocalBootstrapInfo, String> {
ng_net::utils::retrieve_ng_bootstrap(&location)
.await
.ok_or("cannot retrieve bootstrap".to_string())
}
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn file_get( async fn file_get(
session_id: u64, session_id: u64,
@ -1073,6 +1082,7 @@ impl AppBuilder {
signed_snapshot_request, signed_snapshot_request,
update_header, update_header,
fetch_header, fetch_header,
retrieve_ng_bootstrap,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

@ -41,6 +41,7 @@
import ng from "./api"; import ng from "./api";
import AccountInfo from "./routes/AccountInfo.svelte"; import AccountInfo from "./routes/AccountInfo.svelte";
import WalletLoginUsername from "./routes/WalletLoginUsername.svelte";
import WalletLoginQr from "./routes/WalletLoginQr.svelte"; import WalletLoginQr from "./routes/WalletLoginQr.svelte";
import WalletLoginTextCode from "./routes/WalletLoginTextCode.svelte"; import WalletLoginTextCode from "./routes/WalletLoginTextCode.svelte";
@ -48,6 +49,7 @@
routes.set("/", Home); routes.set("/", Home);
routes.set("/test", Test); routes.set("/test", Test);
routes.set("/wallet/login", WalletLogin); routes.set("/wallet/login", WalletLogin);
routes.set("/wallet/username", WalletLoginUsername);
routes.set("/wallet/login-qr", WalletLoginQr); routes.set("/wallet/login-qr", WalletLoginQr);
routes.set("/wallet/login-text-code", WalletLoginTextCode); routes.set("/wallet/login-text-code", WalletLoginTextCode);
routes.set("/wallet/create", WalletCreate); routes.set("/wallet/create", WalletCreate);

@ -54,7 +54,8 @@ const mapping = {
"signed_snapshot_request": ["session_id", "nuri"], "signed_snapshot_request": ["session_id", "nuri"],
"signature_request": ["session_id", "nuri"], "signature_request": ["session_id", "nuri"],
"update_header": ["session_id","nuri","title","about"], "update_header": ["session_id","nuri","title","about"],
"fetch_header": ["session_id", "nuri"] "fetch_header": ["session_id", "nuri"],
"retrieve_ng_bootstrap": ["location"],
} }

@ -13,9 +13,10 @@
export let value: string | undefined = undefined; export let value: string | undefined = undefined;
export let placeholder: string | undefined = undefined; export let placeholder: string | undefined = undefined;
export let className: string | undefined = undefined; export let className: string | undefined = undefined;
export let classNameToggle: string | undefined = "right-0";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
export let auto_complete: string | undefined = undefined; export let auto_complete: string | undefined = undefined;
import { createEventDispatcher } from "svelte";
export let show: boolean = false; export let show: boolean = false;
let input; let input;
@ -28,6 +29,8 @@
value = target.value; value = target.value;
} }
const dispatch = createEventDispatcher();
async function toggle() { async function toggle() {
let { selectionStart, selectionEnd } = input; let { selectionStart, selectionEnd } = input;
show = !show; show = !show;
@ -37,6 +40,11 @@
input.selectionEnd = selectionEnd; input.selectionEnd = selectionEnd;
}, 0); }, 0);
} }
const key_pressed = async (e: any) => {
if (e.key == "Enter" || e.keyCode == 13) {
dispatch("enter");
}
}
</script> </script>
<div class="relative"> <div class="relative">
@ -51,10 +59,11 @@
on:input={handleInput} on:input={handleInput}
class={`${className} pr-12 text-md block`} class={`${className} pr-12 text-md block`}
autocomplete={auto_complete} autocomplete={auto_complete}
on:keypress={key_pressed}
/> />
<div <div
class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm leading-5" class={`${classNameToggle} absolute inset-y-0 pr-3 flex items-center text-sm leading-5`}
> >
<svg <svg
fill="none" fill="none"

@ -152,9 +152,10 @@
}, },
"choose_broker": "Bitte wähle einen Broker aus der Liste", "choose_broker": "Bitte wähle einen Broker aus der Liste",
"register_with_broker": "Beim Broker {broker} registrieren", "register_with_broker": "Beim {broker} registrieren",
"for_eu_citizens": "Bürger der Europäischen Union", "for_eu_citizens": "Europäischen Server",
"for_rest": "Für den Rest der Welt", "for_rest": "internationaler Server",
"this_broker": "dieser Broker",
"enter_invite_link": "Gib einen Einladungslink ein", "enter_invite_link": "Gib einen Einladungslink ein",
"scan_invite_qr": "Scanne einen Einladungs-QR-Code", "scan_invite_qr": "Scanne einen Einladungs-QR-Code",
"self_hosted_broker": "Selbst gehosteter Broker", "self_hosted_broker": "Selbst gehosteter Broker",
@ -162,7 +163,7 @@
"registration_success": "Du wurdest erfolgreich bei {broker} registriert", "registration_success": "Du wurdest erfolgreich bei {broker} registriert",
"choose_pin": { "choose_pin": {
"title": "Beginne mit dem Erstellen deines Wallets und wähle einen PIN", "title": "Beginne mit dem Erstellen deines Wallets und wähle einen PIN",
"description": "Wir empfehlen, einen PIN zu wählen, den du bereits sehr gut kennst.<br /> Dein Kreditkarten-PIN ist zum Beispiel eine gute Wahl.<br />Wir bei NextGraph werden deinen PIN niemals sehen.", "description": "Wir empfehlen, einen PIN zu wählen, den du bereits sehr gut kennst.<br />Wir bei NextGraph werden deinen PIN niemals sehen.",
"rules": "Hier sind die Regeln für den PIN:", "rules": "Hier sind die Regeln für den PIN:",
"1": "Er darf keine Zahlenfolge wie 1234 oder 8765 sein.", "1": "Er darf keine Zahlenfolge wie 1234 oder 8765 sein.",
"2": "Die gleiche Ziffer darf nicht mehr als einmal wiederholt werden. Zum Beispiel ist 4484 ungültig.", "2": "Die gleiche Ziffer darf nicht mehr als einmal wiederholt werden. Zum Beispiel ist 4484 ungültig.",
@ -234,18 +235,18 @@
"from_import.title": "Dein Wallet wurde übertragen", "from_import.title": "Dein Wallet wurde übertragen",
"from_import.description": "Dein Wallet wurde empfangen:", "from_import.description": "Dein Wallet wurde empfangen:",
"from_import.instruction": "Um den Import abzuschließen, melde dich bitte an.", "from_import.instruction": "Um den Import abzuschließen, melde dich bitte an.",
"with_another_wallet": "Mit einem anderen Wallet anmelden", "with_username": "Mit Benutzername",
"import_wallet": "Dein Wallet importieren", "import_file": "Wallet-Datei",
"import_file": "Wallet-Datei importieren", "import_qr": "Mit QR-Code",
"import_qr": "Mit QR-Code importieren", "import_link": "Mit TextCode",
"import_link": "Mit TextCode importieren", "new_wallet": "neues Wallet erstellen",
"new_wallet": "Ein neues Wallet erstellen",
"logged_in": "Du bist angemeldet.<br />Bitte warte, während die App geladen wird." "logged_in": "Du bist angemeldet.<br />Bitte warte, während die App geladen wird."
}, },
"wallet_login_qr": { "wallet_login_qr": {
"title": "Wallet mit QR-Code importieren", "title": "Wallet mit QR-Code importieren",
"scan.description": "Um dein Wallet von einem anderen Gerät zu importieren, erzeuge dort einen Export-QR-Code. Gehe zum Erstellen zu<br /><span class=\"path\">Nutzerbereich > Wallet > Export: QR-Code erstellen</span>.", "scan.description": "Um dein Wallet von einem anderen Gerät zu importieren, erzeuge dort einen Export-QR-Code. Gehe zum Erstellen zu<br /><span class=\"path\">Nutzerbereich > Wallet > Export: QR-Code erstellen</span>.",
"scan.modal.title": "Export-QR-Code scannen", "scan.modal.title": "Export-QR-Code scannen",
"gen.button": "QR-Code anzeigen",
"gen.description": "Um dein Wallet von einem anderen Gerät zu importieren, kannst du hier auf diesem Gerät einen Import-QR-Code generieren und ihn dann mit deinem anderen Gerät scannen. Gehe auf dem anderen Gerät zu<br /><span class=\"path\">Nutzerbereich > Wallet > Export: QR scannen</span>, um zu exportieren.", "gen.description": "Um dein Wallet von einem anderen Gerät zu importieren, kannst du hier auf diesem Gerät einen Import-QR-Code generieren und ihn dann mit deinem anderen Gerät scannen. Gehe auf dem anderen Gerät zu<br /><span class=\"path\">Nutzerbereich > Wallet > Export: QR scannen</span>, um zu exportieren.",
"gen.generated": "Scanne diesen QR-Code vom anderen Gerät aus.", "gen.generated": "Scanne diesen QR-Code vom anderen Gerät aus.",
"success_btn": "Weiter zur Anmeldung" "success_btn": "Weiter zur Anmeldung"

@ -342,7 +342,7 @@
}, },
"no_wallet": { "no_wallet": {
"welcome": "Welcome to NextGraph", "welcome": "Welcome to NextGraph",
"description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Log in\", otherwise, select \"Create Wallet\" here below.", "description": "We could not find a wallet saved on this device.<br /> If you already have a wallet, select \"Login\", otherwise, select \"Create Wallet\" here below.",
"create_wallet": "Create Wallet" "create_wallet": "Create Wallet"
}, },
"login": { "login": {
@ -425,7 +425,8 @@
"choose_broker": "Please choose one broker among the list", "choose_broker": "Please choose one broker among the list",
"register_with_broker": "Register with {broker}", "register_with_broker": "Register with {broker}",
"for_eu_citizens": "European Union Server", "for_eu_citizens": "European Union Server",
"for_rest": "For the rest of the world", "for_rest": "International Server",
"this_broker": "this Broker",
"enter_invite_link": "Enter an invitation link", "enter_invite_link": "Enter an invitation link",
"scan_invite_qr": "Scan an invitation QR-code", "scan_invite_qr": "Scan an invitation QR-code",
"self_hosted_broker": "Self-hosted broker", "self_hosted_broker": "Self-hosted broker",
@ -434,7 +435,7 @@
"registration_success": "You have been successfully registered to {broker}", "registration_success": "You have been successfully registered to {broker}",
"choose_pin": { "choose_pin": {
"title": "Let's start creating your wallet by choosing a PIN code", "title": "Let's start creating your wallet by choosing a PIN code",
"description": "We recommend you to choose a PIN code that you already know very well. <br /> Your credit card PIN, by example, is a good choice.<br />We at NextGraph will never see your PIN.", "description": "We recommend you to choose a PIN code that you already know very well. <br />We at NextGraph will never see your PIN.",
"rules": "Here are the rules for the PIN:", "rules": "Here are the rules for the PIN:",
"1": "It cannot be a series like 1234 or 8765.", "1": "It cannot be a series like 1234 or 8765.",
"2": "The same digit cannot repeat more than once. By example 4484 is invalid.", "2": "The same digit cannot repeat more than once. By example 4484 is invalid.",
@ -509,13 +510,35 @@
"from_import.title": "Your wallet has been transferred", "from_import.title": "Your wallet has been transferred",
"from_import.description": "Your wallet has been received from the other device!", "from_import.description": "Your wallet has been received from the other device!",
"from_import.instruction": "To finish the import, please log in.", "from_import.instruction": "To finish the import, please log in.",
"with_another_wallet": "Log in with another wallet", "with_username": "with my Username",
"import_wallet": "Import your wallet",
"import_file": "Import a Wallet File", "import_file": "Import a Wallet File",
"import_qr": "Import with QR-Code", "import_qr": "Import with QR-Code",
"import_link": "Import with TextCode", "import_link": "Import with TextCode",
"new_wallet": "Create a new Wallet", "new_wallet": "Create a new Wallet",
"logged_in": "You are logged in.<br /> please wait while the app is loading" "logged_in": "You are logged in.<br /> please wait while the app is loading",
"offline_advice": "If you do not have internet on this device, you can use the \"Import a Wallet file\" method instead."
},
"wallet_login_username": {
"title": "Import Wallet with your Username",
"description": "If you have created a username and password for your Wallet, then you can enter the details here below in order to import your wallet from your broker. You only have to do that once on this device.",
"username": "Username",
"username_placeholder_domain": "with the @domain part",
"username_placeholder_without_domain": "without @{domain}",
"username_placeholder_without_at": "without the @domain part",
"password": "Password",
"password_placeholder": "Enter your password here",
"next": "Next",
"connect": "Connect",
"redirect": "Redirect me",
"retrieve_button": "Connect & Retrieve Wallet",
"success_btn": "Continue to Login",
"error.username": "The username you entered is invalid. Only alphanumerical characters are allowed (together with hyphen and underscore)",
"error.nodomainplease": "Please enter your username without a trailing @domain",
"warning.nodomainplease": "You shouldn't enter any @domain part for your username",
"warning.nospecificdomainplease": "You don't need to enter the @{domain} part of your username",
"error.invalid_domain": "the @domain part is invalid",
"error.mandatory_domain": "You must enter the @domain part of your username.",
"error.need_redirect": "This user is from another broker. Do you want to be redirected to that broker?"
}, },
"wallet_login_qr": { "wallet_login_qr": {
"title": "Import Wallet with QR-Code", "title": "Import Wallet with QR-Code",
@ -523,7 +546,6 @@
"scan.modal.title": "Scan Wallet QR-Code", "scan.modal.title": "Scan Wallet QR-Code",
"gen.button": "Generate QR-Code", "gen.button": "Generate QR-Code",
"gen.description": "To import your wallet from another device, you have to generate a QR-Code here on this device, and then scan it with your other device (the one where your wallet is located for now).<br/>If your other device does not have a camera, then you have to use another method for importing your wallet here.", "gen.description": "To import your wallet from another device, you have to generate a QR-Code here on this device, and then scan it with your other device (the one where your wallet is located for now).<br/>If your other device does not have a camera, then you have to use another method for importing your wallet here.",
"offline_advice": "If you do not have internet on this device, you can use the \"Import a Wallet file\" method instead.",
"gen.letsgo": "Ready? On your other device, you first have to be logged-in (wallet is opened) and then you go to<br /><span class=\"path\">User Panel > Wallet > Export by scanning a QR-code</span>.<br />Then on this present device, click below on the<br/><span class=\"path\">Generate QR-Code</span> button.", "gen.letsgo": "Ready? On your other device, you first have to be logged-in (wallet is opened) and then you go to<br /><span class=\"path\">User Panel > Wallet > Export by scanning a QR-code</span>.<br />Then on this present device, click below on the<br/><span class=\"path\">Generate QR-Code</span> button.",
"gen.generated": "Scan this QR-Code from the other device.<br/>You have 5 minutes to do so.", "gen.generated": "Scan this QR-Code from the other device.<br/>You have 5 minutes to do so.",
"success_btn": "Continue to Login" "success_btn": "Continue to Login"

@ -999,7 +999,7 @@
/> />
</svg> </svg>
{$t("pages.wallet_create.register_with_broker", { {$t("pages.wallet_create.register_with_broker", {
values: { broker: pre_invitation.V0.name || "this broker" }, values: { broker: pre_invitation.V0.name || $t("pages.wallet_create.this_broker") },
})} })}
</button> </button>
</div> </div>

@ -37,7 +37,7 @@
redirect_after_login, redirect_after_login,
redirect_if_wallet_is redirect_if_wallet_is
} from "../store"; } from "../store";
import { CheckBadge, ExclamationTriangle, QrCode } from "svelte-heros-v2"; import { CheckBadge, ExclamationTriangle, QrCode, Cloud } from "svelte-heros-v2";
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
@ -363,15 +363,16 @@
</div> </div>
{/each} {/each}
<div class="wallet-box"> <div class="wallet-box">
{#if $has_wallets} <a href="/wallet/username" use:link>
<p class="mt-1"> <button
{$t("pages.wallet_login.with_another_wallet")} style="justify-content: left;"
</p> tabindex="-1"
{:else} class="mt-2.5 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
<p class="mt-1"> >
{$t("pages.wallet_login.import_wallet")} <Cloud class="w-8 h-8 mr-2 -ml-1" tabindex="-1"/>
</p> {$t("pages.wallet_login.with_username")}
{/if} </button>
</a>
<Fileupload <Fileupload
style="display:none;" style="display:none;"
id="import_wallet_file" id="import_wallet_file"
@ -379,7 +380,7 @@
on:change={handleWalletUpload} on:change={handleWalletUpload}
/> />
<button <button
class=" mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2" class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
on:click={() => { on:click={() => {
document.getElementById("import_wallet_file").click(); document.getElementById("import_wallet_file").click();
}} }}
@ -405,7 +406,7 @@
<button <button
style="justify-content: left;" style="justify-content: left;"
tabindex="-1" tabindex="-1"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2" class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
> >
<QrCode class="w-8 h-8 mr-2 -ml-1" tabindex="-1"/> <QrCode class="w-8 h-8 mr-2 -ml-1" tabindex="-1"/>
{$t("pages.wallet_login.import_qr")} {$t("pages.wallet_login.import_qr")}
@ -415,7 +416,7 @@
<button <button
style="justify-content: left;" style="justify-content: left;"
tabindex="-1" tabindex="-1"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2" class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
> >
<svg <svg
class="w-8 h-8 mr-2 -ml-1" class="w-8 h-8 mr-2 -ml-1"
@ -438,7 +439,7 @@
<a href="/wallet/create" use:link> <a href="/wallet/create" use:link>
<button <button
tabindex="-1" tabindex="-1"
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2" class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
> >
<svg <svg
class="w-8 h-8 mr-2 -ml-1" class="w-8 h-8 mr-2 -ml-1"

@ -127,7 +127,7 @@
{@html $t("wallet_sync.offline_warning")} {@html $t("wallet_sync.offline_warning")}
</Alert> </Alert>
<Alert color="blue" class="mt-4"> <Alert color="blue" class="mt-4">
{@html $t("pages.wallet_login_qr.offline_advice")} {@html $t("pages.wallet_login.offline_advice")}
</Alert> </Alert>
</div> </div>
{:else if error} {:else if error}

@ -73,6 +73,9 @@
<Alert color="red"> <Alert color="red">
{@html $t("wallet_sync.offline_warning")} {@html $t("wallet_sync.offline_warning")}
</Alert> </Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_login.offline_advice")}
</Alert>
</div> </div>
{/if} {/if}

@ -0,0 +1,319 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { t, format } from "svelte-i18n";
import { Alert, Spinner } from "flowbite-svelte";
import {
ArrowLeft,
ExclamationTriangle,
Cloud,
ChevronDoubleRight,
} from "svelte-heros-v2";
import { onDestroy, onMount, tick } from "svelte";
import { push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import PasswordInput from "../lib/components/PasswordInput.svelte";
import { wallet_from_import, display_error } from "../store";
import ng from "../api";
let top: HTMLElement;
const set_online = () => { connected = true; };
const set_offline = () => { connected = false; };
let error;
let connected = true;
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let pre_invitation = false;
let domain = undefined;
let for_opaque = undefined ;
let state: "username" | "password" | "connecting" = "username";
function scrollToTop() {
top.scrollIntoView();
}
onMount(async () => {
connected = window.navigator.onLine;
window.addEventListener("offline", set_offline);
window.addEventListener("online", set_online);
state = "username";
username = "";
if (!tauri_platform) {
let res = await ng.get_local_bootstrap_and_domain(
import.meta.env.PROD ? location.href : "http://localhost:14400"
);
pre_invitation = res[0];
domain = res[1];
console.log("pre_invitation", pre_invitation, domain);
}
scrollToTop();
await tick();
username_input.focus();
});
onDestroy(() => {
window.removeEventListener("offline", set_offline);
window.removeEventListener("online", set_online);
});
let password = "";
const validate_password = async () => {
console.log(password, for_opaque);
}
let username_input;
let username = "";
let redirect = undefined;
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/i;
const usernameRegex = /^[a-zA-Z_]+[a-zA-Z0-9_-]*$/;
const validate_username = async (e: any) => {
if (!e || e.key == "Enter" || e.keyCode == 13) {
username_input.blur();
if (pre_invitation) {
if (!domain) {
let u = username.trim();
if (u.includes("@")) {
syntax_error = $t("pages.wallet_login_username.error.nodomainplease");
} else if (!usernameRegex.test(u)) {
syntax_error = $t("pages.wallet_login_username.error.username");
} else {
for_opaque = pre_invitation.V0.bootstrap;
for_opaque.username = u;
next();
}
} else {
let parts = username.trim().split("@");
if (!usernameRegex.test(parts[0])) {
syntax_error = $t("pages.wallet_login_username.error.username");
}
else if ( parts[1] === domain || !parts[1] ) {
username = parts[0];
for_opaque = pre_invitation.V0.bootstrap;
for_opaque.username = username;
next();
} else {
// testing that domain is valid
if (!domainRegex.test(parts[1])) {
syntax_error = $t("pages.wallet_login_username.error.invalid_domain");
} else {
redirect = `https://${parts[1]}/#/wallet/username?u=${parts[0]}`;
syntax_error = $t("pages.wallet_login_username.error.need_redirect");
// TODO: when receiving a ?u=... after fetching it with opaque, if the wallet is already present locally, dont show an error, just log in with the username/password.
}
}
}
} else if (tauri_platform) {
let parts = username.trim().split("@");
if (!usernameRegex.test(parts[0])) {
syntax_error = $t("pages.wallet_login_username.error.username");
}
else if (!parts[1]) {
syntax_error = $t("pages.wallet_login_username.error.mandatory_domain");
} else {
// testing that domain is valid
if (!domainRegex.test(parts[1])) {
syntax_error = $t("pages.wallet_login_username.error.invalid_domain");
} else {
// fetching the .ng_bootstrap of the domain
state = "connecting";
try {
let bootstrap_info = await ng.retrieve_ng_bootstrap(`https://${parts[1]}`);
for_opaque = bootstrap_info.V0.bootstrap;
for_opaque.username = parts[0];
// do opaque with that
next();
} catch (e) {
error = e;
return;
}
}
}
} else {
syntax_error = "your local broker cannot be found (unexpected error)";
}
}
};
let placeholder = "";
$: placeholder = pre_invitation ? domain ? $format("pages.wallet_login_username.username_placeholder_without_domain", {
values: { domain }}) : $t("pages.wallet_login_username.username_placeholder_without_at") :
$t("pages.wallet_login_username.username_placeholder_domain");
let warning = "";
$: warning = domain && username.trim().endsWith("@"+domain) && $format("pages.wallet_login_username.warning.nospecificdomainplease", {
values: { domain }}) || pre_invitation && !domain && username.includes("@")
&& $t("pages.wallet_login_username.warning.nodomainplease") || "";
const next = () => {
for_opaque.username = for_opaque.username.toLowerCase();
state = "password";
}
let syntax_error = "";
</script>
<CenteredLayout>
<div class="container3" bind:this={top}>
<div
class="flex flex-col justify-center max-w-md mb-5 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<!-- Title -->
<div class="mx-6">
<h2 class="text-xl mb-6">{$t("pages.wallet_login_username.title")}</h2>
</div>
{#if !connected}
<!-- Warning, if offline -->
<div class="text-left mx-6">
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_login.offline_advice")}
</Alert>
<!-- Go Back -->
<button
on:click={() => window.history.go(-1)}
class="mt-8 w-full text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
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"
/>{$t("buttons.back")}</button
>
</div>
{:else if error}
<div class="max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" />
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", {
values: { message: display_error(error) },
})}
</p>
<button
on:click={() => window.history.go(-1)}
class="mt-8 mr-2 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
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"
/>{$t("buttons.back")}</button
>
</div>
{:else}
{#if state == "username"}
<div class="mx-6">
<div class="mx-auto">
<div class="my-4 mx-1 mt-4">
{#if syntax_error}
<Alert color="red" class="mb-3">
{syntax_error}
</Alert>
{/if}
{#if warning}
<Alert color="blue" class="mb-3">
{warning}
</Alert>
{/if}
{$t("pages.wallet_login_username.username")} :
<input
bind:this={username_input}
class="w-[240px] mr-0"
id="username_input"
placeholder={placeholder}
bind:value={username}
on:keypress={validate_username}
on:focus={()=>{syntax_error="";redirect=undefined;}}
/>
<!-- Go Back -->
<button
on:click={() => {if (redirect) {username_input.focus();} else {window.history.go(-1)}}}
class="mt-8 mr-2 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
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"
/>{$t("buttons.back")}</button
>
{#if redirect}
<button
on:click={() => {window.location.href = redirect;}}
class="mt-4 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<ChevronDoubleRight
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"
/>
{$t("pages.wallet_login_username.redirect")}
</button>
{:else}
<button
on:click={() => validate_username(null)}
class="mt-4 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<ChevronDoubleRight
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"
/>
{$t("pages.wallet_login_username.next")}
</button>
{/if}
</div>
</div>
</div>
{:else if state === "password"}
<div class="mx-6">
<div class="mx-auto">
<div class="my-4 mx-1 mt-4">
{$t("pages.wallet_login_username.password")} :
<!-- <input
bind:this={password_input}
class="w-[240px] mr-0"
id="password_input"
bind:value={password}
on:keypress={validate_password}
/> -->
<PasswordInput
id="password_input"
placeholder={$t("pages.wallet_login_username.password_placeholder")}
bind:value={password}
on:enter={validate_password}
classNameToggle="right-[-26px]"
className="w-[240px] bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
<!-- Go Back -->
<button
on:click={async () => {state = "username";for_opaque = undefined; await tick(); username_input.focus();}}
class="mt-8 mr-1 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
<button
on:click={validate_password}
class="mt-4 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<ChevronDoubleRight
tabindex="-1"
class="w-8 h-8 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_username.connect")}
</button>
</div>
</div>
</div>
{:else if state === "connecting"}
<div>
<Spinner class="w-full" />
</div>
{/if}
{/if}
</div>
</div>
</CenteredLayout>

@ -37,8 +37,6 @@ regex = "1.8.4"
base64-url = "2.0.0" base64-url = "2.0.0"
web-time = "0.2.0" web-time = "0.2.0"
ng-repo = { path = "../ng-repo", version = "0.1.1-alpha" } ng-repo = { path = "../ng-repo", version = "0.1.1-alpha" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
reqwest = { version = "0.11.18", features = ["json","native-tls-vendored"] } reqwest = { version = "0.11.18", features = ["json","native-tls-vendored"] }
[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]

@ -503,6 +503,14 @@ impl BrokerServerV0 {
} }
} }
pub fn get_domain(&self) -> Option<String> {
if let BrokerServerTypeV0::Domain(domain) = &self.server_type {
Some(domain.clone())
} else {
None
}
}
/// on web browser, returns the connection URL and an optional list of BindAddress if a relay is needed /// on web browser, returns the connection URL and an optional list of BindAddress if a relay is needed
/// filtered by the current location url of the webpage /// filtered by the current location url of the webpage
/// on native apps (do not pass a location), returns or the connection URL without optional BindAddress or an empty string with /// on native apps (do not pass a location), returns or the connection URL without optional BindAddress or an empty string with
@ -825,6 +833,15 @@ impl Invitation {
Invitation::V0(v0) => &v0.bootstrap.servers, Invitation::V0(v0) => &v0.bootstrap.servers,
} }
} }
pub fn get_domain(&self) -> Option<String> {
for bootstrap in self.get_servers() {
let res = bootstrap.get_domain();
if res.is_some() {
return res;
}
}
None
}
pub fn set_name(&mut self, name: Option<String>) { pub fn set_name(&mut self, name: Option<String>) {
if name.is_some() { if name.is_some() {

@ -28,7 +28,6 @@ use ng_repo::types::PubKey;
use ng_repo::{log::*, types::PrivKey}; use ng_repo::{log::*, types::PrivKey};
use crate::types::*; use crate::types::*;
#[cfg(target_arch = "wasm32")]
use crate::NG_BOOTSTRAP_LOCAL_PATH; use crate::NG_BOOTSTRAP_LOCAL_PATH;
use crate::WS_PORT; use crate::WS_PORT;
@ -243,6 +242,42 @@ async fn retrieve_ng_bootstrap(location: &String) -> Option<LocalBootstrapInfo>
} }
} }
#[cfg(not(target_arch = "wasm32"))]
pub async fn retrieve_ng_bootstrap(location: &String) -> Option<LocalBootstrapInfo> {
let url = Url::parse(location).unwrap();
let prefix = url.origin().unicode_serialization();
let url = format!("{}{}", prefix, NG_BOOTSTRAP_LOCAL_PATH);
log_info!("url {}", url);
let resp = reqwest::get(url).await;
//log_info!("{:?}", resp);
if resp.is_ok() {
let resp = resp.unwrap().json::<LocalBootstrapInfo>().await;
return if resp.is_ok() {
Some(resp.unwrap())
} else {
None
};
} else {
//log_info!("err {}", resp.unwrap_err());
return None;
}
}
// #[cfg(target_arch = "wasm32")]
// pub async fn retrieve_domain(location: String) -> Option<String> {
// let info = retrieve_ng_bootstrap(&location).await;
// if info.is_none() {
// return None;
// }
// for bootstrap in info.unwrap().servers() {
// let res = bootstrap.get_domain();
// if res.is_some() {
// return res;
// }
// }
// None
// }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub async fn retrieve_local_url(location: String) -> Option<String> { pub async fn retrieve_local_url(location: String) -> Option<String> {
let info = retrieve_ng_bootstrap(&location).await; let info = retrieve_ng_bootstrap(&location).await;

@ -77,6 +77,17 @@ pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
} }
} }
#[wasm_bindgen]
pub async fn get_local_bootstrap_and_domain(location: String) -> JsValue {
let res = retrieve_local_bootstrap(location, None, false).await;
if res.is_some() {
let domain = res.as_ref().unwrap().get_domain();
serde_wasm_bindgen::to_value(&(res.unwrap(), domain)).unwrap()
} else {
serde_wasm_bindgen::to_value(&(false, false)).unwrap()
}
}
#[wasm_bindgen] #[wasm_bindgen]
pub async fn get_local_bootstrap_with_public( pub async fn get_local_bootstrap_with_public(
location: String, location: String,

Loading…
Cancel
Save