fix: chrome was prompting to save credentials without username because of login's double-form

Massimo Melina committed Jan 31, 2023 at 22:02 UTC ded3b70114520c08918c31d006773214611cdf98
3 files changed +86 -21
frontend/src/index.scss
+13
@@ -281,6 +281,19 @@ button label {
281 & td:nth-child(3) { padding: .2em .5em; word-break: break-word; }
282 }
283
284 +.dialog-login {
285 + form {
286 + display: flex;
287 + flex-direction: column;
288 + gap: 1.2em;
289 + }
290 + label {
291 + display: block;
292 + margin-bottom: .5em;
293 + margin-left: .1em;
294 + }
295 +}
296 +
297 /* Works on Firefox */
298 * {
299 scrollbar-width: thin;
frontend/src/login.ts
+73 -20
@@ -1,10 +1,11 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 -import { apiCall, ApiError } from './api'
3 +import { apiCall } from './api'
4 import { state } from './state'
5 -import { alertDialog, promptDialog } from './dialog'
5 +import { alertDialog, newDialog } from './dialog'
6 import { hIcon, srpSequence, working } from './misc'
7 import { useNavigate } from 'react-router-dom'
8 +import { createElement as h, useEffect, useRef } from 'react'
9
10 async function login(username:string, password:string) {
11 const stopWorking = working()
@@ -13,16 +14,12 @@ async function login(username:string, password:string) {
14 sessionRefresher(res)
15 state.loginRequired = false
16 return res
16 - }, (err: Error) => {
17 + }, (err: any) => {
18 stopWorking()
18 - if (err.message === 'trust')
19 - err = Error("Login aborted: server identity cannot be trusted")
20 - else if (err instanceof ApiError)
21 - if (err.code === 401)
22 - err = Error("Invalid credentials")
23 - else if (err.code === 409)
24 - err = Error("Cookies not working - login failed")
25 - return alertDialog(err)
19 + throw Error(err.message === 'trust' ? "Login aborted: server identity cannot be trusted"
20 + : err.code === 401 ? "Invalid credentials"
21 + : err.code === 409 ? "Cookies not working - login failed"
22 + : err.message)
23 })
24 }
25
@@ -51,13 +48,69 @@ export function logout(){
48 }
49
50 export async function loginDialog(navigate: ReturnType<typeof useNavigate>) {
54 - const title = "Login"
55 - const icon = () => hIcon('login')
56 - const user = await promptDialog('Username', { title, icon })
57 - if (!user) return
58 - const password = await promptDialog('Password', { type: 'password', title, icon })
59 - if (!password) return
60 - const res = await login(user, password)
61 - if (res?.redirect)
62 - navigate(res.redirect)
51 + return new Promise(resolve => {
52 + const closeDialog = newDialog({
53 + className: 'dialog-login',
54 + icon: () => hIcon('login'),
55 + onClose: resolve,
56 + title: "Login",
57 + Content() {
58 + const usrRef = useRef<HTMLInputElement>()
59 + const pwdRef = useRef<HTMLInputElement>()
60 + useEffect(() => {
61 + setTimeout(() => usrRef.current?.focus()) // setTimeout workarounds problem due to double-mount while in dev
62 + }, [])
63 + return h('form', {},
64 + h('div', { className: 'field' },
65 + h('label', { htmlFor: 'username' }, "Username"),
66 + h('input', {
67 + ref: usrRef,
68 + name: 'username',
69 + autoComplete: 'username',
70 + required: true,
71 + onKeyDown
72 + }),
73 + ),
74 + h('div', { className: 'field' },
75 + h('label', { htmlFor: 'password' }, "Password"),
76 + h('input', {
77 + ref: pwdRef,
78 + name: 'password',
79 + type: 'password',
80 + autoComplete: 'current-password',
81 + required: true,
82 + onKeyDown
83 + }),
84 + ),
85 + h('div', { style: { textAlign: 'right' } },
86 + h('button', { onClick: go }, "Continue")),
87 + )
88 +
89 + function onKeyDown(ev: KeyboardEvent) {
90 + const { key } = ev
91 + if (key === 'Escape')
92 + return closeDialog(null)
93 + if (key === 'Enter')
94 + return go()
95 + }
96 +
97 + async function go(ev?: Event) {
98 + ev?.stopPropagation()
99 + const usr = usrRef.current?.value
100 + const pwd = pwdRef.current?.value
101 + if (!usr || !pwd) return
102 + try {
103 + const res = await login(usr, pwd)
104 + closeDialog()
105 + if (res?.redirect)
106 + navigate(res.redirect)
107 + } catch (err: any) {
108 + await alertDialog(err)
109 + usrRef.current?.focus()
110 + }
111 + }
112 +
113 + }
114 + })
115 + })
116 }
todo.md
-1
@@ -3,7 +3,6 @@
3 - plugins: after installing, switch to installed (and perhaps highlight new one)
4 - plugins' log, accessible in admin
5 - fix: cannot switch off https and switch on http at the same time
6 -- fix: chrome is prompting to save credentials without username because of login's double-form
6 - admin/monitor: show user-agent
7 - admin: check + update
8 - frontend: hide closer button on login dialog accessing a protected resource, as it's no use