feat: add resources and view components
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
<section>
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-body">
|
||||
<header>
|
||||
<h4 class="fw-bold mb-3">
|
||||
<i class="bi bi-fingerprint me-2"></i> {{ __('Passkeys (WebAuthn)') }}
|
||||
</h6>
|
||||
<p class="text-secondary small mb-4">
|
||||
{{ __('Register your biometrics (Fingerprint, FaceID) or security keys to login securely without a password.') }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div id="passkey-list" class="mb-4">
|
||||
@if(Auth::user()->webAuthnCredentials->count() > 0)
|
||||
<div class="list-group list-group-flush border-top border-bottom mb-3">
|
||||
@foreach(Auth::user()->webAuthnCredentials as $credential)
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center py-2 px-0">
|
||||
<div>
|
||||
<span class="fw-semibold small d-block">{{ $credential->alias ?: __('Unnamed Device') }}</span>
|
||||
<small class="text-muted">{{ __('Registered on') }}: {{ $credential->created_at->format('d M Y') }}</small>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('webauthn.destroy', $credential->id) }}" onsubmit="return confirm('{{ __('Are you sure you want to remove this passkey?') }}')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger border-0">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-light border small text-center py-3">
|
||||
{{ __('No passkeys registered yet.') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<button type="button" id="register-passkey" class="btn btn-primary theme-black px-4">
|
||||
{{ __('Register New Passkey') }}
|
||||
</button>
|
||||
<div id="passkey-status" class="small text-info d-none">
|
||||
<span class="spinner-border spinner-border-sm me-1"></span> {{ __('Processing...') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(get_setting('webauthn_enabled', false))
|
||||
<script src="{{ asset('vendor/webauthn/webauthn.js') }}"></script>
|
||||
<script>
|
||||
document.getElementById('register-passkey').addEventListener('click', async () => {
|
||||
const btn = document.getElementById('register-passkey');
|
||||
const status = document.getElementById('passkey-status');
|
||||
|
||||
btn.disabled = true;
|
||||
status.classList.remove('d-none');
|
||||
|
||||
if (typeof WebAuthn === 'undefined') {
|
||||
console.error('WebAuthn library not loaded.');
|
||||
return;
|
||||
}
|
||||
|
||||
const webauthn = new WebAuthn();
|
||||
|
||||
if (WebAuthn.supportsWebAuthn()) {
|
||||
try {
|
||||
// Ask for a nickname for the key
|
||||
const alias = prompt("{{ __('Enter a name for this device/key (e.g. My Laptop, iPhone):') }}", "My Device");
|
||||
|
||||
if (alias === null) {
|
||||
btn.disabled = false;
|
||||
status.classList.add('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
// Laragear WebAuthn v5 registration
|
||||
const response = await webauthn.register({ alias: alias });
|
||||
|
||||
if (response) {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WebAuthn Error:', error);
|
||||
|
||||
let errorMsg = 'Failed to register Passkey. Please use a secure connection.';
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
errorMsg = 'Passkeys require a secure context (HTTPS). \n\n' +
|
||||
'Developer Tip: If you are on a local domain (like .test), you can bypass this in Chrome/Edge by going to: \n' +
|
||||
'chrome://flags/#unsafely-treat-insecure-origin-as-secure \n' +
|
||||
'and adding your domain to the list.';
|
||||
} else if (error.name === 'NotAllowedError') {
|
||||
errorMsg = 'Registration was cancelled or timed out.';
|
||||
} else {
|
||||
errorMsg = 'Failed to register Passkey. Ensure your device supports biometrics and is configured correctly.';
|
||||
}
|
||||
|
||||
StandardSwal.fire({
|
||||
icon: 'error',
|
||||
title: 'Registration Failed!',
|
||||
text: errorMsg || 'The system was unable to register your Passkey. Please ensure your device is compatible.',
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
StandardSwal.fire({
|
||||
icon: 'info',
|
||||
title: 'Passkey Not Supported',
|
||||
text: 'Your current browser or device does not support secure Passkey authentication.',
|
||||
confirmButtonText: 'Understood'
|
||||
});
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
status.classList.add('d-none');
|
||||
});
|
||||
</script>
|
||||
@endif
|
||||
</section>
|
||||
Reference in New Issue
Block a user