Switch from client- to server-side generated QR codes

Use the same PHP library as UrlShortener (endroid/qr-code) to
generate QR codes, rather than the out-of-date JS library.

Bug: T348590
Change-Id: I560ac1b384e249aad1866752deac753c764ec553
This commit is contained in:
Sam Wilson 2023-12-12 15:14:27 +08:00
parent 972c9bc00f
commit fbe2f875c4
7 changed files with 17 additions and 1365 deletions

View file

@ -1,6 +1,7 @@
{
"require": {
"christian-riesen/base32": "^1.4.0",
"endroid/qr-code": "4.5.2",
"jakobo/hotp-php": "2.0.0"
},
"require-dev": {

View file

@ -100,17 +100,6 @@
}
},
"ResourceModules": {
"ext.oath.totp.showqrcode": {
"scripts": [
"totp/jquery.qrcode.js",
"totp/qrcode.js",
"totp/ext.oath.showqrcode.js"
],
"targets": [
"desktop",
"mobile"
]
},
"ext.oath.totp.showqrcode.styles": {
"styles": [
"totp/ext.oath.showqrcode.styles.css"

View file

@ -1,5 +0,0 @@
$( function () {
// eslint-disable-next-line no-jquery/no-global-selector
var $elm = $( '.mw-display-qrcode' );
$elm.qrcode( $elm.data( 'mw-qrcode-url' ) );
} );

View file

@ -1,7 +1,3 @@
.client-nojs .mw-display-qrcode {
display: none;
}
kbd {
font-family: monospace, monospace;
white-space: nowrap;

View file

@ -1,99 +0,0 @@
/* global QRCode, QRErrorCorrectLevel */
( function () {
$.fn.qrcode = function ( options ) {
var createCanvas, createTable;
// if options is string,
if ( typeof options === 'string' ) {
options = { text: options };
}
// set default values
// typeNumber < 1 for automatic calculation
options = $.extend( {}, {
render: 'canvas',
width: 256,
height: 256,
typeNumber: -1,
correctLevel: QRErrorCorrectLevel.H,
background: '#ffffff',
foreground: '#000000'
}, options );
createCanvas = function () {
var qrcode, canvas, ctx, tileW, tileH, row, col, w, h;
// create the qrcode itself
qrcode = new QRCode( options.typeNumber, options.correctLevel );
qrcode.addData( options.text );
qrcode.make();
// create canvas element
canvas = document.createElement( 'canvas' );
canvas.width = options.width;
canvas.height = options.height;
ctx = canvas.getContext( '2d' );
// compute tileW/tileH based on options.width/options.height
tileW = options.width / qrcode.getModuleCount();
tileH = options.height / qrcode.getModuleCount();
// draw in the canvas
for ( row = 0; row < qrcode.getModuleCount(); row++ ) {
for ( col = 0; col < qrcode.getModuleCount(); col++ ) {
ctx.fillStyle = qrcode.isDark( row, col ) ?
options.foreground :
options.background;
w = ( Math.ceil( ( col + 1 ) * tileW ) - Math.floor( col * tileW ) );
h = ( Math.ceil( ( row + 1 ) * tileW ) - Math.floor( row * tileW ) );
ctx.fillRect( Math.round( col * tileW ), Math.round( row * tileH ), w, h );
}
}
// return just built canvas
return canvas;
};
// from Jon-Carlos Rivera (https://github.com/imbcmdth)
createTable = function () {
var qrcode, $table, tileW, tileH, row, col, $row;
// create the qrcode itself
qrcode = new QRCode( options.typeNumber, options.correctLevel );
qrcode.addData( options.text );
qrcode.make();
// create table element
$table = $( '<table>' )
.css( 'width', options.width + 'px' )
.css( 'height', options.height + 'px' )
.css( 'border', '0' )
.css( 'border-collapse', 'collapse' )
.css( 'background-color', options.background );
// compute tileS percentage
tileW = options.width / qrcode.getModuleCount();
tileH = options.height / qrcode.getModuleCount();
// draw in the table
for ( row = 0; row < qrcode.getModuleCount(); row++ ) {
$row = $( '<tr>' ).css( 'height', tileH + 'px' ).appendTo( $table );
for ( col = 0; col < qrcode.getModuleCount(); col++ ) {
$( '<td>' )
.css( 'width', tileW + 'px' )
.css( 'background-color', qrcode.isDark( row, col ) ? options.foreground : options.background )
.appendTo( $row );
}
}
// return just built canvas
return $table;
};
return this.each( function () {
var element = options.render === 'canvas' ? createCanvas() : createTable();
$( element ).appendTo( this );
} );
};
}() );

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,11 @@
namespace MediaWiki\Extension\OATHAuth\HTMLForm;
use ConfigException;
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeNone;
use Endroid\QrCode\Writer\SvgWriter;
use Html;
use MediaWiki\Extension\OATHAuth\Key\TOTPKey;
use MediaWiki\Logger\LoggerFactory;
@ -15,7 +20,6 @@ class TOTPEnableForm extends OATHAuthOOUIHTMLForm {
* @return string
*/
public function getHTML( $submitResult ) {
$this->getOutput()->addModules( 'ext.oath.totp.showqrcode' );
$this->getOutput()->addModuleStyles( 'ext.oath.totp.showqrcode.styles' );
return parent::getHTML( $submitResult );
@ -51,13 +55,16 @@ class TOTPEnableForm extends OATHAuthOOUIHTMLForm {
. "&issuer="
. rawurlencode( $this->oathUser->getIssuer() );
$qrcodeElement = Html::element( 'div', [
'data-mw-qrcode-url' => $qrcodeUrl,
'class' => 'mw-display-qrcode',
// Include width/height, so js won't re-arrange layout
// And non-js users will have this hidden with CSS
'style' => 'width: 256px; height: 256px;'
] );
$qrCode = Builder::create()
->writer( new SvgWriter() )
->writerOptions( [ SvgWriter::WRITER_OPTION_EXCLUDE_XML_DECLARATION => true ] )
->data( $qrcodeUrl )
->encoding( new Encoding( 'UTF-8' ) )
->errorCorrectionLevel( new ErrorCorrectionLevelHigh() )
->roundBlockSizeMode( new RoundBlockSizeModeNone() )
->size( 256 )
->margin( 0 )
->build();
return [
'app' => [
@ -68,7 +75,7 @@ class TOTPEnableForm extends OATHAuthOOUIHTMLForm {
],
'qrcode' => [
'type' => 'info',
'default' => $qrcodeElement,
'default' => $qrCode->getString(),
'raw' => true,
'section' => 'step2',
],