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