--- /dev/null
+<section id="{@$container->getPrefixedId()}Container"{*
+ *}{if !$container->getClasses()|empty} class="{implode from=$container->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{foreach from=$container->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}{if !$container->checkDependencies()} style="display: none;"{/if}{*
+*}>
+ {if $container->getLabel() !== null}
+ {if $container->getDescription() !== null}
+ <header class="sectionHeader">
+ <h2 class="sectionTitle">{@$container->getLabel()}{if $container->markAsRequired()} <span class="formFieldRequired">*</span>{/if}</h2>
+ <p class="sectionDescription">{@$container->getDescription()}</p>
+ </header>
+ {else}
+ <h2 class="sectionTitle">{@$container->getLabel()}{if $container->markAsRequired()} <span class="formFieldRequired">*</span>{/if}</h2>
+ {/if}
+ {/if}
+
+ <div class="multifactorTotpNewDevice">
+ {if $container->getNodeById('secret')->isAvailable()}
+ {@$container->getNodeById('secret')->getHtml()}
+ {/if}
+
+ <div class="multifactorTotpNewDeviceFields">
+ {foreach from=$container item='child'}
+ {if $child->getId() !== 'secret' && $child->getId() !== 'submitButton' && $child->isAvailable()}
+ {@$child->getHtml()}
+ {/if}
+ {/foreach}
+
+ {if $container->getNodeById('submitButton')->isAvailable()}
+ <div class="formSubmit">
+ {@$container->getNodeById('submitButton')->getHtml()}
+ </div>
+ {/if}
+ </div>
+ </div>
+</section>
+
+{include file='__formContainerDependencies'}
+
+<script data-relocate="true">
+ require(['WoltLabSuite/Core/Form/Builder/Field/Dependency/Container/Default'], function(DefaultContainerDependency) {
+ new DefaultContainerDependency('{@$container->getPrefixedId()}Container');
+ });
+</script>
<div class="totpSecretContainer">
<input type="hidden" name="{@$field->getPrefixedId()}" value="{$field->getSignedValue()}">
+ <canvas></canvas>
+
<kbd {*
*}class="totpSecret" {*
*}data-issuer="{PAGE_TITLE}" {*
}
const issuer = secret.dataset.issuer;
const label = (issuer ? `${issuer}:` : "") + accountName;
+ const canvas = container.querySelector("canvas");
qr_creator_1.default.render({
text: `otpauth://totp/${encodeURIComponent(label)}?secret=${encodeURIComponent(secret.textContent)}${issuer ? `&issuer=${encodeURIComponent(issuer)}` : ""}`,
- }, container);
+ size: canvas && canvas.clientWidth ? canvas.clientWidth : 200,
+ }, canvas || container);
}
exports.render = render;
exports.default = render;
<?php
namespace wcf\system\user\multifactor;
use ParagonIE\ConstantTime\Hex;
+use wcf\system\form\builder\button\FormButton;
use wcf\system\form\builder\container\FormContainer;
use wcf\system\form\builder\field\HiddenFormField;
use wcf\system\form\builder\field\IFormField;
use wcf\system\form\builder\field\validation\FormFieldValidator;
use wcf\system\form\builder\IFormDocument;
use wcf\system\user\multifactor\totp\CodeFormField;
+use wcf\system\user\multifactor\totp\NewDeviceContainer;
use wcf\system\user\multifactor\totp\SecretFormField;
use wcf\system\user\multifactor\totp\Totp;
use wcf\system\WCF;
* @inheritDoc
*/
public function createManagementForm(IFormDocument $form, ?int $setupId, $returnData = null): void {
- if ($setupId) {
-
- }
-
- $newDeviceContainer = FormContainer::create('newDevice')
+ $form->addDefaultButton(false);
+ $newDeviceContainer = NewDeviceContainer::create()
->label('wcf.user.security.multifactor.totp.newDevice')
->appendChildren([
SecretFormField::create(),
TextFormField::create('deviceName')
->label('wcf.user.security.multifactor.totp.deviceName')
->placeholder('wcf.user.security.multifactor.totp.deviceName.placeholder'),
+ FormButton::create('submitButton')
+ ->label('wcf.global.button.submit')
+ ->accessKey('s')
+ ->submit(true)
+ ->addClass('buttonPrimary'),
]);
$form->appendChild($newDeviceContainer);
}
--- /dev/null
+<?php
+namespace wcf\system\user\multifactor\totp;
+use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\field\TDefaultIdFormField;
+
+/**
+ * Shows the form to add a new TOTP device.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\System\User\Multifactor\Totp
+ * @since 5.4
+ */
+class NewDeviceContainer extends FormContainer {
+ use TDefaultIdFormField;
+
+ /**
+ * @inheritDoc
+ */
+ protected $templateName = '__totpNewDeviceContainer';
+
+ /**
+ * @inheritDoc
+ */
+ protected static function getDefaultId(): string {
+ return 'newDevice';
+ }
+}
}
}
}
+
+.multifactorTotpNewDevice {
+ display: flex;
+
+ .totpSecretContainer {
+ text-align: center;
+ width: 250px;
+ margin: 0 5px;
+
+ canvas {
+ width: 200px;
+ height: 200px;
+ }
+ }
+
+ .multifactorTotpNewDeviceFields {
+ flex: 1 1 auto;
+ }
+}
const issuer = secret.dataset.issuer;
const label = (issuer ? `${issuer}:` : "") + accountName;
+ const canvas = container.querySelector("canvas");
QrCreator.render(
{
text: `otpauth://totp/${encodeURIComponent(label)}?secret=${encodeURIComponent(secret.textContent!)}${
issuer ? `&issuer=${encodeURIComponent(issuer)}` : ""
}`,
+ size: canvas && canvas.clientWidth ? canvas.clientWidth : 200,
},
- container,
+ canvas || container,
);
}