1- {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
1+ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns -Wno-ambiguous-fields #-}
22
33-- This file is part of the Wire Server implementation.
44--
@@ -67,8 +67,10 @@ import Wire.Sem.Now (Now)
6767import Wire.Sem.Random (Random )
6868import Wire.SessionStore
6969import Wire.StoredUser
70+ import Wire.UserActivityStore (UserActivityStore )
7071import Wire.UserKeyStore
7172import Wire.UserStore
73+ import Wire.UserStore qualified as UserStore
7274import Wire.VerificationCode
7375import Wire.VerificationCodeGen
7476import Wire.VerificationCodeStore
@@ -99,38 +101,44 @@ type AllEffects =
99101 TinyLog ,
100102 EmailSubsystem ,
101103 UserStore ,
104+ UserActivityStore ,
105+ State (Map UserId UTCTime ),
102106 UserKeyStore ,
103107 State [MiniEvent ],
104108 State (Map EmailAddress [SentMail ]),
105109 State [StoredApp ]
106110 ]
107111
108112runAllEffects :: Domain -> [StoredUser ] -> Map UserId Password -> Maybe [Text ] -> Sem AllEffects a -> Either AuthenticationSubsystemError a
109- runAllEffects domain users passwords emailDomains action = snd $ runAllEffectsWithEventStateAndFeatures domain users passwords emailDomains def action
113+ runAllEffects domain users passwords emailDomains action = snd $ runAllEffectsWithEventStateAndFeatures domain users passwords emailDomains def Nothing action
110114
111115runAllEffectsWithEventState :: Domain -> [StoredUser ] -> Map UserId Password -> Maybe [Text ] -> Sem AllEffects a -> ([MiniEvent ], Either AuthenticationSubsystemError a )
112116runAllEffectsWithEventState localDomain preexistingUsers preexistingPasswords mAllowedEmailDomains =
113- runAllEffectsWithEventStateAndFeatures localDomain preexistingUsers preexistingPasswords mAllowedEmailDomains def
117+ runAllEffectsWithEventStateAndFeatures localDomain preexistingUsers preexistingPasswords mAllowedEmailDomains def Nothing
114118
115119runAllEffectsWithEventStateAndFeatures ::
116120 Domain ->
117121 [StoredUser ] ->
118122 Map UserId Password ->
119123 Maybe [Text ] ->
120124 AllTeamFeatures ->
125+ Maybe NominalDiffTime ->
121126 Sem AllEffects a ->
122127 ([MiniEvent ], Either AuthenticationSubsystemError a )
123- runAllEffectsWithEventStateAndFeatures localDomain preexistingUsers preexistingPasswords mAllowedEmailDomains galleyFeatures =
128+ runAllEffectsWithEventStateAndFeatures localDomain preexistingUsers preexistingPasswords mAllowedEmailDomains galleyFeatures mSuspendTimeout =
124129 let cfg =
125130 defaultAuthenticationSubsystemConfig
126131 { allowlistEmailDomains = AllowlistEmailDomains <$> mAllowedEmailDomains,
127- local = toLocalUnsafe localDomain ()
132+ local = toLocalUnsafe localDomain () ,
133+ suspendInactiveUsersTimeout = mSuspendTimeout
128134 }
129135 in run
130136 . evalState mempty
131137 . evalState mempty
132138 . runState mempty
133139 . runInMemoryUserKeyStoreIntepreterWithStoredUsers preexistingUsers
140+ . evalState (mempty :: Map UserId UTCTime )
141+ . inMemoryUserActivityStoreInterpreter
134142 . runInMemoryUserStoreInterpreter preexistingUsers preexistingPasswords
135143 . inMemoryEmailSubsystemInterpreter
136144 . discardTinyLogs
@@ -566,7 +574,7 @@ spec = describe "AuthenticationSubsystem.Interpreter" do
566574 luid = toLocalUnsafe testDomain user. id
567575 features = npUpdate @ SndFactorPasswordChallengeConfig (LockableFeature status LockStatusUnlocked def) def
568576 (_, Right result) =
569- runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing features $ do
577+ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing features Nothing $ do
570578 code <- createCodeOverwritePrevious (mk6DigitVerificationCodeGen email) (scopeFromAction action) 2 300 Nothing
571579 enforceVerificationCodeEither luid (Just code. codeValue) action
572580 in result === Right ()
@@ -577,7 +585,7 @@ spec = describe "AuthenticationSubsystem.Interpreter" do
577585 luid = toLocalUnsafe testDomain user. id
578586 features = npUpdate @ SndFactorPasswordChallengeConfig (LockableFeature status LockStatusUnlocked def) def
579587 (_, Right result) =
580- runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing features $ do
588+ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing features Nothing $ do
581589 _ <- createCodeOverwritePrevious (mk6DigitVerificationCodeGen email) (scopeFromAction action) 2 300 Nothing
582590 enforceVerificationCodeEither luid (Just wrongCode) action
583591 in if status == FeatureStatusEnabled
@@ -590,7 +598,7 @@ spec = describe "AuthenticationSubsystem.Interpreter" do
590598 luid = toLocalUnsafe testDomain user. id
591599 features = npUpdate @ SndFactorPasswordChallengeConfig (LockableFeature status LockStatusUnlocked def) def
592600 (_, Right result) =
593- runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing features $ do
601+ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing features Nothing $ do
594602 _ <- createCodeOverwritePrevious (mk6DigitVerificationCodeGen email) (scopeFromAction action) 2 300 Nothing
595603 enforceVerificationCodeEither luid Nothing action
596604 in if status == FeatureStatusEnabled
@@ -603,13 +611,69 @@ spec = describe "AuthenticationSubsystem.Interpreter" do
603611 luid = toLocalUnsafe testDomain user. id
604612 features = npUpdate @ SndFactorPasswordChallengeConfig (LockableFeature status LockStatusUnlocked def) def
605613 (_, Right result) =
606- runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing features $ do
614+ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing features Nothing $ do
607615 code <- createCodeOverwritePrevious (mk6DigitVerificationCodeGen email) (scopeFromAction action) 2 300 Nothing
608616 enforceVerificationCodeEither luid (Just code. codeValue) action
609617 in if status == FeatureStatusEnabled
610618 then result === Left VerificationCodeNoEmail
611619 else result === Right ()
612620
621+ describe " checkAndSuspendInactiveUser" do
622+ let timeout = 3600 :: NominalDiffTime
623+
624+ prop " suspends user after timeout expires" $ \ userNoEmail ->
625+ let user = (userNoEmail :: StoredUser ) {status = Just Active }
626+ uid = user. id
627+ Right finalStatus =
628+ snd $ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing def (Just timeout) $ do
629+ recordUserActivity uid
630+ passTime (timeout + 1 )
631+ _ <- checkAndSuspendInactiveUser uid False
632+ UserStore. lookupStatus uid
633+ in finalStatus === Just Suspended
634+
635+ prop " returns Left when inactive" $ \ userNoEmail ->
636+ let user = (userNoEmail :: StoredUser ) {status = Just Active }
637+ uid = user. id
638+ Right result =
639+ snd $ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing def (Just timeout) $ do
640+ recordUserActivity uid
641+ passTime (timeout + 1 )
642+ checkAndSuspendInactiveUser uid False
643+ in result === Left False
644+
645+ prop " does not suspend user within timeout" $ \ userNoEmail ->
646+ let user = (userNoEmail :: StoredUser ) {status = Just Active }
647+ uid = user. id
648+ Right finalStatus =
649+ snd $ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing def (Just timeout) $ do
650+ recordUserActivity uid
651+ passTime (timeout - 1 )
652+ _ <- checkAndSuspendInactiveUser uid False
653+ UserStore. lookupStatus uid
654+ in finalStatus === Just Active
655+
656+ prop " does not suspend if feature is disabled" $ \ userNoEmail ->
657+ let user = (userNoEmail :: StoredUser ) {status = Just Active }
658+ uid = user. id
659+ Right finalStatus =
660+ snd $ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing def Nothing $ do
661+ recordUserActivity uid
662+ passTime (timeout + 1 )
663+ _ <- checkAndSuspendInactiveUser uid False
664+ UserStore. lookupStatus uid
665+ in finalStatus === Just Active
666+
667+ prop " does not suspend if no activity record exists" $ \ userNoEmail ->
668+ let user = (userNoEmail :: StoredUser ) {status = Just Active }
669+ uid = user. id
670+ Right finalStatus =
671+ snd $ runAllEffectsWithEventStateAndFeatures testDomain [user] mempty Nothing def (Just timeout) $ do
672+ passTime (timeout + 1 )
673+ _ <- checkAndSuspendInactiveUser uid False
674+ UserStore. lookupStatus uid
675+ in finalStatus === Just Active
676+
613677 describe " randomConnId" $ do
614678 it " generates different connection ids" $ do
615679 let connIds = run . runRandomPure $ replicateM 100 randomConnId
0 commit comments