@@ -8,13 +8,15 @@ namespace NSchemeShard {
8
8
9
9
using namespace NTabletFlatExecutor ;
10
10
11
- struct TSchemeShard ::TTxLogin : TSchemeShard::TRwTxBase {
11
+ struct TSchemeShard ::TTxLogin : TTransactionBase< TSchemeShard> {
12
12
TEvSchemeShard::TEvLogin::TPtr Request;
13
13
TPathId SubDomainPathId;
14
14
bool NeedPublishOnComplete = false ;
15
+ THolder<TEvSchemeShard::TEvLoginResult> Result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
16
+ size_t CurrentFailedAttemptCount = 0 ;
15
17
16
18
TTxLogin (TSelf *self, TEvSchemeShard::TEvLogin::TPtr &ev)
17
- : TRwTxBase (self)
19
+ : TTransactionBase<TSchemeShard> (self)
18
20
, Request(std::move(ev))
19
21
{}
20
22
@@ -34,10 +36,11 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
34
36
};
35
37
}
36
38
37
- void DoExecute (TTransactionContext& txc, const TActorContext& ctx) override {
39
+ bool Execute (TTransactionContext& txc, const TActorContext& ctx) override {
38
40
LOG_DEBUG_S (ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
39
- " TTxLogin DoExecute "
41
+ " TTxLogin Execute "
40
42
<< " at schemeshard: " << Self->TabletID ());
43
+ NIceDb::TNiceDb db (txc.DB );
41
44
if (Self->LoginProvider .IsItTimeToRotateKeys ()) {
42
45
LOG_DEBUG_S (ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, " TTxLogin RotateKeys at schemeshard: " << Self->TabletID ());
43
46
std::vector<ui64> keysExpired;
@@ -50,7 +53,6 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
50
53
domainPtr->UpdateSecurityState (Self->LoginProvider .GetSecurityState ());
51
54
domainPtr->IncSecurityStateVersion ();
52
55
53
- NIceDb::TNiceDb db (txc.DB );
54
56
55
57
Self->PersistSubDomainSecurityStateVersion (db, SubDomainPathId, *domainPtr);
56
58
@@ -67,37 +69,130 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
67
69
68
70
NeedPublishOnComplete = true ;
69
71
}
72
+
73
+ return LoginAttempt (db, ctx);
70
74
}
71
75
72
- void DoComplete (const TActorContext &ctx) override {
76
+ void Complete (const TActorContext &ctx) override {
73
77
if (NeedPublishOnComplete) {
74
78
Self->PublishToSchemeBoard (TTxId (), {SubDomainPathId}, ctx);
75
79
}
76
80
77
- THolder<TEvSchemeShard::TEvLoginResult> result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
81
+ LOG_DEBUG_S (ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
82
+ " TTxLogin Complete"
83
+ << " , result: " << Result->Record .ShortDebugString ()
84
+ << " , at schemeshard: " << Self->TabletID ());
85
+
86
+ ctx.Send (Request->Sender , std::move (Result), 0 , Request->Cookie );
87
+ }
88
+
89
+ private:
90
+ bool LoginAttempt (NIceDb::TNiceDb& db, const TActorContext& ctx) {
78
91
const auto & loginRequest = GetLoginRequest ();
79
- if (loginRequest.ExternalAuth || AppData (ctx)->AuthConfig .GetEnableLoginAuthentication ()) {
80
- NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider .LoginUser (loginRequest);
81
- if (loginResponse.Error ) {
82
- result->Record .SetError (loginResponse.Error );
83
- }
84
- if (loginResponse.Token ) {
85
- result->Record .SetToken (loginResponse.Token );
86
- result->Record .SetSanitizedToken (loginResponse.SanitizedToken );
92
+ if (!loginRequest.ExternalAuth && !AppData (ctx)->AuthConfig .GetEnableLoginAuthentication ()) {
93
+ Result->Record .SetError (" Login authentication is disabled" );
94
+ return true ;
95
+ }
96
+ if (loginRequest.ExternalAuth ) {
97
+ return HandleExternalAuth (loginRequest);
98
+ }
99
+ return HandleLoginAuth (loginRequest, db, ctx);
100
+ }
101
+
102
+ bool HandleExternalAuth (const NLogin::TLoginProvider::TLoginUserRequest& loginRequest) {
103
+ const NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider .LoginUser (loginRequest);
104
+ switch (loginResponse.Status ) {
105
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: {
106
+ Result->Record .SetToken (loginResponse.Token );
107
+ Result->Record .SetSanitizedToken (loginResponse.SanitizedToken );
108
+ break ;
109
+ }
110
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD:
111
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER:
112
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY:
113
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: {
114
+ Result->Record .SetError (loginResponse.Error );
115
+ break ;
116
+ }
117
+ }
118
+ return true ;
119
+ }
120
+
121
+ bool HandleLoginAuth (const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db, const TActorContext& ctx) {
122
+ auto row = db.Table <Schema::LoginSids>().Key (loginRequest.User ).Select ();
123
+ if (!row.IsReady ()) {
124
+ return false ;
125
+ }
126
+ if (!row.IsValid ()) {
127
+ Result->Record .SetError (TStringBuilder () << " Cannot find user: " << loginRequest.User );
128
+ return true ;
129
+ }
130
+ CurrentFailedAttemptCount = row.GetValueOrDefault <Schema::LoginSids::FailedAttemptCount>();
131
+ TInstant lastFailedAttempt = TInstant::FromValue (row.GetValue <Schema::LoginSids::LastFailedAttempt>());
132
+ if (CheckAccountLockout ()) {
133
+ if (ShouldUnlockAccount (lastFailedAttempt)) {
134
+ UnlockAccount (loginRequest, db);
135
+ } else {
136
+ Result->Record .SetError (TStringBuilder () << " User " << loginRequest.User << " is locked out" );
137
+ return true ;
87
138
}
139
+ } else if (ShouldResetFailedAttemptCount (lastFailedAttempt)) {
140
+ ResetFailedAttemptCount (loginRequest, db);
141
+ }
142
+ const NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider .LoginUser (loginRequest);
143
+ switch (loginResponse.Status ) {
144
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: {
145
+ HandleLoginAuthSuccess (loginRequest, loginResponse, db);
146
+ Result->Record .SetToken (loginResponse.Token );
147
+ Result->Record .SetSanitizedToken (loginResponse.SanitizedToken );
148
+ break ;
149
+ }
150
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: {
151
+ HandleLoginAuthInvalidPassword (loginRequest, loginResponse, db);
152
+ Result->Record .SetError (loginResponse.Error );
153
+ break ;
154
+ }
155
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER:
156
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY:
157
+ case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: {
158
+ Result->Record .SetError (loginResponse.Error );
159
+ break ;
160
+ }
161
+ }
162
+ return true ;
163
+ }
164
+
165
+ bool CheckAccountLockout () const {
166
+ return (Self->AccountLockout .AttemptThreshold != 0 && CurrentFailedAttemptCount >= Self->AccountLockout .AttemptThreshold );
167
+ }
88
168
89
- } else {
90
- result->Record .SetError (" Login authentication is disabled" );
169
+ bool ShouldResetFailedAttemptCount (const TInstant& lastFailedAttempt) {
170
+ if (Self->AccountLockout .AttemptResetDuration == TDuration::Zero ()) {
171
+ return false ;
91
172
}
173
+ return lastFailedAttempt + Self->AccountLockout .AttemptResetDuration < TAppData::TimeProvider->Now ();
174
+ }
92
175
93
- LOG_DEBUG_S (ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
94
- " TTxLogin DoComplete"
95
- << " , result: " << result->Record .ShortDebugString ()
96
- << " , at schemeshard: " << Self->TabletID ());
176
+ bool ShouldUnlockAccount (const TInstant& lastFailedAttempt) {
177
+ return ShouldResetFailedAttemptCount (lastFailedAttempt);
178
+ }
179
+
180
+ void ResetFailedAttemptCount (const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) {
181
+ db.Table <Schema::LoginSids>().Key (loginRequest.User ).Update <Schema::LoginSids::FailedAttemptCount>(Schema::LoginSids::FailedAttemptCount::Default);
182
+ CurrentFailedAttemptCount = Schema::LoginSids::FailedAttemptCount::Default;
183
+ }
184
+
185
+ void UnlockAccount (const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) {
186
+ ResetFailedAttemptCount (loginRequest, db);
187
+ }
97
188
98
- ctx.Send (Request->Sender , std::move (result), 0 , Request->Cookie );
189
+ void HandleLoginAuthSuccess (const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, const NLogin::TLoginProvider::TLoginUserResponse& loginResponse, NIceDb::TNiceDb& db) {
190
+ db.Table <Schema::LoginSids>().Key (loginRequest.User ).Update <Schema::LoginSids::LastSuccessfulAttempt, Schema::LoginSids::FailedAttemptCount>(TAppData::TimeProvider->Now ().MicroSeconds (), Schema::LoginSids::FailedAttemptCount::Default);
99
191
}
100
192
193
+ void HandleLoginAuthInvalidPassword (const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, const NLogin::TLoginProvider::TLoginUserResponse& loginResponse, NIceDb::TNiceDb& db) {
194
+ db.Table <Schema::LoginSids>().Key (loginRequest.User ).Update <Schema::LoginSids::LastFailedAttempt, Schema::LoginSids::FailedAttemptCount>(TAppData::TimeProvider->Now ().MicroSeconds (), CurrentFailedAttemptCount + 1 );
195
+ }
101
196
};
102
197
103
198
NTabletFlatExecutor::ITransaction* TSchemeShard::CreateTxLogin (TEvSchemeShard::TEvLogin::TPtr &ev) {
0 commit comments