Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
Recover.cs
1using Paiwise;
2using System;
5using System.Text;
6using System.Threading.Tasks;
7using System.Web;
8using System.Xml;
9using Waher.Content;
17using Waher.Script;
25
27{
32 {
36 public Recover()
37 : base("Account/Recover",
38 new KeyValuePair<Type, Expression>(typeof(Dictionary<string, object>), new Expression(jsonPattern)),
39 new KeyValuePair<Type, Expression>(typeof(XmlDocument), new Expression(xmlPattern)))
40 {
41 }
42
43 private static readonly string jsonPattern = Resources.LoadResourceAsText(typeof(Recover).Namespace + ".JSON.Recover.req");
44 private static readonly string xmlPattern = Resources.LoadResourceAsText(typeof(Recover).Namespace + ".XML.Recover.req");
45
54 {
55 return null;
56 }
57
66 public override async Task POST(HttpRequest Request, HttpResponse Response, Dictionary<string, IElement> Parameters)
67 {
68 await this.CheckBlocks(Request);
69
70 string UserName = (string)Parameters["PUserName"]?.AssociatedObjectValue ?? string.Empty;
71 string PersonalNr = (string)Parameters["PPersonalNr"]?.AssociatedObjectValue ?? string.Empty;
72 string Country = (string)Parameters["PCountry"]?.AssociatedObjectValue ?? string.Empty;
73 string EMail = (string)Parameters["PEMail"]?.AssociatedObjectValue ?? string.Empty;
74 string PhoneNr = (string)Parameters["PPhoneNr"]?.AssociatedObjectValue ?? string.Empty;
75
76 if (string.IsNullOrEmpty(UserName) && string.IsNullOrEmpty(PersonalNr))
77 throw new BadRequestException("Either User Name or Personal Number with a corresponding country must be provided.");
78
79 if (string.IsNullOrEmpty(EMail) && string.IsNullOrEmpty(PhoneNr))
80 throw new BadRequestException("Either e-Mail or Phone Number must be provided.");
81
82 if (!await Create.AssertUserNameValid(UserName, Response))
83 return;
84
85 LinkedList<LegalIdentity> Matches = new LinkedList<LegalIdentity>();
87 "RecoverResponse", "https://waher.se/Schema/BrokerAgent.xsd");
89
90 if (!string.IsNullOrEmpty(UserName))
91 {
92 Account = await Database.FindFirstIgnoreRest<DataStorage.Account>(
93 new FilterFieldEqualTo("UserName", UserName));
94
95 if (Account is null)
96 {
97 LoginAuditor.Fail("User tried to recover account that does not exist.", UserName, Request.RemoteEndPoint, "HTTPS");
98
99 await Response.Return(Result);
100 return;
101 }
102 }
103
104 if (string.IsNullOrEmpty(PersonalNr))
105 {
106 foreach (LegalIdentity Identity in await Database.Find<LegalIdentity>(
107 new FilterFieldEqualTo("Account", UserName)))
108 {
109 Matches.AddLast(Identity);
110 }
111 }
112 else
113 {
114 IEnumerable<LegalIdentityReference> References = await Database.Find<LegalIdentityReference>(
115 new FilterAnd(
116 new FilterFieldEqualTo("Country", Country),
117 new FilterFieldEqualTo("PNr", PersonalNr)));
118 bool Match = false;
119
120 foreach (LegalIdentityReference Reference in References)
121 {
122 if (!(XmppServerModule.Legal is null) && !XmppServerModule.Legal.IsComponentDomain(Reference.Provider, true))
123 continue;
124
125 LegalIdentity Identity = await Database.FindFirstIgnoreRest<LegalIdentity>(
126 new FilterFieldEqualTo("Id", Reference.LegalId));
127
128 if (Identity is null)
129 continue;
130
131 Matches.AddLast(Identity);
132
133 if (Account is null)
134 {
135 if (string.IsNullOrEmpty(UserName))
136 {
137 UserName = Identity.Account;
138 Match = true;
139 }
140 else if (UserName != Identity.Account)
141 {
142 LoginAuditor.Fail("Account recovery failed. Provided personal number that matches multiple accounts.", UserName, Request.RemoteEndPoint, "HTTPS");
143
144 throw new UnprocessableEntityException("Multiple accounts registered for that person on the server. You need to specify which account you want to recover.");
145 }
146 }
147 else if (Account.UserName == Identity.Account)
148 Match = true;
149 else
150 continue;
151 }
152
153 if (!Match)
154 {
155 if (Account is null)
156 LoginAuditor.Fail("Account recovery failed. Provided personal number does not match any account.", UserName, Request.RemoteEndPoint, "HTTPS");
157 else
158 LoginAuditor.Fail("Account recovery failed. Provided personal number does not match provided account.", UserName, Request.RemoteEndPoint, "HTTPS");
159
160 await Response.Return(Result);
161 return;
162 }
163
164 if (Account is null)
165 {
166 Account = await Database.FindFirstIgnoreRest<DataStorage.Account>(
167 new FilterFieldEqualTo("UserName", UserName));
168
169 if (Account is null)
170 {
171 LoginAuditor.Fail("User tried to recover account that does not exist.", UserName, Request.RemoteEndPoint, "HTTPS");
172
173 await Response.Return(Result);
174 return;
175 }
176 }
177 }
178
179 if (!string.IsNullOrEmpty(EMail) && Account.EMail != EMail)
180 {
181 LoginAuditor.Fail("User tried to recover account using wrong e-mail.", UserName, Request.RemoteEndPoint, "HTTPS");
182
183 await Response.Return(Result);
184 return;
185 }
186
187 if (!string.IsNullOrEmpty(PhoneNr))
188 {
189 bool PhoneNrMatches = false;
190
191 foreach (LegalIdentity Identity in Matches)
192 {
193 string s = Identity[PersonalInformation.PhoneTag];
194 if (string.IsNullOrEmpty(s))
195 continue;
196
197 if (ComparePhoneNumbers(s, PhoneNr))
198 {
199 PhoneNrMatches = true;
200 break;
201 }
202 }
203
204 if (!PhoneNrMatches)
205 {
206 LoginAuditor.Fail("User tried to recover account using wrong phone number.", UserName, Request.RemoteEndPoint, "HTTPS");
207
208 await Response.Return(Result);
209 return;
210 }
211 }
212
213 if (string.IsNullOrEmpty(Account.EMail))
214 throw new ServiceUnavailableException("Account does not have a registered e-mail address to send recovery information to.");
215
216 LoginAuditor.Success("Account recovery validation successful.", UserName, Request.RemoteEndPoint, "HTTPS");
217
218 if (Matches.First is null)
219 {
220 StringBuilder Xml = new StringBuilder();
221
222 Account.AppendAccountXml(Xml, true);
223
224 byte[] Data = Encoding.UTF8.GetBytes(Xml.ToString());
225 byte[] Key = Gateway.NextBytes(16);
226 byte[] IV = Gateway.NextBytes(16);
227 byte[] Encrypted;
228
229 using (Aes Aes = Aes.Create())
230 {
231 Aes.BlockSize = 128;
232 Aes.KeySize = 256;
233 Aes.Mode = CipherMode.CBC;
234 Aes.Padding = PaddingMode.PKCS7;
235
236 using ICryptoTransform Transform = Aes.CreateEncryptor(Key, IV);
237
238 Encrypted = Transform.TransformFinalBlock(Data, 0, Data.Length);
239 }
240
241 Xml.Clear();
242
243 Xml.Append("<Info xmlns=\"http://waher.se/schema/Onboarding/v1.xsd\" base64=\"");
244 Xml.Append(Convert.ToBase64String(Encrypted));
245 Xml.Append("\" once=\"true\" expires=\"");
246 Xml.Append(XML.Encode(DateTime.Now.AddHours(1).ToUniversalTime()));
247 Xml.Append("\"/>");
248
249 string OnboardingDomainName = await LegalComponent.GetOnboardingNeuronDomainName();
250 XmlElement E = await Gateway.XmppClient.IqSetAsync("onboarding." + OnboardingDomainName, Xml.ToString());
251 string Code = E.GetAttribute("code");
252
253 Xml.Clear();
254
255 Xml.Append("obinfo:");
256 Xml.Append(OnboardingDomainName);
257 Xml.Append(":");
258 Xml.Append(Code);
259 Xml.Append(":");
260 Xml.Append(Convert.ToBase64String(Key));
261 Xml.Append(":");
262 Xml.Append(Convert.ToBase64String(IV));
263
264 string Url = Xml.ToString();
265 StringBuilder Markdown = new StringBuilder();
266
267 Markdown.AppendLine("Account recovery");
268 Markdown.AppendLine("===================");
269 Markdown.AppendLine();
270 Markdown.AppendLine("Someone has requested to recover access to your TAG ID account.");
271 Markdown.AppendLine("If this is not you, you can ignore this message, and the account will not be affected.");
272 Markdown.AppendLine("If it is you that has requested to recover your account, scan the following QR code to get access to the account.");
273 Markdown.AppendLine("If you view this e-mail in the phone containing the TAG ID app (or derivative), you can also click on the QR code itself.");
274 Markdown.AppendLine("This recovery code is only valid for one hour.");
275 Markdown.AppendLine();
276 Markdown.Append("![Recovery Code](");
277 Markdown.Append(Gateway.GetUrl("/QR/" + HttpUtility.UrlEncode(Url) + "?w=400&h=400&q=2"));
278 Markdown.AppendLine(")");
279 Markdown.AppendLine();
280 Markdown.Append("If you have any questions, please let us know through our [feedback page](");
281 Markdown.Append(Gateway.GetUrl("/Feedback.md"));
282 Markdown.AppendLine(").");
283
284 await XmppServerModule.SendMailMessage(Account.EMail, "Account recovery", Markdown.ToString());
285 }
286 else
287 {
288 LegalIdentity[] Reviewers = null;
289 LegalIdentity Latest = null;
290 LegalIdentity LatestApproved = null;
291
292 foreach (LegalIdentity Identity in Matches)
293 {
294 if (Latest is null || Identity.Created > Latest.Created)
295 Latest = Identity;
296
297 if (Identity.State != IdentityState.Approved)
298 continue;
299
300 if (LatestApproved is null || Identity.Created > LatestApproved.Created)
301 LatestApproved = Identity;
302 }
303
304 if (!(LatestApproved is null) && !(LatestApproved.Attachments is null))
305 Reviewers = await LegalComponent.GetPeerReviewers(LatestApproved);
306
307 // TODO: Send signature request to reviewers
308
309 //if (Reviewers is null ||
310 // Reviewers.Length == 0 ||
311 // !LegalIdentityConfiguration.HasApprovedLegalIdentities ||
312 // Gateway.ContractsClient is null)
313 //{
314
316 {
317 StringBuilder Markdown = new StringBuilder();
318
319 Markdown.AppendLine("User requests account recovery");
320 Markdown.AppendLine("==================================");
321 Markdown.AppendLine();
322 Markdown.AppendLine("Someone has requested to recover access to an account.");
323 Markdown.AppendLine("Following is some information provided in the request.");
324 Markdown.AppendLine();
325 Markdown.AppendLine("| Provided by user ||");
326 Markdown.AppendLine("|:--------|:--------|");
327 Markdown.Append("| User Name | `");
328 Markdown.Append(UserName);
329 Markdown.AppendLine("` |");
330 Markdown.Append("| Personal Number | `");
331 Markdown.Append(PersonalNr);
332 Markdown.AppendLine("` |");
333 Markdown.Append("| Country | `");
334 Markdown.Append(Country);
335 Markdown.AppendLine("` |");
336 Markdown.Append("| e-mail | <mailto:");
337 Markdown.Append(EMail);
338 Markdown.AppendLine("> |");
339 Markdown.Append("| Phone Number | <tel:");
340 Markdown.Append(PhoneNr);
341 Markdown.AppendLine("> |");
342 Markdown.AppendLine();
343
345 if (!(Locale is null))
346 {
347 Markdown.AppendLine("| Remote Endpoint ||");
348 Markdown.AppendLine("|:--------|:--------|");
349 Markdown.Append("| Country Code | ");
350 Markdown.Append(MarkdownDocument.Encode(Locale.CountryCode));
351 Markdown.AppendLine(" |");
352 Markdown.Append("| Country | ");
353 Markdown.Append(MarkdownDocument.Encode(Locale.Country));
354 Markdown.AppendLine(" |");
355 Markdown.Append("| Region | ");
356 Markdown.Append(MarkdownDocument.Encode(Locale.Region));
357 Markdown.AppendLine(" |");
358 Markdown.Append("| City | ");
359 Markdown.Append(MarkdownDocument.Encode(Locale.City));
360 Markdown.AppendLine(" |");
361 Markdown.Append("| Latitude | ");
362 Markdown.Append(MarkdownDocument.Encode(CommonTypes.Encode(Locale.Latitude, 6)));
363 Markdown.AppendLine(" |");
364 Markdown.Append("| Longitude | ");
365 Markdown.Append(MarkdownDocument.Encode(CommonTypes.Encode(Locale.Longitude, 6)));
366 Markdown.AppendLine(" |");
367 }
368 else
369 Markdown.AppendLine("No information found about IP address.");
370
371 Markdown.AppendLine();
372
373 if (!(LatestApproved is null))
374 {
375 Markdown.AppendLine("| Latest Approved ID ||");
376 Markdown.AppendLine("|:---------|:---------|");
377 Markdown.Append("| ID | [");
378 Markdown.Append(MarkdownDocument.Encode(LatestApproved.Id));
379 Markdown.Append("](");
380 Markdown.Append(Gateway.GetUrl("/ValidateLegalId.md"));
381 Markdown.Append("?ID=");
382 Markdown.Append(HttpUtility.UrlEncode(LatestApproved.Id));
383 Markdown.AppendLine("&Purpose=Review%20recovery%20application) |");
384
385 LegalComponent.Output(Markdown, LatestApproved.GetTags(false), false);
386 }
387 else if (!(Latest is null))
388 {
389 Markdown.AppendLine("| Latest ID (not approved) ||");
390 Markdown.AppendLine("|:------------|:------------|");
391 Markdown.Append("| ID | [");
392 Markdown.Append(MarkdownDocument.Encode(Latest.Id));
393 Markdown.Append("](");
394 Markdown.Append(Gateway.GetUrl("/ValidateLegalId.md"));
395 Markdown.Append("?ID=");
396 Markdown.Append(HttpUtility.UrlEncode(Latest.Id));
397 Markdown.AppendLine("&Purpose=Review%20recovery%20application) |");
398
399 LegalComponent.Output(Markdown, Latest.GetTags(false), false);
400 }
401 else
402 Markdown.AppendLine("No Legal ID found.");
403
404 await Gateway.SendNotification(Markdown.ToString());
405 }
406
407 //}
408 //else
409 //{
410 // Gateway.ContractsClient.PetitionSignatureAsync()
411 //}
412 }
413
414 await Response.Return(Result);
415 }
416
417 private static bool ComparePhoneNumbers(string Nr1, string Nr2)
418 {
419 return OnlyDigits(Nr1) == OnlyDigits(Nr2);
420 }
421
422 private static string OnlyDigits(string s)
423 {
424 StringBuilder sb = new StringBuilder();
425
426 foreach (char ch in s)
427 {
428 if (char.IsDigit(ch))
429 sb.Append(ch);
430 }
431
432 return sb.ToString();
433 }
434 }
435}
Contains personal information found in a legal identity.
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Definition: CommonTypes.cs:596
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
A Named dictionary is a dictionary, with a local name and a namespace. Use it to return content that ...
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
Definition: Gateway.cs:4335
static Task SendNotification(Graph Graph)
Sends a graph as a notification message to configured notification recipients.
Definition: Gateway.cs:4677
static string GetUrl(string LocalResource)
Gets a URL for a resource.
Definition: Gateway.cs:5079
static XmppClient XmppClient
XMPP Client connection of gateway.
Definition: Gateway.cs:4038
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Base class for all HTTP authentication schemes, as defined in RFC-7235: https://datatracker....
Represents an HTTP request.
Definition: HttpRequest.cs:22
string RemoteEndPoint
Remote end-point.
Definition: HttpRequest.cs:243
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
Task Return(Exception ex)
Returns an error to the client.
The server is currently unable to handle the request due to a temporary overloading or maintenance of...
The request was well-formed but was unable to be followed due to semantic errors.
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
Definition: Component.cs:124
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:13
static string LoadResourceAsText(string ResourceName)
Loads a text resource from an embedded resource.
Definition: Resources.cs:55
Class managing a script expression.
Definition: Expression.cs:41
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
static async void Success(string Message, string UserName, string RemoteEndPoint, string Protocol, params KeyValuePair< string, object >[] Tags)
Handles a successful login attempt.
static void Fail(string Message, string UserName, string RemoteEndPoint, string Protocol)
Handles a failed login attempt.
Contains information about a broker account.
Definition: Account.cs:41
CaseInsensitiveString EMail
E-mail address associated with account.
Definition: Account.cs:176
CaseInsensitiveString UserName
User Name of account
Definition: Account.cs:141
Provides the user with options to control notifications from the Broker.
static BrokerNotificationConfiguration Instance
Current instance of configuration.
bool AccountRecoveryRequest
If a notification should be sent when an account recovery request has been received.
Called when a user wants to recover its account.
Definition: Recover.cs:32
Recover()
Called when a user wants to recover its account.
Definition: Recover.cs:36
override HttpAuthenticationScheme[] GetAuthenticationSchemes(HttpRequest Request)
Any authentication schemes used to authenticate users before access is granted to the corresponding r...
Definition: Recover.cs:53
override async Task POST(HttpRequest Request, HttpResponse Response, Dictionary< string, IElement > Parameters)
Executes the POST method on the resource.
Definition: Recover.cs:66
Abstract base class for agent resources supporting the POST method.
async Task CheckBlocks(HttpRequest Request)
Checks if the client is blocked.
Service Module hosting the XMPP broker and its components.
static Task< bool > SendMailMessage(string To, string Subject, string Markdown)
Sends a mail message
static Task< IP4Localization > FindIpAddress(string RemoteEndPoint)
Finds locale information about an IP Address.