Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
NameEntryViewModel.cs
1using System.Text;
2using System.Text.RegularExpressions;
3using CommunityToolkit.Mvvm.ComponentModel;
4using CommunityToolkit.Mvvm.Input;
9
11{
12 public partial class NameEntryViewModel : BaseRegistrationViewModel
13 {
14 public NameEntryViewModel()
15 : base(RegistrationStep.NameEntry)
16 {
17 }
18
20 public override async Task DoAssignProperties()
21 {
22 await base.DoAssignProperties();
23
24 if (!string.IsNullOrEmpty(ServiceRef.TagProfile.Account))
25 GoToRegistrationStep(RegistrationStep.CreateAccount);
26 }
27
29 protected override async Task OnInitialize()
30 {
31 await base.OnInitialize();
32
33 ServiceRef.XmppService.ConnectionStateChanged += this.XmppService_ConnectionStateChanged;
34 }
35
37 protected override async Task OnDispose()
38 {
39 ServiceRef.XmppService.ConnectionStateChanged -= this.XmppService_ConnectionStateChanged;
40
41 await base.OnDispose();
42 }
43
44 private Task XmppService_ConnectionStateChanged(object _, XmppState NewState)
45 {
46 this.OnPropertyChanged(nameof(this.IsXmppConnected));
47 this.CreateAccountCommand.NotifyCanExecuteChanged();
48 return Task.CompletedTask;
49 }
50
51 [ObservableProperty]
52 private string? username;
53
54 partial void OnUsernameChanged(string? value)
55 {
56 this.UsernameIsValid = IsValidUsernameString(value);
57 this.OnPropertyChanged(nameof(this.CanCreateAccount));
58
59 if (this.UsernameIsValid)
60 {
61 this.AlternativeName = string.Empty;
62 this.LocalizedValidationMessage = string.Empty;
63 }
64 else
65 {
66 this.LocalizedValidationMessage = ServiceRef.Localizer[nameof(AppResources.InvalidUsernameCharacters)];
67 this.AlternativeName = this.GenerateUsername(this.Username);
68 }
69 this.CreateAccountCommand.NotifyCanExecuteChanged();
70 }
71
72 [ObservableProperty]
73 private string? alternativeName;
74
75 [ObservableProperty]
76 [NotifyCanExecuteChangedFor(nameof(CreateAccountCommand))]
77 private bool usernameIsValid;
78
79 [ObservableProperty]
80 private string? localizedValidationMessage;
81
85 public bool IsXmppConnected => ServiceRef.XmppService.State == XmppState.Connected;
86
87 public bool CanCreateAccount => this.UsernameIsValid && !string.IsNullOrEmpty(this.Username) && string.IsNullOrEmpty(this.AlternativeName) && !this.IsBusy;
88
89 [RelayCommand(CanExecute = nameof(CanCreateAccount))]
90 private async Task CreateAccount()
91 {
92 this.IsBusy = true;
93 try
94 {
95 string? account = this.Username;
96
97 if (string.IsNullOrEmpty(account))
98 return;
99 string PasswordToUse = ServiceRef.CryptoService.CreateRandomPassword();
100
101 (string HostName, int PortNumber, bool IsIpAddress) = await ServiceRef.NetworkService.LookupXmppHostnameAndPort(ServiceRef.TagProfile.Domain!);
102
103 async Task OnConnected(XmppClient Client)
104 {
106 await ServiceRef.XmppService.DiscoverServices(Client);
107
109
110 GoToRegistrationStep(RegistrationStep.CreateAccount);
111 }
112
113 (bool Succeeded, string? ErrorMessage, string[]? Alternatives) = await ServiceRef.XmppService.TryConnectAndCreateAccount(ServiceRef.TagProfile.Domain!,
114 IsIpAddress, HostName, PortNumber, account, PasswordToUse, Constants.LanguageCodes.Default,
115 ServiceRef.TagProfile.ApiKey ?? string.Empty, ServiceRef.TagProfile.ApiSecret ?? string.Empty,
116 typeof(App).Assembly, OnConnected);
117
118 if (Succeeded)
119 {
120 this.IsBusy = false;
121 }
122 if (Alternatives is not null && Alternatives.Length > 0)
123 {
124 Random rnd = new Random();
125 //Set alternative name to random alternative
126 this.UsernameIsValid = false;
127 this.AlternativeName = Alternatives[rnd.Next(0, Alternatives.Length)];
128 this.LocalizedValidationMessage = ServiceRef.Localizer[nameof(AppResources.UsernameNameAlreadyTaken)];
129 }
130 else if (ErrorMessage is not null)
131 {
132 await ServiceRef.UiService.DisplayAlert(
133 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], ErrorMessage,
134 ServiceRef.Localizer[nameof(AppResources.Ok)]);
135 }
136 }
137 catch (Exception ex)
138 {
139 ServiceRef.LogService.LogException(ex);
140
141 await ServiceRef.UiService.DisplayAlert(
142 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], ex.Message,
143 ServiceRef.Localizer[nameof(AppResources.Ok)]);
144 }
145
146 this.IsBusy = false;
147 }
148
149
150 [RelayCommand]
151 private void SelectName(string? name)
152 {
153 if (string.IsNullOrEmpty(name))
154 return;
155 this.Username = name;
156 this.AlternativeName = string.Empty;
157 }
158
164 public string GenerateUsername(string? source)
165 {
166 if (string.IsNullOrWhiteSpace(source))
167 return string.Empty;
168
169 // Normalize and convert to lowercase
170 string normalizedSource = source.Normalize(NormalizationForm.FormC).ToLowerInvariant();
171
172 // Split the source by spaces and process each part
173 IEnumerable<string?> processedParts = normalizedSource
174 .Split(' ', StringSplitOptions.RemoveEmptyEntries)
175 .Select(ReplaceInvalidUsernameCharacters)
176 .Where(processedPart => !string.IsNullOrEmpty(processedPart));
177
178 if (!processedParts.Any())
179 return string.Empty;
180
181 // Join parts with dots
182 string result = string.Join(".", processedParts);
183
184 // Generate 4 random digits
185 string randomDigits = new Random().Next(0, 10000).ToString("D4");
186 result += randomDigits;
187
188 // Replace multiple dots with a single dot
189 result = Regex.Replace(result, @"\.+", ".");
190
191 return result;
192 }
193
194
195 internal static bool IsValidUsernameString(string? input)
196 {
197 if (string.IsNullOrEmpty(input))
198 return true;
199 foreach (char c in input)
200 {
201 switch (c)
202 {
203 // From XMPP spec (RFC 6122):
204 case '"':
205 case '&':
206 case '\'':
207 case '/':
208 case ':':
209 case '<':
210 case '>':
211 case '@':
212 // Disallow space
213 case ' ':
214 // Invalid as file name characters (for file sniffers, etc.)
215 case '|':
216 case '\0':
217 case '\u0001':
218 case '\u0002':
219 case '\u0003':
220 case '\u0004':
221 case '\u0005':
222 case '\u0006':
223 case '\a':
224 case '\b':
225 case '\t':
226 case '\n':
227 case '\v':
228 case '\f':
229 case '\r':
230 case '\u000e':
231 case '\u000f':
232 case '\u0010':
233 case '\u0011':
234 case '\u0012':
235 case '\u0013':
236 case '\u0014':
237 case '\u0015':
238 case '\u0016':
239 case '\u0017':
240 case '\u0018':
241 case '\u0019':
242 case '\u001a':
243 case '\u001b':
244 case '\u001c':
245 case '\u001d':
246 case '\u001e':
247 case '\u001f':
248 case '*':
249 case '?':
250 case '\\':
251 return false;
252 }
253 }
254 return true;
255 }
256
257 private static string ReplaceInvalidUsernameCharacters(string input)
258 {
259 StringBuilder sb = new(input.Length);
260 foreach (char c in input)
261 {
262 switch (c)
263 {
264 // From XMPP spec (RFC 6122):
265 case '"':
266 case '&':
267 case '\'':
268 case '/':
269 case ':':
270 case '<':
271 case '>':
272 case '@':
273 // Disallow space
274 case ' ':
275 // Invalid as file name characters (for file sniffers, etc.)
276 case '|':
277 case '\0':
278 case '\u0001':
279 case '\u0002':
280 case '\u0003':
281 case '\u0004':
282 case '\u0005':
283 case '\u0006':
284 case '\a':
285 case '\b':
286 case '\t':
287 case '\n':
288 case '\v':
289 case '\f':
290 case '\r':
291 case '\u000e':
292 case '\u000f':
293 case '\u0010':
294 case '\u0011':
295 case '\u0012':
296 case '\u0013':
297 case '\u0014':
298 case '\u0015':
299 case '\u0016':
300 case '\u0017':
301 case '\u0018':
302 case '\u0019':
303 case '\u001a':
304 case '\u001b':
305 case '\u001c':
306 case '\u001d':
307 case '\u001e':
308 case '\u001f':
309 case '*':
310 case '?':
311 case '\\':
312 break;
313 default:
314 sb.Append(c);
315 break;
316 }
317 }
318
319 return sb.ToString();
320 }
321 }
322}
The Application class, representing an instance of the Neuro-Access app.
Definition: App.xaml.cs:69
const string Default
The default language code.
Definition: Constants.cs:83
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:103
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:55
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static ICryptoService CryptoService
Crypto service.
Definition: ServiceRef.cs:163
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
bool IsXmppConnected
If App is connected to the XMPP network.
override async Task DoAssignProperties()
string GenerateUsername(string? source)
Generates a username based on a source string.
override async Task OnDispose()
override async Task OnInitialize()
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:59
string PasswordHashMethod
Password hash method.
Definition: XmppClient.cs:3445
string PasswordHash
Hash value of password. Depends on method used to authenticate user.
Definition: XmppClient.cs:3436
string? Account
The account name for this profile
Definition: ITagProfile.cs:101
string? ApiKey
API Key, for creating new account.
Definition: ITagProfile.cs:61
string? ApiSecret
API Secret, for creating new account.
Definition: ITagProfile.cs:66
bool NeedsUpdating()
Returns true if the current ITagProfile needs to have its values updated, false otherwise.
void SetAccount(string AccountName, string ClientPasswordHash, string ClientPasswordHashMethod)
Set the account name and password for a new account.
string? Domain
The domain this profile is connected to.
Definition: ITagProfile.cs:51
RegistrationStep
The different steps of a TAG Profile registration journey.
XmppState
State of XMPP connection.
Definition: XmppState.cs:7