@@ -635,3 +635,216 @@ func TestDeleteOldAuditLogConnectionEventsLimit(t *testing.T) {
635
635
636
636
require .Len (t , logs , 0 )
637
637
}
638
+
639
+ //nolint:paralleltest // It uses LockIDDBPurge.
640
+ func TestDeleteExpiredOAuth2ProviderAppCodes (t * testing.T ) {
641
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
642
+ defer cancel ()
643
+
644
+ clk := quartz .NewMock (t )
645
+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
646
+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
647
+
648
+ now := dbtime .Now ()
649
+ clk .Set (now ).MustWait (ctx )
650
+
651
+ // Create test data
652
+ user := dbgen .User (t , db , database.User {})
653
+ app := dbgen .OAuth2ProviderApp (t , db , database.OAuth2ProviderApp {
654
+ Name : fmt .Sprintf ("test-codes-%d" , time .Now ().UnixNano ()),
655
+ })
656
+
657
+ // Create expired authorization code (should be deleted)
658
+ expiredCode := dbgen .OAuth2ProviderAppCode (t , db , database.OAuth2ProviderAppCode {
659
+ ExpiresAt : now .Add (- 1 * time .Hour ), // Expired 1 hour ago
660
+ AppID : app .ID ,
661
+ UserID : user .ID ,
662
+ SecretPrefix : []byte (fmt .Sprintf ("expired-%d" , time .Now ().UnixNano ())),
663
+ })
664
+
665
+ // Create non-expired authorization code (should be retained)
666
+ validCode := dbgen .OAuth2ProviderAppCode (t , db , database.OAuth2ProviderAppCode {
667
+ ExpiresAt : now .Add (1 * time .Hour ), // Expires in 1 hour
668
+ AppID : app .ID ,
669
+ UserID : user .ID ,
670
+ SecretPrefix : []byte (fmt .Sprintf ("valid-%d" , time .Now ().UnixNano ())),
671
+ })
672
+
673
+ // Verify codes exist initially
674
+ _ , err := db .GetOAuth2ProviderAppCodeByID (ctx , expiredCode .ID )
675
+ require .NoError (t , err )
676
+ _ , err = db .GetOAuth2ProviderAppCodeByID (ctx , validCode .ID )
677
+ require .NoError (t , err )
678
+
679
+ // Run cleanup
680
+ done := awaitDoTick (ctx , t , clk )
681
+ closer := dbpurge .New (ctx , logger , db , clk )
682
+ defer closer .Close ()
683
+ <- done
684
+
685
+ // Verify expired code is deleted
686
+ _ , err = db .GetOAuth2ProviderAppCodeByID (ctx , expiredCode .ID )
687
+ require .Error (t , err )
688
+ require .ErrorIs (t , err , sql .ErrNoRows )
689
+
690
+ // Verify non-expired code is retained
691
+ _ , err = db .GetOAuth2ProviderAppCodeByID (ctx , validCode .ID )
692
+ require .NoError (t , err )
693
+ }
694
+
695
+ //nolint:paralleltest // It uses LockIDDBPurge.
696
+ func TestDeleteExpiredOAuth2ProviderAppTokens (t * testing.T ) {
697
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
698
+ defer cancel ()
699
+
700
+ clk := quartz .NewMock (t )
701
+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
702
+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
703
+
704
+ now := dbtime .Now ()
705
+ clk .Set (now ).MustWait (ctx )
706
+
707
+ // Create test data
708
+ user := dbgen .User (t , db , database.User {})
709
+ app := dbgen .OAuth2ProviderApp (t , db , database.OAuth2ProviderApp {
710
+ Name : fmt .Sprintf ("test-tokens-%d" , time .Now ().UnixNano ()),
711
+ })
712
+ appSecret := dbgen .OAuth2ProviderAppSecret (t , db , database.OAuth2ProviderAppSecret {
713
+ AppID : app .ID ,
714
+ })
715
+
716
+ // Create API keys for the tokens
717
+ expiredAPIKey , _ := dbgen .APIKey (t , db , database.APIKey {
718
+ UserID : user .ID ,
719
+ ExpiresAt : now .Add (- 1 * time .Hour ),
720
+ })
721
+ validAPIKey , _ := dbgen .APIKey (t , db , database.APIKey {
722
+ UserID : user .ID ,
723
+ ExpiresAt : now .Add (24 * time .Hour ), // Valid for 24 hours
724
+ })
725
+
726
+ // Create expired access token (should be deleted)
727
+ expiredToken := dbgen .OAuth2ProviderAppToken (t , db , database.OAuth2ProviderAppToken {
728
+ ExpiresAt : now .Add (- 1 * time .Hour ), // Expired 1 hour ago
729
+ AppSecretID : appSecret .ID ,
730
+ APIKeyID : expiredAPIKey .ID ,
731
+ UserID : user .ID ,
732
+ HashPrefix : []byte (fmt .Sprintf ("expired-%d" , time .Now ().UnixNano ())),
733
+ })
734
+
735
+ // Create non-expired access token (should be retained)
736
+ validToken := dbgen .OAuth2ProviderAppToken (t , db , database.OAuth2ProviderAppToken {
737
+ ExpiresAt : now .Add (1 * time .Hour ), // Expires in 1 hour
738
+ AppSecretID : appSecret .ID ,
739
+ APIKeyID : validAPIKey .ID ,
740
+ UserID : user .ID ,
741
+ HashPrefix : []byte (fmt .Sprintf ("valid-%d" , time .Now ().UnixNano ())),
742
+ })
743
+
744
+ // Verify tokens exist initially
745
+ _ , err := db .GetOAuth2ProviderAppTokenByPrefix (ctx , expiredToken .HashPrefix )
746
+ require .NoError (t , err )
747
+ _ , err = db .GetOAuth2ProviderAppTokenByPrefix (ctx , validToken .HashPrefix )
748
+ require .NoError (t , err )
749
+
750
+ // Run cleanup
751
+ done := awaitDoTick (ctx , t , clk )
752
+ closer := dbpurge .New (ctx , logger , db , clk )
753
+ defer closer .Close ()
754
+ <- done
755
+
756
+ // Verify expired token is deleted
757
+ _ , err = db .GetOAuth2ProviderAppTokenByPrefix (ctx , expiredToken .HashPrefix )
758
+ require .Error (t , err )
759
+ require .ErrorIs (t , err , sql .ErrNoRows )
760
+
761
+ // Verify non-expired token is retained
762
+ _ , err = db .GetOAuth2ProviderAppTokenByPrefix (ctx , validToken .HashPrefix )
763
+ require .NoError (t , err )
764
+ }
765
+
766
+ //nolint:paralleltest // It uses LockIDDBPurge.
767
+ func TestDeleteExpiredOAuth2ProviderDeviceCodes (t * testing.T ) {
768
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
769
+ defer cancel ()
770
+
771
+ clk := quartz .NewMock (t )
772
+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
773
+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
774
+
775
+ now := dbtime .Now ()
776
+ clk .Set (now ).MustWait (ctx )
777
+
778
+ // Create test data
779
+ app := dbgen .OAuth2ProviderApp (t , db , database.OAuth2ProviderApp {
780
+ Name : fmt .Sprintf ("test-device-%d" , time .Now ().UnixNano ()),
781
+ })
782
+
783
+ nanoTime := time .Now ().UnixNano ()
784
+
785
+ // Create expired device code with pending status (should be deleted)
786
+ expiredPendingCode := dbgen .OAuth2ProviderDeviceCode (t , db , database.OAuth2ProviderDeviceCode {
787
+ ExpiresAt : now .Add (- 1 * time .Hour ), // Expired 1 hour ago
788
+ ClientID : app .ID ,
789
+ Status : database .OAuth2DeviceStatusPending ,
790
+ DeviceCodePrefix : fmt .Sprintf ("EP%06d" , nanoTime % 1000000 ),
791
+ UserCode : fmt .Sprintf ("EP%06d" , nanoTime % 1000000 ),
792
+ DeviceCodeHash : fmt .Appendf (nil , "hash-exp-pending-%d" , nanoTime ),
793
+ })
794
+
795
+ // Create non-expired device code with pending status (should be retained)
796
+ validPendingCode := dbgen .OAuth2ProviderDeviceCode (t , db , database.OAuth2ProviderDeviceCode {
797
+ ExpiresAt : now .Add (1 * time .Hour ), // Expires in 1 hour
798
+ ClientID : app .ID ,
799
+ Status : database .OAuth2DeviceStatusPending ,
800
+ DeviceCodePrefix : fmt .Sprintf ("VP%06d" , (nanoTime + 1 )% 1000000 ),
801
+ UserCode : fmt .Sprintf ("VP%06d" , (nanoTime + 1 )% 1000000 ),
802
+ DeviceCodeHash : fmt .Appendf (nil , "hash-val-pending-%d" , nanoTime + 1 ),
803
+ })
804
+
805
+ // Create expired device code with authorized status (should be deleted - all expired codes are deleted)
806
+ expiredAuthorizedCode := dbgen .OAuth2ProviderDeviceCode (t , db , database.OAuth2ProviderDeviceCode {
807
+ ExpiresAt : now .Add (- 1 * time .Hour ), // Expired 1 hour ago
808
+ ClientID : app .ID ,
809
+ DeviceCodePrefix : fmt .Sprintf ("EA%06d" , (nanoTime + 2 )% 1000000 ),
810
+ UserCode : fmt .Sprintf ("EA%06d" , (nanoTime + 2 )% 1000000 ),
811
+ DeviceCodeHash : fmt .Appendf (nil , "hash-exp-auth-%d" , nanoTime + 2 ),
812
+ })
813
+
814
+ // Create a user and authorize the device code
815
+ user := dbgen .User (t , db , database.User {})
816
+ expiredAuthorizedCode , err := db .UpdateOAuth2ProviderDeviceCodeAuthorization (ctx , database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams {
817
+ ID : expiredAuthorizedCode .ID ,
818
+ UserID : uuid.NullUUID {UUID : user .ID , Valid : true },
819
+ Status : database .OAuth2DeviceStatusAuthorized ,
820
+ })
821
+ require .NoError (t , err )
822
+
823
+ // Verify device codes exist initially
824
+ _ , err = db .GetOAuth2ProviderDeviceCodeByID (ctx , expiredPendingCode .ID )
825
+ require .NoError (t , err )
826
+ _ , err = db .GetOAuth2ProviderDeviceCodeByID (ctx , validPendingCode .ID )
827
+ require .NoError (t , err )
828
+ _ , err = db .GetOAuth2ProviderDeviceCodeByID (ctx , expiredAuthorizedCode .ID )
829
+ require .NoError (t , err )
830
+
831
+ // Run cleanup
832
+ done := awaitDoTick (ctx , t , clk )
833
+ closer := dbpurge .New (ctx , logger , db , clk )
834
+ defer closer .Close ()
835
+ <- done
836
+
837
+ // Verify expired pending device code is deleted
838
+ _ , err = db .GetOAuth2ProviderDeviceCodeByID (ctx , expiredPendingCode .ID )
839
+ require .Error (t , err )
840
+ require .ErrorIs (t , err , sql .ErrNoRows )
841
+
842
+ // Verify non-expired pending device code is retained
843
+ _ , err = db .GetOAuth2ProviderDeviceCodeByID (ctx , validPendingCode .ID )
844
+ require .NoError (t , err )
845
+
846
+ // Verify expired authorized device code is deleted (all expired codes are deleted)
847
+ _ , err = db .GetOAuth2ProviderDeviceCodeByID (ctx , expiredAuthorizedCode .ID )
848
+ require .Error (t , err )
849
+ require .ErrorIs (t , err , sql .ErrNoRows )
850
+ }
0 commit comments