diff --git a/.editorconfig b/.editorconfig
index 90b4a7b75..a16c1af96 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -22,3 +22,6 @@ indent_size = 2
[*.nsi]
indent_size = 2
+
+[*.{css,js,html}]
+indent_size = 2
diff --git a/client/api.md b/client/api.md
index 3e350ff52..535d24e25 100644
--- a/client/api.md
+++ b/client/api.md
@@ -15,65 +15,65 @@ HTTP
```
Voici la liste des erreurs par ordre alphabétique:
-- `archived_workspace` (HTTP 403)
-- `archiving_not_allowed` (HTTP 403)
-- `archiving_period_is_too_short` (HTTP 400)
-- `authentication_requested` (HTTP 401)
-- `bad_claimer_sas` (HTTP 400)
-- `bad_data` (HTTP 400)
-- `bad_greeter_sas` (HTTP 400)
-- `bad_key` (HTTP 400)
-- `bad_timestamp_configuration` (HTTP 400)
-- `cannot_delete_root_folder` (HTTP 400)
-- `cannot_move_root_folder` (HTTP 400)
-- `cannot_use_both_authentication_modes` (HTTP 400)
-- `claimer_already_member` (HTTP 400)
-- `claimer_not_a_member` (HTTP 400)
-- `connection_refused_by_server` (HTTP 502)
-- `deleted_workspace` (HTTP 410)
-- `destination_parent_not_a_folder` (HTTP 400)
-- `device_not_found` (HTTP 404)
+- `archived_workspace` (HTTP 403) opération non autorisée, cause : espace archivé
+- `archiving_not_allowed` (HTTP 403) archivage impossible
+- `archiving_period_is_too_short` (HTTP 400) la période d'archivage est trop courte
+- `authentication_requested` (HTTP 401) authentification requise
+- `bad_claimer_sas` (HTTP 400) mauvais code sas
+- `bad_data` (HTTP 400) mauvais champ (le champ doit être présent dans le champ fields)
+- `bad_greeter_sas` (HTTP 400) mauvais code sas
+- `bad_key` (HTTP 400) mot de passe incorrect
+- `bad_timestamp_configuration` (HTTP 400) Date incorrecte
+- `cannot_delete_root_folder` (HTTP 400) impossible de supprimer le dossier racine
+- `cannot_move_root_folder` (HTTP 400) impossible de déplacer le dossier racine
+- `claimer_already_member` (HTTP 400) utilisateur déjà enrôlé
+- `claimer_not_a_member` (HTTP 400) Shamir : le compte à récupérer n'existe pas
+- `connection_refused_by_server` (HTTP 502) Connexion refusée par le serveur
+- `deleted_workspace` (HTTP 410) Opération impossible, espace supprimé
+- `destination_parent_not_a_folder` (HTTP 400) déplacement impossible, la destination n'est pas un dossier
+- `device_not_found` (HTTP 404) fichier de clé non trouvé sur le poste
- `email_not_in_recipients` (HTTP 400)
-- `failed_to_disable_offline_availability` (HTTP 400)
-- `failed_to_enable_offline_availability` (HTTP 400)
-- `file_exists` (HTTP 400)
-- `forbidden_workspace` (HTTP 403)
-- `invalid_configuration` (HTTP 400)
-- `invalid_passphrase` (HTTP 400)
-- `invalid_state` (HTTP 409)
-- `invitation_already_used` (HTTP 400)
-- `invitation_not_found` (HTTP 400)
-- `json_body_expected` (HTTP 400)
-- `mountpoint_already_mounted` (HTTP 400)
-- `mountpoint_not_mounted` (HTTP 404)
-- `no_shamir_recovery_setup` (HTTP 400)
-- `not_enough_shares` (HTTP 400)
-- `not_a_file` (HTTP 404)
-- `not_a_folder` (HTTP 404)
-- `not_connected_to_rie` (HTTP 401)
-- `not_found` (HTTP 404)
-- `not_setup` (HTTP 404)
-- `offline` (HTTP 503)
-- `offline_availability_already_disabled` (HTTP 400)
-- `offline_availability_already_enabled` (HTTP 400)
-- `organization_already_bootstrapped` (HTTP 400)
-- `precondition_failed` (HTTP 409)
-- `read_only_workspace` (HTTP 403)
-- `recipient_already_recovered` (HTTP 400)
-- `sharing_not_allowed` (HTTP 403)
-- `source_not_a_folder` (HTTP 404)
-- `unexpected_error` (HTTP 400)
-- `unknown_destination_parent` (HTTP 404)
-- `unknown_email` (HTTP 404)
-- `unknown_entry` (HTTP 404)
-- `unknown_file` (HTTP 404)
-- `unknown_folder` (HTTP 404)
-- `unknown_organization` (HTTP 404)
-- `unknown_parent` (HTTP 404)
-- `unknown_source` (HTTP 404)
-- `unknown_token` (HTTP 404)
-- `unknown_workspace` (HTTP 404)
-- `users_not_found` (HTTP 400)
+- `failed_to_disable_offline_availability` (HTTP 400) impossible de désactiver la rémanence des données
+- `failed_to_enable_offline_availability` (HTTP 400) impossible d'activer la rémanence des données
+- `file_exists` (HTTP 400) le fichier existe déjà
+- `forbidden_workspace` (HTTP 403) accès à l'espace interdit
+- `host_machine_not_compliant` (HTTP 401) le poste de répond pas aux conditions de conformité
+- `invalid_configuration` (HTTP 400) Shamir : mauvaise configuration
+- `invalid_passphrase` (HTTP 400) import de clé : mauvaise passphrase
+- `invalid_state` (HTTP 409) état invalide (désynchonisation des utilisateurs), il faut repartir à l'étape 1
+- `invitation_already_used` (HTTP 400) invitation déjà utilisée
+- `invitation_not_found` (HTTP 400) invitation introuvable
+- `json_body_expected` (HTTP 400) json attendu en payload
+- `mountpoint_already_mounted` (HTTP 400) point de montage déjà monté
+- `mountpoint_not_mounted` (HTTP 404) point de montage non monté
+- `no_shamir_recovery_setup` (HTTP 400) Shamir : pas de configuration présente
+- `not_enough_shares` (HTTP 400) Shamir : pas assez de partage
+- `not_a_file` (HTTP 404) pas un fichier
+- `not_a_folder` (HTTP 404) pas un dossier
+- `not_connected_to_rie` (HTTP 401) connexion impossible, utilisateur non connecté sur le RIE
+- `not_found` (HTTP 404) introuvable
+- `not_setup` (HTTP 404) Shamir : non paramétré
+- `offline` (HTTP 503) serveur injoignable
+- `offline_availability_already_disabled` (HTTP 400) rémanence des donnée déjà désactivé
+- `offline_availability_already_enabled` (HTTP 400) rémanence des donnée déjà activé
+- `organization_already_bootstrapped` (HTTP 400) organisation déjà validée
+- `precondition_failed` (HTTP 409) mauvais nom actuel de l'espace
+- `read_only_workspace` (HTTP 403) espace en lecture seule
+- `recipient_already_recovered` (HTTP 400) Shamir
+- `sharing_not_allowed` (HTTP 403) partage non autorisé
+- `source_not_a_folder` (HTTP 404) la source n'est pas un dossier
+- `unexpected_error` (HTTP 400) Shamir
+- `unknown_destination_parent` (HTTP 404) destination inconnue
+- `unknown_email` (HTTP 404) email inconnu
+- `unknown_entry` (HTTP 404) entrée inconnue
+- `unknown_file` (HTTP 404) fichier inconnue
+- `unknown_folder` (HTTP 404) dossier inconnu
+- `unknown_organization` (HTTP 404) organisation inconnue
+- `unknown_parent` (HTTP 404) parent inconnu
+- `unknown_source` (HTTP 404) source inconnue
+- `unknown_token` (HTTP 404) token inconnu
+- `unknown_workspace` (HTTP 404) espace inconnu
+- `users_not_found` (HTTP 400) utilisateurs non trouvés
Une erreur dans le formatage de la requête est retournée sous la forme suivante:
@@ -173,8 +173,6 @@ Deux différentes options sont disponibles pour l'authentification :
`email` correspondant à l'email utilisé lors de l'enrôlement RESANA Secure.
-`user_password` doit être une chaîne de caractère échappé pour répondre aux normes json
-
**Response:**
diff --git a/misc/test_book/auth.js b/misc/test_book/auth.js
new file mode 100644
index 000000000..54635521e
--- /dev/null
+++ b/misc/test_book/auth.js
@@ -0,0 +1,97 @@
+// LOGIN
+
+function login() {
+ const email = document.getElementById("auth-email").value;
+ const key = document.getElementById("auth-key").value;
+ const organization = document.getElementById("auth-organization-id").value;
+ const encryptedKey = document.getElementById("auth-encrypted-key").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", "http://localhost:5775/auth");
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({
+ email,
+ key,
+ organization,
+ encrypted_key: encryptedKey,
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("auth-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ document.getElementById("auth-info").innerHTML = `${email} | ${organization}`;
+ tokenSession = JSON.parse(http.responseText).token;
+ listWorkspaces();
+ }
+ }
+}
+
+const storageKey = "resana-secure-release-tests-key-auth";
+
+function saveAuth() {
+ const auth = {};
+ let storage = null;
+ auth['email'] = document.getElementById("auth-email").value;
+ auth['key'] = document.getElementById("auth-key").value;
+ auth['organization'] = document.getElementById("auth-organization-id").value;
+ auth['encryptedKey'] = document.getElementById("auth-encrypted-key").value;
+ auth['password'] = document.getElementById("auth-password").value;
+ storage = localStorage.getItem(storageKey) || "[]";
+ storage = JSON.parse(storage);
+ storage.push(auth);
+ localStorage.setItem(storageKey, JSON.stringify(storage));
+}
+
+function listAuth() {
+ let saves = [];
+ const storage = localStorage.getItem(storageKey);
+ saves = JSON.parse(storage) || [];
+ const modal = document.getElementById("save-modal");
+ modal.style.display = "block";
+ const listElem = document.getElementById("saves-list");
+ listElem.innerHTML = "";
+ saves.forEach((save, index) => {
+ listElem.innerHTML += `${save.email} | ${save.key} | ${save.organization}`;
+ });
+}
+
+function loadAuth(index) {
+ const storage = localStorage.getItem(storageKey);
+ const saves = JSON.parse(storage) || [];
+ document.getElementById("auth-email").value = saves[index].email;
+ document.getElementById("auth-key").value = saves[index].key;
+ document.getElementById("auth-organization-id").value = saves[index].organization;
+ document.getElementById("auth-encrypted-key").value = saves[index].encryptedKey;
+ document.getElementById("auth-password").value = saves[index].password;
+ const modal = document.getElementById("save-modal");
+ modal.style.display = "none";
+}
+
+function closeAuthModal() {
+ const elem = document.getElementById("save-modal");
+ elem.style.display = "none";
+}
+
+
+// LOGOUT
+function deconnect(force = false) {
+ const http = new XMLHttpRequest();
+ http.open("DELETE", "http://localhost:5775/auth");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("auth-info").innerHTML = "";
+ document.getElementById("logout-result").innerHTML = getHttpResult(http);
+ }
+ openAccount(force);
+}
+
+function deconnectAll(force = false) {
+ const http = new XMLHttpRequest();
+ http.open("DELETE", "http://localhost:5775/auth/all");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("auth-info").innerHTML = "";
+ document.getElementById("logout-result").innerHTML = getHttpResult(http);
+ }
+ openAccount(force);
+}
diff --git a/misc/test_book/bootstrap.js b/misc/test_book/bootstrap.js
new file mode 100644
index 000000000..353d5b8cb
--- /dev/null
+++ b/misc/test_book/bootstrap.js
@@ -0,0 +1,18 @@
+function boostrap() {
+ const organizationUrl = document.getElementById("bootstrap-url").value;
+ const sequesterVerifyKey = document.getElementById("sequester-verify-key").value;
+ const email = document.getElementById("bootstrap-email").value;
+ const key = document.getElementById("bootstrap-key").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", "http://localhost:5775/organization/bootstrap");
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({
+ organization_url: organizationUrl,
+ email,
+ key,
+ sequester_verify_key: sequesterVerifyKey
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("bootstrap-result").innerHTML = getHttpResult(http);
+ }
+}
diff --git a/misc/test_book/common.js b/misc/test_book/common.js
new file mode 100644
index 000000000..00bf7a287
--- /dev/null
+++ b/misc/test_book/common.js
@@ -0,0 +1,7 @@
+function getHttpResult(http) {
+ try {
+ return `${http.status}
` + JSON.stringify(JSON.parse(http.responseText), null, 4);
+ } catch (error) {
+ return `${http.status} ${http.responseURL}
` + http.responseText.replace(//gi, ">");
+ }
+}
diff --git a/misc/test_book/file.js b/misc/test_book/file.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/misc/test_book/folder.js b/misc/test_book/folder.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/misc/test_book/index.html b/misc/test_book/index.html
new file mode 100644
index 000000000..b437d79a8
--- /dev/null
+++ b/misc/test_book/index.html
@@ -0,0 +1,1242 @@
+
+
+
+ Resana Secure - release tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Version de la release
+
+
+ Création de l'organisation de test
+ L'organisation de test doit être créée sur le serveur Resana Secure de test avec séquestre et anti-virus.
+ ID organisation : test_release_vX.Y.Z
+ parsec core create_organization test_release_<numéro de version> --addr parsec://<server url> --administration-token <administration token>
+
+ Génération des clés de séquestre et anti-virus
+
+ Les clés doivent être générées sur Linux (Ubuntu 22.04).
+
# clé de signature
+openssl genrsa -out ./authority_key.private 4096
+openssl rsa -in ./authority_key.private -out ./authority_key_.pub -pubout -outform PEM
+# clé de séquestre
+openssl genrsa -out ./sequestre_key.private 4096
+openssl rsa -in ./sequestre_key.private -out ./sequestre_key.pub -pubout -outform PEM
+parsec backend sequester generate_service_certificate --service-label="Service sequestre" --service-public-key=./sequestre_key.pub --authority-private-key=./authority_key.private -o ./sequestre_service_certificate.pem
+# clé d'anti-virus
+openssl genrsa -out ./antivirus_key.private 4096
+openssl rsa -in ./antivirus_key.private -out ./antivirus_key.pub -pubout -outform PEM
+parsec backend sequester generate_service_certificate --service-label="Service antivirus" --service-public-key=./antivirus_key.pub --authority-private-key=./authority_key.private -o ./antivirus_service_certificate.pem
+
+
+ Vérifier que le filtrage d'IP fonctionne.
+
+
+
+ Installation
+ Réinstallation
+ Désinstallation
+
+
+
+
+
+
+
+
+
+ Workspaces
+ Tests à réaliser
+
+ - Création d'un workspace avec un nom valide => statut 201
+ - Création d'un workspace avec un nom invalide (contient entre autre un des caractères suivants \ / : * ? " > < |) => statut 400
+ - Vérifier la création en chargeant la liste
+ - Vérifier la création en regardant le point de montage (si actif)
+ - Renommer un point de montage avec un nouveau nom valide => 200
+ - Renommer un point de montage avec l'ancien nom incorrect => 409
+ - Renommer un point de montage avec un nouveau nom invalide => 400
+ - Renommer un point de montage avec un faux ID organisation => 404
+ - Vérifier le renommage en rechargeant la liste
+ - Vérifier le renommage en regardant le point de montage (si actif)
+
+
+
+
Liste
+
+
+
+
+
+
Création
+
+
+
+
+
+
+
+
Renommage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Folders
+ Tests à réaliser
+
+ - TODO
+ - (contient entre autre un des caractères suivants \ / : * ? " > < |)
+
+
+
+
+
+
+
+
+
Création
+
+
+
+
+
+
+
+
+
+
+
+
Renommer/Déplacer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Suppression
+
+
+
+
+
+
+
+
+
+ Files
+ Tests à réaliser
+
+ - TODO
+ - (contient entre autre un des caractères suivants \ / : * ? " > < |)
+
+
+
+
+
+
+
+
+
+
+
+
Création
+
+
+
+
+
+
+
+
+
+
+
+
Création multipart
+
+
+
+
+
+
+
+
+
+
+
+
Ouvrir
+
+
+
+
+
+
+
+
Renommer/Déplacer
+ TODO
+
+
+
+
Suppression
+ TODO
+
+
+
+
+
+
+ Invitation
+ Tests à réaliser
+
+ - Création OK => statut 200
+ - Suppression OK => statut 204
+
+
+
+
+
+
Création User
+
+
+
+
+
+
+
+
+
Création Device
+
+
+
+
+
+
Suppression
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Humans
+
+
+
Révocation
+
+
+
+
+
+
+
+
+
+ Share
+
+
+
+
+
+
+
+
Edit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Shamir
+
+ Get
+
+
+
+
+
+
+
+
+
+ Setup
+
+
+
+
+
+ Delete
+
+
+
+
+
+
+
+ Sequester
+
+ Création du dump du séquestre
+
parsec backend sequester list_services --organization test_20230419
+parsec backend human_accesses --organization test_20230419
+parsec backend sequester export_realm --organization test_20230419 --service 99ec544e445344a8a985f30a24c2c1bf --realm 6005613898fb45e0ab8b563ceeaa49f5 --output realm-6005613898fb45e0ab8b563ceeaa49f5.sequester-export
+
+
+
+
+ Déconnexion
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/misc/test_book/invitation.js b/misc/test_book/invitation.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/misc/test_book/mount.js b/misc/test_book/mount.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/misc/test_book/offline.js b/misc/test_book/offline.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/misc/test_book/recovery.js b/misc/test_book/recovery.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/misc/test_book/release.js b/misc/test_book/release.js
new file mode 100644
index 000000000..ef16422a0
--- /dev/null
+++ b/misc/test_book/release.js
@@ -0,0 +1,9 @@
+let releaseVersion = "vX.Y.Z";
+
+function releaseVersionChange(event) {
+ releaseVersion = document.getElementById("release-version").value;
+ const elements = document.getElementsByClassName("release-version");
+ for (const element of elements) {
+ element.innerHTML = releaseVersion;
+ }
+}
diff --git a/misc/test_book/search.js b/misc/test_book/search.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/misc/test_book/shamir.js b/misc/test_book/shamir.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/misc/test_book/test_book.css b/misc/test_book/test_book.css
new file mode 100644
index 000000000..2d57feeb2
--- /dev/null
+++ b/misc/test_book/test_book.css
@@ -0,0 +1,178 @@
+html {
+ --color-primary: #4092FF;
+ --color-secondary: #f0f7ff;
+ --color-tertiary: #004299;
+ --color-success: #28BA62;
+ --color-warning: #f6bd09;
+ --color-danger: #cd3246;
+ --color-light: #d7d8da;
+ --color-medium: #92949c;
+
+ /* Primary */
+ --parsec-color-light-primary-30-opacity15: hsla(212, 100%, 97.1%, 0.15000000596046448);
+ --parsec-color-light-primary-30: hsla(212, 100%, 97.1%, 1);
+ --parsec-color-light-primary-50: hsla(212, 100%, 94.9%, 1);
+ --parsec-color-light-primary-100: hsla(214, 100%, 90%, 1);
+ --parsec-color-light-primary-200: hsla(214, 100%, 80%, 1);
+ --parsec-color-light-primary-300: hsla(214, 100%, 70%, 1);
+ --parsec-color-light-primary-400: hsla(214, 100%, 60%, 1);
+ --parsec-color-light-primary-500: hsla(214, 100%, 62.5%, 1);
+ --parsec-color-light-primary-600: hsla(214, 100%, 40%, 1);
+ --parsec-color-light-primary-700: hsla(214, 100%, 30%, 1);
+ --parsec-color-light-primary-800: hsla(214, 100%, 20%, 1);
+ --parsec-color-light-primary-900: hsla(214, 100%, 10%, 1);
+
+ /* Secondary */
+ --parsec-color-light-secondary-contrast: hsla(240, 19.4%, 13.1%, 1);
+ --parsec-color-light-secondary-text: hsla(240, 20.2%, 34.9%, 1);
+ --parsec-color-light-secondary-grey: hsla(240, 19.6%, 60%, 1);
+ --parsec-color-light-secondary-light: hsla(240, 19.5%, 84.9%, 1);
+ --parsec-color-light-secondary-disabled: hsla(240, 20%, 93.1%, 1);
+ --parsec-color-light-secondary-medium: hsla(240, 20%, 96.1%, 1);
+ --parsec-color-light-secondary-premiere: hsla(240, 20%, 96.1%, 1);
+ --parsec-color-light-secondary-background: hsla(240, 20%, 98%, 1);
+ --parsec-color-light-secondary-inversed-contrast: hsla(0, 0%, 99.6%, 1);
+
+ /* ------ alert light------ */
+ --parsec-color-light-success-100: hsla(146, 61.5%, 94.9%, 1);
+ --parsec-color-light-success-500: hsla(144, 64.6%, 44.3%, 1);
+ --parsec-color-light-success-700: hsla(144, 64.7%, 40%, 1);
+
+ --parsec-color-light-warning-100: hsla(45, 92.3%, 94.9%, 1);
+ --parsec-color-light-warning-500: hsla(46, 92.9%, 50%, 1);
+ --parsec-color-light-warning-700: hsla(45, 93.1%, 40%, 1);
+
+ --parsec-color-light-danger-100: hsla(352, 60%, 95.1%, 1);
+ --parsec-color-light-danger-500: hsla(352, 60.8%, 50%, 1);
+ --parsec-color-light-danger-700: hsla(352, 60.8%, 40%, 1);
+
+ /* ------ tags light------ */
+ --parsec-color-tags-blue100: hsla(198, 100%, 92%, 1);
+ --parsec-color-tags-blue500: hsla(198, 82%, 36%, 1);
+ --parsec-color-tags-indigo100: hsla(247, 100%, 95%, 1);
+ --parsec-color-tags-indigo500: hsla(245, 100%, 36%, 1);
+ --parsec-color-tags-orange100: hsla(37, 100%, 95%, 1);
+ --parsec-color-tags-orange500: hsla(36, 100%, 36%, 1);
+ --parsec-color-tags-green100: hsla(68, 100%, 92%, 1);
+ --parsec-color-tags-green500: hsla(72, 90%, 32%, 1);
+
+ /* **** Parsec shadow **** */
+ --parsec-shadow-light: 0px 4px 12px rgba(0, 0, 0, 0.08);
+ --parsec-shadow-strong: 0px 4px 20px rgba(0, 0, 0, 0.15);
+ --parsec-shadow-toggle: -2px 1px 6px rgba(0, 0, 0, 0.24);
+}
+
+body {
+ margin: 0;
+}
+
+nav {
+ top: 56px;
+}
+
+#account-menu {
+ top: 56px;
+ right: 0;
+}
+
+main {
+ margin: 0.5em 1em;
+}
+
+section {
+ display: none;
+}
+
+/* The switch - the box around the slider */
+.switch {
+ position: relative;
+ display: inline-block;
+ width: 35px;
+ height: 20px;
+}
+/* Hide default HTML checkbox */
+.switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+/* The slider */
+.slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: var(--parsec-color-light-secondary-grey);
+ -webkit-transition: .2s;
+ transition: .2s;
+}
+.slider:before {
+ position: absolute;
+ content: "";
+ height: 14px;
+ width: 14px;
+ left: 3px;
+ bottom: 3px;
+ background-color: var(--parsec-color-light-primary-30);
+ -webkit-transition: .2s;
+ transition: .2s;
+}
+input:checked + .slider {
+ background-color: var(--parsec-color-light-primary-600);
+}
+input:focus + .slider {
+ box-shadow: 0 0 1px var(--parsec-color-light-primary-600);
+}
+input:checked + .slider:before {
+ -webkit-transform: translateX(15px);
+ -ms-transform: translateX(15px);
+ transform: translateX(15px);
+}
+/* Rounded sliders */
+.slider.round {
+ border-radius: 34px;
+}
+.slider.round:before {
+ border-radius: 50%;
+}
+
+/***** modal *****/
+.modal {
+ display: none;
+}
+.modal-back {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba( 0, 0, 0, .25 )
+}
+.modal-container {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ padding: 25px;
+ background: white;
+ transform: translate(-50%, -50%)
+}
+
+table {
+ text-align: left;
+ margin-top: 0.5em;
+}
+
+table, th, td {
+ border: 1px solid black;
+ border-collapse: collapse;
+}
+
+.result-section {
+ background-color: #92949c;
+}
+
+.not-implemented {
+ background-color: #f6bd09;
+}
diff --git a/misc/test_book/test_book.js b/misc/test_book/test_book.js
new file mode 100644
index 000000000..2f44b471e
--- /dev/null
+++ b/misc/test_book/test_book.js
@@ -0,0 +1,892 @@
+// NAV
+function openNav() {
+ const nav = document.getElementsByTagName("nav")[0];
+ if (nav.style.opacity === "") {
+ nav.style.display = "block";
+ nav.style.opacity = "initial";
+ } else {
+ nav.style.display = "none";
+ nav.style.opacity = "";
+ }
+}
+
+function openAccount(forceClose = false) {
+ const nav = document.getElementById("account-menu");
+ if (nav.style.opacity !== "" || forceClose) {
+ nav.style.display = "none";
+ nav.style.opacity = "";
+ } else {
+ nav.style.display = "block";
+ nav.style.opacity = "initial";
+ }
+}
+
+const sections = [
+ "release",
+ "bootstrap",
+ "installation",
+ "auth",
+ "workspace",
+ "offline",
+ "mount",
+ "folder",
+ "file",
+ "search",
+ "invitation",
+ "invitation-greeter",
+ "invitation-claimer",
+ "human",
+ "share",
+ "recovery",
+ "shamir",
+ "sequester",
+ "logout",
+ "result"
+];
+
+function navTo(className) {
+ sections.forEach(section => {
+ const elm = document.getElementById(section);
+ elm.style.display = section === className ? "block" : "none";
+ });
+ openNav();
+}
+
+// AUTH
+let tokenSession = null;
+
+const saltDerive2 = new Uint8Array("122,205,180,252,110,57,134,101,147,170,189,150,191,228,84,206".split(","));
+
+function getKeyParsecMaterialParsec(passwordACharger) {
+ let enc = new TextEncoder();
+ return window.crypto.subtle.importKey(
+ "raw",
+ enc.encode(passwordACharger),
+ {name: "PBKDF2"},
+ false,
+ ["deriveBits", "deriveKey"]
+ );
+}
+
+async function getKeyParsec(keyMaterial, salt) {
+ return window.crypto.subtle.deriveKey(
+ {
+ "name": "PBKDF2",
+ salt: salt,
+ "iterations": 100000,
+ "hash": "SHA-256"
+ },
+ keyMaterial,
+ { "name": "AES-GCM", "length": 256},
+ true,
+ [ "encrypt", "decrypt" ]
+ );
+}
+
+async function derivationPassword(passwordADeriver, saltDerivation) {
+ let keyMaterial = await getKeyParsecMaterialParsec(passwordADeriver);
+ let key = await getKeyParsec(keyMaterial, saltDerivation);
+ return key;
+}
+
+async function exportCryptoKey(key) {
+ const exported = await window.crypto.subtle.exportKey(
+ "raw", key
+ );
+ const exportedKeyBuffer = new Uint8Array(exported);
+ return exportedKeyBuffer;
+}
+
+async function deriverPasswordParsec(passwordADeriver) {
+ let keyDerive = await derivationPassword(passwordADeriver, saltDerive2);
+ keyDerive = await exportCryptoKey(keyDerive);
+ return keyDerive;
+}
+
+function genererParsecKey() {
+ let parsec_key = window.btoa(window.crypto.getRandomValues(new Uint8Array(8)));
+ return parsec_key;
+}
+
+function importParsecDerivationKey(derivation) {
+ return window.crypto.subtle.importKey(
+ "raw",
+ derivation,
+ "AES-GCM",
+ true,
+ ["encrypt", "decrypt"]
+ );
+}
+
+function getMessageEncodingParsec(message) {
+ let enc = new TextEncoder();
+ return enc.encode(message);
+}
+
+async function cryptageMessageParsec(keyDerive, message) {
+ let iv = window.crypto.getRandomValues(new Uint8Array(12));
+ let messageEncapsule = getMessageEncodingParsec(message);
+ let ciphertext = await window.crypto.subtle.encrypt(
+ {
+ name: "AES-GCM",
+ iv: iv
+ },
+ keyDerive,
+ messageEncapsule
+ );
+ let buffer = new Uint8Array(ciphertext);
+ return iv.toString()+"/"+buffer.toString();
+}
+
+async function crypterParsecKey(parsec_key, passwordADeriver) {
+ try{
+ let keyDerive = await deriverPasswordParsec(passwordADeriver);
+ keyDerive = keyDerive.buffer;
+ keyDerive = await importParsecDerivationKey(keyDerive);
+ const parsecKeyChiffre = await cryptageMessageParsec(keyDerive, parsec_key);
+ return window.btoa(parsecKeyChiffre)
+ }
+ catch(error){
+ if(error?.code){
+ return Promise.reject(error)
+ }
+ return Promise.reject({code: 'crypt-error'})
+ }
+}
+
+async function generateKey() {
+ const password = document.getElementById("generate-password").value;
+ const parsecKey = genererParsecKey();
+ const encryptedKey = await crypterParsecKey(parsecKey, password);
+ document.getElementById("generate-key-result").innerHTML = `
+ parsecKey : ${parsecKey}
+ encryptedKey : ${encryptedKey}
+ `;
+}
+
+async function decryptageMessageParsec(keyDerive, message) {
+ message = message.split("/");
+ let iv = new Uint8Array(message[0].split(","));
+ let parsec_key_crypte = message[1];
+ let ciphertext = new Uint8Array(parsec_key_crypte.split(",")).buffer;
+ let decrypted = await window.crypto.subtle.decrypt(
+ {
+ name: "AES-GCM",
+ iv: iv
+ },
+ keyDerive,
+ ciphertext
+ )
+ let dec = new TextDecoder();
+ return dec.decode(decrypted);
+}
+
+async function decryptParsecKey() {
+ const password = document.getElementById("generate-password").value;
+ let encryptedKey = "==";
+ let keyDerive = await deriverPasswordParsec(password);
+ keyDerive = keyDerive.buffer;
+ keyDerive = await importParsecDerivationKey(keyDerive);
+ encryptedKey = window.atob(encryptedKey);
+ let parsecKey = await decryptageMessageParsec(keyDerive, encryptedKey);
+ return parsecKey;
+}
+
+// WORKSPACES
+let workspaces = [];
+
+function updateWorkspacesSelect() {
+ const elms = document.getElementsByClassName("workspaces-select");
+ for (const elm of elms) {
+ elm.innerHTML = "";
+ elm.innerHTML += ``;
+ elm.innerHTML += ``;
+ for (const workspace of workspaces) {
+ elm.innerHTML += ``;
+ }
+ }
+}
+
+function createWorkspace() {
+ const name = document.getElementById("workspace-name").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", "http://localhost:5775/workspaces");
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ name
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("workspace-create-result").innerHTML = getHttpResult(http);
+ if (http.status === 201) {
+ listWorkspaces();
+ }
+ }
+}
+
+function listWorkspaces() {
+ const http = new XMLHttpRequest();
+ http.open("GET", "http://localhost:5775/workspaces");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("workspaces-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ workspaces = JSON.parse(http.response).workspaces;
+ updateWorkspacesSelect();
+ }
+ }
+}
+
+function renameWorkspace() {
+ const workspaceID = document.getElementById("workspace-rename-id").value;
+ const oldName = document.getElementById("workspace-rename-old-name").value;
+ const newName = document.getElementById("workspace-rename-new-name").value;
+ const http = new XMLHttpRequest();
+ http.open("PATCH", `http://localhost:5775/workspaces/${workspaceID}`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ old_name: oldName,
+ new_name: newName
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("workspace-rename-result").innerHTML = getHttpResult(http);
+ }
+}
+
+// OFFLINE AVAILABILITY
+function checkOffline() {
+ const workspace = document.getElementById("offine-workspace").value;
+ http.open("GET", `http://localhost:5775/workspaces/${workspace}/get_offline_availability_status`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("offine-get-result").innerHTML = getHttpResult(http);
+ }
+}
+
+// MOUNT/UNMOUNT
+function mountWorkspace() {
+ // TODO timestamped workspace json={"timestamp": timestamp}
+ const workspace = document.getElementById("mount-workspace").value;
+ http.open("POST", `http://localhost:5775/workspaces/${workspace}/mount`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("mount-workspace-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function unmountWorkspace() {
+ // TODO timestamped workspace json={"timestamp": timestamp}
+ const workspace = document.getElementById("unmount-workspace").value;
+ http.open("POST", `http://localhost:5775/workspaces/${workspace}/unmount`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("unmount-workspace-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function listMountpoints() {
+ // TODO timestamped workspace json={"timestamp": timestamp}
+ http.open("GET", `http://localhost:5775/workspaces/mountpoints`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("unmount-workspace-result").innerHTML = getHttpResult(http);
+ }
+}
+
+// FOLDER
+let folders = []
+let workspaceID = null;
+
+function updateFoldersSelect() {
+ const elms = document.getElementsByClassName("folders-select");
+ for (const elm of elms) {
+ elm.innerHTML = "";
+ elm.innerHTML += ``;
+ elm.innerHTML += ``;
+ for (const folder of folders) {
+ elm.innerHTML += ``;
+ }
+ }
+}
+
+function createFolder() {
+ const workspace = document.getElementById("workspace-folders-list-id").value;
+ const parent = document.getElementById("folder-parent").value;
+ const name = document.getElementById("folder-name").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/workspaces/${workspace}/folders`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ name,
+ parent
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("folder-create-result").innerHTML = getHttpResult(http);
+ if (http.status === 201) {
+ listFolders();
+ }
+ }
+}
+
+function parseFolderNode(node, path = "") {
+ let result = [];
+ let fullPath = path + node.name;
+ fullPath = fullPath.endsWith("/") ? fullPath : fullPath + "/";
+ result.push({ id: node.id, name: fullPath });
+ if (node.children) {
+ for (const [key, value] of Object.entries(node.children)) {
+ result = result.concat(parseFolderNode(value, fullPath));
+ }
+ }
+ return result;
+}
+
+function listFolders(fromFile = false) {
+ let workspace = document.getElementById("workspace-folders-list-id").value;
+ if (fromFile) {
+ workspace = document.getElementById("workspace-files-list-id").value;
+ }
+ const http = new XMLHttpRequest();
+ http.open("GET", `http://localhost:5775/workspaces/${workspace}/folders`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("folders-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ const root = JSON.parse(http.response);
+ folders = parseFolderNode(root);
+ updateFoldersSelect();
+ }
+ }
+}
+
+function renameFolder() {
+ const workspace = document.getElementById("workspace-folders-list-id").value;
+ const id = document.getElementById("rename-folder-id").value;
+ const newName = document.getElementById("rename-folder-name").value;
+ const newParent = document.getElementById("rename-folder-parent").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/workspaces/${workspace}/folders/rename`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ id,
+ new_name: newName,
+ new_parent: newParent
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("rename-folder-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ listFolders();
+ }
+ }
+}
+
+function deleteFolder() {
+ const workspace = document.getElementById("workspace-folders-list-id").value;
+ const folder = document.getElementById("delete-folder-input").value;
+ const http = new XMLHttpRequest();
+ http.open("DELETE", `http://localhost:5775/workspaces/${workspace}/folders/${folder}`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("delete-folder-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ listFolders();
+ }
+ }
+}
+
+// FILE
+let files = [];
+let smallFileContent = "";
+const smallFileSize = 1024;
+const largeFileSize = 2**20 * 20 // 20mB
+
+function loadSmallFile(e) {
+ const file = e.target.files[0];
+ if (!file) {
+ return;
+ }
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = function(e) {
+ smallFileContent = e.target.result.split(",")[1];
+ }
+}
+
+function loadLargeFile(e) {
+ console.log(e);
+}
+
+document.getElementById('small-file').addEventListener('change', loadSmallFile, false);
+document.getElementById('large-file').addEventListener('change', loadLargeFile, false);
+
+function updateFilesSelect() {
+ const elms = document.getElementsByClassName("files-select");
+ for (const elm of elms) {
+ elm.innerHTML = "";
+ elm.innerHTML += ``;
+ elm.innerHTML += ``;
+ for (const file of files) {
+ elm.innerHTML += ``;
+ }
+ }
+}
+
+function createSmallFile() {
+ const workspace = document.getElementById("workspace-files-list-id").value;
+ const parent = document.getElementById("folder-file-list-id").value;
+ const name = document.getElementById("small-filename").value;
+ // const fileContent = random.randbytes(smallFileSize);
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/workspaces/${workspace}/files`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({
+ name,
+ parent,
+ content: smallFileContent
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("create-small-file-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function createLargeFile() {
+ const workspace = document.getElementById("workspace-files-list-id").value;
+ const parent = document.getElementById("folder-file-list-id").value;
+ const name = document.getElementById("large-filename").value;
+ // const fileContent = random.randbytes(largeFileSize);
+ const formData = new FormData();
+ formData.append("data", JSON.stringify({parent}));
+ formData.append("files");
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/workspaces/${workspace}/files`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ name,
+ parent,
+ content: ""
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("create-large-file-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function listFiles() {
+ const workspace = document.getElementById("workspace-files-list-id").value;
+ const folder = document.getElementById("folder-file-list-id").value;
+ const http = new XMLHttpRequest();
+ http.open("GET", `http://localhost:5775/workspaces/${workspace}/files/${folder}`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+
+ http.onreadystatechange = (e) => {
+ document.getElementById("files-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ files = JSON.parse(http.responseText).files;
+ updateFilesSelect();
+ }
+ }
+}
+
+function openFile() {
+ const workspace = document.getElementById("workspace-files-list-id").value;
+ const fileID = document.getElementById("open-file-id").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/workspaces/${workspace}/open/${fileID}`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+
+ http.onreadystatechange = (e) => {
+ document.getElementById("open-file-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function renameFile() {
+ const workspace = document.getElementById("workspace-files-list-id").value;
+ const id = document.getElementById("file-id-input").value;
+ const newName = document.getElementById("file-id-input").value;
+ const newParent = document.getElementById("file-id-input").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/workspaces/${workspace}/files/rename`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ id,
+ new_name: newName,
+ new_parent: newParent
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("rename-file-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function deleteFiles() {
+ const workspace = document.getElementById("workspace-input").value;
+ for (const fileId of files) {
+ const http = new XMLHttpRequest();
+ http.open("DELETE", `http://localhost:5775/workspaces/${workspace}/files/${fileId}`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("delete-result").innerHTML = getHttpResult(http);
+ }
+ }
+}
+
+// INVITATION
+const inviteeEmail = "eli.vance@blackmesa.nm";
+
+function updateInvitationsSelect(usersInvitations, deviceInvitation) {
+ const elms = document.getElementsByClassName("invitations-select");
+ for (const elm of elms) {
+ elm.innerHTML = "";
+ elm.innerHTML += ``;
+ elm.innerHTML += ``;
+ for (const invitation of usersInvitations) {
+ elm.innerHTML += ``;
+ }
+ if (deviceInvitation) {
+ elm.innerHTML += ``;
+ }
+ }
+}
+
+function createInvitation() {
+ const email = document.getElementById("claimer-email").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", "http://localhost:5775/invitations");
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ type: "user",
+ claimer_email: email
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("create-invitation-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ listInvitations();
+ }
+ }
+}
+
+function createInvitationDevice() {
+ const http = new XMLHttpRequest();
+ http.open("POST", "http://localhost:5775/invitations");
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ type: "device"
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("create-invitation-device-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ listInvitations();
+ }
+ }
+}
+
+function listInvitations() {
+ const http = new XMLHttpRequest();
+ http.open("GET", "http://localhost:5775/invitations");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("invitations-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ const usersInvitations = JSON.parse(http.response).users;
+ const deviceInvitation = JSON.parse(http.response).device;
+ updateInvitationsSelect(usersInvitations, deviceInvitation);
+ }
+ }
+}
+
+function deleteInvitation() {
+ const invitationToken = document.getElementById("delete-invitation-token").value;
+ const http = new XMLHttpRequest();
+ http.open("DELETE", `http://localhost:5775/invitations/${invitationToken}`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("delete-invitation-result").innerHTML = getHttpResult(http);
+ if (http.status === 204) {
+ listInvitations();
+ }
+ }
+}
+
+function claimerRetreiveInfo() {
+ const invitationToken = document.getElementById("claimer-invitation-token").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/claimer/0-retreive-info`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("claimer-retreive-info-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function claimerWaitPeerReady() {
+ const invitationToken = document.getElementById("claimer-invitation-token").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/claimer/1-wait-peer-ready`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("claimer-wait-peer-ready-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function claimerCheckTrust() {
+ const invitationToken = document.getElementById("claimer-invitation-token").value;
+ const greeterSAS = document.getElementById("greeter-sas").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/claimer/2-check-trust`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({
+ greeter_sas: greeterSAS
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("claimer-check-trust-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function claimerWaitPeerTrust() {
+ const invitationToken = document.getElementById("claimer-invitation-token").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/claimer/3-wait-peer-trust`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("claimer-wait-peer-trust-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function claimerFinalize() {
+ const invitationToken = document.getElementById("claimer-invitation-token").value;
+ const key = document.getElementById("claimer-key").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/claimer/4-finalize`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({ key }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("claimer-finalize-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function greeterWaitPeerReady() {
+ const invitationToken = document.getElementById("greeter-invitation-token").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/greeter/1-wait-peer-ready`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("greeter-wait-peer-ready-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function greeterCheckTrust() {
+ const invitationToken = document.getElementById("greeter-invitation-token").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/greeter/2-wait-peer-trust`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("greeter-check-trust-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function greeterWaitPeerTrust() {
+ const invitationToken = document.getElementById("greeter-invitation-token").value;
+ const claimerSAS = document.getElementById("claimer-sas").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/greeter/3-check-trust`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ claimer_sas: claimerSAS
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("greeter-wait-peer-trust-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function greeterFinalize() {
+ const invitationToken = document.getElementById("greeter-invitation-token").value;
+ const grantedProfile = document.getElementById("granted-profile").value || null;
+ const email = document.getElementById("greeter-claimer-email").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/invitations/${invitationToken}/greeter/4-finalize`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ claimer_email: email,
+ granted_profile: grantedProfile
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("greeter-finalize-result").innerHTML = getHttpResult(http);
+ }
+}
+
+// HUMANS
+
+function updateHumansSelect(humans) {
+ const elms = document.getElementsByClassName("humans-select");
+ for (const elm of elms) {
+ elm.innerHTML = "";
+ elm.innerHTML += ``;
+ elm.innerHTML += ``;
+ for (const human of humans) {
+ elm.innerHTML += ``;
+ }
+ }
+}
+
+function getHumans() {
+ const http = new XMLHttpRequest();
+ http.open("GET", "http://localhost:5775/humans");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("humans-result").innerHTML = getHttpResult(http);
+ if (http.status === 200) {
+ updateHumansSelect(JSON.parse(http.response).users);
+ }
+ }
+}
+
+function revoke() {
+ const email = document.getElementById("revoke-email").value;
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/humans/${email}/revoke`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("revoke-result").innerHTML = getHttpResult(http);
+ }
+}
+
+// SHARE
+function share() {
+ const workspace = document.getElementById("share-workspace").value;
+ const email = document.getElementById("share-email").value;
+ const role = document.getElementById("share-role").value || null;
+ const http = new XMLHttpRequest();
+ http.open("PATCH", `http://localhost:5775/workspaces/${workspace}/share`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ email,
+ role
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("share-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function listShares() {
+ const workspace = document.getElementById("share-workspace").value;
+ const http = new XMLHttpRequest();
+ http.open("GET", `http://localhost:5775/workspaces/${workspace}/share`);
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send();
+ http.onreadystatechange = (e) => {
+ document.getElementById("shares-result").innerHTML = getHttpResult(http);
+ }
+}
+
+// RECOVERY
+function exportRecovery() {
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/recovery/export`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("export-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function importRecovery() {
+ const workspace = document.getElementById("workspace").value;
+ const recoveryDeviceFileContent = "";
+ const recoveryDevicePassphrase = "";
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/recovery/import`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.send(JSON.stringify({
+ recovery_device_file_content: recoveryDeviceFileContent,
+ recovery_device_passphrase: recoveryDevicePassphrase
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("import-result").innerHTML = getHttpResult(http);
+ }
+}
+
+
+// SHAMIR
+function shamirSetup() {
+ const http = new XMLHttpRequest();
+ http.open("POST", `http://localhost:5775/recovery/shamir/setup`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({
+ threshold: 0,
+ recipients: [{
+ email: "",
+ weight: 1
+ }]
+ }));
+ http.onreadystatechange = (e) => {
+ document.getElementById("shamir-setup-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function shamirDelete() {
+ const http = new XMLHttpRequest();
+ http.open("DELETE", `http://localhost:5775/recovery/shamir/setup`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("shamir-delete-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function shamirGetCurrent() {
+ const http = new XMLHttpRequest();
+ http.open("GET", `http://localhost:5775/recovery/shamir/setup`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("shamir-get-current-result").innerHTML = getHttpResult(http);
+ }
+}
+
+function shamirGetOthers() {
+ const http = new XMLHttpRequest();
+ http.open("GET", `http://localhost:5775/recovery/shamir/setup/others`);
+ http.setRequestHeader("Content-type", "application/json");
+ http.setRequestHeader("Authorization", `bearer ${tokenSession}`);
+ http.send(JSON.stringify({}));
+ http.onreadystatechange = (e) => {
+ document.getElementById("shamir-get-others-result").innerHTML = getHttpResult(http);
+ }
+}
+
+
+// RESULT
+
+(function() {})();
diff --git a/misc/test_book/workspace.js b/misc/test_book/workspace.js
new file mode 100644
index 000000000..e69de29bb