WebAuthn初步接好

This commit is contained in:
Jerry Yan 2022-08-08 02:07:27 +08:00
parent 50934228ef
commit 30b38e3f4b
16 changed files with 1329 additions and 5 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@ npm-debug.log
yarn-error.log yarn-error.log
/.idea /.idea
/.vscode /.vscode
/public/mix-manifest.json

View File

@ -0,0 +1,168 @@
<?php
namespace App\Http\Controllers;
use App\WebAuthn\Repository\PublicKeyCredentialSourceRepositoryImpl;
use App\WebAuthn\WebAuthnService;
use Cose\Algorithm\Manager;
use Cose\Algorithm\Signature\ECDSA\ES256;
use Cose\Algorithm\Signature\RSA\RS256;
use Cose\Algorithms;
use GuzzleHttp\Psr7\ServerRequest;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Webauthn\AttestationStatement\AttestationObjectLoader;
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
use Webauthn\AuthenticatorAssertionResponse;
use Webauthn\AuthenticatorAssertionResponseValidator;
use Webauthn\AuthenticatorAttestationResponse;
use Webauthn\AuthenticatorAttestationResponseValidator;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialLoader;
use Webauthn\PublicKeyCredentialParameters;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialSourceRepository;
use Webauthn\PublicKeyCredentialUserEntity;
use Webauthn\TokenBinding\IgnoreTokenBindingHandler;
class UserWebAuthnController extends BaseController
{
private $TIMEOUT = 45000;
private $attestationStatementSupportManager = null;
public function register_options(Request $request): PublicKeyCredentialCreationOptions
{
$userEntity = new PublicKeyCredentialUserEntity(
$request->user("web")->name,
$request->user("web")->id,
$request->user("web")->name,
);
$challenge = random_bytes(16);
$request->session()->put("webauthn_register_challenge", $challenge);
return WebAuthnService::createRequestOptions($userEntity, $challenge);
}
public function login_options(Request $request): PublicKeyCredentialRequestOptions
{
$challenge = random_bytes(32);
$request->session()->put("webauthn_login_challenge", $challenge);
$publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
$challenge
);
$publicKeyCredentialRequestOptions->setUserVerification(
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
);
$publicKeyCredentialRequestOptions->allowCredentials([]);
$publicKeyCredentialRequestOptions->setTimeout($this->TIMEOUT);
return $publicKeyCredentialRequestOptions;
}
public function register_validate(Request $request)
{
if (!$request->session()->has("webauthn_register_challenge")) {
///
}
$publicKeyCredential = $this->get_public_key_credential_loader()->loadArray($request->json()->all());
$authenticatorAttestationResponse = $publicKeyCredential->getResponse();
if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) {
//e.g. process here with a redirection to the public key creation page.
}
$userEntity = new PublicKeyCredentialUserEntity(
$request->user("web")->name,
$request->user("web")->id,
$request->user("web")->name,
);
$challenge = $request->session()->remove("webauthn_register_challenge");
$publicKeyCredentialCreationOptions = WebAuthnService::createRequestOptions($userEntity, $challenge);
$publicKeyCredentialSource = $this->get_authn_attestation_response_validator()->check(
$authenticatorAttestationResponse,
$publicKeyCredentialCreationOptions,
ServerRequest::fromGlobals(),
["localhost"]
);
$this->get_pub_key_cred_source_repository()->saveCredentialSource($publicKeyCredentialSource);
return $publicKeyCredentialSource;
}
public function login_validate(Request $request)
{
if (!$request->session()->has("webauthn_login_challenge")) {
///
}
$publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
$request->session()->remove("webauthn_login_challenge")
);
$publicKeyCredential = $this->get_public_key_credential_loader()->loadArray($request->json()->all());
$authenticatorAssertionResponse = $publicKeyCredential->getResponse();
if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) {
//e.g. process here with a redirection to the public key login/MFA page.
}
try {
$publicKeyCredentialSource = $this->get_authn_assertion_response_validator()->check(
$publicKeyCredential->getRawId(),
$authenticatorAssertionResponse,
$publicKeyCredentialRequestOptions,
ServerRequest::fromGlobals(),
$authenticatorAssertionResponse->getUserHandle(),
["localhost"]
);
} catch (\Throwable $e) {
return redirect(route("login"));
}
Auth::loginUsingId($publicKeyCredentialSource->getUserHandle());
return redirect()->intended();
}
private function get_authn_attestation_response_validator(): AuthenticatorAttestationResponseValidator
{
$authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator(
$this->get_attestation_stmt_sup_mgr(),
WebAuthnService::getPublicKeyCredentialSourceRepository(),
new IgnoreTokenBindingHandler(),
new ExtensionOutputCheckerHandler()
);
return $authenticatorAttestationResponseValidator;
}
private function get_authn_assertion_response_validator(): AuthenticatorAssertionResponseValidator
{
$algorithmManager = new Manager();
$algorithmManager->add(new ES256());
$algorithmManager->add(new RS256());
$authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
WebAuthnService::getPublicKeyCredentialSourceRepository(),
new IgnoreTokenBindingHandler(),
new ExtensionOutputCheckerHandler(),
$algorithmManager
);
return $authenticatorAssertionResponseValidator;
}
private function get_public_key_credential_loader(): PublicKeyCredentialLoader
{
$publicKeyCredentialLoader = new PublicKeyCredentialLoader(
$this->get_attestation_loader()
);
return $publicKeyCredentialLoader;
}
private function get_attestation_loader(): AttestationObjectLoader
{
$attestationObjectLoader = new AttestationObjectLoader($this->get_attestation_stmt_sup_mgr());
return $attestationObjectLoader;
}
private function get_attestation_stmt_sup_mgr()
{
if ($this->attestationStatementSupportManager === null) {
$this->attestationStatementSupportManager = new AttestationStatementSupportManager();
$this->attestationStatementSupportManager->add(new NoneAttestationStatementSupport());
}
return $this->attestationStatementSupportManager;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Models\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Webauthn\TrustPath\TrustPathLoader;
class TrustPath implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes): ?\Webauthn\TrustPath\TrustPath
{
return $value !== null
? TrustPathLoader::loadTrustPath(json_decode($value, true))
: null;
}
public function set($model, string $key, $value, array $attributes)
{
return json_encode($value);
}
}

24
app/Models/Casts/Uuid.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Models\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Ramsey\Uuid\Uuid as UuidConvert;
use Ramsey\Uuid\UuidInterface;
class Uuid implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes): ?UuidInterface
{
if ($value !== null && UuidConvert::isValid($value)) {
return UuidConvert::fromString($value);
}
return null;
}
public function set($model, string $key, $value, array $attributes): ?string
{
return (string) $value;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Models\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use function Safe\base64_decode;
class WebAuthnBase64 implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes): ?string
{
return $value !== null ? base64_decode($value) : null;
}
public function set($model, string $key, $value, array $attributes)
{
return $value !== null ? base64_encode($value) : null;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Models;
use App\Models\Casts\TrustPath;
use App\Models\Casts\Uuid;
use App\Models\Casts\WebAuthnBase64;
use Illuminate\Database\Eloquent\Model;
use Webauthn\PublicKeyCredentialSource;
class WebauthnCredential extends Model
{
protected $guarded = ['id'];
protected $visible = [
'id',
'name',
'type',
'attachment_type',
'transports',
'type_free',
'last_used_at',
'created_at',
'updated_at',
];
protected $casts = [
'aaguid' => Uuid::class,
'counter' => 'integer',
'credential_id' => WebAuthnBase64::class,
'credential_public_key' => WebAuthnBase64::class,
'transports' => 'array',
'trust_path' => TrustPath::class,
'last_used_at' => 'immutable_datetime',
];
public function getPublicKeyCredentialSourceAttribute(): PublicKeyCredentialSource
{
return new PublicKeyCredentialSource(
$this->credential_id,
$this->type,
$this->transports,
$this->attestation_type,
$this->trust_path,
$this->aaguid ?? \Ramsey\Uuid\Uuid::uuid4(),
$this->credential_public_key,
(string) $this->user_id,
$this->counter,
);
}
public function setPublicKeyCredentialSourceAttribute(PublicKeyCredentialSource $source): void
{
$this->credential_id = $source->getPublicKeyCredentialId();
$this->type = $source->getType();
$this->transports = $source->getTransports();
$this->attestation_type = $source->getAttestationType();
$this->trust_path = $source->getTrustPath();
$this->aaguid = $source->getAaguid();
$this->credential_public_key = $source->getCredentialPublicKey();
$this->counter = $source->getCounter();
$this->user_id = $source->getUserHandle();
}
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class, "user_id", "id");
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\WebAuthn\Repository;
use App\Models\WebauthnCredential;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialSourceRepository;
use Webauthn\PublicKeyCredentialUserEntity;
class PublicKeyCredentialSourceRepositoryImpl implements PublicKeyCredentialSourceRepository
{
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
{
$model = $this->findOneModelByCredentialId($publicKeyCredentialId);
if ($model) {
return $model->getPublicKeyCredentialSourceAttribute();
} else {
return null;
}
}
/**
* @inheritDoc
*/
public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
{
if ($publicKeyCredentialUserEntity->getId() == 0) {
$modelList = $this->findAllModelByTypeFree();
} else {
$modelList = $this->findAllModelByUserId($publicKeyCredentialUserEntity->getId());
}
return array_map(function (WebauthnCredential $cred) {
return $cred->getPublicKeyCredentialSourceAttribute();
}, $modelList);
}
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
{
$model = $this->findOneModelByCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId());
if ($model === null) {
$model = new WebauthnCredential();
$model->setPublicKeyCredentialSourceAttribute($publicKeyCredentialSource);
}
$model->counter += 1;
$model->last_used_at = now();
$model->save();
}
private function findOneModelByCredentialId(string $publicKeyCredentialId): ?WebauthnCredential
{
/**
* @var WebauthnCredential
*/
return WebauthnCredential::query()->where(function ($query) use ($publicKeyCredentialId) {
$query->where("credential_id", "=", base64_encode($publicKeyCredentialId));
})->first();
}
private function findAllModelByTypeFree(): array
{
return WebauthnCredential::query()->where("type_free", "=", "1")->get()->toArray();
}
private function findAllModelByUserId(string $userId): array
{
return WebauthnCredential::query()->where("user_id", "=", $userId)->get()->toArray();
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\WebAuthn;
use App\WebAuthn\Repository\PublicKeyCredentialSourceRepositoryImpl;
use Cose\Algorithms;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialParameters;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialSourceRepository;
use Webauthn\PublicKeyCredentialUserEntity;
class WebAuthnService
{
private static $rpName = "开心鄢的录播查询小站";
private static $rpId = "localhost";
private static $timeout = 45000;
private static $publicKeyCredentialSourceRepositoryInstance = null;
public static function createRequestOptions(PublicKeyCredentialUserEntity $userEntity, string $challenge): PublicKeyCredentialCreationOptions
{
$publicKeyCredentialParametersList = [
new PublicKeyCredentialParameters("public-key", Algorithms::COSE_ALGORITHM_ES256),
new PublicKeyCredentialParameters("public-key", Algorithms::COSE_ALGORITHM_RS256),
];
return new PublicKeyCredentialCreationOptions(
static::getRpEntity(),
$userEntity,
$challenge,
$publicKeyCredentialParametersList,
static::$timeout
);
}
public static function getPublicKeyCredentialSourceRepository(): PublicKeyCredentialSourceRepository
{
if (static::$publicKeyCredentialSourceRepositoryInstance === null) {
static::$publicKeyCredentialSourceRepositoryInstance = new PublicKeyCredentialSourceRepositoryImpl();
}
return static::$publicKeyCredentialSourceRepositoryInstance;
}
private static function getRpEntity(): PublicKeyCredentialRpEntity
{
return new PublicKeyCredentialRpEntity(
static::$rpName,
static::$rpId
);
}
}

View File

@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
composer install --optimize-autoloader --no-dev
yarn yarn
yarn production yarn production
php artisan config:cache php artisan config:cache

View File

@ -11,7 +11,9 @@
"laravel/framework": "^8.75", "laravel/framework": "^8.75",
"laravel/sanctum": "^2.11", "laravel/sanctum": "^2.11",
"laravel/tinker": "^2.5", "laravel/tinker": "^2.5",
"league/flysystem-aws-s3-v3": "~1.0" "league/flysystem-aws-s3-v3": "~1.0",
"web-auth/webauthn-lib": "^3.3",
"ext-json": "*"
}, },
"require-dev": { "require-dev": {
"facade/ignition": "^2.5", "facade/ignition": "^2.5",

872
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5fa51137fd7e6b6a8193c33cfad61118", "content-hash": "8591e794c9f0a98f4ee8696e4cb97b2d",
"packages": [ "packages": [
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
@ -222,6 +222,79 @@
}, },
"time": "2022-07-07T18:16:39+00:00" "time": "2022-07-07T18:16:39+00:00"
}, },
{
"name": "beberlei/assert",
"version": "v3.3.2",
"source": {
"type": "git",
"url": "https://github.com/beberlei/assert.git",
"reference": "cb70015c04be1baee6f5f5c953703347c0ac1655"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655",
"reference": "cb70015c04be1baee6f5f5c953703347c0ac1655",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"php": "^7.0 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "*",
"phpstan/phpstan": "*",
"phpunit/phpunit": ">=6.0.0",
"yoast/phpunit-polyfills": "^0.1.0"
},
"suggest": {
"ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles"
},
"type": "library",
"autoload": {
"files": [
"lib/Assert/functions.php"
],
"psr-4": {
"Assert\\": "lib/Assert"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de",
"role": "Lead Developer"
},
{
"name": "Richard Quadling",
"email": "rquadling@gmail.com",
"role": "Collaborator"
}
],
"description": "Thin assertion library for input validation in business models.",
"keywords": [
"assert",
"assertion",
"validation"
],
"support": {
"issues": "https://github.com/beberlei/assert/issues",
"source": "https://github.com/beberlei/assert/tree/v3.3.2"
},
"time": "2021-12-16T21:41:27+00:00"
},
{ {
"name": "brick/math", "name": "brick/math",
"version": "0.9.3", "version": "0.9.3",
@ -689,6 +762,87 @@
], ],
"time": "2020-12-29T14:50:06+00:00" "time": "2020-12-29T14:50:06+00:00"
}, },
{
"name": "fgrosse/phpasn1",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/fgrosse/PHPASN1.git",
"reference": "eef488991d53e58e60c9554b09b1201ca5ba9296"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/eef488991d53e58e60c9554b09b1201ca5ba9296",
"reference": "eef488991d53e58e60c9554b09b1201ca5ba9296",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "~2.0",
"phpunit/phpunit": "^6.3 || ^7.0 || ^8.0"
},
"suggest": {
"ext-bcmath": "BCmath is the fallback extension for big integer calculations",
"ext-curl": "For loading OID information from the web if they have not bee defined statically",
"ext-gmp": "GMP is the preferred extension for big integer calculations",
"phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"FG\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Friedrich Große",
"email": "friedrich.grosse@gmail.com",
"homepage": "https://github.com/FGrosse",
"role": "Author"
},
{
"name": "All contributors",
"homepage": "https://github.com/FGrosse/PHPASN1/contributors"
}
],
"description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.",
"homepage": "https://github.com/FGrosse/PHPASN1",
"keywords": [
"DER",
"asn.1",
"asn1",
"ber",
"binary",
"decoding",
"encoding",
"x.509",
"x.690",
"x509",
"x690"
],
"support": {
"issues": "https://github.com/fgrosse/PHPASN1/issues",
"source": "https://github.com/fgrosse/PHPASN1/tree/v2.4.0"
},
"time": "2021-12-11T12:41:06+00:00"
},
{ {
"name": "fruitcake/laravel-cors", "name": "fruitcake/laravel-cors",
"version": "v2.2.0", "version": "v2.2.0",
@ -2005,6 +2159,188 @@
], ],
"time": "2022-04-17T13:12:02+00:00" "time": "2022-04-17T13:12:02+00:00"
}, },
{
"name": "league/uri",
"version": "6.7.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri.git",
"reference": "2d7c87a0860f3126a39f44a8a9bf2fed402dcfea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri/zipball/2d7c87a0860f3126a39f44a8a9bf2fed402dcfea",
"reference": "2d7c87a0860f3126a39f44a8a9bf2fed402dcfea",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-json": "*",
"league/uri-interfaces": "^2.3",
"php": "^7.4 || ^8.0",
"psr/http-message": "^1.0"
},
"conflict": {
"league/uri-schemes": "^1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^v3.3.2",
"nyholm/psr7": "^1.5",
"php-http/psr7-integration-tests": "^1.1",
"phpstan/phpstan": "^1.2.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.1.0",
"phpunit/phpunit": "^9.5.10",
"psr/http-factory": "^1.0"
},
"suggest": {
"ext-fileinfo": "Needed to create Data URI from a filepath",
"ext-intl": "Needed to improve host validation",
"league/uri-components": "Needed to easily manipulate URI objects",
"psr/http-factory": "Needed to use the URI factory"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Uri\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://nyamsprod.com"
}
],
"description": "URI manipulation library",
"homepage": "https://uri.thephpleague.com",
"keywords": [
"data-uri",
"file-uri",
"ftp",
"hostname",
"http",
"https",
"middleware",
"parse_str",
"parse_url",
"psr-7",
"query-string",
"querystring",
"rfc3986",
"rfc3987",
"rfc6570",
"uri",
"uri-template",
"url",
"ws"
],
"support": {
"docs": "https://uri.thephpleague.com",
"forum": "https://thephpleague.slack.com",
"issues": "https://github.com/thephpleague/uri/issues",
"source": "https://github.com/thephpleague/uri/tree/6.7.1"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2022-06-29T09:48:18+00:00"
},
{
"name": "league/uri-interfaces",
"version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri-interfaces.git",
"reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
"reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-json": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.19",
"phpstan/phpstan": "^0.12.90",
"phpstan/phpstan-phpunit": "^0.12.19",
"phpstan/phpstan-strict-rules": "^0.12.9",
"phpunit/phpunit": "^8.5.15 || ^9.5"
},
"suggest": {
"ext-intl": "to use the IDNA feature",
"symfony/intl": "to use the IDNA feature via Symfony Polyfill"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Uri\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://nyamsprod.com"
}
],
"description": "Common interface for URI representation",
"homepage": "http://github.com/thephpleague/uri-interfaces",
"keywords": [
"rfc3986",
"rfc3987",
"uri",
"url"
],
"support": {
"issues": "https://github.com/thephpleague/uri-interfaces/issues",
"source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2021-06-28T04:27:21+00:00"
},
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "2.7.0", "version": "2.7.0",
@ -3381,6 +3717,163 @@
], ],
"time": "2021-09-25T23:10:38+00:00" "time": "2021-09-25T23:10:38+00:00"
}, },
{
"name": "spomky-labs/base64url",
"version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/base64url.git",
"reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d",
"reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.11|^0.12",
"phpstan/phpstan-beberlei-assert": "^0.11|^0.12",
"phpstan/phpstan-deprecation-rules": "^0.11|^0.12",
"phpstan/phpstan-phpunit": "^0.11|^0.12",
"phpstan/phpstan-strict-rules": "^0.11|^0.12"
},
"type": "library",
"autoload": {
"psr-4": {
"Base64Url\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky-Labs/base64url/contributors"
}
],
"description": "Base 64 URL Safe Encoding/Decoding PHP Library",
"homepage": "https://github.com/Spomky-Labs/base64url",
"keywords": [
"base64",
"rfc4648",
"safe",
"url"
],
"support": {
"issues": "https://github.com/Spomky-Labs/base64url/issues",
"source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-11-03T09:10:25+00:00"
},
{
"name": "spomky-labs/cbor-php",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/cbor-php.git",
"reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/28e2712cfc0b48fae661a48ffc6896d7abe83684",
"reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"brick/math": "^0.8.15|^0.9.0",
"ext-mbstring": "*",
"php": ">=7.3"
},
"require-dev": {
"ekino/phpstan-banned-code": "^1.0",
"ext-json": "*",
"infection/infection": "^0.18|^0.25",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-beberlei-assert": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5",
"rector/rector": "^0.12",
"roave/security-advisories": "dev-latest",
"symplify/easy-coding-standard": "^10.0"
},
"suggest": {
"ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags",
"ext-gmp": "GMP or BCMath extensions will drastically improve the library performance"
},
"type": "library",
"autoload": {
"psr-4": {
"CBOR\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/Spomky-Labs/cbor-php/contributors"
}
],
"description": "CBOR Encoder/Decoder for PHP",
"keywords": [
"Concise Binary Object Representation",
"RFC7049",
"cbor"
],
"support": {
"issues": "https://github.com/Spomky-Labs/cbor-php/issues",
"source": "https://github.com/Spomky-Labs/cbor-php/tree/v2.1.0"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2021-12-13T12:46:26+00:00"
},
{ {
"name": "swiftmailer/swiftmailer", "name": "swiftmailer/swiftmailer",
"version": "v6.3.0", "version": "v6.3.0",
@ -5825,6 +6318,151 @@
], ],
"time": "2022-05-21T10:24:18+00:00" "time": "2022-05-21T10:24:18+00:00"
}, },
{
"name": "thecodingmachine/safe",
"version": "v1.3.3",
"source": {
"type": "git",
"url": "https://github.com/thecodingmachine/safe.git",
"reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
"reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=7.2"
},
"require-dev": {
"phpstan/phpstan": "^0.12",
"squizlabs/php_codesniffer": "^3.2",
"thecodingmachine/phpstan-strict-rules": "^0.12"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.1-dev"
}
},
"autoload": {
"files": [
"deprecated/apc.php",
"deprecated/libevent.php",
"deprecated/mssql.php",
"deprecated/stats.php",
"lib/special_cases.php",
"generated/apache.php",
"generated/apcu.php",
"generated/array.php",
"generated/bzip2.php",
"generated/calendar.php",
"generated/classobj.php",
"generated/com.php",
"generated/cubrid.php",
"generated/curl.php",
"generated/datetime.php",
"generated/dir.php",
"generated/eio.php",
"generated/errorfunc.php",
"generated/exec.php",
"generated/fileinfo.php",
"generated/filesystem.php",
"generated/filter.php",
"generated/fpm.php",
"generated/ftp.php",
"generated/funchand.php",
"generated/gmp.php",
"generated/gnupg.php",
"generated/hash.php",
"generated/ibase.php",
"generated/ibmDb2.php",
"generated/iconv.php",
"generated/image.php",
"generated/imap.php",
"generated/info.php",
"generated/ingres-ii.php",
"generated/inotify.php",
"generated/json.php",
"generated/ldap.php",
"generated/libxml.php",
"generated/lzf.php",
"generated/mailparse.php",
"generated/mbstring.php",
"generated/misc.php",
"generated/msql.php",
"generated/mysql.php",
"generated/mysqli.php",
"generated/mysqlndMs.php",
"generated/mysqlndQc.php",
"generated/network.php",
"generated/oci8.php",
"generated/opcache.php",
"generated/openssl.php",
"generated/outcontrol.php",
"generated/password.php",
"generated/pcntl.php",
"generated/pcre.php",
"generated/pdf.php",
"generated/pgsql.php",
"generated/posix.php",
"generated/ps.php",
"generated/pspell.php",
"generated/readline.php",
"generated/rpminfo.php",
"generated/rrd.php",
"generated/sem.php",
"generated/session.php",
"generated/shmop.php",
"generated/simplexml.php",
"generated/sockets.php",
"generated/sodium.php",
"generated/solr.php",
"generated/spl.php",
"generated/sqlsrv.php",
"generated/ssdeep.php",
"generated/ssh2.php",
"generated/stream.php",
"generated/strings.php",
"generated/swoole.php",
"generated/uodbc.php",
"generated/uopz.php",
"generated/url.php",
"generated/var.php",
"generated/xdiff.php",
"generated/xml.php",
"generated/xmlrpc.php",
"generated/yaml.php",
"generated/yaz.php",
"generated/zip.php",
"generated/zlib.php"
],
"psr-4": {
"Safe\\": [
"lib/",
"deprecated/",
"generated/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
"support": {
"issues": "https://github.com/thecodingmachine/safe/issues",
"source": "https://github.com/thecodingmachine/safe/tree/v1.3.3"
},
"time": "2020-10-28T17:51:34+00:00"
},
{ {
"name": "tijsverkoyen/css-to-inline-styles", "name": "tijsverkoyen/css-to-inline-styles",
"version": "2.2.4", "version": "2.2.4",
@ -6050,6 +6688,238 @@
], ],
"time": "2022-01-24T18:55:24+00:00" "time": "2022-01-24T18:55:24+00:00"
}, },
{
"name": "web-auth/cose-lib",
"version": "v3.3.12",
"source": {
"type": "git",
"url": "https://github.com/web-auth/cose-lib.git",
"reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-auth/cose-lib/zipball/efa6ec2ba4e840bc1316a493973c9916028afeeb",
"reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"beberlei/assert": "^3.2",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"fgrosse/phpasn1": "^2.1",
"php": ">=7.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Cose\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-auth/cose/contributors"
}
],
"description": "CBOR Object Signing and Encryption (COSE) For PHP",
"homepage": "https://github.com/web-auth",
"keywords": [
"COSE",
"RFC8152"
],
"support": {
"source": "https://github.com/web-auth/cose-lib/tree/v3.3.12"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2021-12-04T12:13:35+00:00"
},
{
"name": "web-auth/metadata-service",
"version": "v3.3.12",
"source": {
"type": "git",
"url": "https://github.com/web-auth/webauthn-metadata-service.git",
"reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/ef40d2b7b68c4964247d13fab52e2fa8dbd65246",
"reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"beberlei/assert": "^3.2",
"ext-json": "*",
"league/uri": "^6.0",
"php": ">=7.2",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/log": "^1.1"
},
"suggest": {
"web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources",
"web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources"
},
"type": "library",
"autoload": {
"psr-4": {
"Webauthn\\MetadataService\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-auth/metadata-service/contributors"
}
],
"description": "Metadata Service for FIDO2/Webauthn",
"homepage": "https://github.com/web-auth",
"keywords": [
"FIDO2",
"fido",
"webauthn"
],
"support": {
"source": "https://github.com/web-auth/webauthn-metadata-service/tree/v3.3.12"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2021-11-21T11:14:31+00:00"
},
{
"name": "web-auth/webauthn-lib",
"version": "v3.3.12",
"source": {
"type": "git",
"url": "https://github.com/web-auth/webauthn-lib.git",
"reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/5ef9b21c8e9f8a817e524ac93290d08a9f065b33",
"reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"beberlei/assert": "^3.2",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"fgrosse/phpasn1": "^2.1",
"php": ">=7.2",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0",
"psr/log": "^1.1",
"ramsey/uuid": "^3.8|^4.0",
"spomky-labs/base64url": "^2.0",
"spomky-labs/cbor-php": "^1.0|^2.0",
"symfony/process": "^3.0|^4.0|^5.0",
"thecodingmachine/safe": "^1.1",
"web-auth/cose-lib": "self.version",
"web-auth/metadata-service": "self.version"
},
"suggest": {
"psr/log-implementation": "Recommended to receive logs from the library",
"web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support",
"web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support",
"web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support",
"web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support"
},
"type": "library",
"autoload": {
"psr-4": {
"Webauthn\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-auth/webauthn-library/contributors"
}
],
"description": "FIDO2/Webauthn Support For PHP",
"homepage": "https://github.com/web-auth",
"keywords": [
"FIDO2",
"fido",
"webauthn"
],
"support": {
"source": "https://github.com/web-auth/webauthn-lib/tree/v3.3.12"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2022-02-18T07:13:44+00:00"
},
{ {
"name": "webmozart/assert", "name": "webmozart/assert",
"version": "1.11.0", "version": "1.11.0",

View File

@ -17,6 +17,7 @@
"tailwindcss": "^3.1.6" "tailwindcss": "^3.1.6"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/forms": "^0.5.2" "@tailwindcss/forms": "^0.5.2",
"@web-auth/webauthn-helper": "^0.0.13"
} }
} }

14
resources/js/webauthn.js Normal file
View File

@ -0,0 +1,14 @@
import {useRegistration, useLogin} from '@web-auth/webauthn-helper'
const webauthn_register = useRegistration({
actionUrl: "/user/webauthn/",
optionsUrl: "/user/webauthn/options",
})
const webauthn_login = useLogin({
actionUrl: "/login/webauthn/",
optionsUrl: "/login/webauthn/options",
})
window.webauthn_register = webauthn_register
window.webauthn_login = webauthn_login

View File

@ -26,6 +26,8 @@ Route::post('/upload', ["\\App\\Http\\Controllers\\FileController","upload"]);
// 用户部分 // 用户部分
Route::get('/login', ["\\App\\Http\\Controllers\\UserController", "login_page"])->name("login"); Route::get('/login', ["\\App\\Http\\Controllers\\UserController", "login_page"])->name("login");
Route::post('/login', ["\\App\\Http\\Controllers\\UserController", "authenticate"])->name("login.submit"); Route::post('/login', ["\\App\\Http\\Controllers\\UserController", "authenticate"])->name("login.submit");
Route::post("/login/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "login_options"])->name("login.webauthn.options");
Route::post("/login/webauthn/", ["\\App\\Http\\Controllers\\UserWebAuthnController", "login_validate"])->name("login.webauthn.submit");
Route::get('/register', ["\\App\\Http\\Controllers\\UserController", "register_page"])->name("register"); Route::get('/register', ["\\App\\Http\\Controllers\\UserController", "register_page"])->name("register");
Route::post('/register', ["\\App\\Http\\Controllers\\UserController", "register"])->name("register.submit"); Route::post('/register', ["\\App\\Http\\Controllers\\UserController", "register"])->name("register.submit");
Route::get('/logout', ["\\App\\Http\\Controllers\\UserController", "logout"])->name("logout"); Route::get('/logout', ["\\App\\Http\\Controllers\\UserController", "logout"])->name("logout");
@ -52,3 +54,7 @@ Route::prefix("/programs/construct")->middleware("auth:web")->group(function (Ro
$router->get('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","edit"])->name("program.construct.append.edit"); $router->get('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","edit"])->name("program.construct.append.edit");
$router->post('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","submit"])->name("program.construct.append.submit"); $router->post('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","submit"])->name("program.construct.append.submit");
}); });
Route::prefix("/user")->middleware("auth:web")->group(function (Router $router) {
$router->post("/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "register_options"])->name("user.webauthn.bind.options");
$router->post("/webauthn/", ["\\App\\Http\\Controllers\\UserWebAuthnController", "register_validate"])->name("user.webauthn.bind.submit");
});

View File

@ -12,9 +12,13 @@ const mix = require('laravel-mix');
*/ */
mix.js('resources/js/app.js', 'public/js') mix.js('resources/js/app.js', 'public/js')
.extract(['axios', 'lodash'])
.js('resources/js/webauthn.js', 'public/js')
.postCss('resources/css/app.css', 'public/css', [ .postCss('resources/css/app.css', 'public/css', [
require('tailwindcss') require('tailwindcss')
]) ])
.version(); .disableNotifications();
if (mix.inProduction()) {
mix.version();
}
mix.disableNotifications();

View File

@ -1247,6 +1247,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@web-auth/webauthn-helper@^0.0.13":
version "0.0.13"
resolved "https://registry.npmmirror.com/@web-auth/webauthn-helper/-/webauthn-helper-0.0.13.tgz#2e9e6f421a3238bee233fe97c6801a6c6354cf7b"
integrity sha512-yEXWTiZSGFYnCVEwOqWgZFrQ4MiW9WGAiAvdRD6NQRlp7sr39THZiGIix/x3y3zmnsUsZZ0iXQA8zG/gPESwzQ==
"@webassemblyjs/ast@1.11.1": "@webassemblyjs/ast@1.11.1":
version "1.11.1" version "1.11.1"
resolved "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" resolved "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"