mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Improve some small behaviours of login and key management (#1383)
This commit is contained in:
parent
a02337a07a
commit
ad07f2a97e
|
@ -12,7 +12,6 @@ use std::path::Path;
|
|||
use std::str::FromStr;
|
||||
|
||||
use kanidm_proto::messages::ConsoleOutputMode;
|
||||
use rand::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
@ -125,7 +124,6 @@ pub struct Configuration {
|
|||
pub secure_cookies: bool,
|
||||
pub trust_x_forward_for: bool,
|
||||
pub tls_config: Option<TlsConfiguration>,
|
||||
pub cookie_key: [u8; 32],
|
||||
pub integration_test_config: Option<Box<IntegrationTestConfig>>,
|
||||
pub online_backup: Option<OnlineBackup>,
|
||||
pub domain: String,
|
||||
|
@ -171,7 +169,7 @@ impl fmt::Display for Configuration {
|
|||
|
||||
impl Configuration {
|
||||
pub fn new() -> Self {
|
||||
let mut c = Configuration {
|
||||
Configuration {
|
||||
address: String::from("127.0.0.1:8080"),
|
||||
ldapaddress: None,
|
||||
threads: std::thread::available_parallelism()
|
||||
|
@ -189,17 +187,13 @@ impl Configuration {
|
|||
secure_cookies: !cfg!(test),
|
||||
trust_x_forward_for: false,
|
||||
tls_config: None,
|
||||
cookie_key: [0; 32],
|
||||
integration_test_config: None,
|
||||
online_backup: None,
|
||||
domain: "idm.example.com".to_string(),
|
||||
origin: "https://idm.example.com".to_string(),
|
||||
role: ServerRole::WriteReplica,
|
||||
output_mode: ConsoleOutputMode::default(),
|
||||
};
|
||||
let mut rng = StdRng::from_entropy();
|
||||
rng.fill(&mut c.cookie_key);
|
||||
c
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_online_backup(&mut self, cfg: &Option<OnlineBackup>) {
|
||||
|
|
|
@ -669,6 +669,8 @@ pub async fn create_server_core(
|
|||
}
|
||||
};
|
||||
|
||||
let cookie_key: [u8; 32] = idms.get_cookie_key();
|
||||
|
||||
// Any pre-start tasks here.
|
||||
match &config.integration_test_config {
|
||||
Some(itc) => {
|
||||
|
@ -781,8 +783,6 @@ pub async fn create_server_core(
|
|||
// TODO: Remove these when we go to auth bearer!
|
||||
// Copy the max size
|
||||
let _secure_cookies = config.secure_cookies;
|
||||
// domain will come from the qs now!
|
||||
let cookie_key: [u8; 32] = config.cookie_key;
|
||||
|
||||
let maybe_http_acceptor_handle = if config_test {
|
||||
admin_info!("this config rocks! 🪨 ");
|
||||
|
|
|
@ -993,6 +993,7 @@ pub const JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &str = r#"{
|
|||
"domain_uuid",
|
||||
"es256_private_key_der",
|
||||
"fernet_private_key_str",
|
||||
"cookie_private_key",
|
||||
"name",
|
||||
"uuid"
|
||||
],
|
||||
|
@ -1000,6 +1001,7 @@ pub const JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &str = r#"{
|
|||
"domain_display_name",
|
||||
"domain_ssid",
|
||||
"es256_private_key_der",
|
||||
"cookie_private_key",
|
||||
"fernet_private_key_str"
|
||||
],
|
||||
"acp_modify_presentattr": [
|
||||
|
|
|
@ -967,6 +967,35 @@ pub const JSON_SCHEMA_ATTR_JWS_ES256_PRIVATE_KEY: &str = r#"{
|
|||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_SCHEMA_ATTR_PRIVATE_COOKIE_KEY: &str = r#"{
|
||||
"attrs": {
|
||||
"class": [
|
||||
"object",
|
||||
"system",
|
||||
"attributetype"
|
||||
],
|
||||
"description": [
|
||||
"An private cookie hmac key"
|
||||
],
|
||||
"index": [],
|
||||
"unique": [
|
||||
"false"
|
||||
],
|
||||
"multivalue": [
|
||||
"false"
|
||||
],
|
||||
"attributename": [
|
||||
"private_cookie_key"
|
||||
],
|
||||
"syntax": [
|
||||
"PRIVATE_BINARY"
|
||||
],
|
||||
"uuid": [
|
||||
"00000000-0000-0000-0000-ffff00000130"
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
pub const JSON_SCHEMA_ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE: &str = r#"{
|
||||
"attrs": {
|
||||
"class": [
|
||||
|
@ -1630,6 +1659,7 @@ pub const JSON_SCHEMA_CLASS_DOMAIN_INFO: &str = r#"
|
|||
"domain_display_name",
|
||||
"fernet_private_key_str",
|
||||
"es256_private_key_der",
|
||||
"private_cookie_key",
|
||||
"version"
|
||||
],
|
||||
"uuid": [
|
||||
|
|
|
@ -224,6 +224,7 @@ pub const UUID_SCHEMA_ATTR_EMAILPRIMARY: Uuid = uuid!("00000000-0000-0000-0000-f
|
|||
pub const UUID_SCHEMA_ATTR_EMAILALTERNATIVE: Uuid = uuid!("00000000-0000-0000-0000-ffff00000127");
|
||||
pub const UUID_SCHEMA_ATTR_TOTP_IMPORT: Uuid = uuid!("00000000-0000-0000-0000-ffff00000128");
|
||||
pub const UUID_SCHEMA_ATTR_REPLICATED: Uuid = uuid!("00000000-0000-0000-0000-ffff00000129");
|
||||
pub const UUID_SCHEMA_ATTR_PRIVATE_COOKIE_KEY: Uuid = uuid!("00000000-0000-0000-0000-ffff00000130");
|
||||
|
||||
// System and domain infos
|
||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||
|
|
|
@ -451,7 +451,7 @@ impl Entry<EntryInit, EntryNew> {
|
|||
vs.into_iter().map(|v| Value::new_secret_str(&v))
|
||||
)
|
||||
}
|
||||
"es256_private_key_der" => {
|
||||
"es256_private_key_der" | "private_cookie_key" => {
|
||||
valueset::from_value_iter(
|
||||
vs.into_iter().map(|v| Value::new_privatebinary_base64(&v))
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
@ -81,6 +82,7 @@ pub struct IdmServer {
|
|||
uat_jwt_signer: Arc<CowCell<JwsSigner>>,
|
||||
uat_jwt_validator: Arc<CowCell<JwsValidator>>,
|
||||
token_enc_key: Arc<CowCell<Fernet>>,
|
||||
cookie_key: Arc<CowCell<[u8; 32]>>,
|
||||
}
|
||||
|
||||
/// Contains methods that require writes, but in the context of writing to the idm in memory structures (maybe the query server too). This is things like authentication.
|
||||
|
@ -130,6 +132,7 @@ pub struct IdmServerProxyWriteTransaction<'a> {
|
|||
pw_badlist_cache: CowCellWriteTxn<'a, HashSet<String>>,
|
||||
uat_jwt_signer: CowCellWriteTxn<'a, JwsSigner>,
|
||||
uat_jwt_validator: CowCellWriteTxn<'a, JwsValidator>,
|
||||
cookie_key: CowCellWriteTxn<'a, [u8; 32]>,
|
||||
pub(crate) token_enc_key: CowCellWriteTxn<'a, Fernet>,
|
||||
pub(crate) oauth2rs: Oauth2ResourceServersWriteTransaction<'a>,
|
||||
}
|
||||
|
@ -156,13 +159,22 @@ impl IdmServer {
|
|||
let (async_tx, async_rx) = unbounded();
|
||||
|
||||
// Get the domain name, as the relying party id.
|
||||
let (rp_id, rp_name, fernet_private_key, es256_private_key, pw_badlist_set, oauth2rs_set) = {
|
||||
let (
|
||||
rp_id,
|
||||
rp_name,
|
||||
fernet_private_key,
|
||||
es256_private_key,
|
||||
cookie_key,
|
||||
pw_badlist_set,
|
||||
oauth2rs_set,
|
||||
) = {
|
||||
let mut qs_read = task::block_on(qs.read());
|
||||
(
|
||||
qs_read.get_domain_name().to_string(),
|
||||
qs_read.get_domain_display_name().to_string(),
|
||||
qs_read.get_domain_fernet_private_key()?,
|
||||
qs_read.get_domain_es256_private_key()?,
|
||||
qs_read.get_domain_cookie_key()?,
|
||||
qs_read.get_password_badlist()?,
|
||||
// Add a read/reload of all oauth2 configurations.
|
||||
qs_read.get_oauth2rs_set()?,
|
||||
|
@ -220,6 +232,8 @@ impl IdmServer {
|
|||
let uat_jwt_signer = Arc::new(CowCell::new(jwt_signer));
|
||||
let uat_jwt_validator = Arc::new(CowCell::new(jwt_validator));
|
||||
|
||||
let cookie_key = Arc::new(CowCell::new(cookie_key));
|
||||
|
||||
let oauth2rs =
|
||||
Oauth2ResourceServers::try_from((oauth2rs_set, origin_url)).map_err(|e| {
|
||||
admin_error!("Failed to load oauth2 resource servers - {:?}", e);
|
||||
|
@ -240,12 +254,17 @@ impl IdmServer {
|
|||
uat_jwt_signer,
|
||||
uat_jwt_validator,
|
||||
token_enc_key,
|
||||
cookie_key,
|
||||
oauth2rs: Arc::new(oauth2rs),
|
||||
},
|
||||
IdmServerDelayed { async_rx },
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_cookie_key(&self) -> [u8; 32] {
|
||||
*self.cookie_key.read().deref()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn auth(&self) -> IdmServerAuthTransaction {
|
||||
task::block_on(self.auth_async())
|
||||
|
@ -301,6 +320,7 @@ impl IdmServer {
|
|||
uat_jwt_signer: self.uat_jwt_signer.write(),
|
||||
uat_jwt_validator: self.uat_jwt_validator.write(),
|
||||
token_enc_key: self.token_enc_key.write(),
|
||||
cookie_key: self.cookie_key.write(),
|
||||
oauth2rs: self.oauth2rs.write(),
|
||||
}
|
||||
}
|
||||
|
@ -2203,11 +2223,17 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
*self.uat_jwt_signer = new_signer;
|
||||
*self.uat_jwt_validator = new_validator;
|
||||
})?;
|
||||
self.qs_write
|
||||
.get_domain_cookie_key()
|
||||
.map(|new_cookie_key| {
|
||||
*self.cookie_key = new_cookie_key;
|
||||
})?;
|
||||
}
|
||||
// Commit everything.
|
||||
self.oauth2rs.commit();
|
||||
self.uat_jwt_signer.commit();
|
||||
self.uat_jwt_validator.commit();
|
||||
self.cookie_key.commit();
|
||||
self.token_enc_key.commit();
|
||||
self.pw_badlist_cache.commit();
|
||||
self.cred_update_sessions.commit();
|
||||
|
|
|
@ -8,6 +8,7 @@ use std::iter::once;
|
|||
|
||||
use compact_jwt::JwsSigner;
|
||||
use kanidm_proto::v1::OperationError;
|
||||
use rand::prelude::*;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::event::{CreateEvent, ModifyEvent};
|
||||
|
@ -91,6 +92,7 @@ impl Domain {
|
|||
let v = Value::new_secret_str(&k);
|
||||
e.add_ava("fernet_private_key_str", v);
|
||||
}
|
||||
|
||||
if !e.attribute_pres("es256_private_key_der") {
|
||||
security_info!("regenerating domain es256 private key");
|
||||
let der = JwsSigner::generate_es256()
|
||||
|
@ -102,6 +104,16 @@ impl Domain {
|
|||
let v = Value::new_privatebinary(&der);
|
||||
e.add_ava("es256_private_key_der", v);
|
||||
}
|
||||
|
||||
if !e.attribute_pres("private_cookie_key") {
|
||||
security_info!("regenerating domain cookie key");
|
||||
let mut key = [0; 32];
|
||||
let mut rng = StdRng::from_entropy();
|
||||
rng.fill(&mut key);
|
||||
let v = Value::new_privatebinary(&key);
|
||||
e.add_ava("private_cookie_key", v);
|
||||
}
|
||||
|
||||
trace!(?e);
|
||||
Ok(())
|
||||
} else {
|
||||
|
|
|
@ -316,6 +316,10 @@ mod tests {
|
|||
"acp_modify_removedattr",
|
||||
Value::new_iutf8("es256_private_key_der")
|
||||
),
|
||||
(
|
||||
"acp_modify_removedattr",
|
||||
Value::new_iutf8("private_cookie_key")
|
||||
),
|
||||
("acp_modify_presentattr", Value::new_iutf8("class")),
|
||||
("acp_modify_presentattr", Value::new_iutf8("displayname")),
|
||||
("acp_modify_presentattr", Value::new_iutf8("may")),
|
||||
|
@ -335,6 +339,10 @@ mod tests {
|
|||
"acp_modify_presentattr",
|
||||
Value::new_iutf8("es256_private_key_der")
|
||||
),
|
||||
(
|
||||
"acp_modify_presentattr",
|
||||
Value::new_iutf8("private_cookie_key")
|
||||
),
|
||||
("acp_create_class", Value::new_iutf8("object")),
|
||||
("acp_create_class", Value::new_iutf8("person")),
|
||||
("acp_create_class", Value::new_iutf8("system")),
|
||||
|
@ -353,6 +361,7 @@ mod tests {
|
|||
Value::new_iutf8("fernet_private_key_str")
|
||||
),
|
||||
("acp_create_attr", Value::new_iutf8("es256_private_key_der")),
|
||||
("acp_create_attr", Value::new_iutf8("private_cookie_key")),
|
||||
("acp_create_attr", Value::new_iutf8("version"))
|
||||
);
|
||||
pub static ref PRELOAD: Vec<EntryInitNew> =
|
||||
|
@ -492,6 +501,7 @@ mod tests {
|
|||
"domain_ssid": ["Example_Wifi"],
|
||||
"fernet_private_key_str": ["ABCD"],
|
||||
"es256_private_key_der" : ["MTIz"],
|
||||
"private_cookie_key" : ["MTIz"],
|
||||
"version": ["1"]
|
||||
}
|
||||
}"#,
|
||||
|
@ -534,6 +544,7 @@ mod tests {
|
|||
"domain_ssid": ["Example_Wifi"],
|
||||
"fernet_private_key_str": ["ABCD"],
|
||||
"es256_private_key_der" : ["MTIz"],
|
||||
"private_cookie_key" : ["MTIz"],
|
||||
"version": ["1"]
|
||||
}
|
||||
}"#,
|
||||
|
@ -566,6 +577,7 @@ mod tests {
|
|||
"domain_ssid": ["Example_Wifi"],
|
||||
"fernet_private_key_str": ["ABCD"],
|
||||
"es256_private_key_der" : ["MTIz"],
|
||||
"private_cookie_key" : ["MTIz"],
|
||||
"version": ["1"]
|
||||
}
|
||||
}"#,
|
||||
|
|
|
@ -364,6 +364,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
|||
JSON_SCHEMA_CLASS_OAUTH2_RS,
|
||||
JSON_SCHEMA_CLASS_OAUTH2_RS_BASIC,
|
||||
JSON_SCHEMA_CLASS_SYNC_ACCOUNT,
|
||||
JSON_SCHEMA_ATTR_PRIVATE_COOKIE_KEY,
|
||||
];
|
||||
|
||||
let r = idm_schema
|
||||
|
|
|
@ -721,6 +721,27 @@ pub trait QueryServerTransaction<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
fn get_domain_cookie_key(&mut self) -> Result<[u8; 32], OperationError> {
|
||||
self.internal_search_uuid(UUID_DOMAIN_INFO)
|
||||
.and_then(|e| {
|
||||
e.get_ava_single_private_binary("private_cookie_key")
|
||||
.and_then(|s| {
|
||||
let mut x = [0; 32];
|
||||
if s.len() == x.len() {
|
||||
x.copy_from_slice(s);
|
||||
Some(x)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or(OperationError::InvalidEntryState)
|
||||
})
|
||||
.map_err(|e| {
|
||||
admin_error!(?e, "Error getting domain cookie key");
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
// This is a helper to get password badlist.
|
||||
fn get_password_badlist(&mut self) -> Result<HashSet<String>, OperationError> {
|
||||
self.internal_search_uuid(UUID_SYSTEM_CONFIG)
|
||||
|
|
|
@ -774,6 +774,10 @@ function getImports() {
|
|||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_checked_f0b666100ef39e44 = function(arg0) {
|
||||
const ret = getObject(arg0).checked;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_setchecked_f1e1f3e62cdca8e7 = function(arg0, arg1) {
|
||||
getObject(arg0).checked = arg1 !== 0;
|
||||
};
|
||||
|
@ -1144,15 +1148,15 @@ function getImports() {
|
|||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper4732 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbindgen_closure_wrapper4738 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1095, __wbg_adapter_48);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper5600 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbindgen_closure_wrapper5605 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1436, __wbg_adapter_51);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper5678 = function(arg0, arg1, arg2) {
|
||||
imports.wbg.__wbindgen_closure_wrapper5683 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1466, __wbg_adapter_54);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
|
Binary file not shown.
|
@ -33,7 +33,7 @@ enum TotpState {
|
|||
}
|
||||
|
||||
enum LoginState {
|
||||
Init(bool),
|
||||
Init { enable: bool, remember_me: bool },
|
||||
// Select between different cred types, either password (and MFA) or Passkey
|
||||
Select(Vec<AuthMech>),
|
||||
// The choices of authentication mechanism.
|
||||
|
@ -246,7 +246,10 @@ impl LoginApp {
|
|||
fn view_state(&self, ctx: &Context<Self>) -> Html {
|
||||
let inputvalue = self.inputvalue.clone();
|
||||
match &self.state {
|
||||
LoginState::Init(enable) => {
|
||||
LoginState::Init {
|
||||
enable,
|
||||
remember_me,
|
||||
} => {
|
||||
html! {
|
||||
<>
|
||||
<div class="container">
|
||||
|
@ -273,6 +276,18 @@ impl LoginApp {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check form-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
role="switch"
|
||||
id="remember_me_check"
|
||||
disabled={ !enable }
|
||||
checked={ *remember_me }
|
||||
/>
|
||||
<label class="form-check-label" for="remember_me_check">{ "Remember my Username" }</label>
|
||||
</div>
|
||||
|
||||
<div class={CLASS_DIV_LOGIN_BUTTON}>
|
||||
<button
|
||||
type="submit"
|
||||
|
@ -564,7 +579,10 @@ impl Component for LoginApp {
|
|||
// -- clear the bearer to prevent conflict
|
||||
models::clear_bearer_token();
|
||||
// Do we have a login hint?
|
||||
let inputvalue = models::pop_login_hint().unwrap_or_default();
|
||||
let (inputvalue, remember_me) = models::pop_login_hint()
|
||||
.map(|user| (user, false))
|
||||
.or_else(|| models::get_login_remember_me().map(|user| (user, true)))
|
||||
.unwrap_or_default();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
|
@ -581,7 +599,10 @@ impl Component for LoginApp {
|
|||
// Clean any cookies.
|
||||
// TODO: actually check that it's cleaning the cookies.
|
||||
|
||||
let state = LoginState::Init(true);
|
||||
let state = LoginState::Init {
|
||||
enable: true,
|
||||
remember_me,
|
||||
};
|
||||
|
||||
add_body_form_classes!();
|
||||
|
||||
|
@ -603,10 +624,17 @@ impl Component for LoginApp {
|
|||
true
|
||||
}
|
||||
LoginAppMsg::Restart => {
|
||||
// Clear any leftover input
|
||||
self.inputvalue = "".to_string();
|
||||
// Clear any leftover input. Reset to the remembered username if any.
|
||||
let (inputvalue, remember_me) = models::get_login_remember_me()
|
||||
.map(|user| (user, true))
|
||||
.unwrap_or_default();
|
||||
|
||||
self.inputvalue = inputvalue;
|
||||
self.session_id = "".to_string();
|
||||
self.state = LoginState::Init(true);
|
||||
self.state = LoginState::Init {
|
||||
enable: true,
|
||||
remember_me,
|
||||
};
|
||||
true
|
||||
}
|
||||
LoginAppMsg::Begin => {
|
||||
|
@ -614,13 +642,35 @@ impl Component for LoginApp {
|
|||
console::debug!(format!("begin -> {:?}", self.inputvalue));
|
||||
// Disable the button?
|
||||
let username = self.inputvalue.clone();
|
||||
// If the remember-me was checked, stash it here.
|
||||
// If it was false, clear existing data.
|
||||
|
||||
let remember_me = if utils::get_inputelement_by_id("remember_me_check")
|
||||
.map(|element| element.checked())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
models::push_login_remember_me(username.clone());
|
||||
true
|
||||
} else {
|
||||
models::pop_login_remember_me();
|
||||
false
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
console::debug!(format!("begin remember_me -> {:?}", remember_me));
|
||||
|
||||
ctx.link().send_future(async {
|
||||
match Self::auth_init(username).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
});
|
||||
self.state = LoginState::Init(false);
|
||||
|
||||
self.state = LoginState::Init {
|
||||
enable: false,
|
||||
remember_me,
|
||||
};
|
||||
|
||||
true
|
||||
}
|
||||
LoginAppMsg::PasswordSubmit => {
|
||||
|
|
|
@ -69,6 +69,25 @@ pub fn pop_login_hint() -> Option<String> {
|
|||
l.ok()
|
||||
}
|
||||
|
||||
pub fn push_login_remember_me(r: String) {
|
||||
PersistentStorage::set("login_remember_me", r).expect_throw("failed to set login remember me");
|
||||
}
|
||||
|
||||
pub fn get_login_remember_me() -> Option<String> {
|
||||
let l: Result<String, _> = PersistentStorage::get("login_remember_me");
|
||||
#[cfg(debug_assertions)]
|
||||
console::debug!(format!("login_hint::pop_login_remember_me -> {:?}", l).as_str());
|
||||
l.ok()
|
||||
}
|
||||
|
||||
pub fn pop_login_remember_me() -> Option<String> {
|
||||
let l: Result<String, _> = PersistentStorage::get("login_remember_me");
|
||||
#[cfg(debug_assertions)]
|
||||
console::debug!(format!("login_hint::pop_login_remember_me -> {:?}", l).as_str());
|
||||
PersistentStorage::delete("login_remember_me");
|
||||
l.ok()
|
||||
}
|
||||
|
||||
/// Pushes the "cred_update_session" element into the browser's temporary storage
|
||||
pub fn push_cred_update_session(s: (CUSessionToken, CUStatus)) {
|
||||
TemporaryStorage::set("cred_update_session", s)
|
||||
|
|
|
@ -57,11 +57,11 @@ pub fn get_value_from_input_event(e: InputEvent) -> String {
|
|||
// .and_then(|element| element.dyn_into::<web_sys::HtmlButtonElement>().ok())
|
||||
// }
|
||||
|
||||
// pub fn get_inputelement_by_id(id: &str) -> Option<HtmlInputElement> {
|
||||
// document()
|
||||
// .get_element_by_id(id)
|
||||
// .and_then(|element| element.dyn_into::<web_sys::HtmlInputElement>().ok())
|
||||
// }
|
||||
pub fn get_inputelement_by_id(id: &str) -> Option<HtmlInputElement> {
|
||||
document()
|
||||
.get_element_by_id(id)
|
||||
.and_then(|element| element.dyn_into::<web_sys::HtmlInputElement>().ok())
|
||||
}
|
||||
|
||||
pub fn get_value_from_element_id(id: &str) -> Option<String> {
|
||||
document()
|
||||
|
|
Loading…
Reference in a new issue