Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
CreateWebForm.cs
1using System;
3using System.Threading.Tasks;
4using Waher.Content;
15
17{
22 {
27 : base("Account/CreateWebForm")
28 {
29 }
30
39 {
40 base.GetAuthenticationSchemes(Request); // Initialize factory property.
41 return null;
42 }
43
47 public override bool UserSessions => true;
48
52 public virtual bool AllowsPOST => true;
53
60 public async Task POST(HttpRequest Request, HttpResponse Response)
61 {
62 await this.CheckBlocks(Request);
63
64 if (this.Api is null)
65 {
66 await Response.SendResponse(new ServiceUnavailableException("API not available."));
67 return;
68 }
69
70 if (!Request.HasData)
71 {
72 await Response.SendResponse(new BadRequestException("No content is request."));
73 return;
74 }
75
76 ContentResponse Decoded = await Request.DecodeDataAsync();
77
78 if (Decoded.HasError)
79 {
80 await Response.SendResponse(Decoded.Error);
81 return;
82 }
83
84 if (!(Decoded.Decoded is Dictionary<string, string> Form))
85 {
86 await Response.SendResponse(new UnsupportedMediaTypeException("Content does not match specification."));
87 return;
88 }
89
90 if (!Form.TryGetValue("UserName", out string UserName) || string.IsNullOrEmpty(UserName))
91 {
92 await Response.SendResponse(new BadRequestException("Missing user name."));
93 return;
94 }
95
96 if (!Form.TryGetValue("EMail", out string EMail) || string.IsNullOrEmpty(EMail))
97 {
98 await Response.SendResponse(new BadRequestException("Missing e-mail address."));
99 return;
100 }
101
102 if (!Form.TryGetValue("PhoneNr", out string PhoneNr))
103 PhoneNr = string.Empty;
104
105 if (!Form.TryGetValue("Language", out string Language))
106 Language = string.Empty;
107
108 if (!Form.TryGetValue("Password", out string Password) || string.IsNullOrEmpty(Password))
109 {
110 await Response.SendResponse(new BadRequestException("Missing password."));
111 return;
112 }
113
114 if (!Form.TryGetValue("g-recaptcha-response", out string RecaptchaResponse) || string.IsNullOrEmpty(RecaptchaResponse))
115 {
116 await Response.SendResponse(new BadRequestException("Missing reCaptcha-response."));
117 return;
118 }
119
120 if (!Form.TryGetValue("RedirectionUrl", out string RedirectionUrl) || string.IsNullOrEmpty(RedirectionUrl))
121 {
122 await Response.SendResponse(new BadRequestException("Missing redirection URL."));
123 return;
124 }
125
126 if (!Form.TryGetValue("Seconds", out string s) || !int.TryParse(s, out int Seconds) || Seconds <= 0 || Seconds > 3600)
127 {
128 await Response.SendResponse(new BadRequestException("Invalid number of seconds."));
129 return;
130 }
131
132 if (Gateway.HasDomain &&
133 !await Feedback.SiteVerify(RecaptchaResponse, Request.RemoteEndPoint))
134 {
135 await Response.SendResponse(new ForbiddenException(Request, "Bot?"));
136 return;
137 }
138
139 if (!string.IsNullOrEmpty(PhoneNr) && !Create.InternationalNumberFormat.IsMatch(PhoneNr))
140 {
141 await Response.SendResponse(new BadRequestException("Phone numbers must be provided using the international number format, starting with +, then the country code, then the number."));
142 return;
143 }
144
145 if (!await Create.AssertUserNameValid(UserName, Response))
146 return;
147
148 ApiKey Key = await AgentApi.GetAgentApiApiKey();
149 if (Key is null)
150 {
151 string Msg = "Resource disabled. No Agent API Key configured.";
152 await Response.SendResponse(new ServiceUnavailableException(Msg));
153 return;
154 }
155
156 bool SendEMailCode = !string.IsNullOrEmpty(EMail);
157 bool SendPhoneCode = !string.IsNullOrEmpty(PhoneNr);
158
159 DataStorage.Account Account = await Database.FindFirstIgnoreRest<DataStorage.Account>(
160 new FilterFieldEqualTo("UserName", UserName));
161
162 if (Account is null)
163 {
164 if (Key.NrCreated - Key.NrDeleted >= Key.MaxAccounts)
165 {
166 await Response.SendResponse(new ServiceUnavailableException("API Key account limit reached."));
167 return;
168 }
169
170 Account = new DataStorage.Account()
171 {
172 ApiKey = Key.Key,
173 UserName = UserName,
174 Password = Password,
175 EMail = EMail,
176 EMailVerified = null,
177 PhoneNr = PhoneNr,
178 PhoneNrVerified = null,
179 Enabled = false,
180 CanRelayMessages = false,
181 Created = DateTime.UtcNow
182 };
183
184 await Database.Insert(Account);
185 await RuntimeCounters.IncrementCounter(DataStorage.Account.AccountCreatedCounterName);
186
187 Key.NrCreated++;
188 await Database.Update(Key);
189
191 }
192 else
193 {
194 if (Account.Password != Password)
195 {
196 await Response.SendResponse(new ForbiddenException(Request, "Account already exists.",
197 await Create.GetAlterntiveNameSuggestions(UserName)));
198 return;
199 }
200
201 bool Updated = false;
202
203 if (Account.EMail == EMail)
204 SendEMailCode = !Account.EMailVerified.HasValue;
205 else
206 {
208 {
209 await Response.SendResponse(new ForbiddenException(Request, "Not permitted to change e-mail address."));
210 return;
211 }
212
213 Account.EMail = EMail;
214 Account.EMailVerified = null;
215
216 Updated = true;
217 }
218
219 if (Account.PhoneNr == PhoneNr)
220 SendPhoneCode = !Account.PhoneNrVerified.HasValue;
221 else
222 {
224 {
225 await Response.SendResponse(new ForbiddenException(Request, "Not permitted to change phone number."));
226 return;
227 }
228
229 Account.PhoneNr = PhoneNr;
230 Account.PhoneNrVerified = null;
231
232 Updated = true;
233 }
234
235 if (Updated)
236 {
237 Account.Updated = DateTime.UtcNow;
238 await Database.Update(Account);
239 await XmppServerModule.PersistenceLayer.AccountUpdated(UserName);
240 }
241
242 await Account.LoggedIn(Request.RemoteEndPoint);
243 }
244
245 LoginAuditor.Success("Successful Agent API account created.", UserName, Request.RemoteEndPoint, "HTTPS");
246
247 if (!Account.Enabled)
248 {
249 string OnboardingDomainName = await LegalComponent.GetOnboardingNeuronDomainName();
250
251 if (SendEMailCode)
252 {
254 new Uri("https://" + OnboardingDomainName + "/ID/SendVerificationMessage.ws"),
255 new Dictionary<string, object>()
256 {
257 { "EMail", Account.EMail.Value },
258 { "Language", Language }
259 },
260 new KeyValuePair<string, string>("Accept", JsonCodec.DefaultContentType));
261
263 {
264 await Response.SendResponse(ContentResponse.Error);
265 return;
266 }
267
268 if (!(ContentResponse.Decoded is Dictionary<string, object> EMailResult))
269 {
270 await Response.SendResponse(new ServiceUnavailableException("Unable to send verification mail. Unexpected result"));
271 return;
272 }
273
274 if (!EMailResult.TryGetValue("Status", out object Obj) || !(Obj is bool Status) || !Status)
275 {
276 if (EMailResult.TryGetValue("Message", out Obj) && Obj is string Message)
277 await Response.SendResponse(new ServiceUnavailableException("Unable to send verification mail. Error reported: " + Message));
278 else
279 await Response.SendResponse(new ServiceUnavailableException("Unable to send verification mail. Check e-mail address provided."));
280
281 return;
282 }
283 }
284
285 if (SendPhoneCode)
286 {
288 new Uri("https://" + OnboardingDomainName + "/ID/SendVerificationMessage.ws"),
289 new Dictionary<string, object>()
290 {
291 { "Nr", Account.PhoneNr.Value },
292 { "Language", Language }
293 },
294 new KeyValuePair<string, string>("Accept", JsonCodec.DefaultContentType));
295
297 {
298 await Response.SendResponse(ContentResponse.Error);
299 return;
300 }
301
302 if (!(ContentResponse.Decoded is Dictionary<string, object> SmsResult))
303 {
304 await Response.SendResponse(new ServiceUnavailableException("Unable to send verification mail. Unexpected result"));
305 return;
306 }
307
308 if (!SmsResult.TryGetValue("Status", out object Obj) || !(Obj is bool Status2) || !Status2)
309 {
310 if (SmsResult.TryGetValue("Message", out Obj) && Obj is string Message)
311 await Response.SendResponse(new ServiceUnavailableException("Unable to send verification SMS. Error reported: " + Message));
312 else
313 await Response.SendResponse(new ServiceUnavailableException("Unable to send verification SMS. Check phone number provided."));
314
315 return;
316 }
317 }
318 }
319
320 int IssuedAt = (int)Math.Round(DateTime.UtcNow.Subtract(JSON.UnixEpoch).TotalSeconds);
321 int Expires = IssuedAt + (int)Seconds;
322
323 string Token = Factory.Create(
324 new KeyValuePair<string, object>(JwtClaims.JwtId, Convert.ToBase64String(Gateway.NextBytes(32))),
325 new KeyValuePair<string, object>(JwtClaims.Issuer, Gateway.Domain?.Value ?? string.Empty),
326 new KeyValuePair<string, object>(JwtClaims.Subject, Account.UserName.Value + "@" + (Gateway.Domain?.Value ?? string.Empty)),
327 new KeyValuePair<string, object>(JwtClaims.IssueTime, IssuedAt),
328 new KeyValuePair<string, object>(JwtClaims.ExpirationTime, Expires));
329
330 Request.Session[SessionTokenInfoVariableName] = new SessionTokenInfo()
331 {
333 Token = Token,
334 Expires = Expires,
335 Seconds = Seconds
336 };
337
338 await Response.SendResponse(new TemporaryRedirectException(RedirectionUrl));
339 }
340
344 public const string SessionTokenInfoVariableName = " AgentAPI.SessionTokenInfo ";
345 }
346}
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Exception Error
Error response.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static readonly DateTime UnixEpoch
Unix Date and Time epoch, starting at 1970-01-01T00:00:00Z
Definition: JSON.cs:20
const string DefaultContentType
application/json
Definition: JsonCodec.cs:20
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:3087
static byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
Definition: Gateway.cs:4335
static bool HasDomain
If a domain name is configured.
Definition: Gateway.cs:3093
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
The server understood the request, but is refusing to fulfill it. Authorization will not help and the...
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
bool HasData
If the request has data.
Definition: HttpRequest.cs:113
SessionVariables Session
Contains session states, if the resource requires sessions, or null otherwise.
Definition: HttpRequest.cs:212
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:139
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
The server is currently unable to handle the request due to a temporary overloading or maintenance of...
The requested resource resides temporarily under a different URI. Since the redirection MAY be altere...
The server is refusing to service the request because the entity of the request is in a format not su...
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
This filter selects objects that have a named field equal to a given value.
Static class managing persistent counters.
static Task< long > IncrementCounter(CaseInsensitiveString Key)
Increments a counter.
Static class containing predefined JWT claim names.
Definition: JwtClaims.cs:10
const string Issuer
Issuer of the JWT
Definition: JwtClaims.cs:14
const string IssueTime
Time at which the JWT was issued; can be used to determine age of the JWT
Definition: JwtClaims.cs:39
const string JwtId
Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only...
Definition: JwtClaims.cs:44
const string Subject
Subject of the JWT (the user)
Definition: JwtClaims.cs:19
const string ExpirationTime
Time after which the JWT expires
Definition: JwtClaims.cs:29
string Create(params KeyValuePair< string, object >[] Claims)
Creates a new JWT token.
Definition: JwtFactory.cs:379
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.
Contains information about a broker account.
Definition: Account.cs:41
DateTime? PhoneNrVerified
When Phone Number was verified.
Definition: Account.cs:344
CaseInsensitiveString EMail
E-mail address associated with account.
Definition: Account.cs:176
CaseInsensitiveString UserName
User Name of account
Definition: Account.cs:141
bool Enabled
If account is enabled
Definition: Account.cs:354
DateTime? EMailVerified
When e-Mail was verified.
Definition: Account.cs:335
string Password
Password of account
Definition: Account.cs:151
CaseInsensitiveString PhoneNr
Phone number associated with account.
Definition: Account.cs:185
Account()
Contains information about a broker account.
Definition: Account.cs:114
async Task LoggedIn(string RemoteEndPoint)
Registers a log-in event on the account.
Definition: Account.cs:558
static async Task AccountCreated(Account Account, ApiKey ApiKeyObject, string Domain, string RemoteEndPoint)
Notifies operators of a new account being created.
Task AccountUpdated(CaseInsensitiveString UserName)
Called when account has been updated.
static readonly Regex InternationalNumberFormat
International phone number format.
Definition: Create.cs:47
async Task POST(HttpRequest Request, HttpResponse Response)
Executes the POST method on the resource.
CreateWebForm()
Creates an account on the server manually.
const string SessionTokenInfoVariableName
Name of session variable where token information is stored.
override bool UserSessions
If the resource uses user sessions.
override HttpAuthenticationScheme[] GetAuthenticationSchemes(HttpRequest Request)
Any authentication schemes used to authenticate users before access is granted to the corresponding r...
virtual bool AllowsPOST
If the POST method is allowed.
static async Task< ApiKey > GetAgentApiApiKey()
Gets API Key for the Agent API.
Definition: AgentApi.cs:123
Abstract base class for agent resources
async Task CheckBlocks(HttpRequest Request)
Checks if the client is blocked.
static async Task< bool > SiteVerify(string RecaptchaResponse, string RemoteEndPoint)
Allows web pages and web services to verify that Google reCaptcha responses are valid.
Definition: Feedback.cs:166
Service Module hosting the XMPP broker and its components.
POST Interface for HTTP resources.