From 2fb8339d91631b7779349a735e8238ada01e1697 Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Mon, 25 Sep 2023 18:31:38 +0100 Subject: [PATCH] Admin can now hopefully add a passkey to their account --- .eslintrc.yml | 13 ++- .../Controllers/Admin/PasskeysController.php | 93 ++++++++++++++++++ app/Models/Passkey.php | 42 ++++++++ app/Models/User.php | 6 ++ composer.json | 1 + database/factories/PasskeyFactory.php | 34 +++++++ .../2023_08_27_113904_create_passkeys.php | 33 +++++++ postcss.config.js | 28 +++--- public/assets/app.js | 12 ++- public/assets/app.js.br | Bin 16895 -> 20470 bytes resources/js/app.js | 15 +-- resources/js/auth.js | 85 +++++++++++++--- .../views/admin/passkeys/index.blade.php | 18 ++++ resources/views/admin/welcome.blade.php | 2 +- resources/views/master.blade.php | 1 + routes/web.php | 8 ++ 16 files changed, 351 insertions(+), 40 deletions(-) create mode 100644 app/Http/Controllers/Admin/PasskeysController.php create mode 100644 app/Models/Passkey.php create mode 100644 database/factories/PasskeyFactory.php create mode 100644 database/migrations/2023_08_27_113904_create_passkeys.php create mode 100644 resources/views/admin/passkeys/index.blade.php diff --git a/.eslintrc.yml b/.eslintrc.yml index d3156688..8e72ef3e 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,6 +1,6 @@ parserOptions: sourceType: 'module' - ecmaVersion: 8 + ecmaVersion: 'latest' extends: 'eslint:recommended' env: browser: true @@ -25,3 +25,14 @@ rules: - allow: - warn - error + no-await-in-loop: + - error + no-promise-executor-return: + - error + require-atomic-updates: + - error + max-nested-callbacks: + - error + - 3 + prefer-promise-reject-errors: + - error diff --git a/app/Http/Controllers/Admin/PasskeysController.php b/app/Http/Controllers/Admin/PasskeysController.php new file mode 100644 index 00000000..61388175 --- /dev/null +++ b/app/Http/Controllers/Admin/PasskeysController.php @@ -0,0 +1,93 @@ +user(); + $passkeys = $user->passkey; + + return view('admin.passkeys.index', compact('passkeys')); + } + + public function save(Request $request): JsonResponse + { + $validator = Validator::make($request->all(), [ + 'id' => 'required|string|unique:App\Models\Passkey,passkey_id', + 'public_key' => 'required|file', + 'transports' => 'required|json', + 'challenge' => 'required|string', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Passkey could not be saved (validation failed)', + ]); + } + + $validated = $validator->validated(); + + if ( + !session()->has('challenge') || + $validated['challenge'] !== session('challenge') + ) { + return response()->json([ + 'success' => false, + 'message' => 'Passkey could not be saved (challenge failed)', + ]); + } + + $passkey = new Passkey(); + $passkey->passkey_id = $validated['id']; + $passkey->passkey = $validated['public_key']->get(); + $passkey->transports = json_decode($validated['transports'], true, 512, JSON_THROW_ON_ERROR); + $passkey->user_id = auth()->user()->id; + $passkey->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Passkey saved successfully', + ]); + } + + public function init(): JsonResponse + { + /** @var User $user */ + $user = auth()->user(); + $passkeys = $user->passkey()->get(); + + $existing = $passkeys->map(function (Passkey $passkey) { + return [ + 'id' => $passkey->passkey_id, + 'transports' => $passkey->transports, + 'type' => 'public-key', + ]; + })->all(); + + $challenge = Hash::make(random_bytes(32)); + session(['challenge' => $challenge]); + + return response()->json([ + 'challenge' => $challenge, + 'userId' => $user->name, + 'existing' => $existing, + ]); + } +} diff --git a/app/Models/Passkey.php b/app/Models/Passkey.php new file mode 100644 index 00000000..64461eca --- /dev/null +++ b/app/Models/Passkey.php @@ -0,0 +1,42 @@ + stream_get_contents($value), + set: static fn ($value) => pg_escape_bytea($value), + ); + } + + /** + * Save and access the transports appropriately. + */ + protected function transports(): Attribute + { + return Attribute::make( + get: static fn ($value) => json_decode($value, true, 512, JSON_THROW_ON_ERROR), + set: static fn ($value) => json_encode($value, JSON_THROW_ON_ERROR), + ); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index bc57ee54..7bd0472f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -24,4 +25,9 @@ class User extends Authenticatable 'password', 'remember_token', ]; + + public function passkey(): HasMany + { + return $this->hasMany(Passkey::class); + } } diff --git a/composer.json b/composer.json index f644792f..c41e6eff 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "ext-dom": "*", "ext-intl": "*", "ext-json": "*", + "ext-pgsql": "*", "cviebrock/eloquent-sluggable": "^10.0", "guzzlehttp/guzzle": "^7.2", "indieauth/client": "^1.1", diff --git a/database/factories/PasskeyFactory.php b/database/factories/PasskeyFactory.php new file mode 100644 index 00000000..8e70e21d --- /dev/null +++ b/database/factories/PasskeyFactory.php @@ -0,0 +1,34 @@ + + */ +class PasskeyFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = Passkey::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'user_id' => $this->faker->numberBetween(1, 1000), + 'passkey_id' => $this->faker->uuid, + 'passkey' => $this->faker->sha256, + 'transports' => ['internal'], + ]; + } +} diff --git a/database/migrations/2023_08_27_113904_create_passkeys.php b/database/migrations/2023_08_27_113904_create_passkeys.php new file mode 100644 index 00000000..a2450d65 --- /dev/null +++ b/database/migrations/2023_08_27_113904_create_passkeys.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->string('passkey_id')->unique(); + $table->binary('passkey'); + $table->json('transports'); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('passkeys'); + } +}; diff --git a/postcss.config.js b/postcss.config.js index b3f36747..f0ba5a6f 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,16 +1,16 @@ module.exports = { - plugins: { - 'postcss-import': {}, - 'autoprefixer': {}, - '@csstools/postcss-oklab-function': { - preserve: true - }, - 'postcss-nesting': {}, - 'postcss-combine-media-query': {}, - 'postcss-combine-duplicated-selectors': { - removeDuplicatedProperties: true, - removeDuplicatedValues: true - }, - 'cssnano': { preset: 'default' }, - } + plugins: { + 'postcss-import': {}, + 'autoprefixer': {}, + '@csstools/postcss-oklab-function': { + preserve: true + }, + 'postcss-nesting': {}, + 'postcss-combine-media-query': {}, + 'postcss-combine-duplicated-selectors': { + removeDuplicatedProperties: true, + removeDuplicatedValues: true + }, + 'cssnano': { preset: 'default' }, + } }; diff --git a/public/assets/app.js b/public/assets/app.js index e8471e65..f30dcde8 100644 --- a/public/assets/app.js +++ b/public/assets/app.js @@ -16,7 +16,17 @@ \*****************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _css_app_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../css/app.css */ \"./resources/css/app.css\");\n\n\n// import { Auth } from './auth.js';\n//\n// let auth = new Auth();\n\n// auth.createCredentials().then((credentials) => {\n// // eslint-disable-next-line no-console\n// console.log(credentials);\n// });//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9yZXNvdXJjZXMvanMvYXBwLmpzIiwibWFwcGluZ3MiOiI7O0FBQXdCOztBQUV4QjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9qYnVrLWZyb250ZW5kLy4vcmVzb3VyY2VzL2pzL2FwcC5qcz82ZDQwIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAnLi4vY3NzL2FwcC5jc3MnO1xuXG4vLyBpbXBvcnQgeyBBdXRoIH0gZnJvbSAnLi9hdXRoLmpzJztcbi8vXG4vLyBsZXQgYXV0aCA9IG5ldyBBdXRoKCk7XG5cbi8vIGF1dGguY3JlYXRlQ3JlZGVudGlhbHMoKS50aGVuKChjcmVkZW50aWFscykgPT4ge1xuLy8gICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuLy8gICBjb25zb2xlLmxvZyhjcmVkZW50aWFscyk7XG4vLyB9KTtcbiJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///./resources/js/app.js\n"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _css_app_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../css/app.css */ \"./resources/css/app.css\");\n/* harmony import */ var _auth_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./auth.js */ \"./resources/js/auth.js\");\n\n\nlet auth = new _auth_js__WEBPACK_IMPORTED_MODULE_1__.Auth();\ndocument.querySelectorAll('.add-passkey').forEach(el => {\n el.addEventListener('click', () => {\n auth.register();\n });\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9yZXNvdXJjZXMvanMvYXBwLmpzIiwibWFwcGluZ3MiOiI7OztBQUF3QjtBQUVTO0FBRWpDLElBQUlDLElBQUksR0FBRyxJQUFJRCwwQ0FBSSxDQUFDLENBQUM7QUFFckJFLFFBQVEsQ0FBQ0MsZ0JBQWdCLENBQUMsY0FBYyxDQUFDLENBQUNDLE9BQU8sQ0FBRUMsRUFBRSxJQUFLO0VBQ3hEQSxFQUFFLENBQUNDLGdCQUFnQixDQUFDLE9BQU8sRUFBRSxNQUFNO0lBQ2pDTCxJQUFJLENBQUNNLFFBQVEsQ0FBQyxDQUFDO0VBQ2pCLENBQUMsQ0FBQztBQUNKLENBQUMsQ0FBQyIsInNvdXJjZXMiOlsid2VicGFjazovL2pidWstZnJvbnRlbmQvLi9yZXNvdXJjZXMvanMvYXBwLmpzPzZkNDAiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICcuLi9jc3MvYXBwLmNzcyc7XG5cbmltcG9ydCB7IEF1dGggfSBmcm9tICcuL2F1dGguanMnO1xuXG5sZXQgYXV0aCA9IG5ldyBBdXRoKCk7XG5cbmRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJy5hZGQtcGFzc2tleScpLmZvckVhY2goKGVsKSA9PiB7XG4gIGVsLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKCkgPT4ge1xuICAgIGF1dGgucmVnaXN0ZXIoKTtcbiAgfSk7XG59KTtcbiJdLCJuYW1lcyI6WyJBdXRoIiwiYXV0aCIsImRvY3VtZW50IiwicXVlcnlTZWxlY3RvckFsbCIsImZvckVhY2giLCJlbCIsImFkZEV2ZW50TGlzdGVuZXIiLCJyZWdpc3RlciJdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./resources/js/app.js\n"); + +/***/ }), + +/***/ "./resources/js/auth.js": +/*!******************************!*\ + !*** ./resources/js/auth.js ***! + \******************************/ +/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ Auth: function() { return /* binding */ Auth; }\n/* harmony export */ });\nclass Auth {\n constructor() {}\n async register() {\n const {\n challenge,\n userId,\n existing\n } = await this.getRegisterData();\n const publicKeyCredentialCreationOptions = {\n challenge: new TextEncoder().encode(challenge),\n rp: {\n name: 'JB'\n },\n user: {\n id: new TextEncoder().encode(userId),\n name: 'jonny@jonnybarnes.uk',\n displayName: 'Jonny'\n },\n pubKeyCredParams: [{\n alg: -8,\n type: 'public-key'\n },\n // Ed25519\n {\n alg: -7,\n type: 'public-key'\n },\n // ES256\n {\n alg: -257,\n type: 'public-key'\n } // RS256\n ],\n\n excludeCredentials: existing,\n authenticatorSelection: {\n userVerification: 'preferred',\n residentKey: 'required'\n },\n timeout: 60000\n };\n const publicKeyCredential = await navigator.credentials.create({\n publicKey: publicKeyCredentialCreationOptions\n });\n if (!publicKeyCredential) {\n throw new Error('Error generating a passkey');\n }\n const {\n id // the key id a.k.a. kid\n } = publicKeyCredential;\n const publicKey = publicKeyCredential.response.getPublicKey();\n const transports = publicKeyCredential.response.getTransports();\n const response = publicKeyCredential.response;\n const clientJSONArrayBuffer = response.clientDataJSON;\n const clientJSON = JSON.parse(new TextDecoder().decode(clientJSONArrayBuffer));\n const clientChallenge = clientJSON.challenge;\n // base64 decode the challenge\n const clientChallengeDecoded = atob(clientChallenge);\n const saved = await this.savePasskey(id, publicKey, transports, clientChallengeDecoded);\n if (saved) {\n window.location.reload();\n } else {\n alert('There was an error saving the passkey');\n }\n }\n async getRegisterData() {\n const response = await fetch('/admin/passkeys/init');\n return await response.json();\n }\n async savePasskey(id, publicKey, transports, challenge) {\n const formData = new FormData();\n formData.append('id', id);\n formData.append('transports', JSON.stringify(transports));\n formData.append('challenge', challenge);\n\n // Convert the ArrayBuffer to a Uint8Array\n const publicKeyArray = new Uint8Array(publicKey);\n\n // Create a Blob from the Uint8Array\n const publicKeyBlob = new Blob([publicKeyArray], {\n type: 'application/octet-stream'\n });\n formData.append('public_key', publicKeyBlob);\n const response = await fetch('/admin/passkeys/save', {\n method: 'POST',\n body: formData,\n headers: {\n 'X-CSRF-TOKEN': document.querySelector('meta[name=\"csrf-token\"]').getAttribute('content')\n }\n });\n return response.ok;\n }\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,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Q0FBQy9CLEVBQUUsRUFBRWMsU0FBUyxFQUFFSSxVQUFVLEVBQUU1QixTQUFTLEVBQUU7SUFDdEQsTUFBTWdELFFBQVEsR0FBRyxJQUFJQyxRQUFRLENBQUMsQ0FBQztJQUMvQkQsUUFBUSxDQUFDRSxNQUFNLENBQUMsSUFBSSxFQUFFeEMsRUFBRSxDQUFDO0lBQ3pCc0MsUUFBUSxDQUFDRSxNQUFNLENBQUMsWUFBWSxFQUFFakIsSUFBSSxDQUFDa0IsU0FBUyxDQUFDdkIsVUFBVSxDQUFDLENBQUM7SUFDekRvQixRQUFRLENBQUNFLE1BQU0sQ0FBQyxXQUFXLEVBQUVsRCxTQUFTLENBQUM7O0lBRXZDO0lBQ0EsTUFBTW9ELGNBQWMsR0FBRyxJQUFJQyxVQUFVLENBQUM3QixTQUFTLENBQUM7O0lBRWhEO0lBQ0EsTUFBTThCLGFBQWEsR0FBRyxJQUFJQyxJQUFJLENBQUMsQ0FBQ0gsY0FBYyxDQUFDLEVBQUU7TUFBRXRDLElBQUksRUFBRTtJQUEyQixDQUFDLENBQUM7SUFFdEZrQyxRQUFRLENBQUNFLE1BQU0sQ0FBQyxZQUFZLEVBQUVJLGFBQWEsQ0FBQztJQUU1QyxNQUFNNUIsUUFBUSxHQUFHLE1BQU1vQixLQUFLLENBQUMsc0JBQXNCLEVBQUU7TUFDbkRVLE1BQU0sRUFBRSxNQUFNO01BQ2RDLElBQUksRUFBRVQsUUFBUTtNQUNkVSxPQUFPLEVBQUU7UUFDUCxjQUFjLEVBQUVDLFFBQVEsQ0FBQ0MsYUFBYSxDQUFDLHlCQUF5QixDQUFDLENBQUNDLFlBQVksQ0FBQyxTQUFTO01BQzFGO0lBQ0YsQ0FBQyxDQUFDO0lBRUYsT0FBT25DLFFBQVEsQ0FBQ29DLEVBQUU7RUFDcEI7QUFDRiIsInNvdXJjZXMiOlsid2VicGFjazovL2pidWstZnJvbnRlbmQvLi9yZXNvdXJjZXMvanMvYXV0aC5qcz84ZjRmIl0sInNvdXJjZXNDb250ZW50IjpbImNsYXNzIEF1dGgge1xuICBjb25zdHJ1Y3RvcigpIHt9XG5cbiAgYXN5bmMgcmVnaXN0ZXIoKSB7XG4gICAgY29uc3QgeyBjaGFsbGVuZ2UsIHVzZXJJZCwgZXhpc3RpbmcgfSA9IGF3YWl0IHRoaXMuZ2V0UmVnaXN0ZXJEYXRhKCk7XG5cbiAgICBjb25zdCBwdWJsaWNLZXlDcmVkZW50aWFsQ3JlYXRpb25PcHRpb25zID0ge1xuICAgICAgY2hhbGxlbmdlOiBuZXcgVGV4dEVuY29kZXIoKS5lbmNvZGUoY2hhbGxlbmdlKSxcbiAgICAgIHJwOiB7XG4gICAgICAgIG5hbWU6ICdKQicsXG4gICAgICB9LFxuICAgICAgdXNlcjoge1xuICAgICAgICBpZDogbmV3IFRleHRFbmNvZGVyKCkuZW5jb2RlKHVzZXJJZCksXG4gICAgICAgIG5hbWU6ICdqb25ueUBqb25ueWJhcm5lcy51aycsXG4gICAgICAgIGRpc3BsYXlOYW1lOiAnSm9ubnknLFxuICAgICAgfSxcbiAgICAgIHB1YktleUNyZWRQYXJhbXM6IFtcbiAgICAgICAge2FsZzogLTgsIHR5cGU6ICdwdWJsaWMta2V5J30sIC8vIEVkMjU1MTlcbiAgICAgICAge2FsZzogLTcsIHR5cGU6ICdwdWJsaWMta2V5J30sIC8vIEVTMjU2XG4gICAgICAgIHthbGc6IC0yNTcsIHR5cGU6ICdwdWJsaWMta2V5J30sIC8vIFJTMjU2XG4gICAgICBdLFxuICAgICAgZXhjbHVkZUNyZWRlbnRpYWxzOiBleGlzdGluZyxcbiAgICAgIGF1dGhlbnRpY2F0b3JTZWxlY3Rpb246IHtcbiAgICAgICAgdXNlclZlcmlmaWNhdGlvbjogJ3ByZWZlcnJlZCcsXG4gICAgICAgIHJlc2lkZW50S2V5OiAncmVxdWlyZWQnLFxuICAgICAgfSxcbiAgICAgIHRpbWVvdXQ6IDYwMDAwLFxuICAgIH07XG5cbiAgICBjb25zdCBwdWJsaWNLZXlDcmVkZW50aWFsID0gYXdhaXQgbmF2aWdhdG9yLmNyZWRlbnRpYWxzLmNyZWF0ZSh7XG4gICAgICBwdWJsaWNLZXk6IHB1YmxpY0tleUNyZWRlbnRpYWxDcmVhdGlvbk9wdGlvbnNcbiAgICB9KTtcbiAgICBpZiAoIXB1YmxpY0tleUNyZWRlbnRpYWwpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignRXJyb3IgZ2VuZXJhdGluZyBhIHBhc3NrZXknKTtcbiAgICB9XG4gICAgY29uc3Qge1xuICAgICAgaWQgLy8gdGhlIGtleSBpZCBhLmsuYS4ga2lkXG4gICAgfSA9IHB1YmxpY0tleUNyZWRlbnRpYWw7XG4gICAgY29uc3QgcHVibGljS2V5ID0gcHVibGljS2V5Q3JlZGVudGlhbC5yZXNwb25zZS5nZXRQdWJsaWNLZXkoKTtcbiAgICBjb25zdCB0cmFuc3BvcnRzID0gcHVibGljS2V5Q3JlZGVudGlhbC5yZXNwb25zZS5nZXRUcmFuc3BvcnRzKCk7XG4gICAgY29uc3QgcmVzcG9uc2UgPSBwdWJsaWNLZXlDcmVkZW50aWFsLnJlc3BvbnNlO1xuICAgIGNvbnN0IGNsaWVudEpTT05BcnJheUJ1ZmZlciA9IHJlc3BvbnNlLmNsaWVudERhdGFKU09OO1xuICAgIGNvbnN0IGNsaWVudEpTT04gPSBKU09OLnBhcnNlKG5ldyBUZXh0RGVjb2RlcigpLmRlY29kZShjbGllbnRKU09OQXJyYXlCdWZmZXIpKTtcbiAgICBjb25zdCBjbGllbnRDaGFsbGVuZ2UgPSBjbGllbnRKU09OLmNoYWxsZW5nZTtcbiAgICAvLyBiYXNlNjQgZGVjb2RlIHRoZSBjaGFsbGVuZ2VcbiAgICBjb25zdCBjbGllbnRDaGFsbGVuZ2VEZWNvZGVkID0gYXRvYihjbGllbnRDaGFsbGVuZ2UpO1xuXG4gICAgY29uc3Qgc2F2ZWQgPSBhd2FpdCB0aGlzLnNhdmVQYXNza2V5KGlkLCBwdWJsaWNLZXksIHRyYW5zcG9ydHMsIGNsaWVudENoYWxsZW5nZURlY29kZWQpO1xuXG4gICAgaWYgKHNhdmVkKSB7XG4gICAgICB3aW5kb3cubG9jYXRpb24ucmVsb2FkKCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGFsZXJ0KCdUaGVyZSB3YXMgYW4gZXJyb3Igc2F2aW5nIHRoZSBwYXNza2V5Jyk7XG4gICAgfVxuICB9XG5cbiAgYXN5bmMgZ2V0UmVnaXN0ZXJEYXRhKCkge1xuICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2goJy9hZG1pbi9wYXNza2V5cy9pbml0Jyk7XG5cbiAgICByZXR1cm4gYXdhaXQgcmVzcG9uc2UuanNvbigpO1xuICB9XG5cbiAgYXN5bmMgc2F2ZVBhc3NrZXkoaWQsIHB1YmxpY0tleSwgdHJhbnNwb3J0cywgY2hhbGxlbmdlKSB7XG4gICAgY29uc3QgZm9ybURhdGEgPSBuZXcgRm9ybURhdGEoKTtcbiAgICBmb3JtRGF0YS5hcHBlbmQoJ2lkJywgaWQpO1xuICAgIGZvcm1EYXRhLmFwcGVuZCgndHJhbnNwb3J0cycsIEpTT04uc3RyaW5naWZ5KHRyYW5zcG9ydHMpKTtcbiAgICBmb3JtRGF0YS5hcHBlbmQoJ2NoYWxsZW5nZScsIGNoYWxsZW5nZSk7XG5cbiAgICAvLyBDb252ZXJ0IHRoZSBBcnJheUJ1ZmZlciB0byBhIFVpbnQ4QXJyYXlcbiAgICBjb25zdCBwdWJsaWNLZXlBcnJheSA9IG5ldyBVaW50OEFycmF5KHB1YmxpY0tleSk7XG5cbiAgICAvLyBDcmVhdGUgYSBCbG9iIGZyb20gdGhlIFVpbnQ4QXJyYXlcbiAgICBjb25zdCBwdWJsaWNLZXlCbG9iID0gbmV3IEJsb2IoW3B1YmxpY0tleUFycmF5XSwgeyB0eXBlOiAnYXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtJyB9KTtcblxuICAgIGZvcm1EYXRhLmFwcGVuZCgncHVibGljX2tleScsIHB1YmxpY0tleUJsb2IpO1xuXG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCgnL2FkbWluL3Bhc3NrZXlzL3NhdmUnLCB7XG4gICAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICAgIGJvZHk6IGZvcm1EYXRhLFxuICAgICAgaGVhZGVyczoge1xuICAgICAgICAnWC1DU1JGLVRPS0VOJzogZG9jdW1lbnQucXVlcnlTZWxlY3RvcignbWV0YVtuYW1lPVwiY3NyZi10b2tlblwiXScpLmdldEF0dHJpYnV0ZSgnY29udGVudCcpLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIHJldHVybiByZXNwb25zZS5vaztcbiAgfVxufVxuXG5leHBvcnQgeyBBdXRoIH07XG4iXSwibmFtZXMiOlsiQXV0aCIsImNvbnN0cnVjdG9yIiwicmVnaXN0ZXIiLCJjaGFsbGVuZ2UiLCJ1c2VySWQiLCJleGlzdGluZyIsImdldFJlZ2lzdGVyRGF0YSIsInB1YmxpY0tleUNyZWRlbnRpYWxDcmVhdGlvbk9wdGlvbnMiLCJUZXh0RW5jb2RlciIsImVuY29kZSIsInJwIiwibmFtZSIsInVzZXIiLCJpZCIsImRpc3BsYXlOYW1lIiwicHViS2V5Q3JlZFBhcmFtcyIsImFsZyIsInR5cGUiLCJleGNsdWRlQ3JlZGVudGlhbHMiLCJhdXRoZW50aWNhdG9yU2VsZWN0aW9uIiwidXNlclZlcmlmaWNhdGlvbiIsInJlc2lkZW50S2V5IiwidGltZW91dCIsInB1YmxpY0tleUNyZWRlbnRpYWwiLCJuYXZpZ2F0b3IiLCJjcmVkZW50aWFscyIsImNyZWF0ZSIsInB1YmxpY0tleSIsIkVycm9yIiwicmVzcG9uc2UiLCJnZXRQdWJsaWNLZXkiLCJ0cmFuc3BvcnRzIiwiZ2V0VHJhbnNwb3J0cyIsImNsaWVudEpTT05BcnJheUJ1ZmZlciIsImNsaWVudERhdGFKU09OIiwiY2xpZW50SlNPTiIsIkpTT04iLCJwYXJzZSIsIlRleHREZWNvZGVyIiwiZGVjb2RlIiwiY2xpZW50Q2hhbGxlbmdlIiwiY2xpZW50Q2hhbGxlbmdlRGVjb2RlZCIsImF0b2IiLCJzYXZlZCIsInNhdmVQYXNza2V5Iiwid2luZG93IiwibG9jYXRpb24iLCJyZWxvYWQiLCJhbGVydCIsImZldGNoIiwianNvbiIsImZvcm1EYXRhIiwiRm9ybURhdGEiLCJhcHBlbmQiLCJzdHJpbmdpZnkiLCJwdWJsaWNLZXlBcnJheSIsIlVpbnQ4QXJyYXkiLCJwdWJsaWNLZXlCbG9iIiwiQmxvYiIsIm1ldGhvZCIsImJvZHkiLCJoZWFkZXJzIiwiZG9jdW1lbnQiLCJxdWVyeVNlbGVjdG9yIiwiZ2V0QXR0cmlidXRlIiwib2siXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./resources/js/auth.js\n"); /***/ }), diff --git a/public/assets/app.js.br b/public/assets/app.js.br index dc6eb881df1f9dd789d3dfb90c0478bb9aabfa94..ed35c5c40262388d1ecc3d17265dd7ef77644d23 100644 GIT binary patch literal 20470 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 G%mx5QE~(Q1 literal 16895 zcma!E%^P?iFO~T&{zn70+CAVqEN-(fDXX_F za9s-eEL7lONHgZ!6kp^ ztap=hU%d4?^T!)ULYMY-h=$)jEPbENu=3^S?bG6)&p-XLb>7Scf}Uw*5>+?NHfk)I zF@aTQ{!j1A=iYx`{r}h8{ki9>-iNMGdq2Oa$u`cf;a<8^@{$DWDBZkUB~ep1R9Ei5 zQC4}AnITy4VD8FyHh*1B#T}10e_y@+R``?+^<)M8-DBm;ZZo$i9*BflJj~BC?{$C$&dS7*_S}5PjlIMlb7y0bW zyt_;FobC6w534I0o;S{5=(r+v;nF5c$vGaLo8MV}x4YBs?!mTj!-c!EkKYadGjH@x z#Ingd#6-lfoaba`b;=aZWH_?9Ai!&8iAN~k)NFCzGjo@$+qtMjLrQ#U`$p~yk3@ET zwLiF~;)l|EanUIOlk_yFsIYi2PE6SCC)7AuZ|lFXm{MO>^L-mO2(Ucl-j}KU|FRFm zoyY%oA7p5bJ9z*C793>Qf46Ijhp=Y#-;*W#k9Exc{K!17M7_1_ve+LXuN(U7_U_Uu z|IYXQ=Oudu1%*{SN>>#U4m513Z)j{R-B{vhnpc0tPTM#*^V!k=dww#>^KrEva9kk3 zA;Q(@q#JN|_Dv^wz4?y&>Q^{3y<=6lbtUD-?VBgRu|$<;`lcFtDMwT5p%{KqzpYMhLYN8&? zthl&);z^|lZQE;$^-f62uBlm;@1|rQnE6`Dc}Zr^*Af#pkqah=D`zlk?%XzswPx1a z-$nf{Z{ptA{NKBft%^;cGJU}`rYi};*-dk5D$QohD&E1r#U!LV5unFM0UDb=j}8Z*?VC+@8R(B%z_d zyy8n@3afT1|KTaMFC)1p+E(U-NPoHb)G)78I`KYp@b+Ur*M551=>KNvDl>n_6`m}= z4ehO#=ZbW0ni_ZM>nHID=gT)X)-RpRGsF|Y1=J)=iTF5{#8c5uPy#P zd%^YI?TePp7WM45y4!O%C#rh)i+01yo7Y*~n>l0Uo)c;pKg?^maJuB@-`6saeNJTG zs=KuC-dg6Sht)G4g!{7cZL0Y){The8C8t=};zupF15&STJNn?dl&xLM*MDCEYPdrq znBMbgEf@Xv_GFvveeDYd`&SpFKl$@5tu&pn$V2*ySb0N)XB_#Q$Am{u4m zE>k=q`_1+6%n0>Qr)LSzyJ`B>ZvVB=4L;ewLz#D-|L-czb~@nZ;ie-wtzKzQ3Y=oC zon)O;{Y+;bT`|$z@PnL&=JxMVOz)*@-`?F7KX2*Wv-h7?<;^+$Z}s%0A0~%t-`IHi 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 Kwf_r0qXGcgI%CoR diff --git a/resources/js/app.js b/resources/js/app.js index 69946e98..06c635a9 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,10 +1,11 @@ import '../css/app.css'; -// import { Auth } from './auth.js'; -// -// let auth = new Auth(); +import { Auth } from './auth.js'; -// auth.createCredentials().then((credentials) => { -// // eslint-disable-next-line no-console -// console.log(credentials); -// }); +let auth = new Auth(); + +document.querySelectorAll('.add-passkey').forEach((el) => { + el.addEventListener('click', () => { + auth.register(); + }); +}); diff --git a/resources/js/auth.js b/resources/js/auth.js index d181f62d..905db945 100644 --- a/resources/js/auth.js +++ b/resources/js/auth.js @@ -1,35 +1,88 @@ class Auth { constructor() {} - async createCredentials() { + async register() { + const { challenge, userId, existing } = await this.getRegisterData(); + const publicKeyCredentialCreationOptions = { - challenge: Uint8Array.from( - 'randomStringFromServer', - c => c.charCodeAt(0) - ), + challenge: new TextEncoder().encode(challenge), rp: { - id: 'jonnybarnes.localhost', name: 'JB', }, user: { - id: Uint8Array.from( - 'UZSL85T9AFC', - c => c.charCodeAt(0) - ), + id: new TextEncoder().encode(userId), name: 'jonny@jonnybarnes.uk', displayName: 'Jonny', }, - pubKeyCredParams: [{alg: -7, type: 'public-key'}], - // authenticatorSelection: { - // authenticatorAttachment: 'cross-platform', - // }, + pubKeyCredParams: [ + {alg: -8, type: 'public-key'}, // Ed25519 + {alg: -7, type: 'public-key'}, // ES256 + {alg: -257, type: 'public-key'}, // RS256 + ], + excludeCredentials: existing, + authenticatorSelection: { + userVerification: 'preferred', + residentKey: 'required', + }, timeout: 60000, - attestation: 'direct' }; - return await navigator.credentials.create({ + const publicKeyCredential = await navigator.credentials.create({ publicKey: publicKeyCredentialCreationOptions }); + if (!publicKeyCredential) { + throw new Error('Error generating a passkey'); + } + const { + id // the key id a.k.a. kid + } = publicKeyCredential; + const publicKey = publicKeyCredential.response.getPublicKey(); + const transports = publicKeyCredential.response.getTransports(); + const response = publicKeyCredential.response; + const clientJSONArrayBuffer = response.clientDataJSON; + const clientJSON = JSON.parse(new TextDecoder().decode(clientJSONArrayBuffer)); + const clientChallenge = clientJSON.challenge; + // base64 decode the challenge + const clientChallengeDecoded = atob(clientChallenge); + + const saved = await this.savePasskey(id, publicKey, transports, clientChallengeDecoded); + + if (saved) { + window.location.reload(); + } else { + alert('There was an error saving the passkey'); + } + } + + async getRegisterData() { + const response = await fetch('/admin/passkeys/init'); + + return await response.json(); + } + + async savePasskey(id, publicKey, transports, challenge) { + const formData = new FormData(); + formData.append('id', id); + formData.append('transports', JSON.stringify(transports)); + formData.append('challenge', challenge); + + // Convert the ArrayBuffer to a Uint8Array + const publicKeyArray = new Uint8Array(publicKey); + + // Create a Blob from the Uint8Array + const publicKeyBlob = new Blob([publicKeyArray], { type: 'application/octet-stream' }); + + formData.append('public_key', publicKeyBlob); + + const response = await fetch('/admin/passkeys/save', { + method: 'POST', + body: formData, + headers: { + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), + }, + }); + + return response.ok; } } diff --git a/resources/views/admin/passkeys/index.blade.php b/resources/views/admin/passkeys/index.blade.php new file mode 100644 index 00000000..09602162 --- /dev/null +++ b/resources/views/admin/passkeys/index.blade.php @@ -0,0 +1,18 @@ +@extends('master') + +@section('title')Passkeys « Admin CP « @stop + +@section('content') +

Passkeys

+ @if(count($passkeys) > 0) +

You have the following passkeys saved:

+
    + @foreach($passkeys as $passkey) +
  • {{ $passkey->passkey_id }}
  • + @endforeach +
+ @else +

You have no passkey saved.

+ @endif + +@stop diff --git a/resources/views/admin/welcome.blade.php b/resources/views/admin/welcome.blade.php index 3cce67d7..269ccdc5 100644 --- a/resources/views/admin/welcome.blade.php +++ b/resources/views/admin/welcome.blade.php @@ -54,6 +54,6 @@

Passkeys

- List passkeys here? + Manager your passkeys.

@stop diff --git a/resources/views/master.blade.php b/resources/views/master.blade.php index 944627c9..24eb5665 100644 --- a/resources/views/master.blade.php +++ b/resources/views/master.blade.php @@ -2,6 +2,7 @@ + @yield('title'){{ config('app.name') }} @if (!empty(config('app.font_link'))) diff --git a/routes/web.php b/routes/web.php index e5ace4e1..18a17619 100644 --- a/routes/web.php +++ b/routes/web.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Admin\ContactsController as AdminContactsController; use App\Http\Controllers\Admin\HomeController; use App\Http\Controllers\Admin\LikesController as AdminLikesController; use App\Http\Controllers\Admin\NotesController as AdminNotesController; +use App\Http\Controllers\Admin\PasskeysController; use App\Http\Controllers\Admin\PlacesController as AdminPlacesController; use App\Http\Controllers\Admin\SyndicationTargetsController; use App\Http\Controllers\ArticlesController; @@ -141,6 +142,13 @@ Route::group(['domain' => config('url.longurl')], function () { Route::get('/', [BioController::class, 'show'])->name('admin.bio.show'); Route::put('/', [BioController::class, 'update']); }); + + // Passkeys + Route::group(['prefix' => 'passkeys'], static function () { + Route::get('/', [PasskeysController::class, 'index']); + Route::post('save', [PasskeysController::class, 'save']); + Route::get('/init', [PasskeysController::class, 'init']); + }); }); // Blog pages using ArticlesController