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 System;
2using System.Collections.Generic;
3using System.Security.Cryptography;
4using System.Text;
5using System.Threading.Tasks;
6using System.Web;
7using System.Xml;
8using Waher.Content;
15using Waher.Script;
22
24{
29 {
33 public Recover()
34 : base("Account/Recover",
35 new KeyValuePair<Type, Expression>(typeof(Dictionary<string, object>), new Expression(jsonPattern)),
36 new KeyValuePair<Type, Expression>(typeof(XmlDocument), new Expression(xmlPattern)))
37 {
38 }
39
40 private static readonly string jsonPattern = Resources.LoadResourceAsText(typeof(Recover).Namespace + ".JSON.Recover.req");
41 private static readonly string xmlPattern = Resources.LoadResourceAsText(typeof(Recover).Namespace + ".XML.Recover.req");
42
49 {
50 return null;
51 }
52
61 public override async Task POST(HttpRequest Request, HttpResponse Response, Dictionary<string, IElement> Parameters)
62 {
63 await this.CheckBlocks(Request);
64
65 string UserName = (string)Parameters["PUserName"]?.AssociatedObjectValue ?? string.Empty;
66 string PersonalNr = (string)Parameters["PPersonalNr"]?.AssociatedObjectValue ?? string.Empty;
67 string Country = (string)Parameters["PCountry"]?.AssociatedObjectValue ?? string.Empty;
68 string EMail = (string)Parameters["PEMail"]?.AssociatedObjectValue ?? string.Empty;
69 string PhoneNr = (string)Parameters["PPhoneNr"]?.AssociatedObjectValue ?? string.Empty;
70
71 if (string.IsNullOrEmpty(UserName) && string.IsNullOrEmpty(PersonalNr))
72 throw new BadRequestException("Either User Name or Personal Number with a corresponding country must be provided.");
73
74 if (string.IsNullOrEmpty(EMail) && string.IsNullOrEmpty(PhoneNr))
75 throw new BadRequestException("Either e-Mail or Phone Number must be provided.");
76
77 Create.AssertUserNameValid(UserName);
78
79 LinkedList<LegalIdentity> Matches = new LinkedList<LegalIdentity>();
81 "RecoverResponse", "https://waher.se/Schema/BrokerAgent.xsd");
83
84 if (!string.IsNullOrEmpty(UserName))
85 {
86 Account = await Database.FindFirstIgnoreRest<DataStorage.Account>(
87 new FilterFieldEqualTo("UserName", UserName));
88
89 if (Account is null)
90 {
91 LoginAuditor.Fail("User tried to recover account that does not exist.", UserName, Request.RemoteEndPoint, "HTTPS");
92
93 await Response.Return(Result);
94 return;
95 }
96 }
97
98 if (string.IsNullOrEmpty(PersonalNr))
99 {
100 foreach (LegalIdentity Identity in await Database.Find<LegalIdentity>(
101 new FilterFieldEqualTo("Account", UserName)))
102 {
103 Matches.AddLast(Identity);
104 }
105 }
106 else
107 {
108 IEnumerable<LegalIdentityReference> References = await Database.Find<LegalIdentityReference>(
109 new FilterAnd(
110 new FilterFieldEqualTo("Country", Country),
111 new FilterFieldEqualTo("PNr", PersonalNr)));
112 bool Match = false;
113
114 foreach (LegalIdentityReference Reference in References)
115 {
116 if (!(XmppServerModule.Legal is null) && !XmppServerModule.Legal.IsComponentDomain(Reference.Provider, true))
117 continue;
118
119 LegalIdentity Identity = await Database.FindFirstIgnoreRest<LegalIdentity>(
120 new FilterFieldEqualTo("Id", Reference.LegalId));
121
122 if (Identity is null)
123 continue;
124
125 Matches.AddLast(Identity);
126
127 if (Account is null)
128 {
129 if (string.IsNullOrEmpty(UserName))
130 {
131 UserName = Identity.Account;
132 Match = true;
133 }
134 else if (UserName != Identity.Account)
135 {
136 LoginAuditor.Fail("Account recovery failed. Provided personal number that matches multiple accounts.", UserName, Request.RemoteEndPoint, "HTTPS");
137
138 throw new UnprocessableEntityException("Multiple accounts registered for that person on the server. You need to specify which account you want to recover.");
139 }
140 }
141 else if (Account.UserName == Identity.Account)
142 Match = true;
143 else
144 continue;
145 }
146
147 if (!Match)
148 {
149 if (Account is null)
150 LoginAuditor.Fail("Account recovery failed. Provided personal number does not match any account.", UserName, Request.RemoteEndPoint, "HTTPS");
151 else
152 LoginAuditor.Fail("Account recovery failed. Provided personal number does not match provided account.", UserName, Request.RemoteEndPoint, "HTTPS");
153
154 await Response.Return(Result);
155 return;
156 }
157
158 if (Account is null)
159 {
160 Account = await Database.FindFirstIgnoreRest<DataStorage.Account>(
161 new FilterFieldEqualTo("UserName", UserName));
162
163 if (Account is null)
164 {
165 LoginAuditor.Fail("User tried to recover account that does not exist.", UserName, Request.RemoteEndPoint, "HTTPS");
166
167 await Response.Return(Result);
168 return;
169 }
170 }
171 }
172
173 if (!string.IsNullOrEmpty(EMail) && Account.EMail != EMail)
174 {
175 LoginAuditor.Fail("User tried to recover account using wrong e-mail.", UserName, Request.RemoteEndPoint, "HTTPS");
176
177 await Response.Return(Result);
178 return;
179 }
180
181 if (!string.IsNullOrEmpty(PhoneNr))
182 {
183 bool PhoneNrMatches = false;
184
185 foreach (LegalIdentity Identity in Matches)
186 {
187 string s = Identity["PHONE"];
188 if (string.IsNullOrEmpty(s))
189 continue;
190
191 if (ComparePhoneNumbers(s, PhoneNr))
192 {
193 PhoneNrMatches = true;
194 break;
195 }
196 }
197
198 if (!PhoneNrMatches)
199 {
200 LoginAuditor.Fail("User tried to recover account using wrong phone number.", UserName, Request.RemoteEndPoint, "HTTPS");
201
202 await Response.Return(Result);
203 return;
204 }
205 }
206
207 if (string.IsNullOrEmpty(Account.EMail))
208 throw new ServiceUnavailableException("Account does not have a registered e-mail address to send recovery information to.");
209
210 LoginAuditor.Success("Account recovery validation successful.", UserName, Request.RemoteEndPoint, "HTTPS");
211
212 if (Matches.First is null)
213 {
214 StringBuilder Xml = new StringBuilder();
215
216 Xml.Append("<Account xmlns=\"http://waher.se/schema/Onboarding/v1.xsd\" userName=\"");
217 Xml.Append(XML.Encode(Account.UserName));
218 Xml.Append("\" password=\"");
219 Xml.Append(XML.Encode(Account.Password));
220 Xml.Append("\" domain=\"");
221 Xml.Append(XML.Encode(Gateway.Domain));
222 Xml.Append("\"/>");
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
242 Xml.Clear();
243
244 Xml.Append("<Info xmlns=\"http://waher.se/schema/Onboarding/v1.xsd\" base64=\"");
245 Xml.Append(Convert.ToBase64String(Encrypted));
246 Xml.Append("\" once=\"true\" expires=\"");
247 Xml.Append(XML.Encode(DateTime.Now.AddHours(1).ToUniversalTime()));
248 Xml.Append("\"/>");
249
250 string OnboardingDomainName = await LegalComponent.GetOnboardingNeuronDomainName();
251 XmlElement E = await Gateway.XmppClient.IqSetAsync("onboarding." + OnboardingDomainName, Xml.ToString());
252 string Code = E.GetAttribute("code");
253
254 Xml.Clear();
255
256 Xml.Append("obinfo:");
257 Xml.Append(OnboardingDomainName);
258 Xml.Append(":");
259 Xml.Append(Code);
260 Xml.Append(":");
261 Xml.Append(Convert.ToBase64String(Key));
262 Xml.Append(":");
263 Xml.Append(Convert.ToBase64String(IV));
264
265 string Url = Xml.ToString();
266 StringBuilder Markdown = new StringBuilder();
267
268 Markdown.AppendLine("Account recovery");
269 Markdown.AppendLine("===================");
270 Markdown.AppendLine();
271 Markdown.AppendLine("Someone has requested to recover access to your TAG ID account.");
272 Markdown.AppendLine("If this is not you, you can ignore this message, and the account will not be affected.");
273 Markdown.AppendLine("If it is you that has requested to recover your account, scan the following QR code to get access to the account.");
274 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.");
275 Markdown.AppendLine("This recovery code is only valid for one hour.");
276 Markdown.AppendLine();
277 Markdown.Append("![Recovery Code](");
278 Markdown.Append(Gateway.GetUrl("/QR/" + HttpUtility.UrlEncode(Url)));
279 Markdown.AppendLine(")");
280 Markdown.AppendLine();
281 Markdown.Append("If you have any questions, please let us know through our [feedback page](");
282 Markdown.Append(Gateway.GetUrl("/Feedback.md"));
283 Markdown.AppendLine(").");
284
285 await XmppServerModule.SendMailMessage(Account.EMail, "Account recovery", Markdown.ToString());
286 }
287 else
288 {
289 LegalIdentity[] Reviewers = null;
290 LegalIdentity Latest = null;
291 LegalIdentity LatestApproved = null;
292
293 foreach (LegalIdentity Identity in Matches)
294 {
295 if (Latest is null || Identity.Created > Latest.Created)
296 Latest = Identity;
297
298 if (Identity.State != IdentityState.Approved)
299 continue;
300
301 if (LatestApproved is null || Identity.Created > LatestApproved.Created)
302 LatestApproved = Identity;
303 }
304
305 if (!(LatestApproved is null) && !(LatestApproved.Attachments is null))
306 Reviewers = await LegalComponent.GetPeerReviewers(LatestApproved);
307
308 // TODO: Send signature request to reviewers
309
310 //if (Reviewers is null ||
311 // Reviewers.Length == 0 ||
312 // !LegalIdentityConfiguration.HasApprovedLegalIdentities ||
313 // Gateway.ContractsClient is null)
314 //{
315 StringBuilder Markdown = new StringBuilder();
316
317 Markdown.AppendLine("User requests account recovery");
318 Markdown.AppendLine("==================================");
319 Markdown.AppendLine();
320 Markdown.AppendLine("Someone has requested to recover access to an account.");
321 Markdown.AppendLine("Following is some information provided in the request.");
322 Markdown.AppendLine();
323 Markdown.AppendLine("| Provided by user ||");
324 Markdown.AppendLine("|:--------|:--------|");
325 Markdown.Append("| User Name | `");
326 Markdown.Append(UserName);
327 Markdown.AppendLine("` |");
328 Markdown.Append("| Personal Number | `");
329 Markdown.Append(PersonalNr);
330 Markdown.AppendLine("` |");
331 Markdown.Append("| Country | `");
332 Markdown.Append(Country);
333 Markdown.AppendLine("` |");
334 Markdown.Append("| e-mail | <mailto:");
335 Markdown.Append(EMail);
336 Markdown.AppendLine("> |");
337 Markdown.Append("| Phone Number | <tel:");
338 Markdown.Append(PhoneNr);
339 Markdown.AppendLine("> |");
340 Markdown.AppendLine();
341
343 if (!(Locale is null))
344 {
345 Markdown.AppendLine("| Remote Endpoint ||");
346 Markdown.AppendLine("|:--------|:--------|");
347 Markdown.Append("| Country Code | ");
348 Markdown.Append(MarkdownDocument.Encode(Locale.CountryCode));
349 Markdown.AppendLine(" |");
350 Markdown.Append("| Country | ");
351 Markdown.Append(MarkdownDocument.Encode(Locale.Country));
352 Markdown.AppendLine(" |");
353 Markdown.Append("| Region | ");
354 Markdown.Append(MarkdownDocument.Encode(Locale.Region));
355 Markdown.AppendLine(" |");
356 Markdown.Append("| City | ");
357 Markdown.Append(MarkdownDocument.Encode(Locale.City));
358 Markdown.AppendLine(" |");
359 }
360 else
361 Markdown.AppendLine("No information found about IP address.");
362
363 Markdown.AppendLine();
364
365 if (!(LatestApproved is null))
366 {
367 Markdown.AppendLine("| Latest Approved ID ||");
368 Markdown.AppendLine("|:---------|:---------|");
369 Markdown.Append("| ID | [");
370 Markdown.Append(MarkdownDocument.Encode(LatestApproved.Id));
371 Markdown.Append("](");
372 Markdown.Append(Gateway.GetUrl("/ValidateLegalId.md"));
373 Markdown.Append("?ID=");
374 Markdown.Append(HttpUtility.UrlEncode(LatestApproved.Id));
375 Markdown.AppendLine("&Purpose=Review%20recovery%20application) |");
376
377 LegalComponent.Output(Markdown, LatestApproved.GetTags(false), false);
378 }
379 else if (!(Latest is null))
380 {
381 Markdown.AppendLine("| Latest ID (not approved) ||");
382 Markdown.AppendLine("|:------------|:------------|");
383 Markdown.Append("| ID | [");
384 Markdown.Append(MarkdownDocument.Encode(Latest.Id));
385 Markdown.Append("](");
386 Markdown.Append(Gateway.GetUrl("/ValidateLegalId.md"));
387 Markdown.Append("?ID=");
388 Markdown.Append(HttpUtility.UrlEncode(Latest.Id));
389 Markdown.AppendLine("&Purpose=Review%20recovery%20application) |");
390
391 LegalComponent.Output(Markdown, Latest.GetTags(false), false);
392 }
393 else
394 Markdown.AppendLine("No Legal ID found.");
395
396 await Gateway.SendNotification(Markdown.ToString());
397 //}
398 //else
399 //{
400 // Gateway.ContractsClient.PetitionSignatureAsync()
401 //}
402 }
403
404 await Response.Return(Result);
405 }
406
407 private static bool ComparePhoneNumbers(string Nr1, string Nr2)
408 {
409 return OnlyDigits(Nr1) == OnlyDigits(Nr2);
410 }
411
412 private static string OnlyDigits(string s)
413 {
414 StringBuilder sb = new StringBuilder();
415
416 foreach (char ch in s)
417 {
418 if (char.IsDigit(ch))
419 sb.Append(ch);
420 }
421
422 return sb.ToString();
423 }
424 }
425}
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 ...
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:15
static string LoadResourceAsText(string ResourceName)
Loads a text resource from an embedded resource.
Definition: Resources.cs:96
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:2354
static byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
Definition: Gateway.cs:3534
static Task SendNotification(Graph Graph)
Sends a graph as a notification message to configured notification recipients.
Definition: Gateway.cs:3826
static string GetUrl(string LocalResource)
Gets a URL for a resource.
Definition: Gateway.cs:4167
static XmppClient XmppClient
XMPP Client connection of gateway.
Definition: Gateway.cs:3187
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:18
string RemoteEndPoint
Remote end-point.
Definition: HttpRequest.cs:195
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:21
async Task Return(object Object)
Returns an object to the client. This method can only be called once per response,...
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:123
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
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.
Class managing a script expression.
Definition: Expression.cs:39
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 async void Fail(string Message, string UserName, string RemoteEndpoint, string Protocol, params KeyValuePair< string, object >[] Tags)
Handles a failed login attempt.
Contains information about a broker account.
Definition: Account.cs:28
CaseInsensitiveString EMail
E-mail address associated with account.
Definition: Account.cs:129
CaseInsensitiveString UserName
User Name of account
Definition: Account.cs:99
string Password
Password of account
Definition: Account.cs:109
Called when a user wants to recover its account.
Definition: Recover.cs:29
Recover()
Called when a user wants to recover its account.
Definition: Recover.cs:33
override HttpAuthenticationScheme[] GetAuthenticationSchemes(HttpRequest Request)
Any authentication schemes used to authenticate users before access is granted to the corresponding r...
Definition: Recover.cs:48
override async Task POST(HttpRequest Request, HttpResponse Response, Dictionary< string, IElement > Parameters)
Executes the POST method on the resource.
Definition: Recover.cs:61
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.