@@ -639,7 +639,6 @@ func TestDeleteOldAuditLogConnectionEventsLimit(t *testing.T) {
639
639
640
640
require .Len (t ,logs ,0 )
641
641
}
642
-
643
642
func TestExpireOldAPIKeys (t * testing.T ) {
644
643
t .Parallel ()
645
644
@@ -704,3 +703,216 @@ func TestExpireOldAPIKeys(t *testing.T) {
704
703
// Out of an abundance of caution, we do not expire explicitly named prebuilds API keys.
705
704
assertKeyActive (namedPrebuildsAPIKey .ID )
706
705
}
706
+
707
+ //nolint:paralleltest // It uses LockIDDBPurge.
708
+ func TestDeleteExpiredOAuth2ProviderAppCodes (t * testing.T ) {
709
+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitShort )
710
+ defer cancel ()
711
+
712
+ clk := quartz .NewMock (t )
713
+ db ,_ := dbtestutil .NewDB (t ,dbtestutil .WithDumpOnFailure ())
714
+ logger := slogtest .Make (t ,& slogtest.Options {IgnoreErrors :true })
715
+
716
+ now := dbtime .Now ()
717
+ clk .Set (now ).MustWait (ctx )
718
+
719
+ // Create test data
720
+ user := dbgen .User (t ,db , database.User {})
721
+ app := dbgen .OAuth2ProviderApp (t ,db , database.OAuth2ProviderApp {
722
+ Name :fmt .Sprintf ("test-codes-%d" ,time .Now ().UnixNano ()),
723
+ })
724
+
725
+ // Create expired authorization code (should be deleted)
726
+ expiredCode := dbgen .OAuth2ProviderAppCode (t ,db , database.OAuth2ProviderAppCode {
727
+ ExpiresAt :now .Add (- 1 * time .Hour ),// Expired 1 hour ago
728
+ AppID :app .ID ,
729
+ UserID :user .ID ,
730
+ SecretPrefix : []byte (fmt .Sprintf ("expired-%d" ,time .Now ().UnixNano ())),
731
+ })
732
+
733
+ // Create non-expired authorization code (should be retained)
734
+ validCode := dbgen .OAuth2ProviderAppCode (t ,db , database.OAuth2ProviderAppCode {
735
+ ExpiresAt :now .Add (1 * time .Hour ),// Expires in 1 hour
736
+ AppID :app .ID ,
737
+ UserID :user .ID ,
738
+ SecretPrefix : []byte (fmt .Sprintf ("valid-%d" ,time .Now ().UnixNano ())),
739
+ })
740
+
741
+ // Verify codes exist initially
742
+ _ ,err := db .GetOAuth2ProviderAppCodeByID (ctx ,expiredCode .ID )
743
+ require .NoError (t ,err )
744
+ _ ,err = db .GetOAuth2ProviderAppCodeByID (ctx ,validCode .ID )
745
+ require .NoError (t ,err )
746
+
747
+ // Run cleanup
748
+ done := awaitDoTick (ctx ,t ,clk )
749
+ closer := dbpurge .New (ctx ,logger ,db ,clk )
750
+ defer closer .Close ()
751
+ <- done
752
+
753
+ // Verify expired code is deleted
754
+ _ ,err = db .GetOAuth2ProviderAppCodeByID (ctx ,expiredCode .ID )
755
+ require .Error (t ,err )
756
+ require .ErrorIs (t ,err ,sql .ErrNoRows )
757
+
758
+ // Verify non-expired code is retained
759
+ _ ,err = db .GetOAuth2ProviderAppCodeByID (ctx ,validCode .ID )
760
+ require .NoError (t ,err )
761
+ }
762
+
763
+ //nolint:paralleltest // It uses LockIDDBPurge.
764
+ func TestDeleteExpiredOAuth2ProviderAppTokens (t * testing.T ) {
765
+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitShort )
766
+ defer cancel ()
767
+
768
+ clk := quartz .NewMock (t )
769
+ db ,_ := dbtestutil .NewDB (t ,dbtestutil .WithDumpOnFailure ())
770
+ logger := slogtest .Make (t ,& slogtest.Options {IgnoreErrors :true })
771
+
772
+ now := dbtime .Now ()
773
+ clk .Set (now ).MustWait (ctx )
774
+
775
+ // Create test data
776
+ user := dbgen .User (t ,db , database.User {})
777
+ app := dbgen .OAuth2ProviderApp (t ,db , database.OAuth2ProviderApp {
778
+ Name :fmt .Sprintf ("test-tokens-%d" ,time .Now ().UnixNano ()),
779
+ })
780
+ appSecret := dbgen .OAuth2ProviderAppSecret (t ,db , database.OAuth2ProviderAppSecret {
781
+ AppID :app .ID ,
782
+ })
783
+
784
+ // Create API keys for the tokens
785
+ expiredAPIKey ,_ := dbgen .APIKey (t ,db , database.APIKey {
786
+ UserID :user .ID ,
787
+ ExpiresAt :now .Add (- 1 * time .Hour ),
788
+ })
789
+ validAPIKey ,_ := dbgen .APIKey (t ,db , database.APIKey {
790
+ UserID :user .ID ,
791
+ ExpiresAt :now .Add (24 * time .Hour ),// Valid for 24 hours
792
+ })
793
+
794
+ // Create expired access token (should be deleted)
795
+ expiredToken := dbgen .OAuth2ProviderAppToken (t ,db , database.OAuth2ProviderAppToken {
796
+ ExpiresAt :now .Add (- 1 * time .Hour ),// Expired 1 hour ago
797
+ AppSecretID :appSecret .ID ,
798
+ APIKeyID :expiredAPIKey .ID ,
799
+ UserID :user .ID ,
800
+ HashPrefix : []byte (fmt .Sprintf ("expired-%d" ,time .Now ().UnixNano ())),
801
+ })
802
+
803
+ // Create non-expired access token (should be retained)
804
+ validToken := dbgen .OAuth2ProviderAppToken (t ,db , database.OAuth2ProviderAppToken {
805
+ ExpiresAt :now .Add (1 * time .Hour ),// Expires in 1 hour
806
+ AppSecretID :appSecret .ID ,
807
+ APIKeyID :validAPIKey .ID ,
808
+ UserID :user .ID ,
809
+ HashPrefix : []byte (fmt .Sprintf ("valid-%d" ,time .Now ().UnixNano ())),
810
+ })
811
+
812
+ // Verify tokens exist initially
813
+ _ ,err := db .GetOAuth2ProviderAppTokenByPrefix (ctx ,expiredToken .HashPrefix )
814
+ require .NoError (t ,err )
815
+ _ ,err = db .GetOAuth2ProviderAppTokenByPrefix (ctx ,validToken .HashPrefix )
816
+ require .NoError (t ,err )
817
+
818
+ // Run cleanup
819
+ done := awaitDoTick (ctx ,t ,clk )
820
+ closer := dbpurge .New (ctx ,logger ,db ,clk )
821
+ defer closer .Close ()
822
+ <- done
823
+
824
+ // Verify expired token is deleted
825
+ _ ,err = db .GetOAuth2ProviderAppTokenByPrefix (ctx ,expiredToken .HashPrefix )
826
+ require .Error (t ,err )
827
+ require .ErrorIs (t ,err ,sql .ErrNoRows )
828
+
829
+ // Verify non-expired token is retained
830
+ _ ,err = db .GetOAuth2ProviderAppTokenByPrefix (ctx ,validToken .HashPrefix )
831
+ require .NoError (t ,err )
832
+ }
833
+
834
+ //nolint:paralleltest // It uses LockIDDBPurge.
835
+ func TestDeleteExpiredOAuth2ProviderDeviceCodes (t * testing.T ) {
836
+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitShort )
837
+ defer cancel ()
838
+
839
+ clk := quartz .NewMock (t )
840
+ db ,_ := dbtestutil .NewDB (t ,dbtestutil .WithDumpOnFailure ())
841
+ logger := slogtest .Make (t ,& slogtest.Options {IgnoreErrors :true })
842
+
843
+ now := dbtime .Now ()
844
+ clk .Set (now ).MustWait (ctx )
845
+
846
+ // Create test data
847
+ app := dbgen .OAuth2ProviderApp (t ,db , database.OAuth2ProviderApp {
848
+ Name :fmt .Sprintf ("test-device-%d" ,time .Now ().UnixNano ()),
849
+ })
850
+
851
+ nanoTime := time .Now ().UnixNano ()
852
+
853
+ // Create expired device code with pending status (should be deleted)
854
+ expiredPendingCode := dbgen .OAuth2ProviderDeviceCode (t ,db , database.OAuth2ProviderDeviceCode {
855
+ ExpiresAt :now .Add (- 1 * time .Hour ),// Expired 1 hour ago
856
+ ClientID :app .ID ,
857
+ Status :database .OAuth2DeviceStatusPending ,
858
+ DeviceCodePrefix :fmt .Sprintf ("EP%06d" ,nanoTime % 1000000 ),
859
+ UserCode :fmt .Sprintf ("EP%06d" ,nanoTime % 1000000 ),
860
+ DeviceCodeHash :fmt .Appendf (nil ,"hash-exp-pending-%d" ,nanoTime ),
861
+ })
862
+
863
+ // Create non-expired device code with pending status (should be retained)
864
+ validPendingCode := dbgen .OAuth2ProviderDeviceCode (t ,db , database.OAuth2ProviderDeviceCode {
865
+ ExpiresAt :now .Add (1 * time .Hour ),// Expires in 1 hour
866
+ ClientID :app .ID ,
867
+ Status :database .OAuth2DeviceStatusPending ,
868
+ DeviceCodePrefix :fmt .Sprintf ("VP%06d" , (nanoTime + 1 )% 1000000 ),
869
+ UserCode :fmt .Sprintf ("VP%06d" , (nanoTime + 1 )% 1000000 ),
870
+ DeviceCodeHash :fmt .Appendf (nil ,"hash-val-pending-%d" ,nanoTime + 1 ),
871
+ })
872
+
873
+ // Create expired device code with authorized status (should be deleted - all expired codes are deleted)
874
+ expiredAuthorizedCode := dbgen .OAuth2ProviderDeviceCode (t ,db , database.OAuth2ProviderDeviceCode {
875
+ ExpiresAt :now .Add (- 1 * time .Hour ),// Expired 1 hour ago
876
+ ClientID :app .ID ,
877
+ DeviceCodePrefix :fmt .Sprintf ("EA%06d" , (nanoTime + 2 )% 1000000 ),
878
+ UserCode :fmt .Sprintf ("EA%06d" , (nanoTime + 2 )% 1000000 ),
879
+ DeviceCodeHash :fmt .Appendf (nil ,"hash-exp-auth-%d" ,nanoTime + 2 ),
880
+ })
881
+
882
+ // Create a user and authorize the device code
883
+ user := dbgen .User (t ,db , database.User {})
884
+ expiredAuthorizedCode ,err := db .UpdateOAuth2ProviderDeviceCodeAuthorization (ctx , database.UpdateOAuth2ProviderDeviceCodeAuthorizationParams {
885
+ ID :expiredAuthorizedCode .ID ,
886
+ UserID : uuid.NullUUID {UUID :user .ID ,Valid :true },
887
+ Status :database .OAuth2DeviceStatusAuthorized ,
888
+ })
889
+ require .NoError (t ,err )
890
+
891
+ // Verify device codes exist initially
892
+ _ ,err = db .GetOAuth2ProviderDeviceCodeByID (ctx ,expiredPendingCode .ID )
893
+ require .NoError (t ,err )
894
+ _ ,err = db .GetOAuth2ProviderDeviceCodeByID (ctx ,validPendingCode .ID )
895
+ require .NoError (t ,err )
896
+ _ ,err = db .GetOAuth2ProviderDeviceCodeByID (ctx ,expiredAuthorizedCode .ID )
897
+ require .NoError (t ,err )
898
+
899
+ // Run cleanup
900
+ done := awaitDoTick (ctx ,t ,clk )
901
+ closer := dbpurge .New (ctx ,logger ,db ,clk )
902
+ defer closer .Close ()
903
+ <- done
904
+
905
+ // Verify expired pending device code is deleted
906
+ _ ,err = db .GetOAuth2ProviderDeviceCodeByID (ctx ,expiredPendingCode .ID )
907
+ require .Error (t ,err )
908
+ require .ErrorIs (t ,err ,sql .ErrNoRows )
909
+
910
+ // Verify non-expired pending device code is retained
911
+ _ ,err = db .GetOAuth2ProviderDeviceCodeByID (ctx ,validPendingCode .ID )
912
+ require .NoError (t ,err )
913
+
914
+ // Verify expired authorized device code is deleted (all expired codes are deleted)
915
+ _ ,err = db .GetOAuth2ProviderDeviceCodeByID (ctx ,expiredAuthorizedCode .ID )
916
+ require .Error (t ,err )
917
+ require .ErrorIs (t ,err ,sql .ErrNoRows )
918
+ }