1use self::handler_oauth2_client::CredHandlerOAuth2Client;
6use crate::credential::totp::Totp;
7use crate::credential::{BackupCodes, Credential, CredentialType, Password};
8use crate::idm::account::Account;
9use crate::idm::accountpolicy::ResolvedAccountPolicy;
10use crate::idm::audit::AuditEvent;
11use crate::idm::authentication::{AuthCredential, AuthExternal, AuthState};
12use crate::idm::delayed::{
13    AuthSessionRecord, BackupCodeRemoval, DelayedAction, PasswordUpgrade, WebauthnCounterIncrement,
14};
15use crate::idm::oauth2_client::OAuth2ClientProvider;
16use crate::prelude::*;
17use crate::server::keys::KeyObject;
18use crate::value::{AuthType, Session, SessionExtMetadata, SessionState};
19use compact_jwt::Jws;
20use hashbrown::HashSet;
21use kanidm_proto::internal::UserAuthToken;
22use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech};
23use nonempty::NonEmpty;
24use std::collections::BTreeMap;
25use std::sync::Arc;
26use std::time::Duration;
27use time::OffsetDateTime;
28use tokio::sync::mpsc::UnboundedSender as Sender;
29use uuid::Uuid;
30use webauthn_rs::prelude::{
31    AttestationCaList, AttestedPasskey as AttestedPasskeyV4, AttestedPasskeyAuthentication,
32    CredentialID, Passkey as PasskeyV4, PasskeyAuthentication, RequestChallengeResponse,
33    SecurityKeyAuthentication, Webauthn,
34};
35
36mod handler_oauth2_client;
37
38const BAD_PASSWORD_MSG: &str = "incorrect password";
44const BAD_TOTP_MSG: &str = "incorrect totp";
45const BAD_WEBAUTHN_MSG: &str = "invalid webauthn authentication";
46const BAD_ACCOUNT_POLICY: &str = "the credential no longer meets account policy requirements";
47const BAD_BACKUPCODE_MSG: &str = "invalid backup code";
48const BAD_AUTH_TYPE_MSG: &str = "invalid authentication method in this context";
49const BAD_CREDENTIALS: &str = "invalid credential message";
50const ACCOUNT_EXPIRED: &str = "account expired";
51const PW_BADLIST_MSG: &str = "password is in badlist";
52const BAD_OAUTH2_CSRF_STATE_MSG: &str = "invalid oauth2 csrf state";
53
54#[derive(Debug, Clone)]
55enum AuthIntent {
56    InitialAuth {
57        privileged: bool,
58    },
59    Reauth {
60        session_id: Uuid,
61        session_expiry: Option<OffsetDateTime>,
62    },
63}
64
65#[allow(clippy::large_enum_variant)]
69enum CredState {
70    Success {
71        auth_type: AuthType,
72        cred_id: Uuid,
73        ext_session_metadata: SessionExtMetadata,
74    },
75    Continue(Box<NonEmpty<AuthAllowed>>),
76    External(AuthExternal),
77    Denied(&'static str),
78}
79
80#[derive(Clone, Debug, PartialEq)]
81enum CredVerifyState {
83    Init,
84    Success,
85    Fail,
86}
87
88#[derive(Clone, Debug)]
89struct CredTotp {
91    pw: Password,
92    pw_state: CredVerifyState,
93    totp: BTreeMap<String, Totp>,
94    mfa_state: CredVerifyState,
95}
96
97#[derive(Clone, Debug)]
98struct CredBackupCode {
100    pw: Password,
101    pw_state: CredVerifyState,
102    backup_code: BackupCodes,
103    mfa_state: CredVerifyState,
104}
105
106#[derive(Clone, Debug)]
107struct CredSecurityKey {
109    pw: Password,
110    pw_state: CredVerifyState,
111    chal: RequestChallengeResponse,
112    ska: SecurityKeyAuthentication,
113    mfa_state: CredVerifyState,
114}
115
116#[derive(Clone, Debug)]
117struct CredPasskey {
119    chal: RequestChallengeResponse,
120    wan_state: PasskeyAuthentication,
121    state: CredVerifyState,
122}
123
124#[derive(Clone, Debug)]
125struct CredAttestedPasskey {
127    chal: RequestChallengeResponse,
128    wan_state: AttestedPasskeyAuthentication,
129    state: CredVerifyState,
130}
131
132#[derive(Clone, Debug)]
136enum CredHandler {
137    Anonymous {
138        cred_id: Uuid,
139    },
140    Password {
141        pw: Password,
142        generated: bool,
143        cred_id: Uuid,
144    },
145    PasswordTotp {
146        cmfa: CredTotp,
147        cred_id: Uuid,
148    },
149    PasswordBackupCode {
150        cmfa: CredBackupCode,
151        cred_id: Uuid,
152    },
153    PasswordSecurityKey {
154        cmfa: CredSecurityKey,
155        cred_id: Uuid,
156    },
157    Passkey {
158        c_wan: CredPasskey,
159        cred_ids: BTreeMap<CredentialID, Uuid>,
160    },
161    AttestedPasskey {
162        c_wan: CredAttestedPasskey,
163        att_ca_list: AttestationCaList,
165        creds: BTreeMap<AttestedPasskeyV4, Uuid>,
167    },
168    OAuth2Trust {
169        handler: Arc<CredHandlerOAuth2Client>,
170    },
171}
172
173impl CredHandler {
174    fn build_from_set_passkey(
179        wan: impl Iterator<Item = (Uuid, PasskeyV4)>,
180        webauthn: &Webauthn,
181    ) -> Option<Self> {
182        let mut pks = Vec::with_capacity(wan.size_hint().0);
183        let mut cred_ids = BTreeMap::default();
184
185        for (uuid, pk) in wan {
186            cred_ids.insert(pk.cred_id().clone(), uuid);
187            pks.push(pk);
188        }
189
190        if pks.is_empty() {
191            debug!("Account does not have any passkeys");
192            return None;
193        };
194
195        webauthn
196            .start_passkey_authentication(&pks)
197            .map(|(chal, wan_state)| CredHandler::Passkey {
198                c_wan: CredPasskey {
199                    chal,
200                    wan_state,
201                    state: CredVerifyState::Init,
202                },
203                cred_ids,
204            })
205            .map_err(|e| {
206                security_info!(
207                    ?e,
208                    "Unable to create passkey webauthn authentication challenge"
209                );
210                })
212            .ok()
213    }
214
215    fn build_from_single_passkey(
216        cred_id: Uuid,
217        pk: PasskeyV4,
218        webauthn: &Webauthn,
219    ) -> Option<Self> {
220        let cred_ids = btreemap!((pk.cred_id().clone(), cred_id));
221        let pks = vec![pk];
222
223        webauthn
224            .start_passkey_authentication(pks.as_slice())
225            .map(|(chal, wan_state)| CredHandler::Passkey {
226                c_wan: CredPasskey {
227                    chal,
228                    wan_state,
229                    state: CredVerifyState::Init,
230                },
231                cred_ids,
232            })
233            .map_err(|e| {
234                security_info!(
235                    ?e,
236                    "Unable to create passkey webauthn authentication challenge"
237                );
238                })
240            .ok()
241    }
242
243    fn build_from_set_attested_pk(
244        wan: &BTreeMap<Uuid, (String, AttestedPasskeyV4)>,
245        att_ca_list: &AttestationCaList,
246        webauthn: &Webauthn,
247    ) -> Option<Self> {
248        if wan.is_empty() {
249            debug!("Account does not have any attested passkeys");
250            return None;
251        };
252
253        let pks: Vec<_> = wan.values().map(|(_, k)| k).cloned().collect();
254        let creds: BTreeMap<_, _> = wan.iter().map(|(u, (_, k))| (k.clone(), *u)).collect();
255
256        webauthn
257            .start_attested_passkey_authentication(&pks)
258            .map(|(chal, wan_state)| CredHandler::AttestedPasskey {
259                c_wan: CredAttestedPasskey {
260                    chal,
261                    wan_state,
262                    state: CredVerifyState::Init,
263                },
264                att_ca_list: att_ca_list.clone(),
265                creds,
266            })
267            .map_err(|e| {
268                security_info!(
269                    ?e,
270                    "Unable to create attested passkey webauthn authentication challenge"
271                );
272                })
274            .ok()
275    }
276
277    fn build_from_single_attested_pk(
278        cred_id: Uuid,
279        pk: &AttestedPasskeyV4,
280        att_ca_list: &AttestationCaList,
281        webauthn: &Webauthn,
282    ) -> Option<Self> {
283        let creds = btreemap!((pk.clone(), cred_id));
284        let pks = vec![pk.clone()];
285
286        webauthn
287            .start_attested_passkey_authentication(pks.as_slice())
288            .map(|(chal, wan_state)| CredHandler::AttestedPasskey {
289                c_wan: CredAttestedPasskey {
290                    chal,
291                    wan_state,
292                    state: CredVerifyState::Init,
293                },
294                att_ca_list: att_ca_list.clone(),
295                creds,
296            })
297            .map_err(|e| {
298                security_info!(
299                    ?e,
300                    "Unable to create attested passkey webauthn authentication challenge"
301                );
302                })
304            .ok()
305    }
306
307    fn build_from_password_totp(cred: &Credential) -> Option<Self> {
308        match &cred.type_ {
309            CredentialType::PasswordMfa(pw, maybe_totp, _, _) => {
310                if maybe_totp.is_empty() {
311                    None
312                } else {
313                    let cmfa = CredTotp {
314                        pw: pw.clone(),
315                        pw_state: CredVerifyState::Init,
316                        totp: maybe_totp
317                            .iter()
318                            .map(|(l, t)| (l.clone(), t.clone()))
319                            .collect(),
320                        mfa_state: CredVerifyState::Init,
321                    };
322
323                    Some(CredHandler::PasswordTotp {
324                        cmfa,
325                        cred_id: cred.uuid,
326                    })
327                }
328            }
329            _ => None,
330        }
331    }
332
333    fn build_from_password_backup_code(cred: &Credential) -> Option<Self> {
334        match &cred.type_ {
335            CredentialType::PasswordMfa(pw, _, _, Some(backup_code)) => {
336                let cmfa = CredBackupCode {
337                    pw: pw.clone(),
338                    pw_state: CredVerifyState::Init,
339                    backup_code: backup_code.clone(),
340                    mfa_state: CredVerifyState::Init,
341                };
342
343                Some(CredHandler::PasswordBackupCode {
344                    cmfa,
345                    cred_id: cred.uuid,
346                })
347            }
348            _ => None,
349        }
350    }
351
352    fn build_from_password_security_key(cred: &Credential, webauthn: &Webauthn) -> Option<Self> {
353        match &cred.type_ {
354            CredentialType::PasswordMfa(pw, _, maybe_wan, _) => {
355                if !maybe_wan.is_empty() {
356                    let sks: Vec<_> = maybe_wan.values().cloned().collect();
357                    let (chal, ska) = webauthn
358                        .start_securitykey_authentication(&sks)
359                        .map_err(|err| {
360                            warn!(?err, "Unable to create webauthn authentication challenge")
361                        })
362                        .ok()?;
363
364                    let cmfa = CredSecurityKey {
365                        pw: pw.clone(),
366                        pw_state: CredVerifyState::Init,
367                        ska,
368                        chal,
369                        mfa_state: CredVerifyState::Init,
370                    };
371
372                    Some(CredHandler::PasswordSecurityKey {
373                        cmfa,
374                        cred_id: cred.uuid,
375                    })
376                } else {
377                    None
378                }
379            }
380            _ => None,
381        }
382    }
383
384    fn build_from_password_only(cred: &Credential) -> Option<Self> {
385        match &cred.type_ {
386            CredentialType::Password(pw) => Some(CredHandler::Password {
387                pw: pw.clone(),
388                generated: false,
389                cred_id: cred.uuid,
390            }),
391            CredentialType::GeneratedPassword(pw) => Some(CredHandler::Password {
392                pw: pw.clone(),
393                generated: true,
394                cred_id: cred.uuid,
395            }),
396            _ => None,
397        }
398    }
399
400    fn maybe_pw_upgrade(
404        pw: &Password,
405        who: Uuid,
406        cleartext: &str,
407        async_tx: &Sender<DelayedAction>,
408    ) {
409        if pw.requires_upgrade() {
410            if let Err(_e) = async_tx.send(DelayedAction::PwUpgrade(PasswordUpgrade {
411                target_uuid: who,
412                existing_password: cleartext.to_string(),
413            })) {
414                admin_warn!("unable to queue delayed pwupgrade, continuing ... ");
415            };
416        }
417    }
418
419    fn validate_anonymous(cred: &AuthCredential, cred_id: Uuid) -> CredState {
421        match cred {
422            AuthCredential::Anonymous => {
423                security_debug!("Handler::Anonymous -> Result::Success");
425                CredState::Success {
426                    auth_type: AuthType::Anonymous,
427                    cred_id,
428                    ext_session_metadata: Default::default(),
429                }
430            }
431            _ => {
432                security_error!(
433                    "Handler::Anonymous -> Result::Denied - invalid cred type for handler"
434                );
435                CredState::Denied(BAD_AUTH_TYPE_MSG)
436            }
437        }
438    }
439
440    fn validate_password(
442        cred: &AuthCredential,
443        cred_id: Uuid,
444        pw: &mut Password,
445        generated: bool,
446        who: Uuid,
447        async_tx: &Sender<DelayedAction>,
448        pw_badlist_set: &HashSet<String>,
449    ) -> CredState {
450        match cred {
451            AuthCredential::Password(cleartext) => {
452                if pw.verify(cleartext.as_str()).unwrap_or(false) {
453                    if pw_badlist_set.contains(&cleartext.to_lowercase()) {
454                        security_error!("Handler::Password -> Result::Denied - Password found in badlist during login");
455                        CredState::Denied(PW_BADLIST_MSG)
456                    } else {
457                        security_info!("Handler::Password -> Result::Success");
458                        Self::maybe_pw_upgrade(pw, who, cleartext.as_str(), async_tx);
459                        if generated {
460                            CredState::Success {
461                                auth_type: AuthType::GeneratedPassword,
462                                cred_id,
463                                ext_session_metadata: Default::default(),
464                            }
465                        } else {
466                            CredState::Success {
467                                auth_type: AuthType::Password,
468                                cred_id,
469                                ext_session_metadata: Default::default(),
470                            }
471                        }
472                    }
473                } else {
474                    security_error!("Handler::Password -> Result::Denied - incorrect password");
475                    CredState::Denied(BAD_PASSWORD_MSG)
476                }
477            }
478            _ => {
480                security_error!(
481                    "Handler::Password -> Result::Denied - invalid cred type for handler"
482                );
483                CredState::Denied(BAD_AUTH_TYPE_MSG)
484            }
485        }
486    }
487
488    fn validate_password_totp(
492        cred: &AuthCredential,
493        cred_id: Uuid,
494        ts: Duration,
495        pw_mfa: &mut CredTotp,
496        who: Uuid,
497        async_tx: &Sender<DelayedAction>,
498        pw_badlist_set: &HashSet<String>,
499    ) -> CredState {
500        match (&pw_mfa.mfa_state, &pw_mfa.pw_state) {
501            (CredVerifyState::Init, CredVerifyState::Init) => {
502                match cred {
504                    AuthCredential::Totp(totp_chal) => {
505                        if let Some(label) = pw_mfa
509                            .totp
510                            .iter()
511                            .find(|(_, t)| t.verify(*totp_chal, ts))
512                            .map(|(l, _)| l)
513                        {
514                            pw_mfa.mfa_state = CredVerifyState::Success;
515                            security_info!(
516                                "Handler::PasswordMfa -> Result::Continue - TOTP ({}) OK, password -", label
517                            );
518                            CredState::Continue(Box::new(NonEmpty {
519                                head: AuthAllowed::Password,
520                                tail: Vec::with_capacity(0),
521                            }))
522                        } else {
523                            pw_mfa.mfa_state = CredVerifyState::Fail;
524                            security_error!(
525                                "Handler::PasswordMfa -> Result::Denied - TOTP Fail, password -"
526                            );
527                            CredState::Denied(BAD_TOTP_MSG)
528                        }
529                    }
530                    _ => {
531                        security_error!("Handler::PasswordMfa -> Result::Denied - invalid cred type for handler");
532                        CredState::Denied(BAD_AUTH_TYPE_MSG)
533                    }
534                }
535            }
536            (CredVerifyState::Success, CredVerifyState::Init) => {
537                match cred {
539                    AuthCredential::Password(cleartext) => {
540                        if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) {
541                            if pw_badlist_set.contains(&cleartext.to_lowercase()) {
542                                pw_mfa.pw_state = CredVerifyState::Fail;
543                                security_error!("Handler::PasswordMfa -> Result::Denied - Password found in badlist during login");
544                                CredState::Denied(PW_BADLIST_MSG)
545                            } else {
546                                pw_mfa.pw_state = CredVerifyState::Success;
547                                security_info!("Handler::PasswordMfa -> Result::Success - TOTP OK, password OK");
548                                Self::maybe_pw_upgrade(
549                                    &pw_mfa.pw,
550                                    who,
551                                    cleartext.as_str(),
552                                    async_tx,
553                                );
554                                CredState::Success {
555                                    auth_type: AuthType::PasswordTotp,
556                                    cred_id,
557                                    ext_session_metadata: Default::default(),
558                                }
559                            }
560                        } else {
561                            pw_mfa.pw_state = CredVerifyState::Fail;
562                            security_error!(
563                                "Handler::PasswordMfa -> Result::Denied - TOTP OK, password Fail"
564                            );
565                            CredState::Denied(BAD_PASSWORD_MSG)
566                        }
567                    }
568                    _ => {
569                        security_error!("Handler::PasswordMfa -> Result::Denied - invalid cred type for handler");
570                        CredState::Denied(BAD_AUTH_TYPE_MSG)
571                    }
572                }
573            }
574            _ => {
575                security_error!(
576                    "Handler::PasswordMfa -> Result::Denied - invalid credential mfa and pw state"
577                );
578                CredState::Denied(BAD_AUTH_TYPE_MSG)
579            }
580        }
581    } fn validate_password_security_key(
587        cred: &AuthCredential,
588        cred_id: Uuid,
589        pw_mfa: &mut CredSecurityKey,
590        webauthn: &Webauthn,
591        who: Uuid,
592        async_tx: &Sender<DelayedAction>,
593        pw_badlist_set: &HashSet<String>,
594    ) -> CredState {
595        match (&pw_mfa.mfa_state, &pw_mfa.pw_state) {
596            (CredVerifyState::Init, CredVerifyState::Init) => {
597                match cred {
599                    AuthCredential::SecurityKey(resp) => {
600                        match webauthn.finish_securitykey_authentication(resp, &pw_mfa.ska) {
601                            Ok(auth_result) => {
602                                pw_mfa.mfa_state = CredVerifyState::Success;
603                                if auth_result.needs_update() {
606                                    if let Err(_e) =
608                                        async_tx.send(DelayedAction::WebauthnCounterIncrement(
609                                            WebauthnCounterIncrement {
610                                                target_uuid: who,
611                                                auth_result,
612                                            },
613                                        ))
614                                    {
615                                        admin_warn!("unable to queue delayed webauthn property update, continuing ... ");
616                                    };
617                                };
618                                CredState::Continue(Box::new(NonEmpty {
619                                    head: AuthAllowed::Password,
620                                    tail: Vec::with_capacity(0),
621                                }))
622                            }
623                            Err(e) => {
624                                pw_mfa.mfa_state = CredVerifyState::Fail;
625                                security_error!(
627                                    ?e,
628                                    "Handler::Webauthn -> Result::Denied - webauthn error"
629                                );
630                                CredState::Denied(BAD_WEBAUTHN_MSG)
631                            }
632                        }
633                    }
634                    _ => {
635                        security_error!("Handler::PasswordMfa -> Result::Denied - invalid cred type for handler");
636                        CredState::Denied(BAD_AUTH_TYPE_MSG)
637                    }
638                }
639            }
640            (CredVerifyState::Success, CredVerifyState::Init) => {
641                match cred {
643                    AuthCredential::Password(cleartext) => {
644                        if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) {
645                            if pw_badlist_set.contains(&cleartext.to_lowercase()) {
646                                pw_mfa.pw_state = CredVerifyState::Fail;
647                                security_error!("Handler::PasswordMfa -> Result::Denied - Password found in badlist during login");
648                                CredState::Denied(PW_BADLIST_MSG)
649                            } else {
650                                pw_mfa.pw_state = CredVerifyState::Success;
651                                security_info!("Handler::PasswordMfa -> Result::Success - SecurityKey OK, password OK");
652                                Self::maybe_pw_upgrade(
653                                    &pw_mfa.pw,
654                                    who,
655                                    cleartext.as_str(),
656                                    async_tx,
657                                );
658                                CredState::Success {
659                                    auth_type: AuthType::PasswordSecurityKey,
660                                    cred_id,
661                                    ext_session_metadata: Default::default(),
662                                }
663                            }
664                        } else {
665                            pw_mfa.pw_state = CredVerifyState::Fail;
666                            security_error!("Handler::PasswordMfa -> Result::Denied - SecurityKey OK, password Fail");
667                            CredState::Denied(BAD_PASSWORD_MSG)
668                        }
669                    }
670                    _ => {
671                        security_error!("Handler::PasswordMfa -> Result::Denied - invalid cred type for handler");
672                        CredState::Denied(BAD_AUTH_TYPE_MSG)
673                    }
674                }
675            }
676            _ => {
677                security_error!(
678                    "Handler::PasswordMfa -> Result::Denied - invalid credential mfa and pw state"
679                );
680                CredState::Denied(BAD_AUTH_TYPE_MSG)
681            }
682        }
683    }
684
685    fn validate_password_backup_code(
689        cred: &AuthCredential,
690        cred_id: Uuid,
691        pw_mfa: &mut CredBackupCode,
692        who: Uuid,
693        async_tx: &Sender<DelayedAction>,
694        pw_badlist_set: &HashSet<String>,
695    ) -> CredState {
696        match (&pw_mfa.mfa_state, &pw_mfa.pw_state) {
697            (CredVerifyState::Init, CredVerifyState::Init) => {
698                match cred {
700                    AuthCredential::BackupCode(code_chal) => {
701                        if pw_mfa.backup_code.verify(code_chal) {
702                            if let Err(_e) =
703                                async_tx.send(DelayedAction::BackupCodeRemoval(BackupCodeRemoval {
704                                    target_uuid: who,
705                                    code_to_remove: code_chal.to_string(),
706                                }))
707                            {
708                                admin_warn!(
709                                    "unable to queue delayed backup code removal, continuing ... "
710                                );
711                            };
712                            pw_mfa.mfa_state = CredVerifyState::Success;
713                            security_info!("Handler::PasswordMfa -> Result::Continue - BackupCode OK, password -");
714                            CredState::Continue(Box::new(NonEmpty {
715                                head: AuthAllowed::Password,
716                                tail: Vec::with_capacity(0),
717                            }))
718                        } else {
719                            pw_mfa.mfa_state = CredVerifyState::Fail;
720                            security_error!("Handler::PasswordMfa -> Result::Denied - BackupCode Fail, password -");
721                            CredState::Denied(BAD_BACKUPCODE_MSG)
722                        }
723                    }
724                    _ => {
725                        security_error!("Handler::PasswordMfa -> Result::Denied - invalid cred type for handler");
726                        CredState::Denied(BAD_AUTH_TYPE_MSG)
727                    }
728                }
729            }
730            (CredVerifyState::Success, CredVerifyState::Init) => {
731                match cred {
733                    AuthCredential::Password(cleartext) => {
734                        if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) {
735                            if pw_badlist_set.contains(&cleartext.to_lowercase()) {
736                                pw_mfa.pw_state = CredVerifyState::Fail;
737                                security_error!("Handler::PasswordMfa -> Result::Denied - Password found in badlist during login");
738                                CredState::Denied(PW_BADLIST_MSG)
739                            } else {
740                                pw_mfa.pw_state = CredVerifyState::Success;
741                                security_info!("Handler::PasswordMfa -> Result::Success - BackupCode OK, password OK");
742                                Self::maybe_pw_upgrade(
743                                    &pw_mfa.pw,
744                                    who,
745                                    cleartext.as_str(),
746                                    async_tx,
747                                );
748                                CredState::Success {
749                                    auth_type: AuthType::PasswordBackupCode,
750                                    cred_id,
751                                    ext_session_metadata: Default::default(),
752                                }
753                            }
754                        } else {
755                            pw_mfa.pw_state = CredVerifyState::Fail;
756                            security_error!("Handler::PasswordMfa -> Result::Denied - BackupCode OK, password Fail");
757                            CredState::Denied(BAD_PASSWORD_MSG)
758                        }
759                    }
760                    _ => {
761                        security_error!("Handler::PasswordMfa -> Result::Denied - invalid cred type for handler");
762                        CredState::Denied(BAD_AUTH_TYPE_MSG)
763                    }
764                }
765            }
766            _ => {
767                security_error!(
768                    "Handler::PasswordMfa -> Result::Denied - invalid credential mfa and pw state"
769                );
770                CredState::Denied(BAD_AUTH_TYPE_MSG)
771            }
772        }
773    }
774
775    pub fn validate_passkey(
777        cred: &AuthCredential,
778        cred_ids: &BTreeMap<CredentialID, Uuid>,
779        wan_cred: &mut CredPasskey,
780        webauthn: &Webauthn,
781        who: Uuid,
782        async_tx: &Sender<DelayedAction>,
783    ) -> CredState {
784        if wan_cred.state != CredVerifyState::Init {
785            security_error!("Handler::Webauthn -> Result::Denied - Internal State Already Fail");
786            return CredState::Denied(BAD_WEBAUTHN_MSG);
787        }
788
789        match cred {
790            AuthCredential::Passkey(resp) => {
791                match webauthn.finish_passkey_authentication(resp, &wan_cred.wan_state) {
793                    Ok(auth_result) => {
794                        if let Some(cred_id) = cred_ids.get(auth_result.cred_id()).copied() {
795                            wan_cred.state = CredVerifyState::Success;
796                            if auth_result.needs_update() {
799                                if let Err(_e) =
801                                    async_tx.send(DelayedAction::WebauthnCounterIncrement(
802                                        WebauthnCounterIncrement {
803                                            target_uuid: who,
804                                            auth_result,
805                                        },
806                                    ))
807                                {
808                                    admin_warn!("unable to queue delayed webauthn property update, continuing ... ");
809                                };
810                            };
811
812                            CredState::Success {
813                                auth_type: AuthType::Passkey,
814                                cred_id,
815                                ext_session_metadata: Default::default(),
816                            }
817                        } else {
818                            wan_cred.state = CredVerifyState::Fail;
819                            security_error!("Handler::Webauthn -> Result::Denied - webauthn credential id not found");
821                            CredState::Denied(BAD_WEBAUTHN_MSG)
822                        }
823                    }
824                    Err(e) => {
825                        wan_cred.state = CredVerifyState::Fail;
826                        security_error!(?e, "Handler::Webauthn -> Result::Denied - webauthn error");
828                        CredState::Denied(BAD_WEBAUTHN_MSG)
829                    }
830                }
831            }
832            _ => {
833                security_error!(
834                    "Handler::Webauthn -> Result::Denied - invalid cred type for handler"
835                );
836                CredState::Denied(BAD_AUTH_TYPE_MSG)
837            }
838        }
839    }
840
841    pub fn validate_attested_passkey(
843        cred: &AuthCredential,
844        creds: &BTreeMap<AttestedPasskeyV4, Uuid>,
845        wan_cred: &mut CredAttestedPasskey,
846        webauthn: &Webauthn,
847        who: Uuid,
848        async_tx: &Sender<DelayedAction>,
849        att_ca_list: &AttestationCaList,
850    ) -> CredState {
851        if wan_cred.state != CredVerifyState::Init {
852            security_error!("Handler::Webauthn -> Result::Denied - Internal State Already Fail");
853            return CredState::Denied(BAD_WEBAUTHN_MSG);
854        }
855
856        match cred {
857            AuthCredential::Passkey(resp) => {
858                match webauthn.finish_attested_passkey_authentication(resp, &wan_cred.wan_state) {
860                    Ok(auth_result) => {
861                        if let Some((apk, cred_id)) = creds.get_key_value(auth_result.cred_id()) {
862                            if let Err(webauthn_err) = apk.verify_attestation(att_ca_list) {
865                                wan_cred.state = CredVerifyState::Fail;
866                                debug!(?webauthn_err);
868                                security_error!("Handler::Webauthn -> Result::Denied - webauthn credential fails attestation");
869                                return CredState::Denied(BAD_ACCOUNT_POLICY);
870                            }
871
872                            wan_cred.state = CredVerifyState::Success;
873                            if auth_result.needs_update() {
876                                if let Err(_e) =
878                                    async_tx.send(DelayedAction::WebauthnCounterIncrement(
879                                        WebauthnCounterIncrement {
880                                            target_uuid: who,
881                                            auth_result,
882                                        },
883                                    ))
884                                {
885                                    admin_warn!("unable to queue delayed webauthn property update, continuing ... ");
886                                };
887                            };
888
889                            CredState::Success {
890                                auth_type: AuthType::AttestedPasskey,
891                                cred_id: *cred_id,
892                                ext_session_metadata: Default::default(),
893                            }
894                        } else {
895                            wan_cred.state = CredVerifyState::Fail;
896                            security_error!("Handler::Webauthn -> Result::Denied - webauthn credential id not found");
898                            CredState::Denied(BAD_WEBAUTHN_MSG)
899                        }
900                    }
901                    Err(e) => {
902                        wan_cred.state = CredVerifyState::Fail;
903                        security_error!(?e, "Handler::Webauthn -> Result::Denied - webauthn error");
905                        CredState::Denied(BAD_WEBAUTHN_MSG)
906                    }
907                }
908            }
909            _ => {
910                security_error!(
911                    "Handler::Webauthn -> Result::Denied - invalid cred type for handler"
912                );
913                CredState::Denied(BAD_AUTH_TYPE_MSG)
914            }
915        }
916    }
917
918    #[allow(clippy::too_many_arguments)]
919    pub fn validate(
921        &mut self,
922        cred: &AuthCredential,
923        ts: Duration,
924        who: Uuid,
925        async_tx: &Sender<DelayedAction>,
926        webauthn: &Webauthn,
927        pw_badlist_set: &HashSet<String>,
928    ) -> CredState {
929        match self {
930            CredHandler::Anonymous { cred_id } => Self::validate_anonymous(cred, *cred_id),
931            CredHandler::Password {
932                ref mut pw,
933                generated,
934                cred_id,
935            } => Self::validate_password(
936                cred,
937                *cred_id,
938                pw,
939                *generated,
940                who,
941                async_tx,
942                pw_badlist_set,
943            ),
944            CredHandler::PasswordTotp {
945                ref mut cmfa,
946                cred_id,
947            } => Self::validate_password_totp(
948                cred,
949                *cred_id,
950                ts,
951                cmfa,
952                who,
953                async_tx,
954                pw_badlist_set,
955            ),
956            CredHandler::PasswordBackupCode {
957                ref mut cmfa,
958                cred_id,
959            } => Self::validate_password_backup_code(
960                cred,
961                *cred_id,
962                cmfa,
963                who,
964                async_tx,
965                pw_badlist_set,
966            ),
967            CredHandler::PasswordSecurityKey {
968                ref mut cmfa,
969                cred_id,
970            } => Self::validate_password_security_key(
971                cred,
972                *cred_id,
973                cmfa,
974                webauthn,
975                who,
976                async_tx,
977                pw_badlist_set,
978            ),
979            CredHandler::Passkey {
980                ref mut c_wan,
981                cred_ids,
982            } => Self::validate_passkey(cred, cred_ids, c_wan, webauthn, who, async_tx),
983            CredHandler::AttestedPasskey {
984                ref mut c_wan,
985                ref att_ca_list,
986                creds,
987            } => Self::validate_attested_passkey(
988                cred,
989                creds,
990                c_wan,
991                webauthn,
992                who,
993                async_tx,
994                att_ca_list,
995            ),
996            CredHandler::OAuth2Trust { handler } => handler.validate(cred, ts),
997        }
998    }
999
1000    pub fn next_auth_state(&self) -> AuthState {
1003        match &self {
1004            CredHandler::Anonymous { .. } => AuthState::Continue(vec![AuthAllowed::Anonymous]),
1005            CredHandler::Password { .. } => AuthState::Continue(vec![AuthAllowed::Password]),
1006            CredHandler::PasswordTotp { .. } => AuthState::Continue(vec![AuthAllowed::Totp]),
1007            CredHandler::PasswordBackupCode { .. } => {
1008                AuthState::Continue(vec![AuthAllowed::BackupCode])
1009            }
1010
1011            CredHandler::PasswordSecurityKey { ref cmfa, .. } => {
1012                AuthState::Continue(vec![AuthAllowed::SecurityKey(cmfa.chal.clone())])
1013            }
1014            CredHandler::Passkey { c_wan, .. } => {
1015                AuthState::Continue(vec![AuthAllowed::Passkey(c_wan.chal.clone())])
1016            }
1017            CredHandler::AttestedPasskey { c_wan, .. } => {
1018                AuthState::Continue(vec![AuthAllowed::Passkey(c_wan.chal.clone())])
1019            }
1020            CredHandler::OAuth2Trust { handler } => {
1021                let (authorisation_url, request) = handler.start_auth_request();
1022                AuthState::External(AuthExternal::OAuth2AuthorisationRequest {
1023                    authorisation_url,
1024                    request,
1025                })
1026            }
1027        }
1028    }
1029
1030    fn can_proceed(&self, mech: &AuthMech) -> bool {
1032        match (self, mech) {
1033            (CredHandler::Anonymous { .. }, AuthMech::Anonymous)
1034            | (CredHandler::Password { .. }, AuthMech::Password)
1035            | (CredHandler::PasswordTotp { .. }, AuthMech::PasswordTotp)
1036            | (CredHandler::PasswordBackupCode { .. }, AuthMech::PasswordBackupCode)
1037            | (CredHandler::PasswordSecurityKey { .. }, AuthMech::PasswordSecurityKey)
1038            | (CredHandler::Passkey { .. }, AuthMech::Passkey)
1039            | (CredHandler::AttestedPasskey { .. }, AuthMech::Passkey)
1040            | (CredHandler::OAuth2Trust { .. }, AuthMech::OAuth2Trust) => true,
1041            (_, _) => false,
1042        }
1043    }
1044
1045    fn allows_mech(&self) -> AuthMech {
1046        match self {
1047            CredHandler::Anonymous { .. } => AuthMech::Anonymous,
1048            CredHandler::Password { .. } => AuthMech::Password,
1049            CredHandler::PasswordTotp { .. } => AuthMech::PasswordTotp,
1050            CredHandler::PasswordBackupCode { .. } => AuthMech::PasswordBackupCode,
1051            CredHandler::PasswordSecurityKey { .. } => AuthMech::PasswordSecurityKey,
1052            CredHandler::Passkey { .. } => AuthMech::Passkey,
1053            CredHandler::AttestedPasskey { .. } => AuthMech::Passkey,
1054            CredHandler::OAuth2Trust { .. } => AuthMech::OAuth2Trust,
1055        }
1056    }
1057}
1058
1059#[allow(clippy::large_enum_variant)]
1060#[derive(Clone)]
1061enum AuthSessionState {
1067    Init(NonEmpty<CredHandler>),
1068    InProgress(CredHandler),
1074    Success,
1075    Denied(&'static str),
1076}
1077
1078impl AuthSessionState {
1079    fn is_denied(&self) -> Option<&'static str> {
1080        match &self {
1081            AuthSessionState::Denied(x) => Some(x),
1082            _ => None,
1083        }
1084    }
1085}
1086
1087pub(crate) struct AuthSessionData<'a> {
1088    pub(crate) account: Account,
1089    pub(crate) account_policy: ResolvedAccountPolicy,
1090    pub(crate) issue: AuthIssueSession,
1091    pub(crate) webauthn: &'a Webauthn,
1092    pub(crate) ct: Duration,
1093    pub(crate) client_auth_info: ClientAuthInfo,
1094
1095    pub(crate) oauth2_client_provider: Option<&'a OAuth2ClientProvider>,
1096}
1097
1098#[derive(Clone)]
1099pub(crate) struct AuthSession {
1101    account: Account,
1104    account_policy: ResolvedAccountPolicy,
1106
1107    state: AuthSessionState,
1113
1114    issue: AuthIssueSession,
1116
1117    intent: AuthIntent,
1120
1121    source: Source,
1123
1124    key_object: Arc<KeyObject>,
1126}
1127
1128impl AuthSession {
1129    pub fn new(
1133        asd: AuthSessionData<'_>,
1134        privileged: bool,
1135        key_object: Arc<KeyObject>,
1136    ) -> (Option<Self>, AuthState) {
1137        let state = if asd.account.is_within_valid_time(asd.ct) {
1141            if asd.account.is_anonymous() {
1145                AuthSessionState::Init(NonEmpty {
1146                    head: CredHandler::Anonymous {
1147                        cred_id: asd.account.uuid,
1148                    },
1149                    tail: Vec::with_capacity(0),
1150                })
1151            } else {
1152                let mut handlers = Vec::with_capacity(4);
1153
1154                if let Some(cred) = &asd.account.primary {
1161                    if let Some(ch) = CredHandler::build_from_password_totp(cred) {
1163                        handlers.push(ch);
1164                    }
1165
1166                    if let Some(ch) = CredHandler::build_from_password_backup_code(cred) {
1167                        handlers.push(ch);
1168                    }
1169
1170                    if let Some(ch) =
1171                        CredHandler::build_from_password_security_key(cred, asd.webauthn)
1172                    {
1173                        handlers.push(ch);
1174                    }
1175
1176                    if handlers.is_empty() {
1177                        if let Some(ch) = CredHandler::build_from_password_only(cred) {
1179                            handlers.push(ch);
1180                        }
1181                    }
1182                }
1183
1184                trace!(?handlers);
1185
1186                if let Some(att_ca_list) = asd.account_policy.webauthn_attestation_ca_list() {
1188                    if let Some(ch) = CredHandler::build_from_set_attested_pk(
1189                        &asd.account.attested_passkeys,
1190                        att_ca_list,
1191                        asd.webauthn,
1192                    ) {
1193                        handlers.push(ch);
1194                    }
1195                } else {
1196                    let credential_iter = asd
1197                        .account
1198                        .passkeys
1199                        .iter()
1200                        .map(|(u, (_, pk))| (*u, pk.clone()))
1201                        .chain(
1202                            asd.account
1203                                .attested_passkeys
1204                                .iter()
1205                                .map(|(u, (_, pk))| (*u, pk.into())),
1206                        );
1207
1208                    if let Some(ch) =
1209                        CredHandler::build_from_set_passkey(credential_iter, asd.webauthn)
1210                    {
1211                        handlers.push(ch);
1212                    }
1213                };
1214
1215                if let Some(oauth2_client_provider) = asd.oauth2_client_provider {
1216                    if let Some(trust_user) = asd.account.oauth2_client_provider() {
1217                        let handler = Arc::new(CredHandlerOAuth2Client::new(
1218                            oauth2_client_provider,
1219                            trust_user,
1220                        ));
1221                        handlers.push(CredHandler::OAuth2Trust { handler })
1222                    }
1223                };
1224
1225                if let Some(non_empty_handlers) = NonEmpty::collect(handlers) {
1226                    AuthSessionState::Init(non_empty_handlers)
1227                } else {
1228                    security_info!("account has no available credentials");
1229                    AuthSessionState::Denied("invalid credential state")
1230                }
1231            }
1232        } else {
1233            security_info!("account expired");
1234            AuthSessionState::Denied(ACCOUNT_EXPIRED)
1235        };
1236
1237        if let Some(reason) = state.is_denied() {
1239            (None, AuthState::Denied(reason.to_string()))
1241        } else {
1242            let auth_session = AuthSession {
1244                account: asd.account,
1245                account_policy: asd.account_policy,
1246                state,
1247                issue: asd.issue,
1248                intent: AuthIntent::InitialAuth { privileged },
1249                source: asd.client_auth_info.source,
1250                key_object,
1251            };
1252            let valid_mechs = auth_session.valid_auth_mechs();
1256
1257            security_debug!(?valid_mechs, "Offering auth mechanisms");
1258            let as_state = AuthState::Choose(valid_mechs);
1259            (Some(auth_session), as_state)
1260        }
1261    }
1262
1263    pub(crate) fn new_reauth(
1268        asd: AuthSessionData<'_>,
1269        session_id: Uuid,
1270        session: &Session,
1271        cred_id: Uuid,
1272        key_object: Arc<KeyObject>,
1273    ) -> (Option<Self>, AuthState) {
1274        #[allow(clippy::large_enum_variant)]
1275        enum State {
1277            Expired,
1278            NoMatchingCred,
1279            Proceed(CredHandler),
1280        }
1281
1282        let state = if asd.account.is_within_valid_time(asd.ct) {
1283            let mut cred_handler = None;
1290
1291            match session.type_ {
1292                AuthType::Password
1293                | AuthType::GeneratedPassword
1294                | AuthType::PasswordBackupCode => {
1297                    if let Some(primary) = asd.account.primary.as_ref() {
1298                        if primary.uuid == cred_id {
1299                            cred_handler = CredHandler::build_from_password_only(primary)
1300                        }
1301                    }
1302                }
1303                AuthType::PasswordTotp => {
1304                    if let Some(primary) = asd.account.primary.as_ref() {
1305                        if primary.uuid == cred_id {
1306                            cred_handler = CredHandler::build_from_password_totp(primary)
1307                        }
1308                    }
1309                }
1310                AuthType::PasswordSecurityKey => {
1311                    if let Some(primary) = asd.account.primary.as_ref() {
1312                        if primary.uuid == cred_id {
1313                            cred_handler =
1314                                CredHandler::build_from_password_security_key(primary, asd.webauthn)
1315                        }
1316                    }
1317                }
1318                AuthType::Passkey => {
1319                    let maybe_pk: Option<PasskeyV4> = asd
1321                        .account
1322                        .attested_passkeys
1323                        .get(&cred_id)
1324                        .map(|(_, apk)| apk.into())
1325                        .or_else(|| asd.account.passkeys.get(&cred_id).map(|(_, pk)| pk.clone()));
1326
1327                    if let Some(pk) = maybe_pk {
1328                        if let Some(ch) =
1329                            CredHandler::build_from_single_passkey(cred_id, pk, asd.webauthn)
1330                        {
1331                            debug_assert!(cred_handler.is_none());
1333                            cred_handler = Some(ch);
1334                        } else {
1335                            security_critical!(
1336                                "corrupt credentials, unable to start passkey credhandler"
1337                            );
1338                        }
1339                    }
1340                }
1341                AuthType::AttestedPasskey => {
1342                    if let Some(att_ca_list) = asd.account_policy.webauthn_attestation_ca_list() {
1343                        if let Some(pk) = asd
1344                            .account
1345                            .attested_passkeys
1346                            .get(&cred_id)
1347                            .map(|(_, pk)| pk)
1348                        {
1349                            if let Some(ch) = CredHandler::build_from_single_attested_pk(
1350                                cred_id,
1351                                pk,
1352                                att_ca_list,
1353                                asd.webauthn,
1354                            ) {
1355                                debug_assert!(cred_handler.is_none());
1357                                cred_handler = Some(ch);
1358                            } else {
1359                                security_critical!(
1360                            "corrupt credentials, unable to start attested passkey credhandler"
1361                        );
1362                            }
1363                        }
1364                    }
1365                }
1366                AuthType::Anonymous | AuthType::OAuth2Trust => {}
1367            }
1368
1369            if let Some(cred_handler) = cred_handler {
1372                State::Proceed(cred_handler)
1373            } else {
1374                State::NoMatchingCred
1375            }
1376        } else {
1377            State::Expired
1378        };
1379
1380        let session_expiry = match session.state {
1381            SessionState::ExpiresAt(odt) => Some(odt),
1382            SessionState::NeverExpires => None,
1383            SessionState::RevokedAt(_) => {
1384                security_error!(
1385                    "Invalid State - Should not be possible to trigger re-auth on revoked session."
1386                );
1387                return (None, AuthState::Denied(ACCOUNT_EXPIRED.to_string()));
1388            }
1389        };
1390
1391        match state {
1392            State::Proceed(handler) => {
1393                let next_auth_state = handler.next_auth_state();
1394                let auth_session = AuthSession {
1395                    account: asd.account,
1396                    account_policy: asd.account_policy,
1397                    state: AuthSessionState::InProgress(handler),
1398                    issue: asd.issue,
1399                    intent: AuthIntent::Reauth {
1400                        session_id,
1401                        session_expiry,
1402                    },
1403                    source: asd.client_auth_info.source,
1404                    key_object,
1405                };
1406
1407                (Some(auth_session), next_auth_state)
1408            }
1409            State::Expired => {
1410                security_info!("account expired");
1411                (None, AuthState::Denied(ACCOUNT_EXPIRED.to_string()))
1412            }
1413            State::NoMatchingCred => {
1414                security_error!("Unable to select a credential for authentication");
1415                (None, AuthState::Denied(BAD_CREDENTIALS.to_string()))
1416            }
1417        }
1418    }
1419
1420    pub fn get_credential_uuid(&self) -> Result<Option<Uuid>, OperationError> {
1423        match &self.state {
1424            AuthSessionState::InProgress(CredHandler::Password { cred_id, .. })
1425            | AuthSessionState::InProgress(CredHandler::PasswordTotp { cred_id, .. })
1426            | AuthSessionState::InProgress(CredHandler::PasswordBackupCode { cred_id, .. }) => {
1427                Ok(Some(*cred_id))
1428            }
1429            AuthSessionState::InProgress(CredHandler::Anonymous { .. })
1430            | AuthSessionState::InProgress(CredHandler::PasswordSecurityKey { .. })
1431            | AuthSessionState::InProgress(CredHandler::Passkey { .. })
1432            | AuthSessionState::InProgress(CredHandler::OAuth2Trust { .. })
1433            | AuthSessionState::InProgress(CredHandler::AttestedPasskey { .. }) => Ok(None),
1434
1435            AuthSessionState::Init(_) => {
1436                debug!(
1437                    "Request for credential uuid invalid as auth session state not yet initialised"
1438                );
1439                Err(OperationError::AU0001InvalidState)
1440            }
1441            AuthSessionState::Success | AuthSessionState::Denied(_) => {
1442                debug!("Request for credential uuid invalid as auth session state has progressed");
1443                Err(OperationError::AU0001InvalidState)
1444            }
1445        }
1446    }
1447
1448    pub fn start_session(
1452        &mut self,
1453        mech: &AuthMech,
1454        ) -> Result<AuthState, OperationError> {
1457        let (next_state, response) = match &mut self.state {
1463            AuthSessionState::Success
1464            | AuthSessionState::Denied(_)
1465            | AuthSessionState::InProgress(_) => (
1466                None,
1467                Err(OperationError::InvalidAuthState(
1468                    "session already finalised!".to_string(),
1469                )),
1470            ),
1471            AuthSessionState::Init(handlers) => {
1472                let mut allowed_handlers: Vec<_> = handlers
1474                    .iter()
1475                    .filter(|ch| ch.can_proceed(mech))
1476                    .cloned()
1477                    .collect();
1478
1479                if let Some(allowed_handler) = allowed_handlers.pop() {
1480                    let next_auth_state = allowed_handler.next_auth_state();
1481                    (
1482                        Some(AuthSessionState::InProgress(allowed_handler)),
1483                        Ok(next_auth_state),
1484                    )
1485                } else {
1486                    security_error!("Unable to select a credential for authentication");
1487                    (
1488                        Some(AuthSessionState::Denied(BAD_CREDENTIALS)),
1489                        Ok(AuthState::Denied(BAD_CREDENTIALS.to_string())),
1490                    )
1491                }
1492            }
1493        };
1494
1495        if let Some(mut next_state) = next_state {
1496            std::mem::swap(&mut self.state, &mut next_state);
1497        };
1498
1499        response
1500    }
1501
1502    pub fn validate_creds(
1506        &mut self,
1507        cred: &AuthCredential,
1508        time: Duration,
1509        async_tx: &Sender<DelayedAction>,
1510        audit_tx: &Sender<AuditEvent>,
1511        webauthn: &Webauthn,
1512        pw_badlist: &HashSet<String>,
1513    ) -> Result<AuthState, OperationError> {
1514        let (next_state, response) = match &mut self.state {
1515            AuthSessionState::Init(_) | AuthSessionState::Success | AuthSessionState::Denied(_) => {
1516                return Err(OperationError::InvalidAuthState(
1517                    "session already finalised!".to_string(),
1518                ));
1519            }
1520            AuthSessionState::InProgress(ref mut handler) => {
1521                match handler.validate(
1522                    cred,
1523                    time,
1524                    self.account.uuid,
1525                    async_tx,
1526                    webauthn,
1527                    pw_badlist,
1528                ) {
1529                    CredState::Success {
1530                        auth_type,
1531                        cred_id,
1532                        ext_session_metadata,
1533                    } => {
1534                        let uat = self.issue_uat(
1536                            auth_type,
1537                            time,
1538                            async_tx,
1539                            cred_id,
1540                            ext_session_metadata,
1541                        )?;
1542
1543                        let jwt = Jws::into_json(&uat).map_err(|e| {
1544                            admin_error!(?e, "Failed to serialise into Jws");
1545                            OperationError::AU0002JwsSerialisation
1546                        })?;
1547
1548                        let token = self.key_object.jws_es256_sign(&jwt, time).map_err(|e| {
1550                            admin_error!(?e, "Failed to sign UserAuthToken to Jwt");
1551                            OperationError::AU0003JwsSignature
1552                        })?;
1553
1554                        (
1555                            Some(AuthSessionState::Success),
1556                            Ok(AuthState::Success(Box::new(token), self.issue)),
1557                        )
1558                    }
1559                    CredState::Continue(allowed) => {
1560                        security_info!(?allowed, "Request credential continuation");
1561                        (None, Ok(AuthState::Continue(allowed.into_iter().collect())))
1562                    }
1563                    CredState::External(allowed) => {
1564                        security_info!(?allowed, "Request excternal credential continuation");
1565                        (None, Ok(AuthState::External(allowed)))
1566                    }
1567                    CredState::Denied(reason) => {
1568                        if audit_tx
1569                            .send(AuditEvent::AuthenticationDenied {
1570                                source: self.source.clone().into(),
1571                                spn: self.account.spn().into(),
1572                                uuid: self.account.uuid,
1573                                time: OffsetDateTime::UNIX_EPOCH + time,
1574                            })
1575                            .is_err()
1576                        {
1577                            error!("Unable to submit audit event to queue");
1578                        }
1579                        security_info!(%reason, "Credentials denied");
1580                        (
1581                            Some(AuthSessionState::Denied(reason)),
1582                            Ok(AuthState::Denied(reason.to_string())),
1583                        )
1584                    }
1585                }
1586            }
1587        };
1588
1589        if let Some(mut next_state) = next_state {
1590            std::mem::swap(&mut self.state, &mut next_state);
1591        };
1592
1593        response
1606    }
1607
1608    fn issue_uat(
1609        &mut self,
1610        auth_type: AuthType,
1611        time: Duration,
1612        async_tx: &Sender<DelayedAction>,
1613        cred_id: Uuid,
1614        ext_metadata: SessionExtMetadata,
1615    ) -> Result<UserAuthToken, OperationError> {
1616        security_debug!("Successful cred handling");
1617        match self.intent {
1618            AuthIntent::InitialAuth { privileged } => {
1619                let session_id = Uuid::new_v4();
1620                let scope = match auth_type {
1623                    AuthType::Anonymous | AuthType::OAuth2Trust => SessionScope::ReadOnly,
1624                    AuthType::GeneratedPassword => SessionScope::ReadWrite,
1625                    AuthType::Password
1626                    | AuthType::PasswordTotp
1627                    | AuthType::PasswordBackupCode
1628                    | AuthType::PasswordSecurityKey
1629                    | AuthType::Passkey
1630                    | AuthType::AttestedPasskey => {
1631                        if privileged {
1632                            SessionScope::ReadWrite
1633                        } else {
1634                            SessionScope::PrivilegeCapable
1635                        }
1636                    }
1637                };
1638
1639                security_info!(
1640                    "Issuing {:?} session ({:?}) {} for {} {}",
1641                    self.issue,
1642                    scope,
1643                    session_id,
1644                    self.account.spn(),
1645                    self.account.uuid
1646                );
1647
1648                let uat = self
1649                    .account
1650                    .to_userauthtoken(session_id, scope, time, &self.account_policy)
1651                    .ok_or(OperationError::AU0004UserAuthTokenInvalid)?;
1652
1653                match auth_type {
1659                    AuthType::Anonymous => {
1660                        }
1662                    AuthType::Password
1663                    | AuthType::GeneratedPassword
1664                    | AuthType::PasswordTotp
1665                    | AuthType::PasswordBackupCode
1666                    | AuthType::PasswordSecurityKey
1667                    | AuthType::Passkey
1668                    | AuthType::AttestedPasskey
1669                    | AuthType::OAuth2Trust => {
1670                        trace!("⚠️   Queued AuthSessionRecord for {}", self.account.uuid);
1671                        async_tx.send(DelayedAction::AuthSessionRecord(AuthSessionRecord {
1672                            target_uuid: self.account.uuid,
1673                            session_id,
1674                            cred_id,
1675                            label: "Auth Session".to_string(),
1676                            expiry: uat.expiry,
1677                            issued_at: uat.issued_at,
1678                            issued_by: IdentityId::User(self.account.uuid),
1679                            scope,
1680                            type_: auth_type,
1681                            ext_metadata,
1682                        }))
1683                        .map_err(|e| {
1684                            debug!(?e, "queue failure");
1685                            admin_error!("unable to queue failing authentication as the session will not validate ... ");
1686                            OperationError::AU0005DelayedProcessFailure
1687                        })?;
1688                    }
1689                };
1690
1691                Ok(uat)
1692            }
1693            AuthIntent::Reauth {
1694                session_id,
1695                session_expiry,
1696            } => {
1697                let scope = match auth_type {
1700                    AuthType::Anonymous | AuthType::GeneratedPassword | AuthType::OAuth2Trust => {
1701                        error!("AuthType used in Reauth is not valid for session re-issuance. Rejecting");
1702                        return Err(OperationError::AU0006CredentialMayNotReauthenticate);
1703                    }
1704                    AuthType::Password
1705                    | AuthType::PasswordTotp
1706                    | AuthType::PasswordBackupCode
1707                    | AuthType::PasswordSecurityKey
1708                    | AuthType::Passkey
1709                    | AuthType::AttestedPasskey => SessionScope::PrivilegeCapable,
1710                };
1711
1712                let uat = self
1713                    .account
1714                    .to_reissue_userauthtoken(
1715                        session_id,
1716                        session_expiry,
1717                        scope,
1718                        time,
1719                        &self.account_policy,
1720                    )
1721                    .ok_or(OperationError::AU0007UserAuthTokenInvalid)?;
1722
1723                Ok(uat)
1724            }
1725        }
1726    }
1727
1728    pub fn end_session(&mut self, reason: &'static str) -> Result<AuthState, OperationError> {
1730        let mut next_state = AuthSessionState::Denied(reason);
1731        std::mem::swap(&mut self.state, &mut next_state);
1732        Ok(AuthState::Denied(reason.to_string()))
1733    }
1734
1735    fn valid_auth_mechs(&self) -> Vec<AuthMech> {
1736        match &self.state {
1737            AuthSessionState::Success
1738            | AuthSessionState::Denied(_)
1739            | AuthSessionState::InProgress(_) => Vec::with_capacity(0),
1740            AuthSessionState::Init(handlers) => {
1741                handlers.iter().map(|h| h.allows_mech()).collect()
1744            }
1745        }
1746    }
1747}
1748
1749#[cfg(test)]
1750mod tests {
1751    use crate::credential::totp::{Totp, TOTP_DEFAULT_STEP};
1752    use crate::credential::{BackupCodes, Credential};
1753    use crate::idm::account::Account;
1754    use crate::idm::accountpolicy::ResolvedAccountPolicy;
1755    use crate::idm::audit::AuditEvent;
1756    use crate::idm::authentication::{AuthCredential, AuthExternal, AuthState};
1757    use crate::idm::authsession::{
1758        AuthSession, AuthSessionData, BAD_AUTH_TYPE_MSG, BAD_BACKUPCODE_MSG, BAD_PASSWORD_MSG,
1759        BAD_TOTP_MSG, BAD_WEBAUTHN_MSG, PW_BADLIST_MSG,
1760    };
1761    use crate::idm::delayed::DelayedAction;
1762    use crate::idm::oauth2_client::OAuth2ClientProvider;
1763    use crate::migration_data::{BUILTIN_ACCOUNT_ANONYMOUS, BUILTIN_ACCOUNT_TEST_PERSON};
1764    use crate::prelude::*;
1765    use crate::server::keys::KeyObjectInternal;
1766    use crate::utils::readable_password_from_random;
1767    use compact_jwt::{dangernoverify::JwsDangerReleaseWithoutVerify, JwsVerifier};
1768    use hashbrown::HashSet;
1769    use kanidm_lib_crypto::CryptoPolicy;
1770    use kanidm_proto::internal::{UatPurpose, UserAuthToken};
1771    use kanidm_proto::oauth2::{AccessTokenResponse, AccessTokenType};
1772    use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech};
1773    use std::time::Duration;
1774    use tokio::sync::mpsc::unbounded_channel as unbounded;
1775    use webauthn_authenticator_rs::softpasskey::SoftPasskey;
1776    use webauthn_authenticator_rs::WebauthnAuthenticator;
1777    use webauthn_rs::prelude::{RequestChallengeResponse, Webauthn};
1778
1779    fn create_pw_badlist_cache() -> HashSet<String> {
1780        let mut s = HashSet::new();
1781        s.insert("list@no3IBTyqHu$bad".to_lowercase());
1782        s
1783    }
1784
1785    fn create_webauthn() -> webauthn_rs::Webauthn {
1786        webauthn_rs::WebauthnBuilder::new(
1787            "example.com",
1788            &url::Url::parse("https://idm.example.com").unwrap(),
1789        )
1790        .and_then(|builder| builder.build())
1791        .unwrap()
1792    }
1793
1794    #[test]
1795    fn test_idm_authsession_anonymous_auth_mech() {
1796        sketching::test_init();
1797
1798        let webauthn = create_webauthn();
1799
1800        let anon_account: Account = BUILTIN_ACCOUNT_ANONYMOUS.clone().into();
1801
1802        let asd = AuthSessionData {
1803            account: anon_account,
1804            account_policy: ResolvedAccountPolicy::default(),
1805            issue: AuthIssueSession::Token,
1806            webauthn: &webauthn,
1807            ct: duration_from_epoch_now(),
1808            client_auth_info: Source::Internal.into(),
1809            oauth2_client_provider: None,
1810        };
1811
1812        let key_object = KeyObjectInternal::new_test();
1813        let (session, state) = AuthSession::new(asd, false, key_object);
1814        if let AuthState::Choose(auth_mechs) = state {
1815            assert!(auth_mechs.iter().any(|x| matches!(x, AuthMech::Anonymous)));
1816        } else {
1817            panic!("Invalid auth state")
1818        }
1819
1820        let state = session
1821            .expect("Missing auth session?")
1822            .start_session(&AuthMech::Anonymous)
1823            .expect("Failed to select anonymous mech.");
1824
1825        if let AuthState::Continue(auth_mechs) = state {
1826            assert!(auth_mechs
1827                .iter()
1828                .any(|x| matches!(x, AuthAllowed::Anonymous)));
1829        } else {
1830            panic!("Invalid auth state")
1831        }
1832    }
1833
1834    macro_rules! start_password_session {
1835        (
1836            $audit:expr,
1837            $account:expr,
1838            $webauthn:expr,
1839            $privileged:expr
1840        ) => {{
1841            let asd = AuthSessionData {
1842                account: $account.clone(),
1843                account_policy: ResolvedAccountPolicy::default(),
1844                issue: AuthIssueSession::Token,
1845                webauthn: $webauthn,
1846                ct: duration_from_epoch_now(),
1847                client_auth_info: Source::Internal.into(),
1848                oauth2_client_provider: None,
1849            };
1850            let key_object = KeyObjectInternal::new_test();
1851            let (session, state) = AuthSession::new(asd, $privileged, key_object);
1852            let mut session = session.unwrap();
1853
1854            if let AuthState::Choose(auth_mechs) = state {
1855                assert!(auth_mechs.iter().any(|x| matches!(x, AuthMech::Password)));
1856            } else {
1857                panic!();
1858            }
1859
1860            let state = session
1861                .start_session(&AuthMech::Password)
1862                .expect("Failed to select anonymous mech.");
1863
1864            if let AuthState::Continue(auth_mechs) = state {
1865                assert!(auth_mechs
1866                    .iter()
1867                    .any(|x| matches!(x, AuthAllowed::Password)));
1868            } else {
1869                panic!("Invalid auth state")
1870            }
1871
1872            (session, create_pw_badlist_cache())
1873        }};
1874    }
1875
1876    fn start_session_simple_password_mech(privileged: bool) -> UserAuthToken {
1877        let webauthn = create_webauthn();
1878        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
1880        let p = CryptoPolicy::minimum();
1882        let cred = Credential::new_password_only(&p, "test_password").unwrap();
1883        account.primary = Some(cred);
1884
1885        let (async_tx, mut async_rx) = unbounded();
1886        let (audit_tx, mut audit_rx) = unbounded();
1887
1888        let (mut session, pw_badlist_cache) =
1890            start_password_session!(&mut audit, account, &webauthn, false);
1891
1892        let attempt = AuthCredential::Password("bad_password".to_string());
1893        match session.validate_creds(
1894            &attempt,
1895            Duration::from_secs(0),
1896            &async_tx,
1897            &audit_tx,
1898            &webauthn,
1899            &pw_badlist_cache,
1900        ) {
1901            Ok(AuthState::Denied(_)) => {}
1902            _ => panic!(),
1903        };
1904
1905        match audit_rx.try_recv() {
1906            Ok(AuditEvent::AuthenticationDenied { .. }) => {}
1907            _ => panic!("Oh no"),
1908        }
1909
1910        let (mut session, pw_badlist_cache) =
1913            start_password_session!(&mut audit, account, &webauthn, privileged);
1914
1915        let attempt = AuthCredential::Password("test_password".to_string());
1916        let uat: UserAuthToken = match session.validate_creds(
1917            &attempt,
1918            Duration::from_secs(0),
1919            &async_tx,
1920            &audit_tx,
1921            &webauthn,
1922            &pw_badlist_cache,
1923        ) {
1924            Ok(AuthState::Success(jwsc, AuthIssueSession::Token)) => {
1925                let jws_verifier = JwsDangerReleaseWithoutVerify::default();
1926
1927                jws_verifier
1928                    .verify(&*jwsc)
1929                    .unwrap()
1930                    .from_json::<UserAuthToken>()
1931                    .unwrap()
1932            }
1933            _ => panic!(),
1934        };
1935
1936        match async_rx.blocking_recv() {
1937            Some(DelayedAction::AuthSessionRecord(_)) => {}
1938            _ => panic!("Oh no"),
1939        }
1940
1941        drop(async_tx);
1942        assert!(async_rx.blocking_recv().is_none());
1943        drop(audit_tx);
1944        assert!(audit_rx.blocking_recv().is_none());
1945
1946        uat
1947    }
1948
1949    #[test]
1950    fn test_idm_authsession_simple_password_mech() {
1951        sketching::test_init();
1952        let uat = start_session_simple_password_mech(false);
1953        match uat.purpose {
1954            UatPurpose::ReadOnly => panic!("Unexpected UatPurpose::ReadOnly"),
1955            UatPurpose::ReadWrite { expiry } => {
1956                assert!(expiry.is_none())
1958            }
1959        }
1960    }
1961
1962    #[test]
1963    fn test_idm_authsession_simple_password_mech_priv_shortcut() {
1964        sketching::test_init();
1965        let uat = start_session_simple_password_mech(true);
1966        match uat.purpose {
1967            UatPurpose::ReadOnly => panic!("Unexpected UatPurpose::ReadOnly"),
1968            UatPurpose::ReadWrite { expiry } => {
1969                assert!(expiry.is_some())
1971            }
1972        }
1973    }
1974
1975    #[test]
1976    fn test_idm_authsession_simple_password_badlist() {
1977        sketching::test_init();
1978        let webauthn = create_webauthn();
1979        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
1981        let p = CryptoPolicy::minimum();
1983        let cred = Credential::new_password_only(&p, "list@no3IBTyqHu$bad").unwrap();
1984        account.primary = Some(cred);
1985
1986        let (async_tx, mut async_rx) = unbounded();
1987        let (audit_tx, mut audit_rx) = unbounded();
1988
1989        let (mut session, pw_badlist_cache) =
1991            start_password_session!(&mut audit, account, &webauthn, false);
1992
1993        let attempt = AuthCredential::Password("list@no3IBTyqHu$bad".to_string());
1994        match session.validate_creds(
1995            &attempt,
1996            Duration::from_secs(0),
1997            &async_tx,
1998            &audit_tx,
1999            &webauthn,
2000            &pw_badlist_cache,
2001        ) {
2002            Ok(AuthState::Denied(msg)) => assert_eq!(msg, PW_BADLIST_MSG),
2003            _ => panic!(),
2004        };
2005
2006        match audit_rx.try_recv() {
2007            Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2008            _ => panic!("Oh no"),
2009        }
2010
2011        drop(async_tx);
2012        assert!(async_rx.blocking_recv().is_none());
2013        drop(audit_tx);
2014        assert!(audit_rx.blocking_recv().is_none());
2015    }
2016
2017    fn start_password_totp_session(
2018        account: &Account,
2019        webauthn: &Webauthn,
2020    ) -> (AuthSession, HashSet<String>) {
2021        let asd = AuthSessionData {
2022            account: account.clone(),
2023            account_policy: ResolvedAccountPolicy::default(),
2024            issue: AuthIssueSession::Token,
2025            webauthn,
2026            ct: duration_from_epoch_now(),
2027            client_auth_info: Source::Internal.into(),
2028            oauth2_client_provider: None,
2029        };
2030        let key_object = KeyObjectInternal::new_test();
2031        let (session, state) = AuthSession::new(asd, false, key_object);
2032        let mut session = session.expect("Session was unable to be created.");
2033
2034        if let AuthState::Choose(auth_mechs) = state {
2035            assert!(auth_mechs
2036                .iter()
2037                .any(|x| matches!(x, AuthMech::PasswordTotp)))
2038        } else {
2039            panic!();
2040        }
2041
2042        let state = session
2043            .start_session(&AuthMech::PasswordTotp)
2044            .expect("Failed to select password totp mech.");
2045
2046        if let AuthState::Continue(auth_mechs) = state {
2047            assert!(auth_mechs.iter().fold(false, |acc, x| match x {
2048                AuthAllowed::Totp => true,
2049                _ => acc,
2050            }));
2051        } else {
2052            panic!("Invalid auth state")
2053        }
2054
2055        (session, create_pw_badlist_cache())
2056    }
2057
2058    fn start_password_sk_session(
2059        account: &Account,
2060        webauthn: &Webauthn,
2061    ) -> (AuthSession, RequestChallengeResponse, HashSet<String>) {
2062        let asd = AuthSessionData {
2063            account: account.clone(),
2064            account_policy: ResolvedAccountPolicy::default(),
2065            issue: AuthIssueSession::Token,
2066            webauthn,
2067            ct: duration_from_epoch_now(),
2068            client_auth_info: Source::Internal.into(),
2069            oauth2_client_provider: None,
2070        };
2071        let key_object = KeyObjectInternal::new_test();
2072        let (session, state) = AuthSession::new(asd, false, key_object);
2073        let mut session = session.expect("Session was unable to be created.");
2074
2075        if let AuthState::Choose(auth_mechs) = state {
2076            assert!(auth_mechs
2077                .iter()
2078                .any(|x| matches!(x, AuthMech::PasswordSecurityKey)))
2079        } else {
2080            panic!();
2081        }
2082
2083        let state = session
2084            .start_session(&AuthMech::PasswordSecurityKey)
2085            .expect("Failed to select password security key mech.");
2086
2087        let mut rchal = None;
2088
2089        if let AuthState::Continue(auth_mechs) = state {
2090            assert!(auth_mechs.iter().fold(false, |acc, x| match x {
2091                AuthAllowed::SecurityKey(chal) => {
2092                    rchal = Some(chal.clone());
2093                    true
2094                }
2095                _ => acc,
2096            }));
2097        } else {
2098            panic!("Invalid auth state")
2099        }
2100
2101        (session, rchal.unwrap(), create_pw_badlist_cache())
2102    }
2103
2104    fn start_password_bc_session(
2105        account: &Account,
2106        webauthn: &Webauthn,
2107    ) -> (AuthSession, HashSet<String>) {
2108        let asd = AuthSessionData {
2109            account: account.clone(),
2110            account_policy: ResolvedAccountPolicy::default(),
2111            issue: AuthIssueSession::Token,
2112            webauthn,
2113            ct: duration_from_epoch_now(),
2114            client_auth_info: Source::Internal.into(),
2115            oauth2_client_provider: None,
2116        };
2117        let key_object = KeyObjectInternal::new_test();
2118        let (session, state) = AuthSession::new(asd, false, key_object);
2119        let mut session = session.expect("Session was unable to be created.");
2120
2121        if let AuthState::Choose(auth_mechs) = state {
2122            assert!(auth_mechs
2123                .iter()
2124                .any(|x| matches!(x, AuthMech::PasswordBackupCode)))
2125        } else {
2126            panic!();
2127        }
2128
2129        let state = session
2130            .start_session(&AuthMech::PasswordBackupCode)
2131            .expect("Failed to select password backup code mech.");
2132
2133        if let AuthState::Continue(auth_mechs) = state {
2134            assert!(auth_mechs.iter().fold(false, |acc, x| match x {
2135                AuthAllowed::BackupCode => true,
2136                _ => acc,
2137            }));
2138        } else {
2139            panic!("Invalid auth state")
2140        }
2141
2142        (session, create_pw_badlist_cache())
2143    }
2144
2145    #[test]
2146    fn test_idm_authsession_totp_password_mech() {
2147        sketching::test_init();
2148        let webauthn = create_webauthn();
2149        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
2151
2152        let ts = Duration::from_secs(12345);
2154
2155        let totp = Totp::generate_secure(TOTP_DEFAULT_STEP);
2157
2158        let totp_good = totp
2159            .do_totp_duration_from_epoch(&ts)
2160            .expect("failed to perform totp.");
2161        let totp_bad = totp
2162            .do_totp_duration_from_epoch(&Duration::from_secs(1234567))
2163            .expect("failed to perform totp.");
2164        assert!(totp_bad != totp_good);
2165
2166        let pw_good = "test_password";
2167        let pw_bad = "bad_password";
2168
2169        let p = CryptoPolicy::minimum();
2170        let cred = Credential::new_password_only(&p, pw_good)
2171            .unwrap()
2172            .append_totp("totp".to_string(), totp);
2173        account.primary = Some(cred);
2175
2176        let (async_tx, mut async_rx) = unbounded();
2177        let (audit_tx, mut audit_rx) = unbounded();
2178
2179        {
2183            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2184
2185            match session.validate_creds(
2186                &AuthCredential::Anonymous,
2187                ts,
2188                &async_tx,
2189                &audit_tx,
2190                &webauthn,
2191                &pw_badlist_cache,
2192            ) {
2193                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_AUTH_TYPE_MSG),
2194                _ => panic!(),
2195            };
2196
2197            match audit_rx.try_recv() {
2198                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2199                _ => panic!("Oh no"),
2200            }
2201        }
2202
2203        {
2207            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2208
2209            match session.validate_creds(
2210                &AuthCredential::Password(pw_bad.to_string()),
2211                ts,
2212                &async_tx,
2213                &audit_tx,
2214                &webauthn,
2215                &pw_badlist_cache,
2216            ) {
2217                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_AUTH_TYPE_MSG),
2218                _ => panic!(),
2219            };
2220
2221            match audit_rx.try_recv() {
2222                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2223                _ => panic!("Oh no"),
2224            }
2225        }
2226        {
2228            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2229
2230            match session.validate_creds(
2231                &AuthCredential::Totp(totp_bad),
2232                ts,
2233                &async_tx,
2234                &audit_tx,
2235                &webauthn,
2236                &pw_badlist_cache,
2237            ) {
2238                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_TOTP_MSG),
2239                _ => panic!(),
2240            };
2241
2242            match audit_rx.try_recv() {
2243                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2244                _ => panic!("Oh no"),
2245            }
2246        }
2247
2248        {
2251            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2252
2253            match session.validate_creds(
2254                &AuthCredential::Totp(totp_good),
2255                ts,
2256                &async_tx,
2257                &audit_tx,
2258                &webauthn,
2259                &pw_badlist_cache,
2260            ) {
2261                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
2262                _ => panic!(),
2263            };
2264            match session.validate_creds(
2265                &AuthCredential::Password(pw_bad.to_string()),
2266                ts,
2267                &async_tx,
2268                &audit_tx,
2269                &webauthn,
2270                &pw_badlist_cache,
2271            ) {
2272                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_PASSWORD_MSG),
2273                _ => panic!(),
2274            };
2275
2276            match audit_rx.try_recv() {
2277                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2278                _ => panic!("Oh no"),
2279            }
2280        }
2281
2282        {
2285            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2286
2287            match session.validate_creds(
2288                &AuthCredential::Totp(totp_good),
2289                ts,
2290                &async_tx,
2291                &audit_tx,
2292                &webauthn,
2293                &pw_badlist_cache,
2294            ) {
2295                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
2296                _ => panic!(),
2297            };
2298            match session.validate_creds(
2299                &AuthCredential::Password(pw_good.to_string()),
2300                ts,
2301                &async_tx,
2302                &audit_tx,
2303                &webauthn,
2304                &pw_badlist_cache,
2305            ) {
2306                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
2307                _ => panic!(),
2308            };
2309
2310            match async_rx.blocking_recv() {
2311                Some(DelayedAction::AuthSessionRecord(_)) => {}
2312                _ => panic!("Oh no"),
2313            }
2314        }
2315
2316        drop(async_tx);
2317        assert!(async_rx.blocking_recv().is_none());
2318        drop(audit_tx);
2319        assert!(audit_rx.blocking_recv().is_none());
2320    }
2321
2322    #[test]
2323    fn test_idm_authsession_password_mfa_badlist() {
2324        sketching::test_init();
2325        let webauthn = create_webauthn();
2326        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
2328
2329        let ts = Duration::from_secs(12345);
2331
2332        let totp = Totp::generate_secure(TOTP_DEFAULT_STEP);
2334
2335        let totp_good = totp
2336            .do_totp_duration_from_epoch(&ts)
2337            .expect("failed to perform totp.");
2338
2339        let pw_badlist = "list@no3IBTyqHu$bad";
2340
2341        let p = CryptoPolicy::minimum();
2342        let cred = Credential::new_password_only(&p, pw_badlist)
2343            .unwrap()
2344            .append_totp("totp".to_string(), totp);
2345        account.primary = Some(cred);
2347
2348        let (async_tx, mut async_rx) = unbounded();
2349        let (audit_tx, mut audit_rx) = unbounded();
2350
2351        {
2358            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2359
2360            match session.validate_creds(
2361                &AuthCredential::Totp(totp_good),
2362                ts,
2363                &async_tx,
2364                &audit_tx,
2365                &webauthn,
2366                &pw_badlist_cache,
2367            ) {
2368                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
2369                _ => panic!(),
2370            };
2371            match session.validate_creds(
2372                &AuthCredential::Password(pw_badlist.to_string()),
2373                ts,
2374                &async_tx,
2375                &audit_tx,
2376                &webauthn,
2377                &pw_badlist_cache,
2378            ) {
2379                Ok(AuthState::Denied(msg)) => assert_eq!(msg, PW_BADLIST_MSG),
2380                _ => panic!(),
2381            };
2382
2383            match audit_rx.try_recv() {
2384                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2385                _ => panic!("Oh no"),
2386            }
2387        }
2388
2389        drop(async_tx);
2390        assert!(async_rx.blocking_recv().is_none());
2391        drop(audit_tx);
2392        assert!(audit_rx.blocking_recv().is_none());
2393    }
2394
2395    macro_rules! start_webauthn_only_session {
2396        (
2397            $audit:expr,
2398            $account:expr,
2399            $webauthn:expr
2400        ) => {{
2401            let asd = AuthSessionData {
2402                account: $account.clone(),
2403                account_policy: ResolvedAccountPolicy::default(),
2404                issue: AuthIssueSession::Token,
2405                webauthn: $webauthn,
2406                ct: duration_from_epoch_now(),
2407                client_auth_info: Source::Internal.into(),
2408                oauth2_client_provider: None,
2409            };
2410            let key_object = KeyObjectInternal::new_test();
2411            let (session, state) = AuthSession::new(asd, false, key_object);
2412            let mut session = session.unwrap();
2413
2414            if let AuthState::Choose(auth_mechs) = state {
2415                assert!(auth_mechs.iter().any(|x| matches!(x, AuthMech::Passkey)));
2416            } else {
2417                panic!();
2418            }
2419
2420            let state = session
2421                .start_session(&AuthMech::Passkey)
2422                .expect("Failed to select Passkey mech.");
2423
2424            let wan_chal = if let AuthState::Continue(auth_mechs) = state {
2425                assert_eq!(auth_mechs.len(), 1);
2426                auth_mechs
2427                    .into_iter()
2428                    .fold(None, |_acc, x| match x {
2429                        AuthAllowed::Passkey(chal) => Some(chal),
2430                        _ => None,
2431                    })
2432                    .expect("No securitykey challenge found.")
2433            } else {
2434                panic!();
2435            };
2436
2437            (session, wan_chal)
2438        }};
2439    }
2440
2441    fn setup_webauthn_passkey(
2442        name: &str,
2443    ) -> (
2444        webauthn_rs::prelude::Webauthn,
2445        webauthn_authenticator_rs::WebauthnAuthenticator<SoftPasskey>,
2446        webauthn_rs::prelude::Passkey,
2447    ) {
2448        let webauthn = create_webauthn();
2449        let mut wa = WebauthnAuthenticator::new(SoftPasskey::new(true));
2451
2452        let uuid = Uuid::new_v4();
2453
2454        let (chal, reg_state) = webauthn
2455            .start_passkey_registration(uuid, name, name, None)
2456            .expect("Failed to setup passkey rego challenge");
2457
2458        let r = wa
2459            .do_registration(webauthn.get_allowed_origins()[0].clone(), chal)
2460            .expect("Failed to create soft passkey");
2461
2462        let wan_cred = webauthn
2463            .finish_passkey_registration(&r, ®_state)
2464            .expect("Failed to register soft token");
2465
2466        (webauthn, wa, wan_cred)
2467    }
2468
2469    fn setup_webauthn_securitykey(
2470        spn: &str,
2471    ) -> (
2472        webauthn_rs::prelude::Webauthn,
2473        webauthn_authenticator_rs::WebauthnAuthenticator<SoftPasskey>,
2474        webauthn_rs::prelude::SecurityKey,
2475    ) {
2476        let webauthn = create_webauthn();
2477        let mut wa = WebauthnAuthenticator::new(SoftPasskey::new(true));
2479
2480        let uuid = Uuid::new_v4();
2481
2482        let (chal, reg_state) = webauthn
2483            .start_securitykey_registration(uuid, spn, spn, None, None, None)
2484            .expect("Failed to setup passkey rego challenge");
2485
2486        let r = wa
2487            .do_registration(webauthn.get_allowed_origins()[0].clone(), chal)
2488            .expect("Failed to create soft securitykey");
2489
2490        let wan_cred = webauthn
2491            .finish_securitykey_registration(&r, ®_state)
2492            .expect("Failed to register soft token");
2493
2494        (webauthn, wa, wan_cred)
2495    }
2496
2497    #[test]
2498    fn test_idm_authsession_webauthn_only_mech() {
2499        sketching::test_init();
2500        let (async_tx, mut async_rx) = unbounded();
2501        let (audit_tx, mut audit_rx) = unbounded();
2502        let ts = duration_from_epoch_now();
2503        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
2505
2506        let (webauthn, mut wa, wan_cred) = setup_webauthn_passkey(account.spn());
2507
2508        account.passkeys = btreemap![(Uuid::new_v4(), ("soft".to_string(), wan_cred))];
2510
2511        {
2515            let (mut session, _inv_chal) =
2516                start_webauthn_only_session!(&mut audit, account, &webauthn);
2517
2518            match session.validate_creds(
2519                &AuthCredential::Anonymous,
2520                ts,
2521                &async_tx,
2522                &audit_tx,
2523                &webauthn,
2524                &Default::default(),
2525            ) {
2526                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_AUTH_TYPE_MSG),
2527                _ => panic!(),
2528            };
2529
2530            match audit_rx.try_recv() {
2531                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2532                _ => panic!("Oh no"),
2533            }
2534        }
2535
2536        {
2538            let (mut session, chal) = start_webauthn_only_session!(&mut audit, account, &webauthn);
2539
2540            let resp = wa
2541                .do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
2542                .map(Box::new)
2543                .expect("failed to use softtoken to authenticate");
2544
2545            match session.validate_creds(
2546                &AuthCredential::Passkey(resp),
2547                ts,
2548                &async_tx,
2549                &audit_tx,
2550                &webauthn,
2551                &Default::default(),
2552            ) {
2553                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
2554                _ => panic!(),
2555            };
2556
2557            match async_rx.blocking_recv() {
2559                Some(DelayedAction::WebauthnCounterIncrement(_)) => {}
2560                _ => panic!("Oh no"),
2561            }
2562            match async_rx.blocking_recv() {
2563                Some(DelayedAction::AuthSessionRecord(_)) => {}
2564                _ => panic!("Oh no"),
2565            }
2566        }
2567
2568        {
2570            let (_session, inv_chal) = start_webauthn_only_session!(&mut audit, account, &webauthn);
2571            let (mut session, _chal) = start_webauthn_only_session!(&mut audit, account, &webauthn);
2572
2573            let resp = wa
2574                .do_authentication(webauthn.get_allowed_origins()[0].clone(), inv_chal)
2576                .map(Box::new)
2577                .expect("failed to use softtoken to authenticate");
2578
2579            match session.validate_creds(
2580                &AuthCredential::Passkey(resp),
2581                ts,
2582                &async_tx,
2583                &audit_tx,
2584                &webauthn,
2585                &Default::default(),
2586            ) {
2587                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_WEBAUTHN_MSG),
2588                _ => panic!(),
2589            };
2590
2591            match audit_rx.try_recv() {
2592                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2593                _ => panic!("Oh no"),
2594            }
2595        }
2596
2597        {
2599            let mut inv_wa = WebauthnAuthenticator::new(SoftPasskey::new(true));
2600            let (chal, reg_state) = webauthn
2601                .start_passkey_registration(account.uuid, account.spn(), &account.displayname, None)
2602                .expect("Failed to setup webauthn rego challenge");
2603
2604            let r = inv_wa
2605                .do_registration(webauthn.get_allowed_origins()[0].clone(), chal)
2606                .expect("Failed to create soft token");
2607
2608            let inv_cred = webauthn
2609                .finish_passkey_registration(&r, ®_state)
2610                .expect("Failed to register soft token");
2611
2612            let (chal, _auth_state) = webauthn
2614                .start_passkey_authentication(&vec![inv_cred])
2615                .expect("Failed to generate challenge for in inv softtoken");
2616
2617            let resp = inv_wa
2619                .do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
2620                .map(Box::new)
2621                .expect("Failed to use softtoken for response.");
2622
2623            let (mut session, _chal) = start_webauthn_only_session!(&mut audit, account, &webauthn);
2624            match session.validate_creds(
2628                &AuthCredential::Passkey(resp),
2629                ts,
2630                &async_tx,
2631                &audit_tx,
2632                &webauthn,
2633                &Default::default(),
2634            ) {
2635                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_WEBAUTHN_MSG),
2636                _ => panic!(),
2637            };
2638
2639            match audit_rx.try_recv() {
2640                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2641                _ => panic!("Oh no"),
2642            }
2643        }
2644
2645        drop(async_tx);
2646        assert!(async_rx.blocking_recv().is_none());
2647        drop(audit_tx);
2648        assert!(audit_rx.blocking_recv().is_none());
2649    }
2650
2651    #[test]
2652    fn test_idm_authsession_webauthn_password_mech() {
2653        sketching::test_init();
2654        let (async_tx, mut async_rx) = unbounded();
2655        let (audit_tx, mut audit_rx) = unbounded();
2656        let ts = duration_from_epoch_now();
2657        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
2659
2660        let (webauthn, mut wa, wan_cred) = setup_webauthn_securitykey(account.spn());
2661        let pw_good = "test_password";
2662        let pw_bad = "bad_password";
2663
2664        let p = CryptoPolicy::minimum();
2666        let cred = Credential::new_password_only(&p, pw_good)
2667            .unwrap()
2668            .append_securitykey("soft".to_string(), wan_cred)
2669            .unwrap();
2670
2671        account.primary = Some(cred);
2672
2673        {
2675            let (mut session, _, pw_badlist_cache) = start_password_sk_session(&account, &webauthn);
2676
2677            match session.validate_creds(
2678                &AuthCredential::Password(pw_bad.to_string()),
2679                ts,
2680                &async_tx,
2681                &audit_tx,
2682                &webauthn,
2683                &pw_badlist_cache,
2684            ) {
2685                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_AUTH_TYPE_MSG),
2686                _ => panic!(),
2687            };
2688
2689            match audit_rx.try_recv() {
2690                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2691                _ => panic!("Oh no"),
2692            }
2693        }
2694
2695        {
2697            let (mut session, _, pw_badlist_cache) = start_password_sk_session(&account, &webauthn);
2698
2699            match session.validate_creds(
2700                &AuthCredential::Totp(0),
2701                ts,
2702                &async_tx,
2703                &audit_tx,
2704                &webauthn,
2705                &pw_badlist_cache,
2706            ) {
2707                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_AUTH_TYPE_MSG),
2708                _ => panic!(),
2709            };
2710
2711            match audit_rx.try_recv() {
2712                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2713                _ => panic!("Oh no"),
2714            }
2715        }
2716
2717        {
2721            let (_session, inv_chal, pw_badlist_cache) =
2722                start_password_sk_session(&account, &webauthn);
2723            let (mut session, _chal, _) = start_password_sk_session(&account, &webauthn);
2724
2725            let resp = wa
2726                .do_authentication(webauthn.get_allowed_origins()[0].clone(), inv_chal)
2728                .map(Box::new)
2729                .expect("failed to use softtoken to authenticate");
2730
2731            match session.validate_creds(
2732                &AuthCredential::SecurityKey(resp),
2733                ts,
2734                &async_tx,
2735                &audit_tx,
2736                &webauthn,
2737                &pw_badlist_cache,
2738            ) {
2739                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_WEBAUTHN_MSG),
2740                _ => panic!(),
2741            };
2742
2743            match audit_rx.try_recv() {
2744                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2745                _ => panic!("Oh no"),
2746            }
2747        }
2748
2749        {
2751            let (mut session, chal, pw_badlist_cache) =
2752                start_password_sk_session(&account, &webauthn);
2753
2754            let resp = wa
2755                .do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
2756                .map(Box::new)
2757                .expect("failed to use softtoken to authenticate");
2758
2759            match session.validate_creds(
2760                &AuthCredential::SecurityKey(resp),
2761                ts,
2762                &async_tx,
2763                &audit_tx,
2764                &webauthn,
2765                &pw_badlist_cache,
2766            ) {
2767                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
2768                _ => panic!(),
2769            };
2770            match session.validate_creds(
2771                &AuthCredential::Password(pw_bad.to_string()),
2772                ts,
2773                &async_tx,
2774                &audit_tx,
2775                &webauthn,
2776                &pw_badlist_cache,
2777            ) {
2778                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_PASSWORD_MSG),
2779                _ => panic!(),
2780            };
2781
2782            match audit_rx.try_recv() {
2783                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2784                _ => panic!("Oh no"),
2785            }
2786
2787            match async_rx.blocking_recv() {
2789                Some(DelayedAction::WebauthnCounterIncrement(_)) => {}
2790                _ => panic!("Oh no"),
2791            }
2792        }
2793
2794        {
2796            let (mut session, chal, pw_badlist_cache) =
2797                start_password_sk_session(&account, &webauthn);
2798
2799            let resp = wa
2800                .do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
2801                .map(Box::new)
2802                .expect("failed to use softtoken to authenticate");
2803
2804            match session.validate_creds(
2805                &AuthCredential::SecurityKey(resp),
2806                ts,
2807                &async_tx,
2808                &audit_tx,
2809                &webauthn,
2810                &pw_badlist_cache,
2811            ) {
2812                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
2813                _ => panic!(),
2814            };
2815            match session.validate_creds(
2816                &AuthCredential::Password(pw_good.to_string()),
2817                ts,
2818                &async_tx,
2819                &audit_tx,
2820                &webauthn,
2821                &pw_badlist_cache,
2822            ) {
2823                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
2824                _ => panic!(),
2825            };
2826
2827            match async_rx.blocking_recv() {
2829                Some(DelayedAction::WebauthnCounterIncrement(_)) => {}
2830                _ => panic!("Oh no"),
2831            }
2832            match async_rx.blocking_recv() {
2833                Some(DelayedAction::AuthSessionRecord(_)) => {}
2834                _ => panic!("Oh no"),
2835            }
2836        }
2837
2838        drop(async_tx);
2839        assert!(async_rx.blocking_recv().is_none());
2840        drop(audit_tx);
2841        assert!(audit_rx.blocking_recv().is_none());
2842    }
2843
2844    #[test]
2845    fn test_idm_authsession_webauthn_password_totp_mech() {
2846        sketching::test_init();
2847        let (async_tx, mut async_rx) = unbounded();
2848        let (audit_tx, mut audit_rx) = unbounded();
2849        let ts = duration_from_epoch_now();
2850        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
2852
2853        let (webauthn, mut wa, wan_cred) = setup_webauthn_securitykey(account.spn());
2854
2855        let totp = Totp::generate_secure(TOTP_DEFAULT_STEP);
2856        let totp_good = totp
2857            .do_totp_duration_from_epoch(&ts)
2858            .expect("failed to perform totp.");
2859        let totp_bad = totp
2860            .do_totp_duration_from_epoch(&Duration::from_secs(1234567))
2861            .expect("failed to perform totp.");
2862        assert!(totp_bad != totp_good);
2863
2864        let pw_good = "test_password";
2865        let pw_bad = "bad_password";
2866
2867        let p = CryptoPolicy::minimum();
2869        let cred = Credential::new_password_only(&p, pw_good)
2870            .unwrap()
2871            .append_securitykey("soft".to_string(), wan_cred)
2872            .unwrap()
2873            .append_totp("totp".to_string(), totp);
2874
2875        account.primary = Some(cred);
2876
2877        {
2879            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2880
2881            match session.validate_creds(
2882                &AuthCredential::Password(pw_bad.to_string()),
2883                ts,
2884                &async_tx,
2885                &audit_tx,
2886                &webauthn,
2887                &pw_badlist_cache,
2888            ) {
2889                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_AUTH_TYPE_MSG),
2890                _ => panic!(),
2891            };
2892
2893            match audit_rx.try_recv() {
2894                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2895                _ => panic!("Oh no"),
2896            }
2897        }
2898
2899        {
2901            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2902
2903            match session.validate_creds(
2904                &AuthCredential::Totp(totp_bad),
2905                ts,
2906                &async_tx,
2907                &audit_tx,
2908                &webauthn,
2909                &pw_badlist_cache,
2910            ) {
2911                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_TOTP_MSG),
2912                _ => panic!(),
2913            };
2914
2915            match audit_rx.try_recv() {
2916                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2917                _ => panic!("Oh no"),
2918            }
2919        }
2920
2921        {
2923            let (_session, inv_chal, pw_badlist_cache) =
2924                start_password_sk_session(&account, &webauthn);
2925            let (mut session, _chal, _) = start_password_sk_session(&account, &webauthn);
2926
2927            let resp = wa
2928                .do_authentication(webauthn.get_allowed_origins()[0].clone(), inv_chal)
2930                .map(Box::new)
2931                .expect("failed to use softtoken to authenticate");
2932
2933            match session.validate_creds(
2934                &AuthCredential::SecurityKey(resp),
2935                ts,
2936                &async_tx,
2937                &audit_tx,
2938                &webauthn,
2939                &pw_badlist_cache,
2940            ) {
2941                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_WEBAUTHN_MSG),
2942                _ => panic!(),
2943            };
2944
2945            match audit_rx.try_recv() {
2946                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2947                _ => panic!("Oh no"),
2948            }
2949        }
2950
2951        {
2953            let (mut session, chal, pw_badlist_cache) =
2954                start_password_sk_session(&account, &webauthn);
2955
2956            let resp = wa
2957                .do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
2958                .map(Box::new)
2959                .expect("failed to use softtoken to authenticate");
2960
2961            match session.validate_creds(
2962                &AuthCredential::SecurityKey(resp),
2963                ts,
2964                &async_tx,
2965                &audit_tx,
2966                &webauthn,
2967                &pw_badlist_cache,
2968            ) {
2969                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
2970                _ => panic!(),
2971            };
2972            match session.validate_creds(
2973                &AuthCredential::Password(pw_bad.to_string()),
2974                ts,
2975                &async_tx,
2976                &audit_tx,
2977                &webauthn,
2978                &pw_badlist_cache,
2979            ) {
2980                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_PASSWORD_MSG),
2981                _ => panic!(),
2982            };
2983
2984            match audit_rx.try_recv() {
2985                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
2986                _ => panic!("Oh no"),
2987            }
2988
2989            match async_rx.blocking_recv() {
2991                Some(DelayedAction::WebauthnCounterIncrement(_)) => {}
2992                _ => panic!("Oh no"),
2993            }
2994        }
2995
2996        {
2998            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
2999
3000            match session.validate_creds(
3001                &AuthCredential::Totp(totp_good),
3002                ts,
3003                &async_tx,
3004                &audit_tx,
3005                &webauthn,
3006                &pw_badlist_cache,
3007            ) {
3008                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
3009                _ => panic!(),
3010            };
3011            match session.validate_creds(
3012                &AuthCredential::Password(pw_bad.to_string()),
3013                ts,
3014                &async_tx,
3015                &audit_tx,
3016                &webauthn,
3017                &pw_badlist_cache,
3018            ) {
3019                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_PASSWORD_MSG),
3020                _ => panic!(),
3021            };
3022
3023            match audit_rx.try_recv() {
3024                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
3025                _ => panic!("Oh no"),
3026            }
3027        }
3028
3029        {
3031            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
3032
3033            match session.validate_creds(
3034                &AuthCredential::Totp(totp_good),
3035                ts,
3036                &async_tx,
3037                &audit_tx,
3038                &webauthn,
3039                &pw_badlist_cache,
3040            ) {
3041                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
3042                _ => panic!(),
3043            };
3044            match session.validate_creds(
3045                &AuthCredential::Password(pw_good.to_string()),
3046                ts,
3047                &async_tx,
3048                &audit_tx,
3049                &webauthn,
3050                &pw_badlist_cache,
3051            ) {
3052                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
3053                _ => panic!(),
3054            };
3055
3056            match async_rx.blocking_recv() {
3057                Some(DelayedAction::AuthSessionRecord(_)) => {}
3058                _ => panic!("Oh no"),
3059            }
3060        }
3061
3062        {
3064            let (mut session, chal, pw_badlist_cache) =
3065                start_password_sk_session(&account, &webauthn);
3066
3067            let resp = wa
3068                .do_authentication(webauthn.get_allowed_origins()[0].clone(), chal)
3069                .map(Box::new)
3070                .expect("failed to use softtoken to authenticate");
3071
3072            match session.validate_creds(
3073                &AuthCredential::SecurityKey(resp),
3074                ts,
3075                &async_tx,
3076                &audit_tx,
3077                &webauthn,
3078                &pw_badlist_cache,
3079            ) {
3080                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
3081                _ => panic!(),
3082            };
3083            match session.validate_creds(
3084                &AuthCredential::Password(pw_good.to_string()),
3085                ts,
3086                &async_tx,
3087                &audit_tx,
3088                &webauthn,
3089                &pw_badlist_cache,
3090            ) {
3091                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
3092                _ => panic!(),
3093            };
3094
3095            match async_rx.blocking_recv() {
3097                Some(DelayedAction::WebauthnCounterIncrement(_)) => {}
3098                _ => panic!("Oh no"),
3099            }
3100            match async_rx.blocking_recv() {
3101                Some(DelayedAction::AuthSessionRecord(_)) => {}
3102                _ => panic!("Oh no"),
3103            }
3104        }
3105
3106        drop(async_tx);
3107        assert!(async_rx.blocking_recv().is_none());
3108        drop(audit_tx);
3109        assert!(audit_rx.blocking_recv().is_none());
3110    }
3111
3112    #[test]
3113    fn test_idm_authsession_backup_code_mech() {
3114        sketching::test_init();
3115        let webauthn = create_webauthn();
3116        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
3118
3119        let ts = Duration::from_secs(12345);
3121
3122        let totp = Totp::generate_secure(TOTP_DEFAULT_STEP);
3124
3125        let totp_good = totp
3126            .do_totp_duration_from_epoch(&ts)
3127            .expect("failed to perform totp.");
3128
3129        let pw_good = "test_password";
3130        let pw_bad = "bad_password";
3131
3132        let backup_code_good = readable_password_from_random();
3133        let backup_code_bad = readable_password_from_random();
3134        assert!(backup_code_bad != backup_code_good);
3135        let mut code_set = HashSet::new();
3136        code_set.insert(backup_code_good.clone());
3137
3138        let backup_codes = BackupCodes::new(code_set);
3139
3140        let p = CryptoPolicy::minimum();
3142        let cred = Credential::new_password_only(&p, pw_good)
3143            .unwrap()
3144            .append_totp("totp".to_string(), totp)
3145            .update_backup_code(backup_codes)
3146            .unwrap();
3147
3148        account.primary = Some(cred);
3149
3150        let (async_tx, mut async_rx) = unbounded();
3151        let (audit_tx, mut audit_rx) = unbounded();
3152
3153        {
3158            let (mut session, pw_badlist_cache) = start_password_bc_session(&account, &webauthn);
3159
3160            match session.validate_creds(
3161                &AuthCredential::Password(pw_bad.to_string()),
3162                ts,
3163                &async_tx,
3164                &audit_tx,
3165                &webauthn,
3166                &pw_badlist_cache,
3167            ) {
3168                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_AUTH_TYPE_MSG),
3169                _ => panic!(),
3170            };
3171
3172            match audit_rx.try_recv() {
3173                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
3174                _ => panic!("Oh no"),
3175            }
3176        }
3177        {
3179            let (mut session, pw_badlist_cache) = start_password_bc_session(&account, &webauthn);
3180
3181            match session.validate_creds(
3182                &AuthCredential::BackupCode(backup_code_bad),
3183                ts,
3184                &async_tx,
3185                &audit_tx,
3186                &webauthn,
3187                &pw_badlist_cache,
3188            ) {
3189                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_BACKUPCODE_MSG),
3190                _ => panic!(),
3191            };
3192
3193            match audit_rx.try_recv() {
3194                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
3195                _ => panic!("Oh no"),
3196            }
3197        }
3198        {
3201            let (mut session, pw_badlist_cache) = start_password_bc_session(&account, &webauthn);
3202
3203            match session.validate_creds(
3204                &AuthCredential::BackupCode(backup_code_good.clone()),
3205                ts,
3206                &async_tx,
3207                &audit_tx,
3208                &webauthn,
3209                &pw_badlist_cache,
3210            ) {
3211                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
3212                _ => panic!(),
3213            };
3214            match session.validate_creds(
3215                &AuthCredential::Password(pw_bad.to_string()),
3216                ts,
3217                &async_tx,
3218                &audit_tx,
3219                &webauthn,
3220                &pw_badlist_cache,
3221            ) {
3222                Ok(AuthState::Denied(msg)) => assert_eq!(msg, BAD_PASSWORD_MSG),
3223                _ => panic!(),
3224            };
3225
3226            match audit_rx.try_recv() {
3227                Ok(AuditEvent::AuthenticationDenied { .. }) => {}
3228                _ => panic!("Oh no"),
3229            }
3230        }
3231        match async_rx.blocking_recv() {
3233            Some(DelayedAction::BackupCodeRemoval(_)) => {}
3234            _ => panic!("Oh no"),
3235        }
3236
3237        {
3240            let (mut session, pw_badlist_cache) = start_password_bc_session(&account, &webauthn);
3241
3242            match session.validate_creds(
3243                &AuthCredential::BackupCode(backup_code_good),
3244                ts,
3245                &async_tx,
3246                &audit_tx,
3247                &webauthn,
3248                &pw_badlist_cache,
3249            ) {
3250                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
3251                _ => panic!(),
3252            };
3253            match session.validate_creds(
3254                &AuthCredential::Password(pw_good.to_string()),
3255                ts,
3256                &async_tx,
3257                &audit_tx,
3258                &webauthn,
3259                &pw_badlist_cache,
3260            ) {
3261                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
3262                _ => panic!(),
3263            };
3264        }
3265        match async_rx.blocking_recv() {
3267            Some(DelayedAction::BackupCodeRemoval(_)) => {}
3268            _ => panic!("Oh no"),
3269        }
3270
3271        match async_rx.blocking_recv() {
3273            Some(DelayedAction::AuthSessionRecord(_)) => {}
3274            _ => panic!("Oh no"),
3275        }
3276
3277        {
3281            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
3282
3283            match session.validate_creds(
3284                &AuthCredential::Totp(totp_good),
3285                ts,
3286                &async_tx,
3287                &audit_tx,
3288                &webauthn,
3289                &pw_badlist_cache,
3290            ) {
3291                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
3292                _ => panic!(),
3293            };
3294            match session.validate_creds(
3295                &AuthCredential::Password(pw_good.to_string()),
3296                ts,
3297                &async_tx,
3298                &audit_tx,
3299                &webauthn,
3300                &pw_badlist_cache,
3301            ) {
3302                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
3303                _ => panic!(),
3304            };
3305        }
3306
3307        match async_rx.blocking_recv() {
3309            Some(DelayedAction::AuthSessionRecord(_)) => {}
3310            _ => panic!("Oh no"),
3311        }
3312
3313        drop(async_tx);
3314        assert!(async_rx.blocking_recv().is_none());
3315        drop(audit_tx);
3316        assert!(audit_rx.blocking_recv().is_none());
3317    }
3318
3319    #[test]
3320    fn test_idm_authsession_multiple_totp_password_mech() {
3321        sketching::test_init();
3324        let webauthn = create_webauthn();
3325        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
3327
3328        let ts = Duration::from_secs(12345);
3330
3331        let totp_a = Totp::generate_secure(TOTP_DEFAULT_STEP);
3333        let totp_b = Totp::generate_secure(TOTP_DEFAULT_STEP);
3334
3335        let totp_good_a = totp_a
3336            .do_totp_duration_from_epoch(&ts)
3337            .expect("failed to perform totp.");
3338
3339        let totp_good_b = totp_b
3340            .do_totp_duration_from_epoch(&ts)
3341            .expect("failed to perform totp.");
3342
3343        assert!(totp_good_a != totp_good_b);
3344
3345        let pw_good = "test_password";
3346
3347        let p = CryptoPolicy::minimum();
3348        let cred = Credential::new_password_only(&p, pw_good)
3349            .unwrap()
3350            .append_totp("totp_a".to_string(), totp_a)
3351            .append_totp("totp_b".to_string(), totp_b);
3352        account.primary = Some(cred);
3354
3355        let (async_tx, mut async_rx) = unbounded();
3356        let (audit_tx, mut audit_rx) = unbounded();
3357
3358        {
3360            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
3361
3362            match session.validate_creds(
3363                &AuthCredential::Totp(totp_good_a),
3364                ts,
3365                &async_tx,
3366                &audit_tx,
3367                &webauthn,
3368                &pw_badlist_cache,
3369            ) {
3370                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
3371                _ => panic!(),
3372            };
3373            match session.validate_creds(
3374                &AuthCredential::Password(pw_good.to_string()),
3375                ts,
3376                &async_tx,
3377                &audit_tx,
3378                &webauthn,
3379                &pw_badlist_cache,
3380            ) {
3381                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
3382                _ => panic!(),
3383            };
3384
3385            match async_rx.blocking_recv() {
3386                Some(DelayedAction::AuthSessionRecord(_)) => {}
3387                _ => panic!("Oh no"),
3388            }
3389        }
3390
3391        {
3393            let (mut session, pw_badlist_cache) = start_password_totp_session(&account, &webauthn);
3394
3395            match session.validate_creds(
3396                &AuthCredential::Totp(totp_good_b),
3397                ts,
3398                &async_tx,
3399                &audit_tx,
3400                &webauthn,
3401                &pw_badlist_cache,
3402            ) {
3403                Ok(AuthState::Continue(cont)) => assert_eq!(cont, vec![AuthAllowed::Password]),
3404                _ => panic!(),
3405            };
3406            match session.validate_creds(
3407                &AuthCredential::Password(pw_good.to_string()),
3408                ts,
3409                &async_tx,
3410                &audit_tx,
3411                &webauthn,
3412                &pw_badlist_cache,
3413            ) {
3414                Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
3415                _ => panic!(),
3416            };
3417
3418            match async_rx.blocking_recv() {
3419                Some(DelayedAction::AuthSessionRecord(_)) => {}
3420                _ => panic!("Oh no"),
3421            }
3422        }
3423
3424        drop(async_tx);
3425        assert!(async_rx.blocking_recv().is_none());
3426        drop(audit_tx);
3427        assert!(audit_rx.blocking_recv().is_none());
3428    }
3429
3430    #[test]
3431    fn test_idm_authsession_oauth2_client() {
3432        sketching::test_init();
3433        let (async_tx, mut async_rx) = unbounded();
3435        let (audit_tx, mut audit_rx) = unbounded();
3436        let pw_badlist_cache = create_pw_badlist_cache();
3437        let current_time = duration_from_epoch_now();
3438        let webauthn = create_webauthn();
3439        let privileged = false;
3440
3441        let oauth2_client_provider =
3443            OAuth2ClientProvider::new_test("test_trust_client", "https://localhost", ["openid"]);
3444
3445        let mut account: Account = BUILTIN_ACCOUNT_TEST_PERSON.clone().into();
3447        account.setup_oauth2_client_provider(&oauth2_client_provider);
3448
3449        let asd = AuthSessionData {
3451            account,
3452            account_policy: ResolvedAccountPolicy::default(),
3453            issue: AuthIssueSession::Token,
3454            webauthn: &webauthn,
3455            ct: current_time,
3456            client_auth_info: Source::Internal.into(),
3457            oauth2_client_provider: Some(&oauth2_client_provider),
3458        };
3459        let key_object = KeyObjectInternal::new_test();
3460
3461        let (session, state) = AuthSession::new(asd, privileged, key_object);
3462
3463        trace!(?state);
3464
3465        if let AuthState::Choose(auth_mechs) = state {
3467            assert!(auth_mechs
3468                .iter()
3469                .all(|x| matches!(x, AuthMech::OAuth2Trust)));
3470        } else {
3471            panic!("Invalid auth state")
3472        }
3473
3474        let mut session = session.expect("Missing auth session?");
3476
3477        let state = session
3478            .start_session(&AuthMech::OAuth2Trust)
3479            .expect("Failed to select anonymous mech.");
3480
3481        trace!(?state);
3482
3483        let (_auth_url, auth_req) = match state {
3485            AuthState::External(AuthExternal::OAuth2AuthorisationRequest {
3486                authorisation_url,
3487                request,
3488            }) => (authorisation_url, request),
3489            _ => unreachable!(),
3490        };
3491
3492        let state = session
3498            .validate_creds(
3499                &AuthCredential::OAuth2AuthorisationResponse {
3500                    code: "abcdefg1234".into(),
3502                    state: auth_req.state.clone(),
3503                },
3504                current_time,
3505                &async_tx,
3506                &audit_tx,
3507                &webauthn,
3508                &pw_badlist_cache,
3509            )
3510            .expect("Failed to perform credential validation step.");
3511
3512        let (_token_url, _token_request) = match state {
3513            AuthState::External(AuthExternal::OAuth2AccessTokenRequest {
3514                token_url,
3515                client_id: _,
3516                client_secret: _,
3517                request,
3518            }) => (token_url, request),
3519            _ => unreachable!(),
3520        };
3521
3522        let response = AccessTokenResponse {
3525            access_token: "super_secret_access_token".to_string(),
3526            token_type: AccessTokenType::Bearer,
3527            expires_in: 300,
3528            refresh_token: Some("super_secret_refresh_token".to_string()),
3529            scope: oauth2_client_provider.request_scopes.clone(),
3530            id_token: None,
3531        };
3532
3533        let state = session
3534            .validate_creds(
3535                &AuthCredential::OAuth2AccessTokenResponse { response },
3536                current_time,
3537                &async_tx,
3538                &audit_tx,
3539                &webauthn,
3540                &pw_badlist_cache,
3541            )
3542            .expect("Failed to perform credential validation step.");
3543
3544        match state {
3546            AuthState::Success(_, AuthIssueSession::Token) => {}
3547            _ => unreachable!(),
3548        }
3549
3550        match async_rx.blocking_recv() {
3553            Some(DelayedAction::AuthSessionRecord(_)) => {}
3554            _ => panic!("Oh no"),
3555        }
3556
3557        drop(async_tx);
3558        assert!(async_rx.blocking_recv().is_none());
3559        drop(audit_tx);
3560        assert!(audit_rx.blocking_recv().is_none());
3561    }
3562
3563    }