Disable form autofill (#17291)

]* fix aria-hidden and tabindex

* use {{template "base/disable_form_autofill"}} instead of {{DisableFormAutofill}}

Co-authored-by: zeripath <art27@cantab.net>
This commit is contained in:
wxiaoguang 2021-10-19 06:08:41 +08:00 committed by GitHub
parent c59afa752d
commit 4822eed99d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 51 additions and 15 deletions

View file

@ -8,6 +8,7 @@
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="id" value="{{.Source.ID}}"> <input type="hidden" name="id" value="{{.Source.ID}}">
<div class="inline field"> <div class="inline field">
@ -55,7 +56,6 @@
<label for="bind_dn">{{.i18n.Tr "admin.auths.bind_dn"}}</label> <label for="bind_dn">{{.i18n.Tr "admin.auths.bind_dn"}}</label>
<input id="bind_dn" name="bind_dn" value="{{$cfg.BindDN}}" placeholder="e.g. cn=Search,dc=mydomain,dc=com"> <input id="bind_dn" name="bind_dn" value="{{$cfg.BindDN}}" placeholder="e.g. cn=Search,dc=mydomain,dc=com">
</div> </div>
<input class="fake" type="password">
<div class="field"> <div class="field">
<label for="bind_password">{{.i18n.Tr "admin.auths.bind_password"}}</label> <label for="bind_password">{{.i18n.Tr "admin.auths.bind_password"}}</label>
<input id="bind_password" name="bind_password" type="password" value="{{$cfg.BindPassword}}"> <input id="bind_password" name="bind_password" type="password" value="{{$cfg.BindPassword}}">

View file

@ -8,6 +8,7 @@
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<!-- Types and name --> <!-- Types and name -->
<div class="inline required field {{if .Err_Type}}error{{end}}"> <div class="inline required field {{if .Err_Type}}error{{end}}">

View file

@ -30,7 +30,6 @@
<label for="bind_dn">{{.i18n.Tr "admin.auths.bind_dn"}}</label> <label for="bind_dn">{{.i18n.Tr "admin.auths.bind_dn"}}</label>
<input id="bind_dn" name="bind_dn" value="{{.bind_dn}}" placeholder="e.g. cn=Search,dc=mydomain,dc=com"> <input id="bind_dn" name="bind_dn" value="{{.bind_dn}}" placeholder="e.g. cn=Search,dc=mydomain,dc=com">
</div> </div>
<input class="fake" type="password">
<div class="ldap field {{if not (eq .type 2)}}hide{{end}}"> <div class="ldap field {{if not (eq .type 2)}}hide{{end}}">
<label for="bind_password">{{.i18n.Tr "admin.auths.bind_password"}}</label> <label for="bind_password">{{.i18n.Tr "admin.auths.bind_password"}}</label>
<input id="bind_password" name="bind_password" type="password" autocomplete="off" value="{{.bind_password}}"> <input id="bind_password" name="bind_password" type="password" autocomplete="off" value="{{.bind_password}}">

View file

@ -8,6 +8,7 @@
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field {{if .Err_UserName}}error{{end}}"> <div class="field {{if .Err_UserName}}error{{end}}">
<label for="user_name">{{.i18n.Tr "username"}}</label> <label for="user_name">{{.i18n.Tr "username"}}</label>
@ -67,7 +68,6 @@
<label for="email">{{.i18n.Tr "email"}}</label> <label for="email">{{.i18n.Tr "email"}}</label>
<input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required> <input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required>
</div> </div>
<input class="fake" type="password">
<div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}hide{{end}}"> <div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}hide{{end}}">
<label for="password">{{.i18n.Tr "password"}}</label> <label for="password">{{.i18n.Tr "password"}}</label>
<input id="password" name="password" type="password" autocomplete="new-password"> <input id="password" name="password" type="password" autocomplete="new-password">

View file

@ -8,6 +8,7 @@
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<!-- Types and name --> <!-- Types and name -->
<div class="inline required field {{if .Err_LoginType}}error{{end}}"> <div class="inline required field {{if .Err_LoginType}}error{{end}}">
@ -61,7 +62,6 @@
<label for="email">{{.i18n.Tr "email"}}</label> <label for="email">{{.i18n.Tr "email"}}</label>
<input id="email" name="email" type="email" value="{{.email}}" required> <input id="email" name="email" type="email" value="{{.email}}" required>
</div> </div>
<input class="fake" type="password">
<div class="required local field {{if .Err_Password}}error{{end}} {{if not (eq .login_type "0-0")}}hide{{end}}"> <div class="required local field {{if .Err_Password}}error{{end}} {{if not (eq .login_type "0-0")}}hide{{end}}">
<label for="password">{{.i18n.Tr "password"}}</label> <label for="password">{{.i18n.Tr "password"}}</label>
<input id="password" name="password" type="password" autocomplete="new-password" value="{{.password}}" {{if eq .login_type "0-0"}}required{{end}}> <input id="password" name="password" type="password" autocomplete="new-password" value="{{.password}}" {{if eq .login_type "0-0"}}required{{end}}>

View file

@ -0,0 +1,31 @@
{{/*
Why we need to disable form autofill:
1. Many pages contain different password inputs for different usages, eg: repo setting, autofill will make a mess.
2. We have `areYouSure` confirm dialog if a user leaves a pages without submit.
Autofill will make the form changed even if the user didn't input anything. Then the user keeps seeing annoying confirm dialog.
In history, Gitea put `<input class="fake" type="password">` in forms to bypass the autofill,
but there were still many forms suffered the autofill problem.
Now we improve it.
Solutions which do NOT work:
1. Adding `autocomplete=off` doesn't help. New Chrome completely ignores it.
2. Use a JavaScript to run in a few seconds later after the page is loaded to process the autofilled inputs, it doesn't work.
Because for security reason, the inputs won't be filled before the user makes an interaction in the page.
So we can not predict the correct time to run the JavaScript code.
Solutions which work:
1. Some hacky methods like: https://github.com/matteobad/detect-autofill
2. This solution: use invisible inputs. Be aware of:
(a) The inputs must be at the beginning of the form, and can not be hidden.
(b) The input for username must have a valid name.
(c) There should be no negative word (eg: fake) in the `name` attribute.
(d) Chrome seems to use a weighted algorithm to choose an input to fill text, so the using "username" as input name is better than using "user".
We make the names of these dummy inputs begin with an underline to indicate it is for special usage,
and these dummy form values won't be used by backend code.
*/}}
<div class="autofill-dummy" aria-hidden="true">
<input type="text" name="_autofill_dummy_username" class="ays-ignore" tabindex="-1">
<input type="password" name="_autofill_dummy_password" class="ays-ignore" tabindex="-1">
</div>

View file

@ -3,6 +3,7 @@
<div class="ui middle very relaxed page grid"> <div class="ui middle very relaxed page grid">
<div class="column"> <div class="column">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{.i18n.Tr "repo.migrate.migrate" .service.Title}} {{.i18n.Tr "repo.migrate.migrate" .service.Title}}
@ -21,7 +22,6 @@
<label for="auth_username">{{.i18n.Tr "username"}}</label> <label for="auth_username">{{.i18n.Tr "username"}}</label>
<input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}> <input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}>
</div> </div>
<input class="fake" type="password">
<div class="inline field {{if .Err_Auth}}error{{end}}"> <div class="inline field {{if .Err_Auth}}error{{end}}">
<label for="auth_password">{{.i18n.Tr "password"}}</label> <label for="auth_password">{{.i18n.Tr "password"}}</label>
<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}"> <input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}">

View file

@ -3,6 +3,7 @@
<div class="ui middle very relaxed page grid"> <div class="ui middle very relaxed page grid">
<div class="column"> <div class="column">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{.i18n.Tr "repo.migrate.migrate" .service.Title}} {{.i18n.Tr "repo.migrate.migrate" .service.Title}}
@ -22,7 +23,6 @@
<label for="auth_username">{{.i18n.Tr "username"}}</label> <label for="auth_username">{{.i18n.Tr "username"}}</label>
<input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}> <input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}>
</div> </div>
<input class="fake" type="password">
<div class="inline field {{if .Err_Auth}}error{{end}}"> <div class="inline field {{if .Err_Auth}}error{{end}}">
<label for="auth_password">{{.i18n.Tr "password"}}</label> <label for="auth_password">{{.i18n.Tr "password"}}</label>
<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}"> <input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}">

View file

@ -9,6 +9,7 @@
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="action" value="update"> <input type="hidden" name="action" value="update">
<div class="required field {{if .Err_RepoName}}error{{end}}"> <div class="required field {{if .Err_RepoName}}error{{end}}">
@ -104,6 +105,7 @@
<tr> <tr>
<td colspan="4"> <td colspan="4">
<form class="ui form" method="post"> <form class="ui form" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="action" value="mirror"> <input type="hidden" name="action" value="mirror">
<div class="inline field {{if .Err_EnablePrune}}error{{end}}"> <div class="inline field {{if .Err_EnablePrune}}error{{end}}">
@ -132,7 +134,6 @@
<label for="mirror_username">{{.i18n.Tr "username"}}</label> <label for="mirror_username">{{.i18n.Tr "username"}}</label>
<input id="mirror_username" name="mirror_username" value="{{$address.Username}}" {{if not .mirror_username}}data-need-clear="true"{{end}}> <input id="mirror_username" name="mirror_username" value="{{$address.Username}}" {{if not .mirror_username}}data-need-clear="true"{{end}}>
</div> </div>
<input class="fake" type="password">
<div class="inline field {{if .Err_Auth}}error{{end}}"> <div class="inline field {{if .Err_Auth}}error{{end}}">
<label for="mirror_password">{{.i18n.Tr "password"}}</label> <label for="mirror_password">{{.i18n.Tr "password"}}</label>
<input id="mirror_password" name="mirror_password" type="password" placeholder="{{if $address.Password}}{{.i18n.Tr "repo.mirror_password_placeholder"}}{{else}}{{.i18n.Tr "repo.mirror_password_blank_placeholder"}}{{end}}" value="" {{if not .mirror_password}}data-need-clear="true"{{end}} autocomplete="off"> <input id="mirror_password" name="mirror_password" type="password" placeholder="{{if $address.Password}}{{.i18n.Tr "repo.mirror_password_placeholder"}}{{else}}{{.i18n.Tr "repo.mirror_password_blank_placeholder"}}{{end}}" value="" {{if not .mirror_password}}data-need-clear="true"{{end}} autocomplete="off">
@ -195,11 +196,12 @@
<tr> <tr>
<td colspan="4"> <td colspan="4">
<form class="ui form" method="post"> <form class="ui form" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="action" value="push-mirror-add"> <input type="hidden" name="action" value="push-mirror-add">
<div class="field {{if .Err_PushMirrorAddress}}error{{end}}"> <div class="field {{if .Err_PushMirrorAddress}}error{{end}}">
<label for="push_mirror_address">{{.i18n.Tr "repo.settings.mirror_settings.push_mirror.remote_url"}}</label> <label for="push_mirror_address">{{.i18n.Tr "repo.settings.mirror_settings.push_mirror.remote_url"}}</label>
<input id="push_mirror_address" name="push_mirror_address" value="{{.push_mirror_address}}" autocomplete="off" required> <input id="push_mirror_address" name="push_mirror_address" value="{{.push_mirror_address}}" required>
<p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p> <p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p>
</div> </div>
<details class="ui optional field" {{if or .Err_PushMirrorAuth .push_mirror_username}}open{{end}}> <details class="ui optional field" {{if or .Err_PushMirrorAuth .push_mirror_username}}open{{end}}>
@ -211,7 +213,6 @@
<label for="push_mirror_username">{{.i18n.Tr "username"}}</label> <label for="push_mirror_username">{{.i18n.Tr "username"}}</label>
<input id="push_mirror_username" name="push_mirror_username" value="{{.push_mirror_username}}"> <input id="push_mirror_username" name="push_mirror_username" value="{{.push_mirror_username}}">
</div> </div>
<input class="fake" type="password">
<div class="inline field {{if .Err_PushMirrorAuth}}error{{end}}"> <div class="inline field {{if .Err_PushMirrorAuth}}error{{end}}">
<label for="push_mirror_password">{{.i18n.Tr "password"}}</label> <label for="push_mirror_password">{{.i18n.Tr "password"}}</label>
<input id="push_mirror_password" name="push_mirror_password" type="password" value="{{.push_mirror_password}}" autocomplete="off"> <input id="push_mirror_password" name="push_mirror_password" type="password" value="{{.push_mirror_password}}" autocomplete="off">

View file

@ -1,6 +1,7 @@
{{if eq .HookType "gitea"}} {{if eq .HookType "gitea"}}
<p>{{.i18n.Tr "repo.settings.add_webhook_desc" "https://docs.gitea.io/en-us/webhooks/" | Str2html}}</p> <p>{{.i18n.Tr "repo.settings.add_webhook_desc" "https://docs.gitea.io/en-us/webhooks/" | Str2html}}</p>
<form class="ui form" action="{{.BaseLink}}/gitea/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/gitea/{{or .Webhook.ID "new"}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">
<label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
@ -30,7 +31,6 @@
</div> </div>
</div> </div>
</div> </div>
<input class="fake" type="password">
<div class="field {{if .Err_Secret}}error{{end}}"> <div class="field {{if .Err_Secret}}error{{end}}">
<label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label>
<input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off">

View file

@ -1,6 +1,7 @@
{{if eq .HookType "gogs"}} {{if eq .HookType "gogs"}}
<p>{{.i18n.Tr "repo.settings.add_webhook_desc" "https://docs.gitea.io/en-us/webhooks/" | Str2html}}</p> <p>{{.i18n.Tr "repo.settings.add_webhook_desc" "https://docs.gitea.io/en-us/webhooks/" | Str2html}}</p>
<form class="ui form" action="{{.BaseLink}}/gogs/{{or .Webhook.ID "new"}}" method="post"> <form class="ui form" action="{{.BaseLink}}/gogs/{{or .Webhook.ID "new"}}" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required field {{if .Err_PayloadURL}}error{{end}}"> <div class="required field {{if .Err_PayloadURL}}error{{end}}">
<label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label> <label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
@ -18,7 +19,6 @@
</div> </div>
</div> </div>
</div> </div>
<input class="fake" type="password">
<div class="field {{if .Err_Secret}}error{{end}}"> <div class="field {{if .Err_Secret}}error{{end}}">
<label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label>
<input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off">

View file

@ -9,6 +9,7 @@
<div class="ui attached segment"> <div class="ui attached segment">
{{if or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2)}} {{if or (.SignedUser.IsLocal) (.SignedUser.IsOAuth2)}}
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/user/settings/account" method="post"> <form class="ui form ignore-dirty" action="{{AppSubUrl}}/user/settings/account" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
{{if .SignedUser.IsPasswordSet}} {{if .SignedUser.IsPasswordSet}}
<div class="required field {{if .Err_OldPassword}}error{{end}}"> <div class="required field {{if .Err_OldPassword}}error{{end}}">
@ -178,8 +179,8 @@
{{ end }} {{ end }}
</div> </div>
<form class="ui form ignore-dirty" id="delete-form" action="{{AppSubUrl}}/user/settings/account/delete" method="post"> <form class="ui form ignore-dirty" id="delete-form" action="{{AppSubUrl}}/user/settings/account/delete" method="post">
{{template "base/disable_form_autofill"}}
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input class="fake" type="password">
<div class="required field {{if .Err_Password}}error{{end}}"> <div class="required field {{if .Err_Password}}error{{end}}">
<label for="password-confirmation">{{.i18n.Tr "password"}}</label> <label for="password-confirmation">{{.i18n.Tr "password"}}</label>
<input id="password-confirmation" name="password" type="password" autocomplete="off" required> <input id="password-confirmation" name="password" type="password" autocomplete="off" required>

View file

@ -962,10 +962,13 @@ a.ui.card:hover,
} }
.form { .form {
.fake { .autofill-dummy {
display: none !important; position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
z-index: -10000;
} }
.sub.field { .sub.field {
margin-left: 25px; margin-left: 25px;
} }