Basic loading of blueprints and schema

This commit is contained in:
Alexander Rosenberg 2023-10-21 00:53:15 -07:00
parent 0bc7d717f6
commit ce765e30cf
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
18 changed files with 1825 additions and 26 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
/Cargo.lock
.dir-locals.el

1
simplelogin-gui/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/res/gschemas.compiled

View File

@ -6,3 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gtk4 = { version = "0.7.3", features = ["blueprint"] }
simplelogin = { version = "0.1.0", path = "../simplelogin" }
tokio = { version = "1.33.0", features = ["full"] }

29
simplelogin-gui/build.rs Normal file
View File

@ -0,0 +1,29 @@
use std::{env, io, fs, path::Path, process::Command};
const SCHEMA_NAME: &'static str = "im.zander.SimpleLogin.gschema.xml";
fn main() -> io::Result<()> {
println!("cargo:rerun-if-changed=data/{}", SCHEMA_NAME);
#[cfg(debug_assertions)] {
let out_dir = env::var("OUT_DIR").unwrap();
let source_path = Path::new("data/").join(SCHEMA_NAME);
let compile_dir = Path::new(&out_dir).join("schemas");
let compile_path = compile_dir.join(SCHEMA_NAME);
eprintln!("Creating \"{}\"...", compile_dir.display());
fs::create_dir_all(&compile_dir)?;
eprintln!(
"Copping \"{}\" to \"{}\"...",
source_path.display(),
compile_path.display()
);
fs::copy(source_path, &compile_path)?;
eprintln!("Compiling schemas...");
let status = Command::new("glib-compile-schemas")
.args(["--strict", compile_dir.to_str().unwrap()])
.status()?;
if !status.success() {
panic!("failed to compile schemas");
}
}
Ok(())
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema id="im.zander.SimpleLogin" path="/im/zander/SimpleLogin/">
<key name="api-key" type="s">
<default>""</default>
<summary>Api key</summary>
<description>The api key for the current user</description>
</key>
<key name="window-size" type="(ii)">
<default>(-1,-1)</default>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,240 @@
using Gtk 4.0;
Popover device_entry_popover {
child: Label {
accessible-role: tooltip;
label: "This is the human readable name that will show up in the API key section of the SimpleLogin website.";
max-width-chars: 30;
wrap: true;
}
;
}
template $LoginWindow : ApplicationWindow {
title: "SimpleLogin";
Box {
orientation: vertical;
Overlay forms_overlay {
child: forms_wrapper;
valign: end;
vexpand: true;
Box forms_wrapper {
valign: end;
vexpand: true;
Grid {
halign: end;
hexpand: true;
margin-bottom: 10;
margin-end: 10;
margin-start: 10;
margin-top: 10;
valign: center;
Label {
hexpand: true;
label: "Login:";
margin-bottom: 5;
vexpand: true;
styles [
"login-title-label",
]
layout {
column: "0";
column-span: "2";
row: "0";
}
}
Label error_label {
label: "ERROR";
margin-top: 5;
styles [
"error-message-label",
]
layout {
column: "0";
column-span: "2";
row: "4";
}
}
Entry device_entry {
margin-start: 6;
placeholder-text: "My Awesome Computer";
secondary-icon-name: "dialog-question-symbolic";
truncate-multiline: true;
width-chars: 18;
layout {
column: "1";
row: "3";
}
}
Label {
label: "Device:";
xalign: 0.0;
layout {
column: "0";
row: "3";
}
}
Label {
label: "Email:";
xalign: 0.0;
layout {
column: "0";
row: "1";
}
}
Label {
label: "Password:";
xalign: 0.0;
layout {
column: "0";
row: "2";
}
}
Entry email_entry {
hexpand: true;
input-purpose: email;
margin-start: 6;
receives-default: true;
truncate-multiline: true;
styles [
]
layout {
column: "1";
row: "1";
}
}
PasswordEntry password_entry {
hexpand: true;
margin-start: 6;
show-peek-icon: true;
layout {
column: "1";
row: "2";
}
}
Button login_button {
halign: center;
label: "Login";
margin-top: 5;
sensitive: false;
valign: center;
layout {
column: "0";
column-span: "2";
row: "5";
}
}
styles [
"login-form-warpper",
]
}
Label {
halign: center;
justify: center;
label: "- or -";
}
Box {
halign: start;
hexpand: true;
margin-bottom: 10;
margin-end: 10;
margin-start: 10;
margin-top: 10;
orientation: vertical;
valign: center;
Label {
hexpand: true;
label: "Set API Key:";
margin-bottom: 5;
styles [
"login-title-label",
]
}
PasswordEntry api_key_entry {
hexpand: true;
margin-end: 3;
margin-start: 8;
show-peek-icon: true;
width-chars: 27;
}
Button api_key_button {
halign: center;
label: "Accept";
margin-top: 5;
sensitive: false;
}
}
}
}
Label {
label: "Don\'t have an account? You can register <a href=\"https://app.simplelogin.io/auth/register\">here</a>.";
margin-bottom: 10;
use-markup: true;
valign: start;
vexpand: true;
yalign: 0.0;
}
}
}
Box loading_overlay {
orientation: vertical;
Spinner {
spinning: true;
valign: end;
vexpand: true;
styles [
"loading-label",
]
}
Label {
label: "Loading...";
valign: start;
vexpand: true;
styles [
"loading-label",
]
}
styles [
"loading-label",
]
}

View File

@ -0,0 +1,121 @@
using Gtk 4.0;
template $TotpWindow : Window {
modal: true;
title: "TOTP Entry";
Overlay overlay {
child: form_wrapper;
halign: center;
hexpand: true;
valign: center;
vexpand: true;
Grid form_wrapper {
column-spacing: 5;
halign: center;
hexpand: true;
margin-bottom: 10;
margin-end: 10;
margin-start: 10;
margin-top: 10;
row-spacing: 5;
valign: center;
vexpand: true;
Box {
halign: center;
hexpand: true;
Image {
halign: center;
icon-name: "dialog-password-symbolic";
}
Label {
label: "TOTP";
styles [
"login-title-label",
]
}
layout {
column: "0";
column-span: "2";
row: "0";
}
}
Button accept_button {
label: "Accept";
layout {
column: "1";
row: "3";
}
}
Button cancel_button {
label: "Cancel";
layout {
column: "0";
row: "3";
}
}
Entry code_entry {
placeholder-text: "TOTP Code";
layout {
column: "0";
column-span: "2";
row: "1";
}
}
Label error_label {
label: "ERROR";
styles [
"error-message-label",
]
layout {
column: "0";
column-span: "2";
row: "2";
}
}
}
}
}
Box loading_overlay {
orientation: vertical;
Spinner {
spinning: true;
valign: end;
vexpand: true;
styles [
"loading-label",
]
}
Label {
label: "Loading...";
valign: start;
vexpand: true;
styles [
"loading-label",
]
}
styles [
"loading-label",
]
}

View File

@ -0,0 +1,15 @@
.login-title-label {
font-size: x-large;
}
.error-message-label {
color: red;
}
.loading-label {
font-size: xx-large;
}
.bold {
font-weight: bold;
}

View File

@ -0,0 +1,188 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.16.0 -->
<interface>
<!-- interface-name main-window.ui -->
<requires lib="gtk" version="4.12"/>
<template class="SLMainPanel" parent="GtkBox">
<child>
<object class="GtkOverlay" id="overlay">
<child>
<object class="GtkBox" id="content_wrapper">
<property name="orientation">vertical</property>
<property name="vexpand">True</property>
<child>
<object class="GtkBox">
<child>
<object class="GtkSearchEntry" id="search_box">
<property name="hexpand">True</property>
<property name="placeholder-text">Filter by email, description, etc.</property>
</object>
</child>
<child>
<object class="GtkDropDown" id="view_dropdown">
<property name="hexpand">True</property>
<property name="model">
<object class="GtkStringList">
<items>
<item>All aliases</item>
<item>Pinned only</item>
<item>Enabled only</item>
<item>Disabled only</item>
</items>
</object>
</property>
<property name="selected">0</property>
</object>
</child>
<child>
<object class="GtkButton" id="search_button">
<property name="label">Search</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<child>
<object class="GtkButton" id="new_button">
<property name="label">New</property>
</object>
</child>
<child>
<object class="GtkButton" id="edit_button">
<property name="label">Edit</property>
</object>
</child>
<child>
<object class="GtkButton" id="delete_button">
<property name="label">Delete</property>
</object>
</child>
<child>
<object class="GtkButton" id="prev_page_button">
<property name="halign">end</property>
<property name="hexpand">True</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
<child>
<object class="GtkEntry" id="page_label">
<property name="has-frame">False</property>
<property name="input-hints">no-emoji | no-spellcheck</property>
<property name="input-purpose">digits</property>
<property name="max-width-chars">0</property>
<property name="text">1</property>
<property name="truncate-multiline">True</property>
<property name="width-chars">5</property>
<property name="xalign">0.5</property>
</object>
</child>
<child>
<object class="GtkButton" id="next_page_button">
<property name="icon-name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkOverlay" id="scroll_overlay">
<child>
<object class="GtkScrolledWindow">
<property name="vexpand">True</property>
<child>
<object class="GtkListView" id="alias_list_view">
<property name="factory">
<object class="GtkSignalListItemFactory" id="alias_item_factory"/>
</property>
<property name="model">
<object class="GtkMultiSelection" id="alias_selection_model"/>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
<object class="GtkBox" id="loading_overlay">
<property name="orientation">vertical</property>
<child>
<object class="GtkSpinner">
<property name="spinning">True</property>
<property name="valign">end</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Loading...</property>
<property name="valign">start</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
<style>
<class name="loading-label"/>
</style>
</object>
<object class="GtkLabel" id="no_content_label">
<property name="hexpand">True</property>
<property name="label">No more results</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
<object class="GtkBox" id="error_overlay">
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="valign">end</property>
<child>
<object class="GtkButton" id="error_refresh_button">
<property name="halign">center</property>
<property name="icon-name">view-refresh-symbolic</property>
<property name="valign">end</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="error_close_button">
<property name="halign">center</property>
<property name="icon-name">window-close-symbolic</property>
<property name="valign">end</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="error_label">
<property name="label">ERROR</property>
<property name="valign">start</property>
<property name="vexpand">True</property>
<style>
<class name="error-message-label"/>
<class name="loading-label"/>
</style>
</object>
</child>
</object>
<!-- Custom fragments -->
</interface>

View File

@ -0,0 +1,263 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.16.0 -->
<interface>
<requires lib="gtk" version="4.12"/>
<template class="SLSettingsPanel" parent="GtkBox">
<child>
<object class="GtkOverlay" id="overlay">
<property name="child">content_wrapper</property>
<property name="hexpand">True</property>
<child>
<object class="GtkBox" id="content_wrapper">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkGrid" id="content">
<property name="hexpand">True</property>
<property name="row-homogeneous">True</property>
<property name="valign">start</property>
<child>
<object class="GtkButton" id="profile_image_button">
<property name="has-frame">False</property>
<child>
<object class="GtkImage" id="profile_image">
<property name="halign">center</property>
<property name="icon-name">user-info-symbolic</property>
<property name="icon-size">large</property>
<property name="margin-end">5</property>
<property name="valign">center</property>
<property name="vexpand">True</property>
</object>
</child>
<!-- Custom fragments -->
<layout>
<property name="column">0</property>
<property name="column-span">1</property>
<property name="row">0</property>
<property name="row-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkBox">
<child>
<object class="GtkButton" id="remove_picture_button">
<property name="icon-name">window-close-symbolic</property>
</object>
</child>
<child>
<object class="GtkEntry" id="name_label">
<property name="editable">False</property>
<property name="has-frame">False</property>
<property name="hexpand">True</property>
<property name="input-hints">no-emoji | no-spellcheck</property>
<property name="text">Testy McTesterface</property>
<style>
<class name="login-title-label"/>
</style>
</object>
</child>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">0</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkBox">
<child>
<object class="GtkButton" id="edit_button">
<property name="halign">start</property>
<property name="label">Edit</property>
<property name="valign">center</property>
</object>
</child>
<child>
<object class="GtkButton" id="cancel_edit_button">
<property name="label">Cancel</property>
</object>
</child>
<layout>
<property name="column">2</property>
<property name="column-span">1</property>
<property name="row">0</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="stats_label">
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="label">Stats go here</property>
<layout>
<property name="column">0</property>
<property name="column-span">3</property>
<property name="row">2</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="email_label">
<property name="hexpand">True</property>
<property name="label">mctesterface@test.tld</property>
<property name="xalign">0.0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkButton" id="logout_button">
<property name="halign">start</property>
<property name="label">Logout</property>
<property name="valign">center</property>
<layout>
<property name="column">2</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Account Status:</property>
<property name="margin-end">5</property>
<property name="xalign">1.0</property>
<layout>
<property name="column">0</property>
<property name="column-span">1</property>
<property name="row">3</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Api Key:</property>
<property name="margin-end">5</property>
<property name="xalign">1.0</property>
<attributes/>
<layout>
<property name="column">0</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkPasswordEntry" id="api_key_field">
<property name="editable">False</property>
<property name="enable-undo">False</property>
<property name="hexpand">True</property>
<property name="show-peek-icon">True</property>
<property name="valign">center</property>
<layout>
<property name="column">1</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="status_label">
<property name="hexpand">True</property>
<property name="label">Premium</property>
<property name="xalign">0.0</property>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">3</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkButton" id="api_key_clear_button">
<property name="halign">start</property>
<property name="label">Clear</property>
<property name="valign">center</property>
<layout>
<property name="column">2</property>
<property name="row">4</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
<object class="GtkBox" id="loading_overlay">
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkSpinner">
<property name="spinning">True</property>
<property name="valign">end</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Loading...</property>
<property name="valign">start</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
<style>
<class name="loading-label"/>
</style>
</object>
<object class="GtkBox" id="error_overlay">
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="valign">end</property>
<child>
<object class="GtkButton" id="error_refresh_button">
<property name="halign">center</property>
<property name="icon-name">view-refresh-symbolic</property>
<property name="valign">end</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="error_close_button">
<property name="halign">center</property>
<property name="icon-name">window-close-symbolic</property>
<property name="valign">end</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="error_label">
<property name="label">ERROR</property>
<property name="valign">start</property>
<property name="vexpand">True</property>
<style>
<class name="error-message-label"/>
<class name="loading-label"/>
</style>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,531 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<cambalache-project version="0.13.1" target_tk="gtk-4.0">
<ui>
(1,1,"main-window.ui","main-panel.ui",None,None,None,None,None,None,""),
(3,18,None,"settings-panel.ui",None,None,None,None,None,None,None),
(4,13,None,"login-window.ui",None,None,None,None,None,None,None),
(5,1,None,"totp-dialog.ui",None,None,None,None,None,None,None)
</ui>
<css>
(1,"global.css",None,1)
</css>
<css_ui>
(1,1),
(1,3),
(1,4),
(1,5)
</css_ui>
<object>
(1,1,"GtkBox","SLMainPanel",None,None,None,None,None,None),
(1,2,"GtkOverlay","overlay",1,None,None,None,None,None),
(1,3,"GtkBox","content_wrapper",2,None,None,None,None,None),
(1,4,"GtkBox",None,3,None,None,None,None,None),
(1,5,"GtkSearchEntry","search_box",4,None,None,None,None,None),
(1,8,"GtkDropDown","view_dropdown",4,None,None,None,1,None),
(1,9,"GtkStringList",None,8,None,None,None,None,None),
(1,10,"GtkButton","search_button",4,None,None,None,2,None),
(1,11,"GtkBox",None,3,None,None,None,1,None),
(1,12,"GtkButton","new_button",11,None,None,None,None,None),
(1,13,"GtkButton","edit_button",11,None,None,None,1,None),
(1,14,"GtkButton","delete_button",11,None,None,None,2,None),
(1,22,"GtkBox","loading_overlay",None,None,None,None,None,None),
(1,23,"GtkSpinner",None,22,None,None,None,None,None),
(1,24,"GtkLabel",None,22,None,None,None,1,None),
(1,28,"GtkButton","prev_page_button",11,None,None,None,3,None),
(1,29,"GtkButton","next_page_button",11,None,None,None,5,None),
(1,30,"GtkEntry","page_label",11,None,None,None,4,None),
(1,31,"GtkOverlay","scroll_overlay",3,None,None,None,2,None),
(1,32,"GtkScrolledWindow",None,31,None,None,None,None,None),
(1,33,"GtkListView","alias_list_view",32,None,None,None,None,None),
(1,34,"GtkSignalListItemFactory","alias_item_factory",33,None,None,None,None,None),
(1,35,"GtkMultiSelection","alias_selection_model",33,None,None,None,None,None),
(1,36,"GtkLabel","no_content_label",None,None,None,None,None,None),
(1,37,"GtkBox","error_overlay",None,None,None,None,None,None),
(1,38,"GtkBox",None,37,None,None,None,None,None),
(1,39,"GtkButton","error_refresh_button",38,None,None,None,None,None),
(1,40,"GtkButton","error_close_button",38,None,None,None,1,None),
(1,41,"GtkLabel","error_label",37,None,None,None,1,None),
(3,18,"GtkBox","SLSettingsPanel",None,None,None,None,None,None),
(3,19,"GtkOverlay","overlay",18,None,None,None,None,None),
(3,30,"GtkBox","loading_overlay",None,None,None,None,None,None),
(3,31,"GtkSpinner",None,30,None,None,None,None,None),
(3,32,"GtkLabel",None,30,None,None,None,1,None),
(3,33,"GtkBox","error_overlay",None,None,None,None,None,None),
(3,35,"GtkLabel","error_label",33,None,None,None,1,None),
(3,45,"GtkBox",None,33,None,None,None,None,None),
(3,46,"GtkButton","error_refresh_button",45,None,None,None,None,None),
(3,47,"GtkButton","error_close_button",45,None,None,None,1,None),
(3,48,"GtkBox","content_wrapper",19,None,None,None,-1,None),
(3,49,"GtkGrid","content",48,None,None,None,None,None),
(3,51,"GtkLabel","stats_label",49,None,None,None,1,None),
(3,52,"GtkLabel","email_label",49,None,None,None,2,None),
(3,53,"GtkButton","logout_button",49,None,None,None,3,None),
(3,55,"GtkLabel",None,49,None,None,None,5,None),
(3,57,"GtkLabel",None,49,None,None,None,7,None),
(3,58,"GtkPasswordEntry","api_key_field",49,None,None,None,8,None),
(3,59,"GtkLabel","status_label",49,None,None,None,9,None),
(3,60,"GtkButton","api_key_clear_button",49,None,None,None,10,None),
(3,63,"GtkButton","profile_image_button",49,None,None,None,None,""),
(3,64,"GtkImage","profile_image",63,None,None,None,None,None),
(3,65,"GtkBox",None,49,None,None,None,None,None),
(3,66,"GtkButton","remove_picture_button",65,None,None,None,None,None),
(3,67,"GtkEntry","name_label",65,None,None,None,1,None),
(3,68,"GtkBox",None,49,None,None,None,None,None),
(3,69,"GtkButton","edit_button",68,None,None,None,None,None),
(3,70,"GtkButton","cancel_edit_button",68,None,None,None,1,None),
(4,11,"GtkPopover","device_entry_popover",None,None,None,None,None,None),
(4,12,"GtkLabel",None,11,None,None,None,None,None),
(4,13,"GtkApplicationWindow","LoginWindow",None,None,None,None,None,None),
(4,14,"GtkBox",None,13,None,None,None,-1,None),
(4,30,"GtkLabel",None,14,None,None,None,1,None),
(4,31,"GtkOverlay","forms_overlay",14,None,None,None,None,None),
(4,32,"GtkBox","forms_wrapper",31,None,None,None,None,None),
(4,33,"GtkGrid",None,32,None,None,None,None,None),
(4,34,"GtkLabel",None,33,None,None,None,None,None),
(4,35,"GtkLabel","error_label",33,None,None,None,1,None),
(4,36,"GtkEntry","device_entry",33,None,None,None,2,None),
(4,37,"GtkLabel",None,33,None,None,None,3,None),
(4,38,"GtkLabel",None,33,None,None,None,4,None),
(4,39,"GtkLabel",None,33,None,None,None,5,None),
(4,40,"GtkEntry","email_entry",33,None,None,None,6,None),
(4,41,"GtkPasswordEntry","password_entry",33,None,None,None,7,None),
(4,42,"GtkButton","login_button",33,None,None,None,8,None),
(4,43,"GtkLabel",None,32,None,None,None,1,None),
(4,44,"GtkBox",None,32,None,None,None,2,None),
(4,45,"GtkLabel",None,44,None,None,None,None,None),
(4,46,"GtkPasswordEntry","api_key_entry",44,None,None,None,1,None),
(4,47,"GtkButton","api_key_button",44,None,None,None,2,None),
(4,48,"GtkBox","loading_overlay",None,None,None,None,None,None),
(4,49,"GtkSpinner",None,48,None,None,None,None,None),
(4,50,"GtkLabel",None,48,None,None,None,1,None),
(5,1,"GtkWindow","SLTotpDialog",None,None,None,None,None,None),
(5,2,"GtkOverlay","overlay",1,None,None,None,-1,None),
(5,11,"GtkBox","loading_overlay",None,None,None,None,None,None),
(5,12,"GtkSpinner",None,11,None,None,None,None,None),
(5,13,"GtkLabel",None,11,None,None,None,1,None),
(5,14,"GtkGrid","form_wrapper",2,None,None,None,None,None),
(5,15,"GtkBox",None,14,None,None,None,None,None),
(5,16,"GtkImage",None,15,None,None,None,None,None),
(5,17,"GtkLabel",None,15,None,None,None,1,None),
(5,19,"GtkButton","accept_button",14,None,None,None,2,None),
(5,20,"GtkButton","cancel_button",14,None,None,None,3,None),
(5,21,"GtkEntry","code_entry",14,None,None,None,4,None),
(5,23,"GtkLabel","error_label",14,None,None,None,4,None)
</object>
<object_property>
(1,3,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(1,3,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(1,5,"GtkSearchEntry","placeholder-text","Filter by email, description, etc.",None,None,None,None,None,None,None,None,None),
(1,5,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(1,8,"GtkDropDown","model",None,None,None,None,None,9,None,None,None,None),
(1,8,"GtkDropDown","selected","0",None,None,None,None,None,None,None,None,None),
(1,8,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(1,10,"GtkButton","label","Search",None,None,None,None,None,None,None,None,None),
(1,12,"GtkButton","label","New",None,None,None,None,None,None,None,None,None),
(1,13,"GtkButton","label","Edit",None,None,None,None,None,None,None,None,None),
(1,14,"GtkButton","label","Delete",None,None,None,None,None,None,None,None,None),
(1,22,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(1,23,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
(1,23,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(1,23,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(1,24,"GtkLabel","label","Loading...",None,None,None,None,None,None,None,None,None),
(1,24,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(1,24,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(1,28,"GtkButton","icon-name","go-previous-symbolic",None,None,None,None,None,None,None,None,None),
(1,28,"GtkWidget","halign","end",None,None,None,None,None,None,None,None,None),
(1,28,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(1,29,"GtkButton","icon-name","go-next-symbolic",None,None,None,None,None,None,None,None,None),
(1,30,"GtkEditable","max-width-chars","0",None,None,None,None,None,None,None,None,None),
(1,30,"GtkEditable","text","1",None,None,None,None,None,None,None,None,None),
(1,30,"GtkEditable","width-chars","5",None,None,None,None,None,None,None,None,None),
(1,30,"GtkEditable","xalign","0.5",None,None,None,None,None,None,None,None,None),
(1,30,"GtkEntry","has-frame","False",None,None,None,None,None,None,None,None,None),
(1,30,"GtkEntry","input-hints","no-emoji | no-spellcheck",None,None,None,None,None,None,None,None,None),
(1,30,"GtkEntry","input-purpose","digits",None,None,None,None,None,None,None,None,None),
(1,30,"GtkEntry","truncate-multiline","True",None,None,None,None,None,None,None,None,None),
(1,32,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(1,33,"GtkListView","factory",None,None,None,None,None,34,None,None,None,None),
(1,33,"GtkListView","model",None,None,None,None,None,35,None,None,None,None),
(1,36,"GtkLabel","label","No more results",None,None,None,None,None,None,None,None,None),
(1,36,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(1,36,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(1,37,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(1,37,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(1,38,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(1,38,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(1,39,"GtkButton","icon-name","view-refresh-symbolic",None,None,None,None,None,None,None,None,None),
(1,39,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(1,39,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(1,39,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(1,40,"GtkButton","icon-name","window-close-symbolic",None,None,None,None,None,None,None,None,None),
(1,40,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(1,40,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(1,41,"GtkLabel","label","ERROR",None,None,None,None,None,None,None,None,None),
(1,41,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(1,41,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(3,19,"GtkOverlay","child","48",None,None,None,None,None,None,None,None,None),
(3,19,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,30,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(3,30,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,31,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
(3,31,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(3,31,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(3,32,"GtkLabel","label","Loading...",None,None,None,None,None,None,None,None,None),
(3,32,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(3,32,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(3,33,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(3,33,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,35,"GtkLabel","label","ERROR",None,None,None,None,None,None,None,None,None),
(3,35,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(3,35,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(3,45,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(3,45,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(3,46,"GtkButton","icon-name","view-refresh-symbolic",None,None,None,None,None,None,None,None,None),
(3,46,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(3,46,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(3,46,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(3,47,"GtkButton","icon-name","window-close-symbolic",None,None,None,None,None,None,None,None,None),
(3,47,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(3,47,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(3,48,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,48,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(3,49,"GtkGrid","row-homogeneous","True",None,None,None,None,None,None,None,None,None),
(3,49,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,49,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(3,51,"GtkLabel","label","Stats go here",None,None,None,None,None,None,None,None,None),
(3,51,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(3,51,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,52,"GtkLabel","label","mctesterface@test.tld",None,None,None,None,None,None,None,None,None),
(3,52,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
(3,52,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,53,"GtkButton","label","Logout",None,None,None,None,None,None,None,None,None),
(3,53,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
(3,53,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(3,55,"GtkLabel","label","Account Status:",None,None,None,None,None,None,None,None,None),
(3,55,"GtkLabel","xalign","1.0",None,None,None,None,None,None,None,None,None),
(3,55,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None),
(3,57,"GtkLabel","label","Api Key:",None,None,None,None,None,None,None,None,None),
(3,57,"GtkLabel","xalign","1.0",None,None,None,None,None,None,None,None,None),
(3,57,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None),
(3,58,"GtkEditable","editable","False",None,None,None,None,None,None,None,None,None),
(3,58,"GtkEditable","enable-undo","False",None,None,None,None,None,None,None,None,None),
(3,58,"GtkPasswordEntry","show-peek-icon","True",None,None,None,None,None,None,None,None,None),
(3,58,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,58,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(3,59,"GtkLabel","label","Premium",None,None,None,None,None,None,None,None,None),
(3,59,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
(3,59,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,60,"GtkButton","label","Clear",None,None,None,None,None,None,None,None,None),
(3,60,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
(3,60,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(3,63,"GtkButton","has-frame","False",None,None,None,None,None,None,None,None,None),
(3,64,"GtkImage","icon-name","user-info-symbolic",None,None,None,None,None,None,None,None,None),
(3,64,"GtkImage","icon-size","large",None,None,None,None,None,None,None,None,None),
(3,64,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(3,64,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None),
(3,64,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(3,64,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(3,66,"GtkButton","icon-name","window-close-symbolic",None,None,None,None,None,None,None,None,None),
(3,67,"GtkEditable","editable","False",None,None,None,None,None,None,None,None,None),
(3,67,"GtkEditable","text","Testy McTesterface",None,None,None,None,None,None,None,None,None),
(3,67,"GtkEntry","has-frame","False",None,None,None,None,None,None,None,None,None),
(3,67,"GtkEntry","input-hints","no-emoji | no-spellcheck",None,None,None,None,None,None,None,None,None),
(3,67,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(3,69,"GtkButton","label","Edit",None,None,None,None,None,None,None,None,None),
(3,69,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
(3,69,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(3,70,"GtkButton","label","Cancel",None,None,None,None,None,None,None,None,None),
(4,11,"GtkPopover","child",None,None,None,None,None,12,None,None,None,None),
(4,12,"GtkAccessible","accessible-role","tooltip",None,None,None,None,None,None,None,None,None),
(4,12,"GtkLabel","label","This is the human readable name that will show up in the API key section of the SimpleLogin website.",None,None,None,None,None,None,None,None,None),
(4,12,"GtkLabel","max-width-chars","30",None,None,None,None,None,None,None,None,None),
(4,12,"GtkLabel","wrap","True",None,None,None,None,None,None,None,None,None),
(4,13,"GtkWindow","title","SimpleLogin",None,None,None,None,None,None,None,None,None),
(4,14,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(4,30,"GtkLabel","label","Don't have an account? You can register &lt;a href=\"https://app.simplelogin.io/auth/register\"&gt;here&lt;/a&gt;.",None,None,None,None,None,None,None,None,None),
(4,30,"GtkLabel","use-markup","True",None,None,None,None,None,None,None,None,None),
(4,30,"GtkLabel","yalign","0.0",None,None,None,None,None,None,None,None,None),
(4,30,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None),
(4,30,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(4,30,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(4,31,"GtkOverlay","child","32",None,None,None,None,None,None,None,None,None),
(4,31,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(4,31,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(4,32,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(4,32,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(4,33,"GtkWidget","halign","end",None,None,None,None,None,None,None,None,None),
(4,33,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(4,33,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None),
(4,33,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
(4,33,"GtkWidget","margin-start","10",None,None,None,None,None,None,None,None,None),
(4,33,"GtkWidget","margin-top","10",None,None,None,None,None,None,None,None,None),
(4,33,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(4,34,"GtkLabel","label","Login:",None,None,None,None,None,None,None,None,None),
(4,34,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(4,34,"GtkWidget","margin-bottom","5",None,None,None,None,None,None,None,None,None),
(4,34,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(4,35,"GtkLabel","label","ERROR",None,None,None,None,None,None,None,None,None),
(4,35,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None),
(4,36,"GtkEditable","width-chars","18",None,None,None,None,None,None,None,None,None),
(4,36,"GtkEntry","placeholder-text","My Awesome Computer",None,None,None,None,None,None,None,None,None),
(4,36,"GtkEntry","secondary-icon-name","dialog-question-symbolic",None,None,None,None,None,None,None,None,None),
(4,36,"GtkEntry","truncate-multiline","True",None,None,None,None,None,None,None,None,None),
(4,36,"GtkWidget","margin-start","6",None,None,None,None,None,None,None,None,None),
(4,37,"GtkLabel","label","Device:",None,None,None,None,None,None,None,None,None),
(4,37,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
(4,38,"GtkLabel","label","Email:",None,None,None,None,None,None,None,None,None),
(4,38,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
(4,39,"GtkLabel","label","Password:",None,None,None,None,None,None,None,None,None),
(4,39,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
(4,40,"GtkEntry","input-purpose","email",None,None,None,None,None,None,None,None,None),
(4,40,"GtkEntry","truncate-multiline","True",None,None,None,None,None,None,None,None,None),
(4,40,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(4,40,"GtkWidget","margin-start","6",None,None,None,None,None,None,None,None,None),
(4,40,"GtkWidget","receives-default","True",None,None,None,None,None,None,None,None,None),
(4,41,"GtkPasswordEntry","show-peek-icon","True",None,None,None,None,None,None,None,None,None),
(4,41,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(4,41,"GtkWidget","margin-start","6",None,None,None,None,None,None,None,None,None),
(4,42,"GtkButton","label","Login",None,None,None,None,None,None,None,None,None),
(4,42,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(4,42,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None),
(4,42,"GtkWidget","sensitive","False",None,None,None,None,None,None,None,None,None),
(4,42,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(4,43,"GtkLabel","justify","center",None,None,None,None,None,None,None,None,None),
(4,43,"GtkLabel","label","- or -",None,None,None,None,None,None,None,None,None),
(4,43,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(4,44,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(4,44,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
(4,44,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(4,44,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None),
(4,44,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
(4,44,"GtkWidget","margin-start","10",None,None,None,None,None,None,None,None,None),
(4,44,"GtkWidget","margin-top","10",None,None,None,None,None,None,None,None,None),
(4,44,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(4,45,"GtkLabel","label","Set API Key:",None,None,None,None,None,None,None,None,None),
(4,45,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(4,45,"GtkWidget","margin-bottom","5",None,None,None,None,None,None,None,None,None),
(4,46,"GtkEditable","width-chars","27",None,None,None,None,None,None,None,None,None),
(4,46,"GtkPasswordEntry","show-peek-icon","True",None,None,None,None,None,None,None,None,None),
(4,46,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(4,46,"GtkWidget","margin-end","3",None,None,None,None,None,None,None,None,None),
(4,46,"GtkWidget","margin-start","8",None,None,None,None,None,None,None,None,None),
(4,47,"GtkButton","label","Accept",None,None,None,None,None,None,None,None,None),
(4,47,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(4,47,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None),
(4,47,"GtkWidget","sensitive","False",None,None,None,None,None,None,None,None,None),
(4,48,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(4,49,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
(4,49,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(4,49,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(4,50,"GtkLabel","label","Loading...",None,None,None,None,None,None,None,None,None),
(4,50,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(4,50,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(5,1,"GtkWindow","modal","True",None,None,None,None,None,None,None,None,None),
(5,1,"GtkWindow","title","TOTP Entry",None,None,None,None,None,None,None,None,None),
(5,2,"GtkOverlay","child","14",None,None,None,None,None,None,None,None,None),
(5,2,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(5,2,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(5,2,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(5,2,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(5,11,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(5,12,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
(5,12,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
(5,12,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(5,13,"GtkLabel","label","Loading...",None,None,None,None,None,None,None,None,None),
(5,13,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(5,13,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(5,14,"GtkGrid","column-spacing","5",None,None,None,None,None,None,None,None,None),
(5,14,"GtkGrid","row-spacing","5",None,None,None,None,None,None,None,None,None),
(5,14,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(5,14,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(5,14,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None),
(5,14,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
(5,14,"GtkWidget","margin-start","10",None,None,None,None,None,None,None,None,None),
(5,14,"GtkWidget","margin-top","10",None,None,None,None,None,None,None,None,None),
(5,14,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
(5,14,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(5,15,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(5,15,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(5,16,"GtkImage","icon-name","dialog-password-symbolic",None,None,None,None,None,None,None,None,None),
(5,16,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
(5,17,"GtkLabel","label","TOTP",None,None,None,None,None,None,None,None,None),
(5,19,"GtkButton","label","Accept",None,None,None,None,None,None,None,None,None),
(5,20,"GtkButton","label","Cancel",None,None,None,None,None,None,None,None,None),
(5,21,"GtkEntry","placeholder-text","TOTP Code",None,None,None,None,None,None,None,None,None),
(5,23,"GtkLabel","label","ERROR",None,None,None,None,None,None,None,None,None)
</object_property>
<object_layout_property>
(3,49,51,"GtkGridLayoutChild","column","0",None,None,None,None),
(3,49,51,"GtkGridLayoutChild","column-span","3",None,None,None,None),
(3,49,51,"GtkGridLayoutChild","row","2",None,None,None,None),
(3,49,51,"GtkGridLayoutChild","row-span","1",None,None,None,None),
(3,49,52,"GtkGridLayoutChild","column","1",None,None,None,None),
(3,49,52,"GtkGridLayoutChild","row","1",None,None,None,None),
(3,49,53,"GtkGridLayoutChild","column","2",None,None,None,None),
(3,49,53,"GtkGridLayoutChild","row","1",None,None,None,None),
(3,49,55,"GtkGridLayoutChild","column","0",None,None,None,None),
(3,49,55,"GtkGridLayoutChild","column-span","1",None,None,None,None),
(3,49,55,"GtkGridLayoutChild","row","3",None,None,None,None),
(3,49,55,"GtkGridLayoutChild","row-span","1",None,None,None,None),
(3,49,57,"GtkGridLayoutChild","column","0",None,None,None,None),
(3,49,57,"GtkGridLayoutChild","row","4",None,None,None,None),
(3,49,58,"GtkGridLayoutChild","column","1",None,None,None,None),
(3,49,58,"GtkGridLayoutChild","row","4",None,None,None,None),
(3,49,59,"GtkGridLayoutChild","column","1",None,None,None,None),
(3,49,59,"GtkGridLayoutChild","column-span","1",None,None,None,None),
(3,49,59,"GtkGridLayoutChild","row","3",None,None,None,None),
(3,49,59,"GtkGridLayoutChild","row-span","1",None,None,None,None),
(3,49,60,"GtkGridLayoutChild","column","2",None,None,None,None),
(3,49,60,"GtkGridLayoutChild","row","4",None,None,None,None),
(3,49,63,"GtkGridLayoutChild","column","0",None,None,None,None),
(3,49,63,"GtkGridLayoutChild","column-span","1",None,None,None,None),
(3,49,63,"GtkGridLayoutChild","row-span","2",None,None,None,None),
(3,49,65,"GtkGridLayoutChild","column","1",None,None,None,None),
(3,49,65,"GtkGridLayoutChild","column-span","1",None,None,None,None),
(3,49,65,"GtkGridLayoutChild","row","0",None,None,None,None),
(3,49,65,"GtkGridLayoutChild","row-span","1",None,None,None,None),
(3,49,68,"GtkGridLayoutChild","column","2",None,None,None,None),
(3,49,68,"GtkGridLayoutChild","column-span","1",None,None,None,None),
(3,49,68,"GtkGridLayoutChild","row","0",None,None,None,None),
(3,49,68,"GtkGridLayoutChild","row-span","1",None,None,None,None),
(4,33,34,"GtkGridLayoutChild","column","0",None,None,None,None),
(4,33,34,"GtkGridLayoutChild","column-span","2",None,None,None,None),
(4,33,34,"GtkGridLayoutChild","row","0",None,None,None,None),
(4,33,35,"GtkGridLayoutChild","column","0",None,None,None,None),
(4,33,35,"GtkGridLayoutChild","column-span","2",None,None,None,None),
(4,33,35,"GtkGridLayoutChild","row","4",None,None,None,None),
(4,33,36,"GtkGridLayoutChild","column","1",None,None,None,None),
(4,33,36,"GtkGridLayoutChild","row","3",None,None,None,None),
(4,33,37,"GtkGridLayoutChild","column","0",None,None,None,None),
(4,33,37,"GtkGridLayoutChild","row","3",None,None,None,None),
(4,33,38,"GtkGridLayoutChild","column","0",None,None,None,None),
(4,33,38,"GtkGridLayoutChild","row","1",None,None,None,None),
(4,33,39,"GtkGridLayoutChild","column","0",None,None,None,None),
(4,33,39,"GtkGridLayoutChild","row","2",None,None,None,None),
(4,33,40,"GtkGridLayoutChild","column","1",None,None,None,None),
(4,33,40,"GtkGridLayoutChild","row","1",None,None,None,None),
(4,33,41,"GtkGridLayoutChild","column","1",None,None,None,None),
(4,33,41,"GtkGridLayoutChild","row","2",None,None,None,None),
(4,33,42,"GtkGridLayoutChild","column","0",None,None,None,None),
(4,33,42,"GtkGridLayoutChild","column-span","2",None,None,None,None),
(4,33,42,"GtkGridLayoutChild","row","5",None,None,None,None),
(5,14,15,"GtkGridLayoutChild","column","0",None,None,None,None),
(5,14,15,"GtkGridLayoutChild","column-span","2",None,None,None,None),
(5,14,15,"GtkGridLayoutChild","row","0",None,None,None,None),
(5,14,19,"GtkGridLayoutChild","column","1",None,None,None,None),
(5,14,19,"GtkGridLayoutChild","row","3",None,None,None,None),
(5,14,20,"GtkGridLayoutChild","column","0",None,None,None,None),
(5,14,20,"GtkGridLayoutChild","row","3",None,None,None,None),
(5,14,21,"GtkGridLayoutChild","column","0",None,None,None,None),
(5,14,21,"GtkGridLayoutChild","column-span","2",None,None,None,None),
(5,14,21,"GtkGridLayoutChild","row","1",None,None,None,None),
(5,14,23,"GtkGridLayoutChild","column-span","2",None,None,None,None),
(5,14,23,"GtkGridLayoutChild","row","2",None,None,None,None)
</object_layout_property>
<object_data>
(4,34,"GtkWidget",1,1,None,None,None,None,None,None),
(4,34,"GtkWidget",2,2,None,1,None,None,None,None),
(4,35,"GtkWidget",1,1,None,None,None,None,None,None),
(4,35,"GtkWidget",2,2,None,1,None,None,None,None),
(4,40,"GtkWidget",1,1,None,None,None,None,None,None),
(4,33,"GtkWidget",1,1,None,None,None,None,None,None),
(4,33,"GtkWidget",2,2,None,1,None,None,None,None),
(4,45,"GtkWidget",1,1,None,None,None,None,None,None),
(4,45,"GtkWidget",2,2,None,1,None,None,None,None),
(1,9,"GtkStringList",2,5,"Disabled only",1,None,None,None,None),
(1,9,"GtkStringList",2,4,"Enabled only",1,None,None,None,None),
(1,9,"GtkStringList",2,3,"Pinned only",1,None,None,None,None),
(1,9,"GtkStringList",2,2,"All aliases",1,None,None,None,None),
(1,9,"GtkStringList",1,1,None,None,None,None,None,None),
(1,22,"GtkWidget",1,1,None,None,None,None,None,None),
(1,22,"GtkWidget",2,2,None,1,None,None,None,None),
(1,24,"GtkWidget",1,1,None,None,None,None,None,None),
(1,24,"GtkWidget",2,2,None,1,None,None,None,None),
(1,23,"GtkWidget",1,1,None,None,None,None,None,None),
(1,23,"GtkWidget",2,2,None,1,None,None,None,None),
(4,49,"GtkWidget",1,1,None,None,None,None,None,None),
(4,49,"GtkWidget",2,2,None,1,None,None,None,None),
(4,50,"GtkWidget",1,1,None,None,None,None,None,None),
(4,50,"GtkWidget",2,2,None,1,None,None,None,None),
(4,48,"GtkWidget",1,1,None,None,None,None,None,None),
(4,48,"GtkWidget",2,2,None,1,None,None,None,None),
(5,17,"GtkWidget",1,1,None,None,None,None,None,None),
(5,17,"GtkWidget",2,2,None,1,None,None,None,None),
(5,23,"GtkWidget",1,1,None,None,None,None,None,None),
(5,23,"GtkWidget",2,2,None,1,None,None,None,None),
(5,13,"GtkWidget",2,2,None,1,None,None,None,None),
(5,13,"GtkWidget",1,1,None,None,None,None,None,None),
(5,12,"GtkWidget",2,2,None,1,None,None,None,None),
(5,12,"GtkWidget",1,1,None,None,None,None,None,None),
(5,11,"GtkWidget",2,2,None,1,None,None,None,None),
(5,11,"GtkWidget",1,1,None,None,None,None,None,None),
(1,36,"GtkWidget",1,1,None,None,None,None,None,None),
(1,36,"GtkWidget",2,2,None,1,None,None,None,None),
(3,31,"GtkWidget",1,1,None,None,None,None,None,None),
(3,31,"GtkWidget",2,2,None,1,None,None,None,None),
(3,32,"GtkWidget",1,1,None,None,None,None,None,None),
(3,32,"GtkWidget",2,2,None,1,None,None,None,None),
(3,30,"GtkWidget",1,1,None,None,None,None,None,None),
(3,30,"GtkWidget",2,2,None,1,None,None,None,None),
(3,57,"GtkLabel",1,1,None,None,None,None,None,None),
(1,39,"GtkWidget",1,1,None,None,None,None,None,None),
(1,39,"GtkWidget",2,2,None,1,None,None,None,None),
(1,40,"GtkWidget",1,1,None,None,None,None,None,None),
(1,40,"GtkWidget",2,2,None,1,None,None,None,None),
(1,41,"GtkWidget",1,1,None,None,None,None,None,None),
(1,41,"GtkWidget",2,2,None,1,None,None,None,None),
(1,41,"GtkWidget",2,3,None,1,None,None,None,None),
(3,67,"GtkWidget",1,1,None,None,None,None,None,None),
(3,67,"GtkWidget",2,2,None,1,None,None,None,None),
(3,47,"GtkWidget",2,2,None,1,None,None,None,None),
(3,47,"GtkWidget",1,1,None,None,None,None,None,None),
(3,46,"GtkWidget",2,2,None,1,None,None,None,None),
(3,46,"GtkWidget",1,1,None,None,None,None,None,None),
(3,35,"GtkWidget",2,3,None,1,None,None,None,None),
(3,35,"GtkWidget",2,2,None,1,None,None,None,None),
(3,35,"GtkWidget",1,1,None,None,None,None,None,None)
</object_data>
<object_data_arg>
(4,34,"GtkWidget",2,2,"name","login-title-label"),
(4,35,"GtkWidget",2,2,"name","error-message-label"),
(4,33,"GtkWidget",2,2,"name","login-form-warpper"),
(4,45,"GtkWidget",2,2,"name","login-title-label"),
(1,9,"GtkStringList",2,5,"translatable",None),
(1,9,"GtkStringList",2,5,"context",None),
(1,9,"GtkStringList",2,5,"comments",None),
(1,9,"GtkStringList",2,4,"translatable",None),
(1,9,"GtkStringList",2,4,"context",None),
(1,9,"GtkStringList",2,4,"comments",None),
(1,9,"GtkStringList",2,3,"translatable",None),
(1,9,"GtkStringList",2,3,"context",None),
(1,9,"GtkStringList",2,3,"comments",None),
(1,9,"GtkStringList",2,2,"translatable",None),
(1,9,"GtkStringList",2,2,"context",None),
(1,9,"GtkStringList",2,2,"comments",None),
(1,24,"GtkWidget",2,2,"name","loading-label"),
(1,22,"GtkWidget",2,2,"name","loading-label"),
(1,23,"GtkWidget",2,2,"name","loading-label"),
(4,49,"GtkWidget",2,2,"name","loading-label"),
(4,50,"GtkWidget",2,2,"name","loading-label"),
(4,48,"GtkWidget",2,2,"name","loading-label"),
(5,17,"GtkWidget",2,2,"name","login-title-label"),
(5,23,"GtkWidget",2,2,"name","error-message-label"),
(5,13,"GtkWidget",2,2,"name","loading-label"),
(5,12,"GtkWidget",2,2,"name","loading-label"),
(5,11,"GtkWidget",2,2,"name","loading-label"),
(1,36,"GtkWidget",2,2,"name","loading-label"),
(3,31,"GtkWidget",2,2,"name","loading-label"),
(3,32,"GtkWidget",2,2,"name","loading-label"),
(3,30,"GtkWidget",2,2,"name","loading-label"),
(1,39,"GtkWidget",2,2,"name","loading-label"),
(1,40,"GtkWidget",2,2,"name","loading-label"),
(1,41,"GtkWidget",2,2,"name","error-message-label"),
(1,41,"GtkWidget",2,3,"name","loading-label"),
(3,67,"GtkWidget",2,2,"name","login-title-label"),
(3,47,"GtkWidget",2,2,"name","loading-label"),
(3,46,"GtkWidget",2,2,"name","loading-label"),
(3,35,"GtkWidget",2,3,"name","loading-label"),
(3,35,"GtkWidget",2,2,"name","error-message-label")
</object_data_arg>
</cambalache-project>

View File

@ -0,0 +1,8 @@
[wrap-git]
directory = blueprint-compiler
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
revision = v0.10.0
depth = 1
[provide]
program_names = blueprint-compiler

View File

@ -0,0 +1,124 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.16.0 -->
<interface>
<requires lib="gtk" version="4.12"/>
<template class="SLTotpDialog" parent="GtkWindow">
<property name="modal">True</property>
<property name="title">TOTP Entry</property>
<child>
<object class="GtkOverlay" id="overlay">
<property name="child">form_wrapper</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="valign">center</property>
<property name="vexpand">True</property>
<child>
<object class="GtkGrid" id="form_wrapper">
<property name="column-spacing">5</property>
<property name="halign">center</property>
<property name="hexpand">True</property>
<property name="margin-bottom">10</property>
<property name="margin-end">10</property>
<property name="margin-start">10</property>
<property name="margin-top">10</property>
<property name="row-spacing">5</property>
<property name="valign">center</property>
<property name="vexpand">True</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="hexpand">True</property>
<child>
<object class="GtkImage">
<property name="halign">center</property>
<property name="icon-name">dialog-password-symbolic</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">TOTP</property>
<style>
<class name="login-title-label"/>
</style>
</object>
</child>
<layout>
<property name="column">0</property>
<property name="column-span">2</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkButton" id="accept_button">
<property name="label">Accept</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">Cancel</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkEntry" id="code_entry">
<property name="placeholder-text">TOTP Code</property>
<layout>
<property name="column">0</property>
<property name="column-span">2</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="error_label">
<property name="label">ERROR</property>
<style>
<class name="error-message-label"/>
</style>
<layout>
<property name="column">0</property>
<property name="column-span">2</property>
<property name="row">2</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
<object class="GtkBox" id="loading_overlay">
<property name="orientation">vertical</property>
<child>
<object class="GtkSpinner">
<property name="spinning">True</property>
<property name="valign">end</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Loading...</property>
<property name="valign">start</property>
<property name="vexpand">True</property>
<style>
<class name="loading-label"/>
</style>
</object>
</child>
<style>
<class name="loading-label"/>
</style>
</object>
</interface>

View File

@ -0,0 +1,50 @@
use crate::login_window::LoginWindow;
use gtk4 as gtk;
use gtk::{glib, gio, prelude::*, subclass::prelude::*};
mod imp {
use super::*;
#[derive(Debug)]
pub struct Application {
pub settings: gio::Settings,
}
impl Default for Application {
fn default() -> Self {
Self {
settings: gio::Settings::new(crate::APP_ID)
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for Application {
const NAME: &'static str = "Application";
type Type = super::Application;
type ParentType = gtk::Application;
}
impl ApplicationImpl for Application {
fn activate(&self) {
let login_window = LoginWindow::new(&self.obj());
login_window.present();
}
}
impl ObjectImpl for Application {}
impl GtkApplicationImpl for Application {}
}
glib::wrapper! {
pub struct Application(ObjectSubclass<imp::Application>)
@extends gtk::Application, gio::Application,
@implements gio::ActionMap, gio::ActionGroup;
}
impl Application {
pub fn new(app_id: &str) -> Self {
glib::Object::builder().property("application-id", app_id).build()
}
}

View File

@ -0,0 +1,65 @@
use gtk4 as gtk;
use gtk::{glib, gio, subclass::prelude::*};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/login_window.blp")]
pub struct LoginWindow {
#[template_child]
pub forms_overlay: TemplateChild<gtk::Overlay>,
#[template_child]
pub forms_wrapper: TemplateChild<gtk::Box>,
#[template_child]
pub error_label: TemplateChild<gtk::Label>,
#[template_child]
pub device_entry: TemplateChild<gtk::Entry>,
#[template_child]
pub email_entry: TemplateChild<gtk::Entry>,
#[template_child]
pub password_entry: TemplateChild<gtk::PasswordEntry>,
#[template_child]
pub login_button: TemplateChild<gtk::Button>,
#[template_child]
pub api_key_entry: TemplateChild<gtk::PasswordEntry>,
#[template_child]
pub api_key_button: TemplateChild<gtk::Button>,
#[template_child]
pub device_entry_popover: TemplateChild<gtk::Popover>,
#[template_child]
pub loading_overlay: TemplateChild<gtk::Box>,
}
#[glib::object_subclass]
impl ObjectSubclass for LoginWindow {
const NAME: &'static str = "LoginWindow";
type Type = super::LoginWindow;
type ParentType = gtk::ApplicationWindow;
fn class_init(class: &mut Self::Class) {
class.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for LoginWindow {}
impl WidgetImpl for LoginWindow {}
impl WindowImpl for LoginWindow {}
impl ApplicationWindowImpl for LoginWindow {}
}
glib::wrapper! {
pub struct LoginWindow(ObjectSubclass<imp::LoginWindow>)
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow,
@implements gio::ActionMap, gio::ActionGroup;
}
impl LoginWindow {
pub fn new(app: &crate::application::Application) -> Self {
glib::Object::builder().property("application", app).build()
}
}

View File

@ -1,3 +1,20 @@
fn main() {
println!("Hello, world!");
mod login_window;
mod totp_window;
mod application;
use gtk4 as gtk;
use gtk::prelude::*;
use gtk::glib::{self, g_message};
const APP_ID: &str = "im.zander.SimpleLogin";
fn main() -> glib::ExitCode {
if cfg!(debug_assertions) {
// allow us to use a test schema for debug runs
let schema_dir = concat!(env!("OUT_DIR"), "/schemas/");
g_message!("SimpleLogin", "Using schema dir: \"{}\"", schema_dir);
std::env::set_var("GSETTINGS_SCHEMA_DIR", schema_dir);
}
let app = application::Application::new(APP_ID);
app.run()
}

View File

@ -0,0 +1,43 @@
use gtk4 as gtk;
use gtk::{glib, gio, prelude::*, subclass::prelude::*};
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/totp_window.blp")]
pub struct TotpWindow {
}
#[glib::object_subclass]
impl ObjectSubclass for TotpWindow {
const NAME: &'static str = "TotpWindow";
type Type = super::TotpWindow;
type ParentType = gtk::Window;
fn class_init(class: &mut Self::Class) {
class.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for TotpWindow {}
impl WidgetImpl for TotpWindow {}
impl WindowImpl for TotpWindow {}
}
glib::wrapper! {
pub struct TotpWindow(ObjectSubclass<imp::TotpWindow>)
@extends gtk::Widget, gtk::Window,
@implements gio::ActionMap, gio::ActionGroup;
}
impl TotpWindow {
pub fn new<P: IsA<gtk::Application>>(app: &P) -> Self {
glib::Object::builder().property("application", app).build()
}
}

View File

@ -1,7 +1,9 @@
#![allow(dead_code)]
use core::fmt;
use serde::{Serialize, Deserialize};
struct Context {
pub struct Context {
client: reqwest::Client,
api_key: Option<String>,
url: String,
@ -32,12 +34,60 @@ struct LoginResponse {
api_key: Option<String>,
}
pub enum Error {
FIDO(String),
Json(String),
Auth(String),
Http(String),
Internal(String)
#[derive(Serialize)]
struct MfaRequest<'a> {
mfa_token: &'a str,
mfa_key: &'a str,
device: &'a str
}
#[derive(Deserialize)]
struct MfaResponse {
name: String,
api_key: String,
email: String,
}
pub enum ErrorKind {
State,
Json,
Http,
Auth,
}
pub struct Error {
kind: ErrorKind,
msg: String,
}
impl Error {
pub(crate) fn new<T>(
kind: ErrorKind, msg: T
) -> Self where T: Into<String> {
Self {kind, msg: msg.into()}
}
pub fn is_state(&self) -> bool {
matches!(self.kind, ErrorKind::State)
}
pub fn is_http(&self) -> bool {
matches!(self.kind, ErrorKind::Http)
}
pub fn is_auth(&self) -> bool {
matches!(self.kind, ErrorKind::Auth)
}
pub fn is_json(&self) -> bool {
matches!(self.kind, ErrorKind::Json)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.msg)
}
}
pub const DEFAULT_URL: &str = "https://app.simplelogin.io/";
@ -63,6 +113,14 @@ impl Context {
}
}
pub fn api_key(&self) -> Option<&String> {
self.api_key.as_ref()
}
pub fn set_api_key(&mut self, api_key: Option<&str>) {
self.api_key = api_key.map(|s| s.to_owned());
}
fn parse_error_json(error_body: &String) -> String {
match serde_json::from_str(error_body) {
Ok::<ErrorResponse, _>(obj) => obj.error,
@ -77,22 +135,43 @@ impl Context {
Ok(resp) => {
let status = resp.status();
let resp_text = resp.text().await
.map_err(|e| Error::Http(e.to_string()))?;
.map_err(|e| Error::new(ErrorKind::Http, e.to_string()))?;
return if status.is_success() {
Ok(resp_text)
} else if status.is_client_error() {
Err(Error::Auth(Self::parse_error_json(&resp_text)))
Err(Error::new(ErrorKind::Auth, Self::parse_error_json(&resp_text)))
} else {
Err(Error::Http("Unknown error! Response code: ".to_owned()
+ status.as_str()))
Err(Error::new(ErrorKind::Http, status.to_string()))
}
},
Err(err) => {
Err(Error::Http(err.to_string()))
Err(Error::new(ErrorKind::Http, err.to_string()))
},
}
}
async fn post_request(
&mut self, endpoint: &str, body: &str
) -> Result<String, Error> {
Self::perform_request(self.client.post(self.url.to_owned() + endpoint)
.header("Content-Type", "application/json")
.body(body.to_owned())).await
}
pub fn obj_to_json<T>(
obj: &T
) -> Result<String, Error> where T: serde::Serialize {
serde_json::to_string(obj)
.map_err(|e| Error::new(ErrorKind::Json, e.to_string()))
}
pub fn json_to_obj<'a, T>(
json: &'a str
) -> Result<T, Error> where T: serde::Deserialize<'a> {
serde_json::from_str(json)
.map_err(|e| Error::new(ErrorKind::Json, e.to_string()))
}
pub async fn login(
&mut self,
email: &str,
@ -100,21 +179,16 @@ impl Context {
device: &str
) -> Result<bool, Error> {
let req_body = LoginRequest {email, password, device};
let response = Self::perform_request(self.client.post(self.url.to_owned())
.header("Content-Type", "application/json")
.body(serde_json::to_string(&req_body).unwrap()));
let body = response.await?;
let resp_obj: LoginResponse = serde_json::from_str(body.as_str())
.map_err(|_| Error::Json("Could not parse response json"
.to_owned()))?;
let body = self.post_request("api/auth/login",
Self::obj_to_json(&req_body)?.as_str()).await?;
let resp_obj: LoginResponse = Self::json_to_obj(body.as_str())?;
if resp_obj.mfa_enabled {
if resp_obj.mfa_key.is_some() {
self.device = Some(device.to_owned());
self.mfa_key = resp_obj.mfa_key;
Ok(true)
} else {
Err(Error::Json("Error response did not contain mfa key"
.to_owned()))
Err(Error::new(ErrorKind::Json, "Error response did not contain mfa key"))
}
} else {
if resp_obj.api_key.is_some() {
@ -123,13 +197,26 @@ impl Context {
self.device = None;
Ok(true)
} else {
Err(Error::Json("Response did not contain api key".to_owned()))
Err(Error::new(ErrorKind::Json, "Response did not contain api key"))
}
}
}
pub async fn mfa(&mut self, mfa_token: &str) -> Result<(), Error> {
// TODO implement
if self.mfa_key.is_some() && self.device.is_some() {
let req_json = Self::obj_to_json(&MfaRequest {
mfa_token,
mfa_key: self.mfa_key.as_ref().unwrap().as_str(),
device: self.device.as_ref().unwrap().as_str(),
})?;
let body = self.post_request("api/auth/mfa", req_json.as_str()).await?;
let resp_obj: MfaResponse = Self::json_to_obj(body.as_str())?;
self.api_key = Some(resp_obj.api_key);
self.device = None;
self.mfa_key = None;
Ok(())
} else {
Err(Error::new(ErrorKind::State, "No login operation in progress"))
}
}
}