1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-04 02:33:33 +00:00

Initial commit

This commit is contained in:
Bernd Schoolmann
2025-11-16 03:09:37 +01:00
parent 3b97093338
commit 2ac1c694e0
21 changed files with 1924 additions and 65 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ members = [
"bitwarden_chromium_import_helper",
"chromium_importer",
"core",
"fido2_client",
"macos_provider",
"napi",
"process_isolation",

View File

@@ -0,0 +1,22 @@
[package]
name = "fido2_client"
edition = { workspace = true }
license = { workspace = true }
version = { workspace = true }
publish = { workspace = true }
[dependencies]
base64.workspace = true
ctap-hid-fido2 = "3.5.1"
pinentry = "0.5.0"
home = "=0.5.0"
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
secrecy = "0.8.0"
hex.workspace = true
webauthn-authenticator-rs = { version = "0.5.3", features = ["ctap2", "cable", "usb"] }
tokio.workspace = true
futures-util = "0.3.31"
webauthn-rs-proto = "0.5.3"
sha2.workspace = true

View File

@@ -0,0 +1,104 @@
use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
use ctap_hid_fido2::{
fidokey::{AssertionExtension, GetAssertionArgsBuilder},
Cfg, FidoKeyHidFactory,
};
use pinentry::PassphraseInput;
use secrecy::ExposeSecret;
use crate::{
prf_to_hmac, AssertionOptions, AuthenticatorAssertionResponse, Fido2ClientError,
PublicKeyCredential,
};
fn get_pin() -> Option<String> {
if let Some(mut input) = PassphraseInput::with_default_binary() {
input
.with_description("Enter your FIDO2 Authenticator PIN:")
.with_prompt("PIN:")
.interact()
.ok()
.map(|p| p.expose_secret().to_owned())
} else {
None
}
}
pub fn available() -> bool {
true
}
pub fn get(options: AssertionOptions) -> Result<PublicKeyCredential, Fido2ClientError> {
let device = FidoKeyHidFactory::create(&Cfg::init()).map_err(|_| Fido2ClientError::NoDevice)?;
let client_data_json = format!(
r#"{{"type":"webauthn.get","challenge":"{}","origin":"https://{}","crossOrigin": true}}"#,
BASE64_URL_SAFE_NO_PAD.encode(&options.challenge),
options.rpid
);
let mut get_assertion_args =
GetAssertionArgsBuilder::new(options.rpid.as_str(), client_data_json.as_bytes())
.extensions(&[AssertionExtension::HmacSecret(Some(prf_to_hmac(
&options.prf_eval_first,
)))]);
let mut pin: Option<String> = None;
if options.user_verification == crate::UserVerification::Required
|| options.user_verification == crate::UserVerification::Preferred
{
pin = Some(get_pin().ok_or(Fido2ClientError::WrongPin)?);
get_assertion_args = get_assertion_args.pin(pin.as_ref().unwrap());
}
let assertions = device
.get_assertion_with_args(&get_assertion_args.build())
.map_err(|_e| Fido2ClientError::AssertionError)?;
let assertion = assertions.get(0).ok_or(Fido2ClientError::AssertionError)?;
let prf_extension = assertion
.extensions
.iter()
.find_map(|ext| {
if let AssertionExtension::HmacSecret(results) = ext {
Some(*results)
} else {
None
}
})
.flatten();
Ok(PublicKeyCredential {
authenticator_attachment: "cross-platform".to_string(),
id: BASE64_URL_SAFE_NO_PAD.encode(&assertion.credential_id),
raw_id: assertion.credential_id.clone(),
response: AuthenticatorAssertionResponse {
authenticator_data: assertion.auth_data.clone(),
client_data_json: client_data_json.as_bytes().to_vec(),
signature: assertion.signature.clone(),
user_handle: assertion.user.id.clone(),
},
r#type: "public-key".to_string(),
prf: prf_extension,
})
}
#[cfg(test)]
mod tests {
use crate::{ctap_hid_fido2::get, AssertionOptions};
#[test]
#[ignore]
fn assertion() {
get(AssertionOptions {
challenge: vec![],
timeout: 0,
rpid: "example.com".to_string(),
user_verification: crate::UserVerification::Required,
allow_credentials: vec![],
prf_eval_first: [0u8; 32],
prf_eval_second: None,
})
.unwrap();
}
}

View File

@@ -0,0 +1,69 @@
#[cfg(all(target_os = "linux", target_env = "gnu"))]
mod ctap_hid_fido2;
#[cfg(all(target_os = "linux", target_env = "gnu"))]
use ctap_hid_fido2::*;
#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
mod unimplemented;
#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
use unimplemented::*;
fn prf_to_hmac(prf_salt: &[u8]) -> [u8; 32] {
use sha2::Digest;
sha2::Sha256::digest(&[b"WebAuthn PRF".as_slice(), &[0], prf_salt].concat()).into()
}
#[derive(Debug, PartialEq)]
pub enum UserVerification {
Discouraged,
Preferred,
Required,
}
#[derive(Debug)]
pub struct AssertionOptions {
pub challenge: Vec<u8>,
pub timeout: u64,
pub rpid: String,
pub user_verification: UserVerification,
pub allow_credentials: Vec<Vec<u8>>,
pub prf_eval_first: [u8; 32],
pub prf_eval_second: Option<[u8; 32]>,
}
pub struct AuthenticatorAssertionResponse {
pub authenticator_data: Vec<u8>,
pub client_data_json: Vec<u8>,
pub signature: Vec<u8>,
pub user_handle: Vec<u8>,
}
pub struct PublicKeyCredential {
pub authenticator_attachment: String,
pub id: String,
pub raw_id: Vec<u8>,
pub response: AuthenticatorAssertionResponse,
pub r#type: String,
pub prf: Option<[u8; 32]>,
}
#[derive(Debug)]
pub enum Fido2ClientError {
WrongPin,
NoCredentials,
NoDevice,
InvalidInput,
AssertionError,
}
pub mod fido2_client {
pub fn get(
assertion_options: super::AssertionOptions,
) -> Result<super::PublicKeyCredential, super::Fido2ClientError> {
super::get(assertion_options)
}
pub fn available() -> bool {
super::available()
}
}

View File

@@ -0,0 +1,7 @@
pub fn get(options: AssertionOptions) -> Result<PublicKeyCredential, Fido2ClientError> {
todo!("Fido2Client is unimplemented on this platform");
}
pub fn available() -> bool {
false
}

View File

@@ -19,6 +19,7 @@ autotype = { path = "../autotype" }
base64 = { workspace = true }
chromium_importer = { path = "../chromium_importer" }
desktop_core = { path = "../core" }
fido2_client = { path = "../fido2_client" }
hex = { workspace = true }
napi = { workspace = true, features = ["async"] }
napi-derive = { workspace = true }

View File

@@ -3,6 +3,33 @@
/* auto-generated by NAPI-RS */
export const enum UserVerification {
Preferred = 'Preferred',
Required = 'Required',
Discouraged = 'Discouraged'
}
export interface CredentialAssertionOptions {
challenge: Uint8Array
timeout: number
rpid: string
userVerification: UserVerification
allowCredentials: Array<Array<number>>
prfEvalFirst: Uint8Array
}
export interface AuthenticatorAssertionResponse {
authenticatorData: Uint8Array
clientDataJson: Uint8Array
signature: Uint8Array
userHandle: Uint8Array
}
export interface PublicKeyCredential {
authenticatorAttachment: string
id: string
rawId: Uint8Array
response: AuthenticatorAssertionResponse
type: string
prf?: Uint8Array
}
export declare namespace passwords {
/** The error message returned when a password is not found during retrieval or deletion. */
export const PASSWORD_NOT_FOUND: string
@@ -254,3 +281,7 @@ export declare namespace autotype {
export function getForegroundWindowTitle(): string
export function typeInput(input: Array<number>, keyboardShortcut: Array<string>): void
}
export declare namespace navigator_credentials {
export function get(assertionOptions: CredentialAssertionOptions): PublicKeyCredential
export function available(): boolean
}

View File

@@ -1,3 +1,6 @@
use napi::bindgen_prelude::{Buffer, Uint8Array};
use tokio_util::bytes::Buf;
#[macro_use]
extern crate napi_derive;
@@ -1199,3 +1202,107 @@ pub mod autotype {
})
}
}
#[napi(string_enum)]
pub enum UserVerification {
Preferred,
Required,
Discouraged,
}
impl Into<fido2_client::UserVerification> for UserVerification {
fn into(self) -> fido2_client::UserVerification {
match self {
UserVerification::Preferred => fido2_client::UserVerification::Preferred,
UserVerification::Required => fido2_client::UserVerification::Required,
UserVerification::Discouraged => fido2_client::UserVerification::Discouraged,
}
}
}
#[napi(object)]
pub struct CredentialAssertionOptions {
pub challenge: Uint8Array,
pub timeout: i64,
pub rpid: String,
pub user_verification: UserVerification,
pub allow_credentials: Vec<Vec<u8>>,
pub prf_eval_first: Uint8Array,
}
impl Into<fido2_client::AssertionOptions> for CredentialAssertionOptions {
fn into(self) -> fido2_client::AssertionOptions {
fido2_client::AssertionOptions {
challenge: self.challenge.to_vec(),
timeout: self.timeout as u64,
rpid: self.rpid,
user_verification: self.user_verification.into(),
allow_credentials: self.allow_credentials,
prf_eval_first: self.prf_eval_first.to_vec().try_into().unwrap_or([0u8; 32]),
prf_eval_second: None,
}
}
}
#[napi(object)]
pub struct AuthenticatorAssertionResponse {
pub authenticator_data: Uint8Array,
pub client_data_json: Uint8Array,
pub signature: Uint8Array,
pub user_handle: Uint8Array,
}
impl From<fido2_client::AuthenticatorAssertionResponse> for AuthenticatorAssertionResponse {
fn from(response: fido2_client::AuthenticatorAssertionResponse) -> Self {
AuthenticatorAssertionResponse {
authenticator_data: Uint8Array::from(response.authenticator_data),
client_data_json: Uint8Array::from(response.client_data_json),
signature: Uint8Array::from(response.signature),
user_handle: Uint8Array::from(response.user_handle),
}
}
}
#[napi(object)]
pub struct PublicKeyCredential {
pub authenticator_attachment: String,
pub id: String,
pub raw_id: Uint8Array,
pub response: AuthenticatorAssertionResponse,
pub r#type: String,
pub prf: Option<Uint8Array>,
}
impl Into<PublicKeyCredential> for fido2_client::PublicKeyCredential {
fn into(self) -> PublicKeyCredential {
PublicKeyCredential {
authenticator_attachment: self.authenticator_attachment,
id: self.id,
raw_id: Uint8Array::from(self.raw_id),
response: self.response.into(),
r#type: self.r#type,
prf: self.prf.map(|p| Uint8Array::from(p)),
}
}
}
#[napi]
pub mod navigator_credentials {
use crate::CredentialAssertionOptions;
#[napi]
pub fn get(
assertion_options: CredentialAssertionOptions,
) -> napi::Result<crate::PublicKeyCredential> {
let options: fido2_client::AssertionOptions = assertion_options.into();
let resp = fido2_client::fido2_client::get(options).map_err(|e| {
napi::Error::from_reason(format!("FIDO2 Authentication failed: {:?}", e))
})?;
Ok(resp.into())
}
#[napi]
pub fn available() -> bool {
fido2_client::fido2_client::available()
}
}

View File

@@ -12,6 +12,7 @@ import {
tdeDecryptionRequiredGuard,
unauthGuardFn,
} from "@bitwarden/angular/auth/guards";
import { LoginViaWebAuthnComponent } from "@bitwarden/angular/auth/login-via-webauthn/login-via-webauthn.component";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
import {
@@ -23,6 +24,7 @@ import {
VaultIcon,
LockIcon,
DomainIcon,
TwoFactorAuthSecurityKeyIcon,
} from "@bitwarden/assets/svg";
import {
LoginComponent,
@@ -123,6 +125,27 @@ const routes: Routes = [
path: "",
component: AnonLayoutWrapperComponent,
children: [
{
path: AuthRoute.LoginWithPasskey,
canActivate: [unauthGuardFn()],
data: {
pageIcon: TwoFactorAuthSecurityKeyIcon,
pageTitle: {
key: "logInWithPasskey",
},
pageSubtitle: {
key: "readingPasskeyLoadingInfo",
},
} satisfies RouteDataProperties & AnonLayoutWrapperData,
children: [
{ path: "", component: LoginViaWebAuthnComponent },
{
path: "",
component: EnvironmentSelectorComponent,
outlet: "environment-selector",
},
],
},
{
path: AuthRoute.SignUp,
canActivate: [unauthGuardFn()],

View File

@@ -33,6 +33,7 @@ import {
import {
InternalUserDecryptionOptionsServiceAbstraction,
LoginEmailService,
LoginStrategyServiceAbstraction,
SsoUrlService,
} from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -51,6 +52,9 @@ import {
} from "@bitwarden/common/auth/abstractions/auth.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction";
import { WebAuthnLoginPrfKeyServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
import { ClientType } from "@bitwarden/common/enums";
@@ -119,6 +123,7 @@ import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarde
import { DesktopLoginApprovalDialogComponentService } from "../../auth/login/desktop-login-approval-dialog-component.service";
import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service";
import { DesktopTwoFactorAuthDuoComponentService } from "../../auth/services/desktop-two-factor-auth-duo-component.service";
import { DesktopWebAuthnLoginService } from "../../auth/services/desktop-webauthn-login.serivce";
import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service";
import { DesktopAutofillService } from "../../autofill/services/desktop-autofill.service";
import { DesktopAutotypeDefaultSettingPolicy } from "../../autofill/services/desktop-autotype-policy.service";
@@ -310,6 +315,17 @@ const safeProviders: SafeProvider[] = [
useClass: WebCryptoFunctionService,
deps: [WINDOW],
}),
safeProvider({
provide: WebAuthnLoginServiceAbstraction,
useClass: DesktopWebAuthnLoginService,
deps: [
WebAuthnLoginApiServiceAbstraction,
LoginStrategyServiceAbstraction,
WebAuthnLoginPrfKeyServiceAbstraction,
WINDOW,
LogService,
],
}),
safeProvider({
provide: KeyServiceAbstraction,
useClass: ElectronKeyService,

View File

@@ -1,5 +1,7 @@
import { ipcRenderer } from "electron";
import { PublicKeyCredential } from "@bitwarden/desktop-napi";
export default {
loginRequest: (alertTitle: string, alertBody: string, buttonText: string): Promise<void> =>
ipcRenderer.invoke("loginRequest", {
@@ -7,4 +9,7 @@ export default {
alertBody,
buttonText,
}),
navigatorCredentialsGet: (
options: CredentialRequestOptions,
): Promise<PublicKeyCredential | null> => ipcRenderer.invoke("navigatorCredentialsGet", options),
};

View File

@@ -0,0 +1,86 @@
import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common";
import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction";
import { WebAuthnLoginPrfKeyServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { WebAuthnLoginCredentialAssertionOptionsView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion-options.view";
import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view";
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service";
import { PrfKey } from "@bitwarden/common/types/key";
import { LogService } from "@bitwarden/logging";
export class DesktopWebAuthnLoginService extends WebAuthnLoginService {
constructor(
webAuthnLoginApiService: WebAuthnLoginApiServiceAbstraction,
loginStrategyService: LoginStrategyServiceAbstraction,
webAuthnLoginPrfKeyService: WebAuthnLoginPrfKeyServiceAbstraction,
window: Window,
logService?: LogService,
) {
super(
webAuthnLoginApiService,
loginStrategyService,
webAuthnLoginPrfKeyService,
window,
logService,
);
}
async assertCredential(
credentialAssertionOptions: WebAuthnLoginCredentialAssertionOptionsView,
): Promise<WebAuthnLoginCredentialAssertionView> {
const nativeOptions: CredentialRequestOptions = {
publicKey: credentialAssertionOptions.options,
};
// TODO: Remove `any` when typescript typings add support for PRF
nativeOptions.publicKey.extensions = {
prf: { eval: { first: await this.webAuthnLoginPrfKeyService.getLoginWithPrfSalt() } },
} as any;
try {
const response = await ipc.auth.navigatorCredentialsGet(nativeOptions);
this.logService.info("navigator.credentials.get response received", response);
// if (!(response instanceof PublicKeyCredential)) {
// return undefined;
// }
// TODO: Remove `any` when typescript typings add support for PRF
const prfResult = (response as any).prf as Uint8Array | undefined;
let symmetricPrfKey: PrfKey | undefined;
if (prfResult != undefined) {
// Ensure we pass a plain ArrayBuffer (not a SharedArrayBuffer) by copying the bytes.
symmetricPrfKey = await this.webAuthnLoginPrfKeyService.createSymmetricKeyFromPrf(
prfResult.slice().buffer,
);
}
const deviceResponse = new WebAuthnLoginAssertionResponseRequest(
response as any as PublicKeyCredential,
);
// Verify that we aren't going to send PRF information to the server in any case.
// Note: this will only happen if a dev has done something wrong.
if ("prf" in deviceResponse.extensions) {
throw new Error("PRF information is not allowed to be sent to the server.");
}
return new WebAuthnLoginCredentialAssertionView(
credentialAssertionOptions.token,
deviceResponse,
symmetricPrfKey,
);
} catch (error) {
this.logService?.error(error);
return undefined;
}
}
async logIn(assertion: WebAuthnLoginCredentialAssertionView): Promise<AuthResult> {
const credential = new WebAuthnLoginCredentials(
assertion.token,
assertion.deviceResponse,
assertion.prfKey,
);
const result = await this.loginStrategyService.logIn(credential);
return result;
}
}

View File

@@ -0,0 +1,29 @@
import { ipcMain } from "electron";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { navigator_credentials, UserVerification } from "@bitwarden/desktop-napi";
export class MainNavigatorCredentialsService {
constructor(private logService: LogService) {
ipcMain.handle("navigatorCredentialsGet", async (event: any, message: any) => {
this.logService.info("Handling navigatorCredentials.get request from renderer", message);
const challenge = Utils.fromB64ToArray(message.publicKey.response.challenge);
this.logService.info("navigatorCredentials.get challenge", challenge);
this.logService.info(
"navigatorCredentials.get prfEvalFirst",
message.publicKey.extensions.prf.eval.first,
);
const result = navigator_credentials.get({
challenge: Uint8Array.from(challenge),
timeout: message.publicKey.timeout,
rpid: message.publicKey.rpId,
userVerification: UserVerification.Required,
allowCredentials: [],
prfEvalFirst: Uint8Array.from(message.publicKey.extensions.prf.eval.first),
});
this.logService.info("navigatorCredentials.get result", result);
return result;
});
}
}

View File

@@ -34,6 +34,7 @@ import {
import { SerializedMemoryStorageService, StorageServiceProvider } from "@bitwarden/storage-core";
import { ChromiumImporterService } from "./app/tools/import/chromium-importer.service";
import { MainNavigatorCredentialsService } from "./auth/services/main-navigator-credentials.serivce";
import { MainDesktopAutotypeService } from "./autofill/main/main-desktop-autotype.service";
import { MainSshAgentService } from "./autofill/main/main-ssh-agent.service";
import { DesktopAutofillSettingsService } from "./autofill/services/desktop-autofill-settings.service";
@@ -284,6 +285,7 @@ export class Main {
app.getPath("exe"),
app.getAppPath(),
);
new MainNavigatorCredentialsService(this.logService);
this.desktopAutofillSettingsService = new DesktopAutofillSettingsService(stateProvider);

View File

@@ -113,7 +113,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
}
isDev(): boolean {
return ipc.platform.isDev;
return true;
}
isSelfHost(): boolean {

View File

@@ -98,6 +98,7 @@ export class LoginViaWebAuthnComponent implements OnInit {
let assertion: WebAuthnLoginCredentialAssertionView;
try {
const options = await this.webAuthnLoginService.getCredentialAssertionOptions();
this.logService.info("Starting WebAuthn assertion with options:", options);
assertion = await this.webAuthnLoginService.assertCredential(options);
} catch (error) {
this.validationService.showError(error);

View File

@@ -107,7 +107,6 @@ import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/ab
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction";
import { WebAuthnLoginPrfKeyServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { SendTokenService, DefaultSendTokenService } from "@bitwarden/common/auth/send-access";
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
@@ -130,8 +129,6 @@ import { UserVerificationApiService } from "@bitwarden/common/auth/services/user
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import { WebAuthnLoginApiService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-api.service";
import { WebAuthnLoginPrfKeyService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-prf-key.service";
import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service";
import { TwoFactorApiService, DefaultTwoFactorApiService } from "@bitwarden/common/auth/two-factor";
import {
AutofillSettingsService,
AutofillSettingsServiceAbstraction,
@@ -1339,17 +1336,17 @@ const safeProviders: SafeProvider[] = [
useClass: WebAuthnLoginApiService,
deps: [ApiServiceAbstraction, EnvironmentService],
}),
safeProvider({
provide: WebAuthnLoginServiceAbstraction,
useClass: WebAuthnLoginService,
deps: [
WebAuthnLoginApiServiceAbstraction,
LoginStrategyServiceAbstraction,
WebAuthnLoginPrfKeyServiceAbstraction,
WINDOW,
LogService,
],
}),
// safeProvider({
// provide: WebAuthnLoginServiceAbstraction,
// useClass: WebAuthnLoginService,
// deps: [
// WebAuthnLoginApiServiceAbstraction,
// LoginStrategyServiceAbstraction,
// WebAuthnLoginPrfKeyServiceAbstraction,
// WINDOW,
// LogService,
// ],
// }),
safeProvider({
provide: StorageServiceProvider,
useClass: StorageServiceProvider,

View File

@@ -24,7 +24,7 @@ export class DefaultLoginComponentService implements LoginComponentService {
}
isLoginWithPasskeySupported(): boolean {
return this.clientType === ClientType.Web;
return true;
}
/**

View File

@@ -20,15 +20,16 @@ export class WebAuthnLoginAssertionResponseRequest extends WebAuthnLoginResponse
constructor(credential: PublicKeyCredential) {
super(credential);
if (!(credential.response instanceof AuthenticatorAssertionResponse)) {
throw new Error("Invalid authenticator response");
}
// if (!(credential.response instanceof AuthenticatorAssertionResponse)) {
// throw new Error("Invalid authenticator response");
// }
const resp = credential.response as AuthenticatorAssertionResponse;
this.response = {
authenticatorData: Utils.fromBufferToUrlB64(credential.response.authenticatorData),
signature: Utils.fromBufferToUrlB64(credential.response.signature),
clientDataJSON: Utils.fromBufferToUrlB64(credential.response.clientDataJSON),
userHandle: Utils.fromBufferToUrlB64(credential.response.userHandle),
authenticatorData: Utils.fromBufferToUrlB64(resp.authenticatorData),
signature: Utils.fromBufferToUrlB64(resp.signature),
clientDataJSON: Utils.fromBufferToUrlB64((resp as any).clientDataJson),
userHandle: Utils.fromBufferToUrlB64(resp.userHandle),
};
}

View File

@@ -20,10 +20,10 @@ export class WebAuthnLoginService implements WebAuthnLoginServiceAbstraction {
constructor(
private webAuthnLoginApiService: WebAuthnLoginApiServiceAbstraction,
private loginStrategyService: LoginStrategyServiceAbstraction,
private webAuthnLoginPrfKeyService: WebAuthnLoginPrfKeyServiceAbstraction,
protected loginStrategyService: LoginStrategyServiceAbstraction,
protected webAuthnLoginPrfKeyService: WebAuthnLoginPrfKeyServiceAbstraction,
private window: Window,
private logService?: LogService,
protected logService?: LogService,
) {
this.navigatorCredentials = this.window.navigator.credentials;
}