FEATURE: use raster image and autofill in 2FA input (#15429)
- switches to a raster image QR code so it can be long-pressed (or right clicked) and added to iCloud keychain - adds `autocomplete="one-time-code"` to the 2FA input for better discoverability
This commit is contained in:
parent
ed83d7573e
commit
be599513e3
|
@ -4,7 +4,9 @@
|
||||||
maxlength=maxlength
|
maxlength=maxlength
|
||||||
class="second-factor-token-input"
|
class="second-factor-token-input"
|
||||||
id=inputId
|
id=inputId
|
||||||
autocorrect="off"
|
|
||||||
autocapitalize="off"
|
autocapitalize="off"
|
||||||
|
autocomplete="one-time-code"
|
||||||
|
autocorrect="off"
|
||||||
autofocus="autofocus"
|
autofocus="autofocus"
|
||||||
placeholder=placeholder}}
|
placeholder=placeholder
|
||||||
|
}}
|
||||||
|
|
|
@ -16,12 +16,9 @@
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="qr-code-container">
|
|
||||||
<div class="qr-code">
|
<div class="qr-code">
|
||||||
{{html-safe secondFactorImage}}
|
<img src={{html-safe secondFactorImage}}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{{#if showSecondFactorKey}}
|
{{#if showSecondFactorKey}}
|
||||||
{{secondFactorKey}}
|
{{secondFactorKey}}
|
||||||
|
|
|
@ -28,7 +28,7 @@ function preferencesPretender(server, helper) {
|
||||||
server.post("/u/create_second_factor_totp.json", () => {
|
server.post("/u/create_second_factor_totp.json", () => {
|
||||||
return helper.response({
|
return helper.response({
|
||||||
key: "rcyryaqage3jexfj",
|
key: "rcyryaqage3jexfj",
|
||||||
qr: '<div id="test-qr">qr-code</div>',
|
qr: "data:image/png;base64,notarealimage",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ acceptance("User Preferences", function (needs) {
|
||||||
assert.notOk(exists("#password"), "it hides the password input");
|
assert.notOk(exists("#password"), "it hides the password input");
|
||||||
|
|
||||||
await click(".new-totp");
|
await click(".new-totp");
|
||||||
assert.ok(exists("#test-qr"), "shows qr code");
|
assert.ok(exists(".qr-code img"), "shows qr code image");
|
||||||
|
|
||||||
await click(".add-totp");
|
await click(".add-totp");
|
||||||
|
|
||||||
|
|
|
@ -747,14 +747,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-code-container {
|
|
||||||
display: flex;
|
|
||||||
.qr-code {
|
|
||||||
padding: 5px 5px 0 5px;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.second-factor {
|
.second-factor {
|
||||||
&.instructions {
|
&.instructions {
|
||||||
color: var(--primary-medium);
|
color: var(--primary-medium);
|
||||||
|
|
|
@ -1412,16 +1412,14 @@ class UsersController < ApplicationController
|
||||||
require 'rotp' if !defined? ROTP
|
require 'rotp' if !defined? ROTP
|
||||||
totp_data = ROTP::Base32.random
|
totp_data = ROTP::Base32.random
|
||||||
secure_session["staged-totp-#{current_user.id}"] = totp_data
|
secure_session["staged-totp-#{current_user.id}"] = totp_data
|
||||||
qrcode_svg = RQRCode::QRCode.new(current_user.totp_provisioning_uri(totp_data)).as_svg(
|
qrcode_png = RQRCode::QRCode.new(current_user.totp_provisioning_uri(totp_data)).as_png(
|
||||||
offset: 0,
|
border_modules: 1,
|
||||||
color: '000',
|
size: 240
|
||||||
shape_rendering: 'crispEdges',
|
|
||||||
module_size: 4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
render json: success_json.merge(
|
render json: success_json.merge(
|
||||||
key: totp_data.scan(/.{4}/).join(" "),
|
key: totp_data.scan(/.{4}/).join(" "),
|
||||||
qr: qrcode_svg
|
qr: qrcode_png.to_data_url
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue