@@ -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
+ }