Repository: pypi/warehouse

Issue:
Improve 2fa instructions
### On the webauthn provisioning page

- "Webauthn" is not commonly understood. Instead, refer to webauthn as "phyiscal security key (e.g. USB)".  Include examples of key brands (e.g. "Yubikey") in instructional text
- Avoid using 'provisioning'.  Instead use 'set up'
- "Name key" is not a clear enough input label. Add an instruction above, or rename the label to "Enter key name to begin"
- Remove 'How it works' session and replace with set up guidelines
- Add 'troubleshooting' callout block - something like "Not working? You may be using an old USB security key that's not FIDO compatible (the standard supported by PyPI)."
- Add/fix link to help page
- Consider adding image of USB to page to give visual instruction

### On the TOTP provisioning page

- "TOTP application" should be referred to as "Authentication application"
- Avoid using 'provisioning'.  Instead use 'set up'
- Remove 'How it works' session and replace with set up guidelines
- Add list of application names to page (e.g. "Authy", "Google Auth" etc.)
- Consider adding image of phone/app to page to give visual instruction
- Add/fix link to help page


Discussion:

Resolving patch:
diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py
--- a/warehouse/manage/views.py
+++ b/warehouse/manage/views.py
@@ -429,7 +429,7 @@ def delete_totp(self):
         if form.validate():
             self.user_service.update_user(self.request.user.id, totp_secret=None)
             self.request.session.flash(
-                "Authentication application deleted from PyPI. "
+                "Authentication application removed from PyPI. "
                 "Remember to remove PyPI from your application.",
                 queue="success",
             )
@@ -502,9 +502,9 @@ def validate_webauthn_provision(self):
                 sign_count=form.validated_credential.sign_count,
             )
             self.request.session.flash(
-                "Physical security key successfully set up", queue="success"
+                "Security device successfully set up", queue="success"
             )
-            return {"success": "Physical security key successfully set up"}
+            return {"success": "Security device successfully set up"}
 
         errors = [
             str(error) for error_list in form.errors.values() for error in error_list
@@ -519,7 +519,7 @@ def validate_webauthn_provision(self):
     def delete_webauthn(self):
         if len(self.request.user.webauthn) == 0:
             self.request.session.flash(
-                "There is no physical security key to delete", queue="error"
+                "There is no security device to delete", queue="error"
             )
             return HTTPSeeOther(self.request.route_path("manage.account"))
 
@@ -532,7 +532,7 @@ def delete_webauthn(self):
 
         if form.validate():
             self.request.user.webauthn.remove(form.webauthn)
-            self.request.session.flash("Physical security key deleted", queue="success")
+            self.request.session.flash("Security device removed", queue="success")
         else:
             self.request.session.flash("Invalid credentials", queue="error")
 


Test patch:
diff --git a/tests/unit/manage/test_views.py b/tests/unit/manage/test_views.py
--- a/tests/unit/manage/test_views.py
+++ b/tests/unit/manage/test_views.py
@@ -1017,7 +1017,7 @@ def test_delete_totp(self, monkeypatch, db_request):
         ]
         assert request.session.flash.calls == [
             pretend.call(
-                "Authentication application deleted from PyPI. "
+                "Authentication application removed from PyPI. "
                 "Remember to remove PyPI from your application.",
                 queue="success",
             )
@@ -1206,9 +1206,9 @@ def test_validate_webauthn_provision(self, monkeypatch):
             )
         ]
         assert request.session.flash.calls == [
-            pretend.call("Physical security key successfully set up", queue="success")
+            pretend.call("Security device successfully set up", queue="success")
         ]
-        assert result == {"success": "Physical security key successfully set up"}
+        assert result == {"success": "Security device successfully set up"}
 
     def test_validate_webauthn_provision_invalid_form(self, monkeypatch):
         user_service = pretend.stub(
@@ -1275,7 +1275,7 @@ def test_delete_webauthn(self, monkeypatch):
         result = view.delete_webauthn()
 
         assert request.session.flash.calls == [
-            pretend.call("Physical security key deleted", queue="success")
+            pretend.call("Security device removed", queue="success")
         ]
         assert request.route_path.calls == [pretend.call("manage.account")]
         assert isinstance(result, HTTPSeeOther)
@@ -1293,7 +1293,7 @@ def test_delete_webauthn_not_provisioned(self):
         result = view.delete_webauthn()
 
         assert request.session.flash.calls == [
-            pretend.call("There is no physical security key to delete", queue="error")
+            pretend.call("There is no security device to delete", queue="error")
         ]
         assert request.route_path.calls == [pretend.call("manage.account")]
         assert isinstance(result, HTTPSeeOther)