diff --git a/app/Auth/CredentialSourceRepository.php b/app/Auth/CredentialSourceRepository.php
index 14c3482..6bf0aad 100644
--- a/app/Auth/CredentialSourceRepository.php
+++ b/app/Auth/CredentialSourceRepository.php
@@ -12,7 +12,10 @@ class CredentialSourceRepository implements PublicKeyCredentialSourceRepository
{
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
{
- $authenticator = Authenticator::where('credential_id', base64_encode($publicKeyCredentialId))->first();
+ $authenticator = Authenticator::where(
+ 'credential_id',
+ base64_encode($publicKeyCredentialId)
+ )->first();
if (!$authenticator) {
return null;
@@ -23,16 +26,23 @@ public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKey
public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
{
- return User::with('authenticators')->where('id', $publicKeyCredentialUserEntity->getId())->first()->authenticators->toArray();
+ return User::with('authenticators')
+ ->where('id', $publicKeyCredentialUserEntity->getId())
+ ->first()
+ ->authenticators
+ ->toArray();
}
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
{
- $user = User::where('id', $publicKeyCredentialSource->getUserHandle())->firstOrFail();
+ $user = User::where(
+ 'id',
+ $publicKeyCredentialSource->getUserHandle()
+ )->firstOrFail();
$user->authenticators()->save(new Authenticator([
'credential_id' => $publicKeyCredentialSource->getPublicKeyCredentialId(),
- 'public_key' => $publicKeyCredentialSource->jsonSerialize(),
+ 'public_key' => $publicKeyCredentialSource->jsonSerialize(),
]));
}
}
diff --git a/app/Http/Controllers/RegistrationController.php b/app/Http/Controllers/RegistrationController.php
index 686e92d..da98d7e 100644
--- a/app/Http/Controllers/RegistrationController.php
+++ b/app/Http/Controllers/RegistrationController.php
@@ -8,7 +8,6 @@
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Psr\Http\Message\ServerRequestInterface;
use Webauthn\AttestationStatement\AttestationObjectLoader;
@@ -41,10 +40,15 @@ public function generateOptions(Request $request)
'username' => $request->input('username'),
]);
- if (!$user->exists) {
- $user->save();
+ if ($user->exists) {
+ // We're in registration mode, they shouldn't be able to register a new device to an existing user
+ throw ValidationException::withMessages([
+ 'username' => 'Username already exists',
+ ]);
}
+ $user->save();
+
$userEntity = PublicKeyCredentialUserEntity::create(
$user->username,
$user->id,
diff --git a/config/services.php b/config/services.php
index 0ace530..5829c54 100644
--- a/config/services.php
+++ b/config/services.php
@@ -31,4 +31,8 @@
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
+ 'fathom_analytics' => [
+ 'id' => env('FATHOM_SITE_ID'),
+ ],
+
];
diff --git a/database/migrations/2014_10_12_100000_create_password_resets_table.php b/database/migrations/2014_10_12_100000_create_password_resets_table.php
deleted file mode 100644
index fcacb80..0000000
--- a/database/migrations/2014_10_12_100000_create_password_resets_table.php
+++ /dev/null
@@ -1,32 +0,0 @@
-string('email')->index();
- $table->string('token');
- $table->timestamp('created_at')->nullable();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('password_resets');
- }
-};
diff --git a/resources/js/app.js b/resources/js/app.js
index a02973a..808530e 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -9,7 +9,6 @@ document.addEventListener('alpine:init', () => {
Alpine.data('authForm', () => ({
mode: 'login',
username: '',
- name: '',
browserSupported: browserSupportsWebAuthn(),
error: null,
submit() {
@@ -22,14 +21,22 @@ document.addEventListener('alpine:init', () => {
return this.submitRegister();
},
submitRegister() {
+ this.trackEvent('register-start');
+
window.axios
+ // Ask for the registration options
.post('/registration/options', {
username: this.username,
})
+ // Prompt the user to create a passkey
.then((response) => startRegistration(response.data))
+ // Verify the data with the server
.then((attResp) => axios.post('/registration/verify', attResp))
.then((verificationResponse) => {
if (verificationResponse.data?.verified) {
+ // If we're good, reload the page and
+ // the server will redirect us to the dashboard
+ this.trackEvent('register-complete');
return window.location.reload();
}
@@ -41,16 +48,24 @@ document.addEventListener('alpine:init', () => {
});
},
submitLogin() {
+ this.trackEvent('login-start');
+
window.axios
+ // Ask for the authentication options
.post('/authentication/options', {
username: this.username,
})
+ // Prompt the user to authenticate with their passkey
.then((response) => startAuthentication(response.data))
+ // Verify the data with the server
.then((attResp) =>
axios.post('/authentication/verify', attResp),
)
.then((verificationResponse) => {
+ // If we're good, reload the page and
+ // the server will redirect us to the dashboard
if (verificationResponse.data?.verified) {
+ this.trackEvent('login-complete');
return window.location.reload();
}
@@ -69,5 +84,12 @@ document.addEventListener('alpine:init', () => {
this.error = error?.response?.data?.message || error;
});
},
+ trackEvent(eventId) {
+ if (typeof fathom === 'undefined') {
+ return;
+ }
+
+ fathom.trackGoal(eventId, 0);
+ },
}));
});
diff --git a/resources/views/layout.blade.php b/resources/views/layout.blade.php
index bd6869a..eb619d1 100644
--- a/resources/views/layout.blade.php
+++ b/resources/views/layout.blade.php
@@ -11,6 +11,11 @@
@vite(['resources/css/app.css', 'resources/js/app.js'])
+
+ @if (config('services.fathom_analytics.id'))
+
+ @endif