mirror of
https://github.com/Ride-The-Lightning/RTL.git
synced 2026-08-13 12:33:07 +02:00
Mark the 2FA token input as a one-time code
The token input in the two-factor login dialog had no autocomplete attribute. Its id, name and label were all "token". A password manager had no data to find the field, and did not offer the code. Add autocomplete="one-time-code" to the input. This is the standard value for this field, and password managers and browsers read it directly. Add a test that guards the attribute, and build frontend/ again. Fixes #1674.
This commit is contained in:
parent
d4e2554ca4
commit
8d958e6451
8 changed files with 30 additions and 4 deletions
File diff suppressed because one or more lines are too long
1
frontend/main.6c1c604b32fb2e06.js
Normal file
1
frontend/main.6c1c604b32fb2e06.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
(()=>{"use strict";var e,v={},m={};function r(e){var o=m[e];if(void 0!==o)return o.exports;var t=m[e]={exports:{}};return v[e].call(t.exports,t,t.exports,r),t.exports}r.m=v,e=[],r.O=(o,t,i,f)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,f]=e[n],s=!0,u=0;u<t.length;u++)(!1&f||a>=f)&&Object.keys(r.O).every(b=>r.O[b](t[u]))?t.splice(u--,1):(s=!1,f<a&&(a=f));if(s){e.splice(n--,1);var d=i();void 0!==d&&(o=d)}}return o}f=f||0;for(var n=e.length;n>0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,i,f]},r.d=(e,o)=>{for(var t in o)r.o(o,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((o,t)=>(r.f[t](e,o),o),[])),r.u=e=>e+"."+{17:"da5e0b6abb96d103",190:"0e6572086349bd7c",193:"5eec0042e2c6f1a7",853:"e46ed60b577aec33"}[e]+".js",r.miniCssF=e=>{},r.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),(()=>{var e={},o="RTLApp:";r.l=(t,i,f,n)=>{if(e[t])e[t].push(i);else{var a,s;if(void 0!==f)for(var u=document.getElementsByTagName("script"),d=0;d<u.length;d++){var l=u[d];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==o+f){a=l;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",o+f),a.src=r.tu(t)),e[t]=[i];var c=(h,b)=>{a.onerror=a.onload=null,clearTimeout(p);var g=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),g&&g.forEach(_=>_(b)),h)return h(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:o=>o},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={121:0};r.f.j=(i,f)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)f.push(n[2]);else if(121!=i){var a=new Promise((l,c)=>n=e[i]=[l,c]);f.push(n[2]=a);var s=r.p+r.u(i),u=new Error;r.l(s,l=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;u.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",u.name="ChunkLoadError",u.type=c,u.request=p,n[1](u)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var o=(i,f)=>{var u,d,[n,a,s]=f,l=0;if(n.some(p=>0!==e[p])){for(u in a)r.o(a,u)&&(r.m[u]=a[u]);if(s)var c=s(r)}for(i&&i(f);l<n.length;l++)r.o(e,d=n[l])&&e[d]&&e[d][0](),e[d]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})()})();
|
||||
1
frontend/runtime.c47de087e039585c.js
Normal file
1
frontend/runtime.c47de087e039585c.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
(()=>{"use strict";var e,v={},m={};function r(e){var f=m[e];if(void 0!==f)return f.exports;var t=m[e]={exports:{}};return v[e].call(t.exports,t,t.exports,r),t.exports}r.m=v,e=[],r.O=(f,t,i,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,o]=e[n],s=!0,u=0;u<t.length;u++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[u]))?t.splice(u--,1):(s=!1,o<a&&(a=o));if(s){e.splice(n--,1);var d=i();void 0!==d&&(f=d)}}return f}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,i,o]},r.d=(e,f)=>{for(var t in f)r.o(f,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:f[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((f,t)=>(r.f[t](e,f),f),[])),r.u=e=>e+"."+{17:"da5e0b6abb96d103",190:"0e6572086349bd7c",193:"5eec0042e2c6f1a7",853:"e46ed60b577aec33"}[e]+".js",r.miniCssF=e=>{},r.o=(e,f)=>Object.prototype.hasOwnProperty.call(e,f),(()=>{var e={},f="RTLApp:";r.l=(t,i,o,n)=>{if(e[t])e[t].push(i);else{var a,s;if(void 0!==o)for(var u=document.getElementsByTagName("script"),d=0;d<u.length;d++){var l=u[d];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==f+o){a=l;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",f+o),a.src=r.tu(t)),e[t]=[i];var c=(h,b)=>{a.onerror=a.onload=null,clearTimeout(p);var g=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),g&&g.forEach(_=>_(b)),h)return h(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:f=>f},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={121:0};r.f.j=(i,o)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)o.push(n[2]);else if(121!=i){var a=new Promise((l,c)=>n=e[i]=[l,c]);o.push(n[2]=a);var s=r.p+r.u(i),u=new Error;r.l(s,l=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;u.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",u.name="ChunkLoadError",u.type=c,u.request=p,n[1](u)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var f=(i,o)=>{var u,d,[n,a,s]=o,l=0;if(n.some(p=>0!==e[p])){for(u in a)r.o(a,u)&&(r.m[u]=a[u]);if(s)var c=s(r)}for(i&&i(o);l<n.length;l++)r.o(e,d=n[l])&&e[d]&&e[d][0](),e[d]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(f.bind(null,0)),t.push=f.bind(null,t.push.bind(t))})()})();
|
||||
20
release-notes/Release-notes-0.15.11.md
Normal file
20
release-notes/Release-notes-0.15.11.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Release Notes — 0.15.11
|
||||
|
||||
This document collects the changes that go into the 0.15.11 release. Each PR merged for
|
||||
this release should add its entry under the appropriate section below.
|
||||
|
||||
## Enhancements
|
||||
|
||||
- **2FA login: password managers can offer the one-time code**
|
||||
([#1673](https://github.com/Ride-The-Lightning/RTL/pull/1673), fixes
|
||||
[#1674](https://github.com/Ride-The-Lightning/RTL/issues/1674)).
|
||||
The token input in the two-factor login dialog had no `autocomplete` attribute, and its
|
||||
`id`, `name` and label were all `token`. A password manager had no data to find the
|
||||
field. Bitwarden, for example, accepts a field only if the field has
|
||||
`autocomplete="one-time-code"`, or if it contains a word from the `TotpFieldNames` list,
|
||||
which does not include `token`. RTL also makes this dialog only after you send the
|
||||
password, so the field is not in the page during the first autofill operation. Users
|
||||
typed each code by hand. The input now has `autocomplete="one-time-code"`, the standard
|
||||
value for this field, which password managers and browsers read directly. The change is
|
||||
in the frontend only, and the token check does not change. A new test in
|
||||
`login-2fa-token.component.spec.ts` guards the attribute.
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<form #tokenForm="ngForm" fxLayout="column" fxFlex="100" (ngSubmit)="onVerifyToken()">
|
||||
<mat-form-field>
|
||||
<mat-label>Token</mat-label>
|
||||
<input autoFocus matInput type="text" id="token" name="token" tabindex="2" required [(ngModel)]="token">
|
||||
<input autoFocus matInput type="text" id="token" name="token" autocomplete="one-time-code" tabindex="2" required [(ngModel)]="token">
|
||||
<mat-error *ngIf="!token">Token is required.</mat-error>
|
||||
</mat-form-field>
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="end center" class="mt-1">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
|
||||
|
|
@ -41,6 +42,11 @@ describe('LoginTokenComponent', () => {
|
|||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should advertise the token input as a one-time code', () => {
|
||||
const tokenInput = fixture.debugElement.query(By.css('input#token')).nativeElement;
|
||||
expect(tokenInput.getAttribute('autocomplete')).toBe('one-time-code');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue