@@ -661,3 +661,83 @@ def test_without_passkey_flag_unverified_user_is_blocked(self):
661661
662662 response = self .client .get (settings .LOGIN_REDIRECT_URL , follow = True )
663663 self .assertContains (response , "Permission Denied" )
664+
665+
666+ # ---------------------------------------------------------------------------
667+ # Production gate — passkeys disabled unless WEBAUTHN_RP_ID is set
668+ # ---------------------------------------------------------------------------
669+
670+
671+ @override_settings (DEBUG = False , WEBAUTHN_RP_ID = None , RATELIMIT_ENABLE = False )
672+ class TestPasskeysDisabledInProduction (TestCase ):
673+ """Without WEBAUTHN_RP_ID and DEBUG=False, all passkey views must 404."""
674+
675+ def setUp (self ):
676+ self .user = UserFactory ()
677+
678+ def test_auth_begin_returns_404 (self ):
679+ response = self .client .post (AUTH_BEGIN_URL )
680+ self .assertEqual (response .status_code , 404 )
681+
682+ def test_auth_complete_returns_404 (self ):
683+ response = self .client .post (
684+ AUTH_COMPLETE_URL ,
685+ data = json .dumps ({}),
686+ content_type = "application/json" ,
687+ )
688+ self .assertEqual (response .status_code , 404 )
689+
690+ def test_register_begin_returns_404 (self ):
691+ self .client .force_login (self .user )
692+ response = self .client .post (REGISTER_BEGIN_URL )
693+ self .assertEqual (response .status_code , 404 )
694+
695+ def test_register_complete_returns_404 (self ):
696+ self .client .force_login (self .user )
697+ response = self .client .post (
698+ REGISTER_COMPLETE_URL ,
699+ data = json .dumps ({}),
700+ content_type = "application/json" ,
701+ )
702+ self .assertEqual (response .status_code , 404 )
703+
704+ def test_passkey_list_returns_404 (self ):
705+ self .client .force_login (self .user )
706+ response = self .client .get (PASSKEY_LIST_URL )
707+ self .assertEqual (response .status_code , 404 )
708+
709+ def test_passkey_delete_returns_404 (self ):
710+ passkey = make_passkey (self .user )
711+ self .client .force_login (self .user )
712+ response = self .client .post (reverse ("users:passkey_delete" , args = [passkey .pk ]))
713+ self .assertEqual (response .status_code , 404 )
714+ self .assertTrue (Passkey .objects .filter (pk = passkey .pk ).exists ())
715+
716+ def test_passkey_rename_returns_404 (self ):
717+ passkey = make_passkey (self .user , name = "Original" )
718+ self .client .force_login (self .user )
719+ response = self .client .post (
720+ reverse ("users:passkey_rename" , args = [passkey .pk ]),
721+ {"name" : "New Name" },
722+ )
723+ self .assertEqual (response .status_code , 404 )
724+ passkey .refresh_from_db ()
725+ self .assertEqual (passkey .name , "Original" )
726+
727+
728+ @override_settings (DEBUG = True , WEBAUTHN_RP_ID = None , RATELIMIT_ENABLE = False )
729+ class TestPasskeysEnabledInDev (TestCase ):
730+ """DEBUG=True falls back to the request host even without WEBAUTHN_RP_ID."""
731+
732+ def test_auth_begin_works_in_debug_without_rp_id (self ):
733+ response = self .client .post (AUTH_BEGIN_URL )
734+ self .assertEqual (response .status_code , 200 )
735+
736+
737+ @override_settings (DEBUG = False , WEBAUTHN_RP_ID = "example.com" , RATELIMIT_ENABLE = False )
738+ class TestPasskeysEnabledWhenRpIdSet (TestCase ):
739+ """DEBUG=False with WEBAUTHN_RP_ID set enables passkeys in production."""
740+
741+ def test_auth_begin_works_in_production_with_rp_id (self ):
742+ response = self .client .post (AUTH_BEGIN_URL )
743+ self .assertEqual (response .status_code , 200 )
0 commit comments