Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
AuthenticationService.cs
1using System;
3using System.Globalization;
4using System.Linq;
5using System.Text;
6using System.Threading.Tasks;
7using Microsoft.Extensions.Localization;
14using Waher.Events;
16using Waher.Security;
20
22{
23 [Singleton]
24 public class AuthenticationService : IAuthenticationService, IDisposable, IAsyncDisposable
25 {
26 private readonly ITagProfile tagProfile;
27 private readonly IUiService uiService;
28 private readonly IPopupService popupService;
29 private readonly IPlatformSpecific platformSpecific;
30 private readonly ISettingsService settingsService;
31 private readonly IStringLocalizer localizer;
32 private readonly LoginAuditor loginAuditor;
33
34 private DateTime lastAuthenticationTime = DateTime.MinValue;
35 private DateTime savedStartTime = DateTime.MinValue;
36 private bool displayedPasswordPopup = false;
37
38 private static readonly LoginInterval[] loginIntervals =
39 [
43 ];
44
46 {
47 this.tagProfile = ServiceRef.Provider.GetRequiredService<TagProfile>();
48
49 this.uiService = ServiceRef.Provider.GetRequiredService<IUiService>();
50 this.popupService = ServiceRef.Provider.GetRequiredService<IPopupService>();
51 this.platformSpecific = ServiceRef.Provider.GetRequiredService<IPlatformSpecific>();
52 this.loginAuditor = new LoginAuditor(Constants.Password.LogAuditorObjectID, loginIntervals);
53 this.settingsService = ServiceRef.Provider.GetRequiredService<ISettingsService>();
54 this.localizer = ServiceRef.Localizer;
55 }
56
60 public async Task<string?> InputPasswordAsync(AuthenticationPurpose purpose)
61 {
62 if (!this.tagProfile.HasLocalPassword)
63 return string.Empty;
64
65 return await this.InputPasswordInternalAsync(purpose);
66 }
67
68 private async Task<string?> InputPasswordInternalAsync(AuthenticationPurpose purpose)
69 {
70 this.displayedPasswordPopup = true;
71 try
72 {
73 if (!this.tagProfile.HasLocalPassword)
74 return string.Empty;
75
76 CheckPasswordViewModel ViewModel = new(purpose);
77 string? Result = await this.popupService.PushAsync<CheckPasswordPopup, CheckPasswordViewModel, string>(ViewModel, new PopupOptions
78 {
79 CloseOnBackButton = true,
80 CloseOnBackgroundTap = true
81 });
82 await this.CheckUserBlockingAsync();
83 return Result;
84 }
85 catch
86 {
87 return null;
88 }
89 finally
90 {
91 this.displayedPasswordPopup = false;
92 }
93 }
94
98 public Task<bool> AuthenticateUserAsync(AuthenticationPurpose purpose, bool force = false)
99 {
100 // MainThread is needed for Maui UI operations
101 TaskCompletionSource<bool> Tcs = new TaskCompletionSource<bool>();
102
103 MainThread.BeginInvokeOnMainThread(async () =>
104 {
105 bool Result = await this.AuthenticateUserOnMainThreadAsync(purpose, force);
106 if (Result)
107 this.lastAuthenticationTime = DateTime.UtcNow;
108 Tcs.TrySetResult(Result);
109 });
110 return Tcs.Task;
111 }
112
113 private async Task<bool> AuthenticateUserOnMainThreadAsync(AuthenticationPurpose purpose, bool force = false)
114 {
115 bool needToVerifyPassword = this.IsInactivitySafeIntervalPassed();
116 if (!force && !needToVerifyPassword)
117 return true;
118
119 switch (this.tagProfile.AuthenticationMethod)
120 {
121 case AuthenticationMethod.Fingerprint:
122 if (!this.platformSpecific.SupportsFingerprintAuthentication)
123 this.tagProfile.AuthenticationMethod = AuthenticationMethod.Password;
124
125 if (await this.platformSpecific.AuthenticateUserFingerprint(
126 this.localizer[nameof(AppResources.FingerprintTitle)],
127 null,
128 this.localizer[nameof(AppResources.FingerprintDescription)],
129 this.localizer[nameof(AppResources.Cancel)],
130 null))
131 {
132 await this.UserAuthenticationSuccessfulAsync();
133 return true;
134 }
135 goto case AuthenticationMethod.Password;
136
137 case AuthenticationMethod.Password:
138 if (!this.tagProfile.HasLocalPassword)
139 return true;
140
141 if (this.displayedPasswordPopup)
142 return false;
143
144 return await this.InputPasswordInternalAsync(purpose) is not null;
145
146 default:
147 return false;
148 }
149 }
150
154 public async Task CheckUserBlockingAsync()
155 {
156 DateTime? nextLoginTime = await this.loginAuditor.GetEarliestLoginOpportunity(Constants.Password.RemoteEndpoint, Constants.Password.Protocol);
157 if (nextLoginTime.HasValue)
158 {
159 string messageAlert;
160 if (nextLoginTime == DateTime.MaxValue)
161 {
162 messageAlert = this.localizer[nameof(AppResources.PasswordIsInvalidAplicationBlockedForever)];
163 }
164 else
165 {
166 DateTime localDateTime = nextLoginTime.Value.ToLocalTime();
167 if (nextLoginTime.Value.ToShortDateString() == DateTime.Today.ToShortDateString())
168 {
169 string dateString = localDateTime.ToShortTimeString();
170 messageAlert = this.localizer[nameof(AppResources.PasswordIsInvalidAplicationBlocked), dateString];
171 }
172 else if (nextLoginTime.Value.ToShortDateString() == DateTime.Today.AddDays(1).ToShortDateString())
173 {
174 string dateString = localDateTime.ToShortTimeString();
175 messageAlert = this.localizer[nameof(AppResources.PasswordIsInvalidAplicationBlockedTillTomorrow), dateString];
176 }
177 else
178 {
179 string dateString = localDateTime.ToString("yyyy-MM-dd, 'at' HH:mm", CultureInfo.InvariantCulture);
180 messageAlert = this.localizer[nameof(AppResources.PasswordIsInvalidAplicationBlocked), dateString];
181 }
182 }
183
184 await this.uiService.DisplayAlert(this.localizer[nameof(AppResources.ErrorTitle)], messageAlert);
185 await this.CloseApplicationAsync();
186 }
187 }
188
192 public async Task<bool> CheckPasswordAndUnblockUserAsync(string password)
193 {
194 if (password is null)
195 return false;
196
197 if (this.tagProfile.ComputePasswordHash(password) == this.tagProfile.LocalPasswordHash)
198 {
199 await this.UserAuthenticationSuccessfulAsync();
200 return true;
201 }
202 else
203 {
204 await this.UserAuthenticationFailedAsync();
205 return false;
206 }
207 }
208
209 private async Task UserAuthenticationSuccessfulAsync()
210 {
211 this.SetStartInactivityTime();
212 await this.SetCurrentPasswordCounterAsync(0);
213 await this.loginAuditor.UnblockAndReset(Constants.Password.RemoteEndpoint);
214 }
215
216 private async Task UserAuthenticationFailedAsync()
217 {
218 await this.loginAuditor.ProcessLoginFailure(Constants.Password.RemoteEndpoint, Constants.Password.Protocol, DateTime.UtcNow, Constants.Password.Reason);
219 long CurrentCounter = await this.GetCurrentPasswordCounterAsync();
220 CurrentCounter++;
221 await this.SetCurrentPasswordCounterAsync(CurrentCounter);
222 }
223
224 private void SetStartInactivityTime()
225 {
226 this.savedStartTime = DateTime.UtcNow;
227 }
228
229 private bool IsInactivitySafeIntervalPassed()
230 {
231 // Uses last successful authentication to determine if re-prompt is needed
232 if (DateTime.UtcNow < this.lastAuthenticationTime)
233 return true; // Require authentication.
234 return DateTime.Compare(DateTime.UtcNow,
235 this.lastAuthenticationTime.AddMinutes(Constants.Password.PossibleInactivityInMinutes)) > 0;
236 }
237
238 public async Task<long> GetCurrentPasswordCounterAsync()
239 {
240 return await this.settingsService.RestoreLongState(Constants.Password.CurrentPasswordAttemptCounter);
241 }
242
243 private async Task SetCurrentPasswordCounterAsync(long counter)
244 {
245 await this.settingsService.SaveState(Constants.Password.CurrentPasswordAttemptCounter, counter);
246 }
247
248 private async Task CloseApplicationAsync()
249 {
250 try
251 {
252 await this.platformSpecific.CloseApplication();
253 }
254 catch
255 {
256 Environment.Exit(0);
257 }
258 }
259
260
261 #region IDisposable Implementation
262 private bool isDisposed = false;
263
264 public void Dispose()
265 {
266 this.Dispose(disposing: true);
267 GC.SuppressFinalize(this);
268 }
269
273 protected virtual void Dispose(bool disposing)
274 {
275 if (disposing)
276 this.DisposeAsync().AsTask().Wait();
277 }
278
279
283 public virtual async ValueTask DisposeAsync()
284 {
285 if (this.isDisposed)
286 return;
287
288 await this.loginAuditor.DisposeAsync();
289
290 this.isDisposed = true;
291 }
292
293 #endregion
294 }
295}
Constants for Password
Definition: Constants.cs:792
const int FirstMaxPasswordAttempts
Maximum password enetring attempts, first interval
Definition: Constants.cs:802
const int PossibleInactivityInMinutes
Possible time of inactivity
Definition: Constants.cs:797
const string CurrentPasswordAttemptCounter
Key for password attempt counter
Definition: Constants.cs:832
const string RemoteEndpoint
Endpoint for LogAuditor
Definition: Constants.cs:842
const string LogAuditorObjectID
Log Object ID
Definition: Constants.cs:837
const int SecondBlockInHours
Second Block in hours after 3 attempts
Definition: Constants.cs:817
const int ThirdBlockInHours
Third Block in hours after 3 attempts
Definition: Constants.cs:827
const int FirstBlockInHours
First Block in hours after FirstMaxPasswordAttempts attempts
Definition: Constants.cs:807
const string Protocol
Protocol for LogAuditor
Definition: Constants.cs:847
const int ThirdMaxPasswordAttempts
Maximum password enetring attempts, third interval
Definition: Constants.cs:822
const int SecondMaxPasswordAttempts
Maximum password enetring attempts, second interval
Definition: Constants.cs:812
const string Reason
Reason for LogAuditor
Definition: Constants.cs:852
A set of never changing property constants and helpful values.
Definition: Constants.cs:24
A strongly-typed resource class, for looking up localized strings, etc.
static string PasswordIsInvalidAplicationBlockedTillTomorrow
Looks up a localized string similar to You have exceeded the number of attempts to provide your PIN....
static string FingerprintTitle
Looks up a localized string similar to Authentication Required.
static string Cancel
Looks up a localized string similar to Cancel.
static string PasswordIsInvalidAplicationBlocked
Looks up a localized string similar to You have exceeded the number of attempts to provide your PIN....
static string PasswordIsInvalidAplicationBlockedForever
Looks up a localized string similar to You have exceeded the number of attempts to provide your PIN....
static string FingerprintDescription
Looks up a localized string similar to Scan to authenticate.
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
Task< bool > AuthenticateUserAsync(AuthenticationPurpose purpose, bool force=false)
Main entry point for authentication UI flow (password or biometrics).
async Task< bool > CheckPasswordAndUnblockUserAsync(string password)
Validates password and unblocks user if correct.
virtual async ValueTask DisposeAsync()
IDisposableAsync.Dispose
virtual void Dispose(bool disposing)
IDisposableAsync.Dispose
async Task< string?> InputPasswordAsync(AuthenticationPurpose purpose)
Prompts for password if required by user profile.
async Task CheckUserBlockingAsync()
Checks if user is blocked from authentication and shows UI as needed.
Base class that references services in the app.
Definition: ServiceRef.cs:43
static IServiceProvider Provider
The service provider for the app. This is set before the app is started, and will be used to resolve ...
Definition: ServiceRef.cs:48
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
The TAG Profile is the heart of the digital identity for a specific user/device. Use this instance to...
Definition: TagProfile.cs:28
Options controlling popup presentation and behavior.
Definition: PopupOptions.cs:11
Popup view for entering a password that will be validated against the stored credentials.
View model for page letting the user enter a password to be verified with the password defined by the...
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
Number of failing login attempts possible during given time period.
Interface for platform-specific functions.
Handles common runtime settings that need to be persisted during sessions.
The TAG Profile is the heart of the digital identity for a specific user/device. Use this instance to...
Definition: ITagProfile.cs:18
Service serializing and managing UI-related tasks.
Definition: IUiService.cs:12
Service responsible for presenting and dismissing application popups.
AuthenticationMethod
How the user authenticates itself with the App.
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.