MongoDB connected. User registration implemented.

This commit is contained in:
Andrey Shabarshov 2021-12-28 13:37:10 +00:00
parent 2525e37f83
commit 96411f70f7
8 changed files with 96 additions and 68 deletions

View File

@ -18,7 +18,7 @@ namespace QRBee.Api.Controllers
_service = service; _service = service;
} }
[HttpPost] [HttpPost("Register")]
public Task<RegistrationResponse> Register([FromBody] RegistrationRequest value) public Task<RegistrationResponse> Register([FromBody] RegistrationRequest value)
{ {
return _service.Register(value); return _service.Register(value);

View File

@ -5,6 +5,7 @@ namespace QRBee.Api
public class DatabaseSettings public class DatabaseSettings
{ {
public string? ConnectionString { get; set;} public string? ConnectionString { get; set;}
public string? DatabaseName { get; set; }
public MongoClientSettings ToMongoDbSettings() public MongoClientSettings ToMongoDbSettings()
{ {

View File

@ -1,3 +1,4 @@
using Microsoft.Extensions.Options;
using MongoDB.Driver; using MongoDB.Driver;
using QRBee.Api; using QRBee.Api;
using QRBee.Api.Services; using QRBee.Api.Services;
@ -12,10 +13,12 @@ builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IQRBeeAPI,QRBeeAPI>(); builder.Services
builder.Services.AddSingleton<IStorage, Storage>(); .AddSingleton<IQRBeeAPI,QRBeeAPI>()
builder.Services.Configure<DatabaseSettings>(builder.Configuration.GetSection("QRBeeDatabase")); .AddSingleton<IStorage, Storage>()
builder.Services.AddSingleton<IMongoClient>( cfg => new MongoClient(cfg.GetRequiredService<DatabaseSettings>().ToMongoDbSettings())); .Configure<DatabaseSettings>(builder.Configuration.GetSection("QRBeeDatabase"))
.AddSingleton<IMongoClient>( cfg => new MongoClient(cfg.GetRequiredService<IOptions<DatabaseSettings>>().Value.ToMongoDbSettings()))
;
var app = builder.Build(); var app = builder.Build();

View File

@ -1,12 +1,10 @@
using Microsoft.AspNetCore.Identity; namespace QRBee.Api.Services.Database
namespace QRBee.Api.Services
{ {
public interface IStorage public interface IStorage
{ {
void PutUserInfo(UserInfo info); Task<string> PutUserInfo(UserInfo info);
UserInfo GetUserInfo(string email); Task<UserInfo> GetUserInfo(string email);
} }
} }

View File

@ -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 class Storage: IStorage
{ {
public void PutUserInfo(UserInfo info)
private readonly IMongoDatabase _database;
public Storage(IMongoClient client, IOptions<DatabaseSettings> settings)
{ {
throw new NotImplementedException(); var name = settings.Value.DatabaseName;
_database = client.GetDatabase(name);
} }
public UserInfo GetUserInfo(string email) public async Task<string> PutUserInfo(UserInfo info)
{ {
throw new NotImplementedException();
var collection = _database.GetCollection<UserInfo>("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<UserInfo?> TryGetUserInfo(string email)
{
var collection = _database.GetCollection<UserInfo>("Users");
using var cursor = await collection.FindAsync($"{{ Email: \"{email}\" }}");
if (!await cursor.MoveNextAsync())
{
return null;
}
return cursor.Current.FirstOrDefault();
}
public async Task<UserInfo> GetUserInfo(string email)
{
var user = await TryGetUserInfo(email);
return user ?? throw new ApplicationException($"User {email} not found.");
}
} }
} }

View File

@ -1,40 +1,32 @@
namespace QRBee.Api.Services; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace QRBee.Api.Services.Database;
public record UserInfo public record UserInfo
{ {
#pragma warning disable CS8618
public UserInfo() public UserInfo()
#pragma warning restore CS8618
{ {
} }
public UserInfo(string name, string email, string dateOfBirth) public UserInfo(string name, string email, string dateOfBirth)
{ {
Name = name; Name = name;
Email = email; Email = email;
DateOfBirth = dateOfBirth; DateOfBirth = dateOfBirth;
} }
public string? Name /// <summary>
{ /// Never use directly. Use <see cref="ClientId"/> instead.
get; /// </summary>
set; [BsonId] public ObjectId Id { get; set; }
}
public string? Email [BsonIgnore] public string? ClientId => Id == ObjectId.Empty ? null : Id.ToString();
{ public string Name { get; set; }
get; public string Email { get; set; }
set; public string DateOfBirth { get; set; }
} public bool RegisterAsMerchant { get; set; }
public string? DateOfBirth
{
get;
set;
}
public bool RegisterAsMerchant
{
get;
set;
}
} }

View File

@ -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;
using QRBee.Core.Data; using QRBee.Core.Data;
@ -7,32 +9,27 @@ namespace QRBee.Api.Services
public class QRBeeAPI: IQRBeeAPI public class QRBeeAPI: IQRBeeAPI
{ {
private readonly IStorage _storage; private readonly IStorage _storage;
private const int MaxNameLength = 512;
private const int MaxEmailLength = 512;
public QRBeeAPI(IStorage storage) public QRBeeAPI(IStorage storage)
{ {
_storage = storage; _storage = storage;
} }
public Task<RegistrationResponse> Register(RegistrationRequest request) public async Task<RegistrationResponse> Register(RegistrationRequest request)
{ {
throw new NotImplementedException();
Validate(request); Validate(request);
var info = Convert(request); var info = Convert(request);
var clientId = await _storage.PutUserInfo(info);
if (UserExists(info)) return new RegistrationResponse{ClientId = clientId};
{
// throw error
}
else
{
_storage.PutUserInfo(info);
}
} }
private void Validate(RegistrationRequest request) private static void Validate(RegistrationRequest request)
{ {
if (request == null) if (request == null)
{ {
@ -43,19 +40,25 @@ namespace QRBee.Api.Services
var email = request.Email; var email = request.Email;
var dateOfBirth = request.DateOfBirth; 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); return new UserInfo(request.Name, request.Email, request.DateOfBirth);
} }
private bool UserExists(UserInfo info)
{
var user = _storage.GetUserInfo(info.Email);
return user == info;
}
} }
} }

View File

@ -2,9 +2,7 @@
"QRBeeDatabase": { "QRBeeDatabase": {
"ConnectionString": "mongodb://localhost:27017", "ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "QRBee", "DatabaseName": "QRBee"
"User": "",
"Password": ""
}, },
"Logging": { "Logging": {