diff --git a/QRBeeApi/Controllers/QRBeeController.cs b/QRBeeApi/Controllers/QRBeeController.cs index c10bd13..308b221 100644 --- a/QRBeeApi/Controllers/QRBeeController.cs +++ b/QRBeeApi/Controllers/QRBeeController.cs @@ -18,7 +18,7 @@ namespace QRBee.Api.Controllers _service = service; } - [HttpPost] + [HttpPost("Register")] public Task Register([FromBody] RegistrationRequest value) { return _service.Register(value); diff --git a/QRBeeApi/DatabaseSettings.cs b/QRBeeApi/DatabaseSettings.cs index 7d3a856..54d960c 100644 --- a/QRBeeApi/DatabaseSettings.cs +++ b/QRBeeApi/DatabaseSettings.cs @@ -5,6 +5,7 @@ namespace QRBee.Api public class DatabaseSettings { public string? ConnectionString { get; set;} + public string? DatabaseName { get; set; } public MongoClientSettings ToMongoDbSettings() { diff --git a/QRBeeApi/Program.cs b/QRBeeApi/Program.cs index 5ad4e67..7e8c52e 100644 --- a/QRBeeApi/Program.cs +++ b/QRBeeApi/Program.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Options; using MongoDB.Driver; using QRBee.Api; using QRBee.Api.Services; @@ -12,10 +13,12 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.Configure(builder.Configuration.GetSection("QRBeeDatabase")); -builder.Services.AddSingleton( cfg => new MongoClient(cfg.GetRequiredService().ToMongoDbSettings())); +builder.Services + .AddSingleton() + .AddSingleton() + .Configure(builder.Configuration.GetSection("QRBeeDatabase")) + .AddSingleton( cfg => new MongoClient(cfg.GetRequiredService>().Value.ToMongoDbSettings())) + ; var app = builder.Build(); diff --git a/QRBeeApi/Services/Database/IStorage.cs b/QRBeeApi/Services/Database/IStorage.cs index c48a748..d18cf42 100644 --- a/QRBeeApi/Services/Database/IStorage.cs +++ b/QRBeeApi/Services/Database/IStorage.cs @@ -1,12 +1,10 @@ -using Microsoft.AspNetCore.Identity; - -namespace QRBee.Api.Services +namespace QRBee.Api.Services.Database { public interface IStorage { - void PutUserInfo(UserInfo info); - UserInfo GetUserInfo(string email); + Task PutUserInfo(UserInfo info); + Task GetUserInfo(string email); } } diff --git a/QRBeeApi/Services/Database/Storage.cs b/QRBeeApi/Services/Database/Storage.cs index 550b43e..6cb6b03 100644 --- a/QRBeeApi/Services/Database/Storage.cs +++ b/QRBeeApi/Services/Database/Storage.cs @@ -1,17 +1,56 @@ -namespace QRBee.Api.Services.Database +using Microsoft.Extensions.Options; +using MongoDB.Driver; + +namespace QRBee.Api.Services.Database { public class Storage: IStorage { - public void PutUserInfo(UserInfo info) + + private readonly IMongoDatabase _database; + + public Storage(IMongoClient client, IOptions settings) { - throw new NotImplementedException(); + var name = settings.Value.DatabaseName; + + _database = client.GetDatabase(name); } - public UserInfo GetUserInfo(string email) + public async Task PutUserInfo(UserInfo info) { - throw new NotImplementedException(); + + var collection = _database.GetCollection("Users"); + + var user = await TryGetUserInfo(info.Email); + + // Ignore re-register + // Potential vulnerability if user registers with other email + if (user == null) + { + await collection.InsertOneAsync(info); + return info.ClientId ?? throw new ApplicationException($"ClientId is null while adding user {info.Email}"); + } + return user.ClientId ?? throw new ApplicationException($"ClientId is null while adding user {info.Email}"); + + } + internal async Task TryGetUserInfo(string email) + { + var collection = _database.GetCollection("Users"); + using var cursor = await collection.FindAsync($"{{ Email: \"{email}\" }}"); + if (!await cursor.MoveNextAsync()) + { + return null; + } + + return cursor.Current.FirstOrDefault(); + } + + public async Task GetUserInfo(string email) + { + var user = await TryGetUserInfo(email); + return user ?? throw new ApplicationException($"User {email} not found."); + } } } diff --git a/QRBeeApi/Services/Database/UserInfo.cs b/QRBeeApi/Services/Database/UserInfo.cs index 739f523..82e8f6f 100644 --- a/QRBeeApi/Services/Database/UserInfo.cs +++ b/QRBeeApi/Services/Database/UserInfo.cs @@ -1,40 +1,32 @@ -namespace QRBee.Api.Services; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace QRBee.Api.Services.Database; public record UserInfo { +#pragma warning disable CS8618 public UserInfo() +#pragma warning restore CS8618 { } public UserInfo(string name, string email, string dateOfBirth) { - Name = name; - Email = email; + Name = name; + Email = email; DateOfBirth = dateOfBirth; } - public string? Name - { - get; - set; - } + /// + /// Never use directly. Use instead. + /// + [BsonId] public ObjectId Id { get; set; } - public string? Email - { - get; - set; - } - - public string? DateOfBirth - { - get; - set; - } - - public bool RegisterAsMerchant - { - get; - set; - } + [BsonIgnore] public string? ClientId => Id == ObjectId.Empty ? null : Id.ToString(); + public string Name { get; set; } + public string Email { get; set; } + public string DateOfBirth { get; set; } + public bool RegisterAsMerchant { get; set; } } \ No newline at end of file diff --git a/QRBeeApi/Services/QRBeeAPI.cs b/QRBeeApi/Services/QRBeeAPI.cs index 64dc427..1dcb47d 100644 --- a/QRBeeApi/Services/QRBeeAPI.cs +++ b/QRBeeApi/Services/QRBeeAPI.cs @@ -1,4 +1,6 @@ -using System.Text.RegularExpressions; +using System.Globalization; +using System.Text.RegularExpressions; +using QRBee.Api.Services.Database; using QRBee.Core; using QRBee.Core.Data; @@ -7,32 +9,27 @@ namespace QRBee.Api.Services public class QRBeeAPI: IQRBeeAPI { private readonly IStorage _storage; + private const int MaxNameLength = 512; + private const int MaxEmailLength = 512; public QRBeeAPI(IStorage storage) { _storage = storage; } - public Task Register(RegistrationRequest request) + public async Task Register(RegistrationRequest request) { - throw new NotImplementedException(); Validate(request); var info = Convert(request); + + var clientId = await _storage.PutUserInfo(info); - if (UserExists(info)) - { - // throw error - } - else - { - _storage.PutUserInfo(info); - } - + return new RegistrationResponse{ClientId = clientId}; } - private void Validate(RegistrationRequest request) + private static void Validate(RegistrationRequest request) { if (request == null) { @@ -43,19 +40,25 @@ namespace QRBee.Api.Services var email = request.Email; var dateOfBirth = request.DateOfBirth; - if (string.IsNullOrEmpty(name) || name.All(char.IsLetter)==false || name.Length>=30) + if (string.IsNullOrEmpty(name) || name.All(char.IsLetter)==false || name.Length>=MaxNameLength) { - // throw exception + throw new ApplicationException($"Name \"{name}\" isn't valid"); } - var freq = Regex.Matches(email, '@'.ToString()).Count; + var freq = Regex.Matches(email, @"[^@]+@[^@]+").Count; - if (string.IsNullOrEmpty(email) || email.Contains('@')==false || freq>=2 || email.Length >= 30) + if (string.IsNullOrEmpty(email) || email.IndexOf('@')<0 || freq>=2 || email.Length >= MaxEmailLength) { - // throw exception + throw new ApplicationException($"Email \"{email}\" isn't valid"); } - // DateOfBirth check + if (!DateTime.TryParseExact(dateOfBirth, "yyyy-MM-dd", null, DateTimeStyles.AssumeUniversal, out var check) + || check > DateTime.UtcNow - TimeSpan.FromDays(365 * 8) + || check < DateTime.UtcNow - TimeSpan.FromDays(365 * 100) + ) + { + throw new ApplicationException($"DateOfBirth \"{dateOfBirth}\" isn't valid"); + } } @@ -64,11 +67,5 @@ namespace QRBee.Api.Services return new UserInfo(request.Name, request.Email, request.DateOfBirth); } - private bool UserExists(UserInfo info) - { - var user = _storage.GetUserInfo(info.Email); - - return user == info; - } } } diff --git a/QRBeeApi/appsettings.json b/QRBeeApi/appsettings.json index f13cb12..ed89d62 100644 --- a/QRBeeApi/appsettings.json +++ b/QRBeeApi/appsettings.json @@ -2,9 +2,7 @@ "QRBeeDatabase": { "ConnectionString": "mongodb://localhost:27017", - "DatabaseName": "QRBee", - "User": "", - "Password": "" + "DatabaseName": "QRBee" }, "Logging": {