Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,24 @@ parameters:
count: 1
path: src/webauthn/src/AuthenticationExtensions/AuthenticationExtensions.php

-
message: '#^Cannot access offset string on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/webauthn/src/AuthenticationExtensions/PseudoRandomFunctionInputExtensionBuilder.php

-
message: '#^PHPDoc tag @var has invalid value \(array\{eval\?\: array\{first\: string, second\?\: string\}, evalByCredential\?\: array\<string, array\{first\: string, second\?\: string\}\>\)\: Unexpected token "\*/", expected ''\}'' at offset 145 on line 3$#'
identifier: phpDoc.parseError
count: 1
path: src/webauthn/src/AuthenticationExtensions/PseudoRandomFunctionInputExtensionBuilder.php

-
message: '#^Property Webauthn\\AuthenticationExtensions\\PseudoRandomFunctionInputExtensionBuilder\:\:\$values type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/webauthn/src/AuthenticationExtensions/PseudoRandomFunctionInputExtensionBuilder.php

-
message: '#^Cannot access offset 1 on array\|false\.$#'
identifier: offsetAccess.nonOffsetAccessible
Expand Down
6 changes: 6 additions & 0 deletions src/stimulus/assets/dist/controller.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,10 @@ export default class extends Controller {
private _getAttestationResponse;
private _getAssertionResponse;
private _getResult;
private _processExtensionsInput;
private _processPrfInput;
private _importPrfValues;
private _processExtensionsOutput;
private _processPrfOutput;
private _exportPrfValues;
}
62 changes: 58 additions & 4 deletions src/stimulus/assets/dist/controller.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller } from '@hotwired/stimulus';
import { browserSupportsWebAuthnAutofill, browserSupportsWebAuthn, startAuthentication, startRegistration } from '@simplewebauthn/browser';
import { browserSupportsWebAuthnAutofill, browserSupportsWebAuthn, startAuthentication, startRegistration, base64URLStringToBuffer, bufferToBase64URLString } from '@simplewebauthn/browser';

class default_1 extends Controller {
constructor() {
Expand Down Expand Up @@ -42,7 +42,9 @@ class default_1 extends Controller {
async _processSignin(optionsResponseJson, useBrowserAutofill) {
var _a;
try {
const authenticatorResponse = await startAuthentication({ optionsJSON: optionsResponseJson, useBrowserAutofill });
optionsResponseJson = this._processExtensionsInput(optionsResponseJson);
let authenticatorResponse = await startAuthentication({ optionsJSON: optionsResponseJson, useBrowserAutofill });
authenticatorResponse = this._processExtensionsOutput(authenticatorResponse);
this._dispatchEvent('webauthn:authenticator:response', { response: authenticatorResponse });
if (this.requestResultFieldValue && this.element instanceof HTMLFormElement) {
(_a = this.element.querySelector(this.requestResultFieldValue)) === null || _a === void 0 ? void 0 : _a.setAttribute('value', JSON.stringify(authenticatorResponse));
Expand All @@ -67,11 +69,13 @@ class default_1 extends Controller {
return;
}
event.preventDefault();
const optionsResponseJson = await this._getPublicKeyCredentialCreationOptions(null);
let optionsResponseJson = await this._getPublicKeyCredentialCreationOptions(null);
if (!optionsResponseJson) {
return;
}
const authenticatorResponse = await startRegistration({ optionsJSON: optionsResponseJson });
optionsResponseJson = this._processExtensionsInput(optionsResponseJson);
let authenticatorResponse = await startRegistration({ optionsJSON: optionsResponseJson });
authenticatorResponse = this._processExtensionsOutput(authenticatorResponse);
this._dispatchEvent('webauthn:authenticator:response', { response: authenticatorResponse });
if (this.creationResultFieldValue && this.element instanceof HTMLFormElement) {
(_a = this.element.querySelector(this.creationResultFieldValue)) === null || _a === void 0 ? void 0 : _a.setAttribute('value', JSON.stringify(authenticatorResponse));
Expand Down Expand Up @@ -161,6 +165,56 @@ class default_1 extends Controller {
this._dispatchEvent(eventPrefix + 'success', { data: attestationResponseJSON });
return attestationResponseJSON;
}
_processExtensionsInput(options) {
if (!options || !options.extensions) {
return options;
}
if (options.extensions.prf) {
options.extensions.prf = this._processPrfInput(options.extensions.prf);
}
return options;
}
_processPrfInput(prf) {
if (prf.eval) {
prf.eval = this._importPrfValues(eval);
}
if (prf.evalByCredential) {
Object.keys(prf.evalByCredential).forEach((key) => {
prf.evalByCredential[key] = this._importPrfValues(prf.evalByCredential[key]);
});
}
return prf;
}
_importPrfValues(values) {
values.first = base64URLStringToBuffer(values.first);
if (values.second) {
values.second = base64URLStringToBuffer(values.second);
}
return values;
}
_processExtensionsOutput(options) {
if (!options || !options.extensions) {
return options;
}
if (options.extensions.prf) {
options.extensions.prf = this._processPrfOutput(options.extensions.prf);
}
return options;
}
_processPrfOutput(prf) {
if (!prf.result) {
return prf;
}
prf.result = this._exportPrfValues(prf.result);
return prf;
}
_exportPrfValues(values) {
values.first = bufferToBase64URLString(values.first);
if (values.second) {
values.second = bufferToBase64URLString(values.second);
}
return values;
}
}
default_1.values = {
requestResultUrl: { type: String, default: '/request' },
Expand Down
104 changes: 99 additions & 5 deletions src/stimulus/assets/src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import { Controller } from '@hotwired/stimulus';
import {
AuthenticationResponseJSON,
RegistrationResponseJSON
RegistrationResponseJSON,
PublicKeyCredentialRequestOptionsJSON,
PublicKeyCredentialCreationOptionsJSON
} from '@simplewebauthn/types';
import { browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, startAuthentication, startRegistration } from '@simplewebauthn/browser';
import { browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, startAuthentication, startRegistration, base64URLStringToBuffer, bufferToBase64URLString } from '@simplewebauthn/browser';

export default class extends Controller {
static values = {
Expand Down Expand Up @@ -89,7 +91,11 @@ export default class extends Controller {
private async _processSignin(optionsResponseJson: Object, useBrowserAutofill: boolean): Promise<void> {
try {
// @ts-ignore
const authenticatorResponse = await startAuthentication({ optionsJSON: optionsResponseJson, useBrowserAutofill });
optionsResponseJson = this._processExtensionsInput(optionsResponseJson);
// @ts-ignore
let authenticatorResponse = await startAuthentication({ optionsJSON: optionsResponseJson, useBrowserAutofill });
// @ts-ignore
authenticatorResponse = this._processExtensionsOutput(authenticatorResponse);
this._dispatchEvent('webauthn:authenticator:response', { response: authenticatorResponse });
if (this.requestResultFieldValue && this.element instanceof HTMLFormElement) {
this.element.querySelector(this.requestResultFieldValue)?.setAttribute('value', JSON.stringify(authenticatorResponse));
Expand All @@ -114,13 +120,16 @@ export default class extends Controller {
return;
}
event.preventDefault();
const optionsResponseJson = await this._getPublicKeyCredentialCreationOptions(null);
let optionsResponseJson = await this._getPublicKeyCredentialCreationOptions(null);
if (!optionsResponseJson) {
return;
}

optionsResponseJson = this._processExtensionsInput(optionsResponseJson);
// @ts-ignore
let authenticatorResponse = await startRegistration({ optionsJSON: optionsResponseJson });
// @ts-ignore
const authenticatorResponse = await startRegistration({ optionsJSON: optionsResponseJson });
authenticatorResponse = this._processExtensionsOutput(authenticatorResponse);
this._dispatchEvent('webauthn:authenticator:response', { response: authenticatorResponse });
if (this.creationResultFieldValue && this.element instanceof HTMLFormElement) {
this.element.querySelector(this.creationResultFieldValue)?.setAttribute('value', JSON.stringify(authenticatorResponse));
Expand Down Expand Up @@ -228,4 +237,89 @@ export default class extends Controller {

return attestationResponseJSON;
}

private _processExtensionsInput(options: Object|PublicKeyCredentialRequestOptionsJSON|PublicKeyCredentialCreationOptionsJSON): Object|PublicKeyCredentialRequestOptionsJSON|PublicKeyCredentialCreationOptionsJSON {
// @ts-ignore
if (!options || !options.extensions) {
return options;
}

// @ts-ignore
if (options.extensions.prf) {
// @ts-ignore
options.extensions.prf = this._processPrfInput(options.extensions.prf);
}

return options;
}

private _processPrfInput(prf: Object): Object {
// @ts-ignore
if (prf.eval) {
// @ts-ignore
prf.eval = this._importPrfValues(eval);
}

// @ts-ignore
if (prf.evalByCredential) {
// @ts-ignore
Object.keys(prf.evalByCredential).forEach((key) => {
// @ts-ignore
prf.evalByCredential[key] = this._importPrfValues(prf.evalByCredential[key]);
});
}

return prf;
}

private _importPrfValues(values: Object): Object {
// @ts-ignore
values.first = base64URLStringToBuffer(values.first);
// @ts-ignore
if (values.second) {
// @ts-ignore
values.second = base64URLStringToBuffer(values.second);
}

return values;
}

private _processExtensionsOutput(options: Object|AuthenticationResponseJSON|RegistrationResponseJSON): Object|PublicKeyCredentialRequestOptionsJSON|PublicKeyCredentialCreationOptionsJSON {
// @ts-ignore
if (!options || !options.extensions) {
return options;
}

// @ts-ignore
if (options.extensions.prf) {
// @ts-ignore
options.extensions.prf = this._processPrfOutput(options.extensions.prf);
}

return options;
}

private _processPrfOutput(prf: Object): Object {
// @ts-ignore
if (!prf.result) {
return prf
}

// @ts-ignore
prf.result = this._exportPrfValues(prf.result);

return prf;
}

private _exportPrfValues(values: Object): Object {
// @ts-ignore
values.first = bufferToBase64URLString(values.first);
// @ts-ignore
if (values.second) {
// @ts-ignore
values.second = bufferToBase64URLString(values.second);
}

return values;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Webauthn\AuthenticationExtensions;

final class AppIdExcludeInputExtension extends AuthenticationExtension
{
public static function enable(string $value): AuthenticationExtension
{
return self::create('appidExclude', $value);
}
}
18 changes: 18 additions & 0 deletions src/webauthn/src/AuthenticationExtensions/AppIdInputExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Webauthn\AuthenticationExtensions;

final class AppIdInputExtension extends AuthenticationExtension
{
public static function enable(): AuthenticationExtension
{
return self::create('appid', true);
}

public static function disable(): AuthenticationExtension
{
return self::create('appid', false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Webauthn\AuthenticationExtensions;

final class CredentialPropertiesInputExtension extends AuthenticationExtension
{
public static function enable(): AuthenticationExtension
{
return self::create('credProps', true);
}

public static function disable(): AuthenticationExtension
{
return self::create('credProps', false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Webauthn\AuthenticationExtensions;

use function assert;
use function in_array;

final class LargeBlobInputExtension extends AuthenticationExtension
{
public const REQUIRED = 'required';

public const PREFERRED = 'preferred';

public static function support(string $support): AuthenticationExtension
{
assert(in_array($support, [self::REQUIRED, self::PREFERRED], true), 'Invalid support value.');

return self::create('largeBlob', [
'support' => $support,
]);
}

public static function read(): AuthenticationExtension
{
return self::create('largeBlob', [
'read' => true,
]);
}

public static function write(string $value): AuthenticationExtension
{
return self::create('largeBlob', [
'write' => $value,
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Webauthn\AuthenticationExtensions;

final class PseudoRandomFunctionInputExtension extends AuthenticationExtension
{
}
Loading