Commit 110480e5 authored by Oliver Bartsch's avatar Oliver Bartsch Committed by Georg Ringer
Browse files

[TASK] Show otpauth URL next to shared secret

The MFA TOTP provider is based on a shared secret. Therefore,
to set this provider up, the secret has to be exchanged between
TYPO3 and the corresponding OTP application or device.

This can either be done by:

* Scanning the QR code
* Entering the shared secret directly

While the shared secret does not contain any additional data,
the QR code contains the so-called otpauth URL, a common URI
scheme, accepted by most OTP applications. This URL features
additional information like the issuer and the related account.

To improve the usability, the user is now able to also access
this URL directly through a new modal with further explanation.

Resolves: #93666
Releases: master
Change-Id: I27dc805bcca7aeb29d15a5c20223426999277625
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68293


Tested-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
parent 51ccba9d
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
import {render, html, TemplateResult} from 'lit-html';
import {customElement, property, LitElement} from 'lit-element';
import Modal = require('TYPO3/CMS/Backend/Modal');
enum Selectors {
modalBody = '.t3js-modal-body'
}
@customElement('typo3-mfa-totp-url-info-button')
class MfaTotpUrlButton extends LitElement {
@property({type: String}) url: string;
@property({type: String}) title: string;
@property({type: String}) description: string;
@property({type: String}) ok: string;
public constructor() {
super();
this.addEventListener('click', (e: Event): void => {
e.preventDefault();
this.showTotpAuthUrlModal();
});
}
protected render(): TemplateResult {
return html`<slot></slot>`;
}
private showTotpAuthUrlModal(): void {
Modal.advanced({
title: this.title,
buttons: [
{
trigger: (): void => Modal.dismiss(),
text: this.ok || 'OK',
active: true,
btnClass: 'btn-default',
name: 'ok'
}
],
callback: (currentModal: HTMLCollection): void => {
render(
html`
<p>${this.description}</p>
<pre>${this.url}</pre>
`,
currentModal[0].querySelector(Selectors.modalBody)
);
}
});
}
}
......@@ -266,10 +266,15 @@ class TotpProvider implements MfaProviderInterface
$userData = $propertyManager->getUser()->user ?? [];
$secret = Totp::generateEncodedSecret([(string)($userData['uid'] ?? ''), (string)($userData['username'] ?? '')]);
$totpInstance = GeneralUtility::makeInstance(Totp::class, $secret);
$totpAuthUrl = $totpInstance->getTotpAuthUrl(
(string)($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? 'TYPO3'),
(string)($userData['email'] ?? '') ?: (string)($userData['username'] ?? '')
);
$view->setTemplate('Setup');
$view->assignMultiple([
'secret' => $secret,
'qrCode' => $this->getSvgQrCode($totpInstance, $userData),
'totpAuthUrl' => $totpAuthUrl,
'qrCode' => $this->getSvgQrCode($totpAuthUrl),
// Generate hmac of the secret to prevent it from being changed in the setup from
'checksum' => GeneralUtility::hmac($secret, 'totp-setup')
]);
......@@ -317,21 +322,16 @@ class TotpProvider implements MfaProviderInterface
/**
* Internal helper method for generating a svg QR-code for TOTP applications
*
* @param Totp $totp
* @param array $userData
* @param string $content
* @return string
*/
protected function getSvgQrCode(Totp $totp, array $userData): string
protected function getSvgQrCode(string $content): string
{
$qrCodeRenderer = new ImageRenderer(
new RendererStyle(225, 4),
new SvgImageBackEnd()
);
$content = $totp->getTotpAuthUrl(
(string)($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?? 'TYPO3'),
(string)($userData['email'] ?? '') ?: (string)($userData['username'] ?? '')
);
return (new Writer($qrCodeRenderer))->writeString($content);
}
......
......@@ -18,7 +18,9 @@
1. Scan the QR-code or directly enter the shared secret in your application or device
2. Add a specific name for this provider (optional)
3. Enter the generated six-digit code in the corresponding field
4. Submit the form to activate the provider</source>
4. Submit the form to activate the provider
Note: In case your application supports otpauth:// urls, you can retrieve the url by clicking on the info button next to the shared secret.</source>
</trans-unit>
<trans-unit id="recoveryCodes.title" resname="recoveryCodes.title">
<source>Recovery codes</source>
......@@ -137,6 +139,23 @@
authentication credentials. Therefore, please activate a comprehensive MFA provider first.
</source>
</trans-unit>
<trans-unit id="setup.totpAuthUrlButton.title" resname="setup.totpAuthUrlButton.title">
<source>Show authentication URL</source>
</trans-unit>
<trans-unit id="setup.totpAuthUrlModal.title" resname="setup.totpAuthUrlModal.title">
<source>Authentication URL</source>
</trans-unit>
<trans-unit id="setup.totpAuthUrlModal.description" resname="setup.totpAuthUrlModal.description">
<source>In case you are not able to scan the QR code, but your OTP application supports
authentication URLs next to, or instead of plain shared secrets, you can copy and
paste the URL shown below. The URL contains some information like the issuer (this site)
and your email address or username. Most OTP applications will display this information
next to the generated codes.
</source>
</trans-unit>
<trans-unit id="setup.totpAuthUrlModal.ok" resname="setup.totpAuthUrlModal.ok">
<source>OK</source>
</trans-unit>
<trans-unit id="auth.totp.inputLabel" resname="auth.totp.inputLabel">
<source>Enter the six-digit code</source>
</trans-unit>
......
<html
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:be.pageRenderer includeRequireJsModules="{0: 'TYPO3/CMS/Core/Authentication/MfaProvider/Totp'}" />
<fieldset class="row">
<div class="col-lg-4">
<div class="mt-3">
......@@ -24,7 +31,21 @@
<f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_mfa_provider.xlf:setup.secret.help"/>
</span>
<div class="form-control-wrap mt-1">
<input type="text" id="secret" name="secret" value="{secret}" readonly="readonly" class="form-control"/>
<div class="input-group">
<input type="text" id="secret" name="secret" value="{secret}" readonly="readonly" class="form-control"/>
<span class="input-group-btn">
<typo3-mfa-totp-url-info-button url="{totpAuthUrl}"
title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_mfa_provider.xlf:setup.totpAuthUrlModal.title')}"
description="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_mfa_provider.xlf:setup.totpAuthUrlModal.description')}"
ok="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_mfa_provider.xlf:setup.totpAuthUrlModal.ok')}">
<button type="button"
class="btn btn-default"
title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_mfa_provider.xlf:setup.totpAuthUrlButton.title')}">
<core:icon identifier="actions-info"/>
</button>
</typo3-mfa-totp-url-info-button>
</span>
</div>
</div>
</div>
</div>
......@@ -57,3 +78,5 @@
<input type="hidden" name="checksum" value="{checksum}"/>
</div>
</fieldset>
</html>
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
var __decorate=this&&this.__decorate||function(t,e,o,r){var i,l=arguments.length,n=l<3?e:null===r?r=Object.getOwnPropertyDescriptor(e,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(t,e,o,r);else for(var p=t.length-1;p>=0;p--)(i=t[p])&&(n=(l<3?i(n):l>3?i(e,o,n):i(e,o))||n);return l>3&&n&&Object.defineProperty(e,o,n),n};define(["require","exports","lit-html","lit-element","TYPO3/CMS/Backend/Modal"],(function(t,e,o,r,i){"use strict";var l;Object.defineProperty(e,"__esModule",{value:!0}),function(t){t.modalBody=".t3js-modal-body"}(l||(l={}));let n=class extends r.LitElement{constructor(){super(),this.addEventListener("click",t=>{t.preventDefault(),this.showTotpAuthUrlModal()})}render(){return o.html`<slot></slot>`}showTotpAuthUrlModal(){i.advanced({title:this.title,buttons:[{trigger:()=>i.dismiss(),text:this.ok||"OK",active:!0,btnClass:"btn-default",name:"ok"}],callback:t=>{o.render(o.html`
<p>${this.description}</p>
<pre>${this.url}</pre>
`,t[0].querySelector(l.modalBody))}})}};__decorate([r.property({type:String})],n.prototype,"url",void 0),__decorate([r.property({type:String})],n.prototype,"title",void 0),__decorate([r.property({type:String})],n.prototype,"description",void 0),__decorate([r.property({type:String})],n.prototype,"ok",void 0),n=__decorate([r.customElement("typo3-mfa-totp-url-info-button")],n)}));
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment