public async Task <APIResponse> Confirm([FromBody] ConfirmModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = (DAL.Models.Identity.User)null; var agent = GetUserAgentInfo(); var userLocale = GetUserLocale(); // check token if (!await JWT.IsValid( appConfig: AppConfig, jwtToken: model.Token, expectedAudience: JwtAudience.Cabinet, expectedArea: Common.JwtArea.Registration, validStamp: async(jwt, id) => { user = await UserManager.FindByNameAsync(id); return(""); } ) || user == null) { return(APIResponse.BadRequest(nameof(model.Token), "Invalid token")); } if (!user.EmailConfirmed) { user.EmailConfirmed = true; await DbContext.SaveChangesAsync(); } return(APIResponse.Success()); }
public async Task <APIResponse> NewPassword([FromBody] NewPasswordModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = (DAL.Models.Identity.User)null; var audience = GetCurrentAudience(); var agent = GetUserAgentInfo(); var userLocale = GetUserLocale(); // check token if (!await Core.Tokens.JWT.IsValid( appConfig: AppConfig, jwtToken: model.Token, expectedAudience: JwtAudience.Cabinet, expectedArea: JwtArea.RestorePassword, validStamp: async(jwt, id) => { user = await UserManager.FindByNameAsync(id); return(""); } ) || user == null) { return(APIResponse.BadRequest(nameof(model.Token), "Invalid token")); } await UserManager.RemovePasswordAsync(user); await UserManager.AddPasswordAsync(user, model.Password); if (audience != null) { UserAccount.GenerateJwtSalt(user, audience.Value); await DbContext.SaveChangesAsync(); } // notification await EmailComposer.FromTemplate(await TemplateProvider.GetEmailTemplate(EmailTemplate.PasswordChanged, userLocale)) .Initiator(agent.Ip, agent.Agent, DateTime.UtcNow) .Send(user.Email, user.UserName, EmailQueue) ; // activity var userActivity = CoreLogic.User.CreateUserActivity( user: user, type: Common.UserActivityType.Password, comment: "Password changed", ip: agent.Ip, agent: agent.Agent, locale: userLocale ); DbContext.UserActivity.Add(userActivity); await DbContext.SaveChangesAsync(); return(APIResponse.Success()); }
public async Task <APIResponse> Estimate([FromBody] EstimateModel model) { if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var exchangeCurrency = FiatCurrency.Usd; TradableCurrency?cryptoToken = null; // try parse fiat currency if (Enum.TryParse(model.Currency, true, out FiatCurrency fc)) { exchangeCurrency = fc; } // or crypto currency else if (Enum.TryParse(model.Currency, true, out TradableCurrency cc)) { cryptoToken = cc; } else { return(APIResponse.BadRequest(nameof(model.Currency), "Invalid format")); } // try parse amount if (!BigInteger.TryParse(model.Amount, out var inputAmount) || inputAmount < 1 || (cryptoToken == null && !model.Reversed && inputAmount > long.MaxValue)) { return(APIResponse.BadRequest(nameof(model.Amount), "Invalid amount")); } // --- var userOrNull = await GetUserFromDb(); var rcfg = RuntimeConfigHolder.Clone(); // get user limits var limits = cryptoToken != null ? DepositLimits(rcfg, cryptoToken.Value) : await DepositLimits(rcfg, DbContext, userOrNull?.Id, exchangeCurrency) ; // estimate var estimation = await Estimation(rcfg, inputAmount, cryptoToken, exchangeCurrency, model.Reversed, 0, limits.Min, limits.Max); if (!estimation.TradingAllowed) { return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } if (estimation.IsLimitExceeded) { return(APIResponse.BadRequest(APIErrorCode.TradingExchangeLimit, estimation.View.Limits)); } return(APIResponse.Success(estimation.View)); }
private APIResponse OnSignInResultCheck(IServiceProvider services, Microsoft.AspNetCore.Identity.SignInResult result, DAL.Models.Identity.User user, JwtAudience audience, bool tfaRequired) { if (result != null) { if (result.Succeeded || result.RequiresTwoFactor) { // tfa token if (tfaRequired || result.RequiresTwoFactor) { return(APIResponse.Success( new AuthenticateView() { Token = JWT.CreateAuthToken( appConfig: AppConfig, user: user, audience: audience, area: JwtArea.Tfa ), TfaRequired = true, } )); } // new jwt salt UserAccount.GenerateJwtSalt(user, audience); DbContext.SaveChanges(); // auth token return(APIResponse.Success( new AuthenticateView() { Token = JWT.CreateAuthToken( appConfig: AppConfig, user: user, audience: audience, area: JwtArea.Authorized ), TfaRequired = false, } )); } if (result.IsLockedOut) { return(APIResponse.BadRequest(APIErrorCode.AccountLocked, "Too many unsuccessful attempts to sign in. Account is locked, try to sign in later")); } if (result.IsNotAllowed) { return(APIResponse.BadRequest(APIErrorCode.AccountEmailNotConfirmed, "Email is not confirmed yet")); } } // not found return(null); }
public async Task <APIResponse> ChangePassword([FromBody] ChangePasswordModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = await GetUserFromDb(); var agent = GetUserAgentInfo(); var userLocale = GetUserLocale(); // first check tfa if (user.TwoFactorEnabled && !Core.Tokens.GoogleAuthenticator.Validate(model.TfaCode, user.TfaSecret)) { return(APIResponse.BadRequest(nameof(model.TfaCode), "Invalid 2fa code")); } // check current password if (await UserManager.HasPasswordAsync(user) && (model.Current == null || !await UserManager.CheckPasswordAsync(user, model.Current))) { return(APIResponse.BadRequest(nameof(model.Current), "Invalid current password")); } // set new password await UserManager.RemovePasswordAsync(user); await UserManager.AddPasswordAsync(user, model.New); // notification await EmailComposer.FromTemplate(await TemplateProvider.GetEmailTemplate(EmailTemplate.PasswordChanged, userLocale)) .Initiator(agent.Ip, agent.Agent, DateTime.UtcNow) .Send(user.Email, user.UserName, EmailQueue) ; // activity var userActivity = CoreLogic.User.CreateUserActivity( user: user, type: Common.UserActivityType.Password, comment: "Password changed", ip: agent.Ip, agent: agent.Agent, locale: userLocale ); DbContext.UserActivity.Add(userActivity); await DbContext.SaveChangesAsync(); return(APIResponse.Success( new ChangePasswordView() { } )); }
public IActionResult PostEmployee( [FromBody] EmployeeModel employee ) { try { Employee lastEmployee = _employeeService.GetEmployees().OrderByDescending(prop => prop.EmployeeNumber).First(); int newEmployeeId = lastEmployee.EmployeeNumber + 1; employee.EmployeeNumber = newEmployeeId; Employee employeeToInsert = new Employee { EmployeeNumber = newEmployeeId, FirstName = employee.FirstName, LastName = employee.LastName, Gender = employee.EmployeeGender, BirthDate = employee.BirthDate, HireDate = employee.HireDate }; _employeeService.InsertEmployee(employeeToInsert); return(Created($"/api/employees/{newEmployeeId}", employee)); } catch (GenericServiceException genericServiceException) { if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.ConflictException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.ApiConflict(genericServiceException.Message))); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.BadConstraintsException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.BadRequest())); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.InternalErrorException)) { return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } catch (Exception ex) { return(StatusCode(500, APIResponse.DefaultErrorMessage(ex.Message, 500))); } }
public async Task <APIResponse> Activity([FromBody] ActivityModel model) { var sortExpression = new Dictionary <string, System.Linq.Expressions.Expression <Func <DAL.Models.UserActivity, object> > >() { { "date", _ => _.TimeCreated }, }; // validate if (BasePagerModel.IsInvalid(model, sortExpression.Keys, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = await GetUserFromDb(); var query = ( from a in DbContext.UserActivity where a.UserId == user.Id select a ); var page = await DalExtensions.PagerAsync(query, model.Offset, model.Limit, sortExpression.GetValueOrDefault(model.Sort), model.Ascending ); var list = from i in page.Selected select new ActivityViewItem() { Type = i.Type.ToLower(), Comment = i.Comment, Ip = i.Ip, Agent = i.Agent, Date = ((DateTimeOffset)i.TimeCreated).ToUnixTimeSeconds(), } ; return(APIResponse.Success( new ActivityView() { Items = list.ToArray(), Limit = model.Limit, Offset = model.Offset, Total = page.TotalCount, } )); }
public async Task <APIResponse> Tfa([FromBody] TfaModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var audience = GetCurrentAudience(); if (audience == null) { return(APIResponse.BadRequest(APIErrorCode.Unauthorized)); } var user = await GetUserFromDb(); if (user != null && user.TwoFactorEnabled) { // locked out if (await UserManager.IsLockedOutAsync(user)) { return(APIResponse.BadRequest(APIErrorCode.AccountLocked, "Too many unsuccessful attempts. Account is locked, try to sign in later")); } // by code if (GoogleAuthenticator.Validate(model.Code, user.TfaSecret)) { return(OnSignInResultCheck( services: HttpContext.RequestServices, result: Microsoft.AspNetCore.Identity.SignInResult.Success, user: user, audience: audience.Value, tfaRequired: false )); } // +1 failed login DbContext.Attach <DAL.Models.Identity.User>(user); await UserManager.AccessFailedAsync(user); } return(APIResponse.BadRequest(nameof(model.Code), "Invalid code")); }
public async Task <APIResponse> Confirm([FromBody] ConfirmModel model) { if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = await GetUserFromDb(); var userLocale = GetUserLocale(); var agent = GetUserAgentInfo(); // --- var request = await ( from r in DbContext.BuyGoldEth where r.Status == BuySellGoldRequestStatus.Unconfirmed && r.Id == model.RequestId && r.UserId == user.Id && (DateTime.UtcNow - r.TimeCreated).TotalHours < 1.0 select r ) .AsTracking() .FirstOrDefaultAsync() ; // request not exists if (request == null) { return(APIResponse.BadRequest(nameof(model.RequestId), "Invalid id")); } request.Status = BuySellGoldRequestStatus.Confirmed; await DbContext.SaveChangesAsync(); return(APIResponse.Success( new ConfirmView() { } )); }
public async Task <APIResponse> VerificationEdit([FromBody] VerificationEditModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = await GetUserFromDb(); var userTier = CoreLogic.User.GetTier(user); if (userTier == UserTier.Tier1 && !CoreLogic.User.HasKycVerification(user.UserVerification)) { // format phone number var phoneFormatted = Common.TextFormatter.NormalizePhoneNumber(model.PhoneNumber); // parse dob var dob = DateTime.ParseExact(model.Dob, "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); { user.UserVerification.FirstName = model.FirstName.Limit(64); user.UserVerification.MiddleName = model.MiddleName?.Limit(64); user.UserVerification.LastName = model.LastName.Limit(64); user.UserVerification.DoB = dob; user.UserVerification.PhoneNumber = phoneFormatted.Limit(32); user.UserVerification.Country = Common.Countries.GetNameByAlpha2(model.Country); user.UserVerification.CountryCode = model.Country.ToUpper(); user.UserVerification.State = model.State.Limit(256); user.UserVerification.City = model.City.Limit(256); user.UserVerification.PostalCode = model.PostalCode.Limit(16); user.UserVerification.Street = model.Street.Limit(256); user.UserVerification.Apartment = model.Apartment?.Limit(128); user.UserVerification.TimeUserChanged = DateTime.UtcNow; } await DbContext.SaveChangesAsync(); } return(APIResponse.Success(MakeVerificationView(user))); }
public IActionResult PostTitle( [FromBody] TitleModel titleModel ) { try { Title titleToInsert = new Title { EmployeeNumber = titleModel.EmployeeNumber, Title = titleModel.EmployeeTitle, FromDate = titleModel.FromDate, ToDate = titleModel.ToDate }; _titleService.InsertTitle(titleToInsert); return(Created($" /api/employees/{titleModel.EmployeeNumber}/titles/{titleModel.EmployeeTitle}/startDate/{titleModel.FromDate}", titleModel)); } catch (GenericServiceException genericServiceException) { if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.ConflictException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.ApiConflict(genericServiceException.Message))); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.BadConstraintsException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.BadRequest())); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.InternalErrorException)) { return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } catch (Exception ex) { return(StatusCode(500, APIResponse.DefaultErrorMessage(ex.Message, 500))); } }
public IActionResult PostSalary( [FromBody] SalaryModel salaryModel ) { try { Salary salaryToInsert = new Salary { EmployeeNumber = salaryModel.EmployeeNumber, EmployeeSalary = salaryModel.EmployeeSalary, FromDate = salaryModel.FromDate, ToDate = salaryModel.ToDate }; _salaryService.InsertSalary(salaryToInsert); return(Created($"/api/employees/{salaryModel.EmployeeNumber}/salaries/startDate/{salaryModel.FromDate}", salaryModel)); } catch (GenericServiceException genericServiceException) { if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.ConflictException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.ApiConflict(genericServiceException.Message))); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.BadConstraintsException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.BadRequest())); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.InternalErrorException)) { return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } catch (Exception ex) { return(StatusCode(500, APIResponse.DefaultErrorMessage(ex.Message, 500))); } }
public IActionResult PostDepartment( [FromBody] DepartmentModel department ) { try { Department lastDepartment = _departmentService.GetDepartments().OrderByDescending(prop => prop.DepartmentNumber).First(); string departmentNumber = UtilityKit.ConstructDepartmentNumber(lastDepartment.DepartmentNumber.Substring(1)); department.DepartmentNumber = departmentNumber; _departmentService.InsertDepartment(new Department { DepartmentNumber = department.DepartmentNumber, DepartmentName = department.DepartmentName }); return(Created($"/api/departments/{department.DepartmentNumber}", department)); } catch (GenericServiceException genericServiceException) { if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.ConflictException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.ApiConflict(genericServiceException.Message))); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.BadConstraintsException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.BadRequest())); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.InternalErrorException)) { return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } catch (Exception ex) { return(StatusCode(500, APIResponse.DefaultErrorMessage(ex.Message, 500))); } }
public IActionResult PostDepartmentManager( [FromBody] DepartmentManagerModel departmentManager ) { try { _departmentManagerService.InsertDepartmentManager( new DepartmentManager { EmployeeNumber = departmentManager.EmployeeNumber, DepartmentNumber = departmentManager.DepartmentNumber, FromDate = departmentManager.FromDate, ToDate = departmentManager.ToDate }); return(Created($"/api/departments/{departmentManager.DepartmentNumber}/managers/{departmentManager.EmployeeNumber}", departmentManager)); } catch (GenericServiceException genericServiceException) { if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.ConflictException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.ApiConflict(genericServiceException.Message))); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.BadConstraintsException)) { return(StatusCode(genericServiceException.StatusCode, APIResponse.BadRequest())); } else if (genericServiceException.GenericExceptionResponse.Equals(GenericExceptionResponse.InternalErrorException)) { return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } return(StatusCode(500, APIResponse.DefaultErrorMessage(genericServiceException.Message, 500))); } catch (Exception ex) { return(StatusCode(500, APIResponse.DefaultErrorMessage(ex.Message, 500))); } }
public async Task <APIResponse> Refresh() { var user = await GetUserFromDb(); var audience = GetCurrentAudience(); if (audience == null) { return(APIResponse.BadRequest(APIErrorCode.Unauthorized)); } return(APIResponse.Success( new RefreshView() { Token = JWT.CreateAuthToken( appConfig: AppConfig, user: user, audience: audience.Value, area: JwtArea.Authorized ), } )); }
public async Task <APIResponse> AssetEth([FromBody] AssetEthModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } // try parse amount if (!BigInteger.TryParse(model.Amount, out var inputAmount) || inputAmount < 1) { return(APIResponse.BadRequest(nameof(model.Amount), "Invalid amount")); } // try parse fiat currency var exchangeCurrency = FiatCurrency.Usd; if (Enum.TryParse(model.Currency, true, out FiatCurrency fc)) { exchangeCurrency = fc; } // --- var rcfg = RuntimeConfigHolder.Clone(); var user = await GetUserFromDb(); // --- if (!rcfg.Gold.AllowTradingEth) { return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } var limits = DepositLimits(rcfg, TradableCurrency.Eth); // estimation var estimation = await Estimation(rcfg, inputAmount, TradableCurrency.Eth, exchangeCurrency, model.Reversed, 0d, limits.Min, limits.Max); if (!estimation.TradingAllowed || estimation.ResultCurrencyAmount < 1) { return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } if (estimation.IsLimitExceeded) { return(APIResponse.BadRequest(APIErrorCode.TradingExchangeLimit, estimation.View.Limits)); } var timeNow = DateTime.UtcNow; // request var request = new DAL.Models.BuyGoldEth() { Status = BuySellGoldRequestStatus.Unconfirmed, ExchangeCurrency = exchangeCurrency, GoldRateCents = estimation.CentsPerGoldRate, EthRateCents = estimation.CentsPerAssetRate, TimeCreated = timeNow, UserId = user.Id, }; DbContext.BuyGoldEth.Add(request); await DbContext.SaveChangesAsync(); // get a token from eth2gold service var contractToken = ""; { try { var reply = await Bus.Request( Eth2Gold.Subject.Request.OrderCreate, new Eth2Gold.Request.OrderCreate() { ExternalID = (ulong)request.Id, }, Eth2Gold.Request.OrderCreateReply.Parser ); if (reply.ResultCase == Eth2Gold.Request.OrderCreateReply.ResultOneofCase.Token) { if (reply.Token.ToByteArray().Length != 32) { throw new Exception($"token is length of {reply.Token.ToByteArray().Length}"); } contractToken = BitConverter.ToString(reply.Token.ToByteArray()).Replace("-", string.Empty); } else { throw new Exception(reply.Error); } } catch (Exception e) { Logger.Error(e, "Failed to get token from eth2gold service"); return(APIResponse.Failure(APIErrorCode.InternalServerError)); } } var assetPerGold = CoreLogic.Finance.Estimation.AssetPerGold(TradableCurrency.Eth, estimation.CentsPerAssetRate, estimation.CentsPerGoldRate); return(APIResponse.Success( new AssetEthView() { RequestId = request.Id, EthRate = estimation.CentsPerAssetRate / 100d, GoldRate = estimation.CentsPerGoldRate / 100d, EthPerGoldRate = assetPerGold.ToString(), Currency = exchangeCurrency.ToString().ToUpper(), Expires = ((DateTimeOffset)timeNow.AddHours(1)).ToUnixTimeSeconds(), Estimation = estimation.View, ContractToken = contractToken, } )); }
public async Task <APIResponse> Register([FromBody] RegisterModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var agent = GetUserAgentInfo(); var userLocale = GetUserLocale(); // captcha if (!HostingEnvironment.IsDevelopment()) { if (!await Core.Recaptcha.Verify(AppConfig.Services.Recaptcha.SecretKey, model.Captcha, agent.Ip)) { return(APIResponse.BadRequest(nameof(model.Captcha), "Failed to validate captcha")); } } var result = await Core.UserAccount.CreateUserAccount(HttpContext.RequestServices, model.Email, model.Password); if (result.User != null) { // confirmation token var token = Core.Tokens.JWT.CreateSecurityToken( appConfig: AppConfig, entityId: result.User.UserName, audience: JwtAudience.Cabinet, securityStamp: "", area: Common.JwtArea.Registration, validFor: TimeSpan.FromDays(2) ); var callbackUrl = this.MakeAppLink(JwtAudience.Cabinet, fragment: AppConfig.Apps.Cabinet.RouteSignUpConfirmation.Replace(":token", token)); // email confirmation await EmailComposer.FromTemplate(await TemplateProvider.GetEmailTemplate(EmailTemplate.EmailConfirmation, userLocale)) .Link(callbackUrl) .Send(model.Email, "", EmailQueue) ; // auth token return(APIResponse.Success( new Models.API.v1.User.UserModels.AuthenticateView() { Token = JWT.CreateAuthToken( appConfig: AppConfig, user: result.User, audience: JwtAudience.Cabinet, area: JwtArea.Authorized ), TfaRequired = false, } )); } else { if (result.IsUsernameExists || result.IsEmailExists) { return(APIResponse.BadRequest(APIErrorCode.AccountEmailTaken, "Email is already taken")); } } throw new Exception("Registration failed"); }
public async Task <APIResponse> FiatHistory([FromBody] FiatHistoryModel model) { var sortExpression = new Dictionary <string, System.Linq.Expressions.Expression <Func <DAL.Models.UserFinHistory, object> > >() { { "date", _ => _.TimeCreated }, { "type", _ => _.Type }, { "status", _ => _.Status } }; // validate if (BasePagerModel.IsInvalid(model, sortExpression.Keys, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = await GetUserFromDb(); var query = ( from h in DbContext.UserFinHistory where h.UserId == user.Id && ( h.Status == UserFinHistoryStatus.Manual || h.Status == UserFinHistoryStatus.Processing || h.Status == UserFinHistoryStatus.Completed || h.Status == UserFinHistoryStatus.Failed ) select h ); var page = await query.PagerAsync( model.Offset, model.Limit, sortExpression.GetValueOrDefault(model.Sort), model.Ascending ); var nowTime = DateTime.UtcNow; var list = from i in page.Selected select new FiatHistoryViewItem() { Type = i.Type.ToString().ToLower(), Status = ( i.Status == UserFinHistoryStatus.Completed ? 2 // success : i.Status == UserFinHistoryStatus.Failed ? 3 // cancelled/failed : 1 // pending ), Comment = i.Comment, Src = i.Source, SrcAmount = i.SourceAmount, Dst = i.Destination, DstAmount = i.DestinationAmount, Date = ((DateTimeOffset)i.TimeCreated).ToUnixTimeSeconds(), } ; return(APIResponse.Success( new FiatHistoryView() { Items = list.ToArray(), Limit = model.Limit, Offset = model.Offset, Total = page.TotalCount, } )); }
public async Task <APIResponse> VerificationKycStart([FromBody] VerificationKycStartModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = await GetUserFromDb(); var userTier = CoreLogic.User.GetTier(user); // tos not signed, didn't fill personal data, has kyc already if ( !CoreLogic.User.HasTosSigned(user.UserVerification) || !CoreLogic.User.HasFilledPersonalData(user.UserVerification) || CoreLogic.User.HasKycVerification(user.UserVerification) ) { return(APIResponse.BadRequest(APIErrorCode.AccountNotVerified)); } // check previous verification attempt var status = MakeVerificationView(user); if (status.IsKycPending) { return(APIResponse.BadRequest(APIErrorCode.RateLimit)); } // --- // new kyc ticket var ticket = new KycTicket() { UserId = user.Id, ReferenceId = Guid.NewGuid().ToString("N"), Method = "general", FirstName = user.UserVerification.FirstName, LastName = user.UserVerification.LastName, DoB = user.UserVerification.DoB.Value, CountryCode = user.UserVerification.CountryCode, PhoneNumber = user.UserVerification.PhoneNumber, TimeCreated = DateTime.UtcNow, }; DbContext.KycShuftiProTicket.Add(ticket); await DbContext.SaveChangesAsync(); // set last ticket user.UserVerification.LastKycTicket = ticket; await DbContext.SaveChangesAsync(); // new redirect var kycUser = new CoreLogic.Services.KYC.UserData() { FirstName = ticket.FirstName, LastName = ticket.LastName, CountryCode = ticket.CountryCode, LanguageCode = GetUserLocale().ToString().ToUpper(), DoB = ticket.DoB, PhoneNumber = ticket.PhoneNumber, Email = user.Email, }; var callbackUrl = Url.Link("CallbackShuftiPro", new { /*secret = AppConfig.Services.ShuftiPro.CallbackSecret*/ }); var userTempRedirectUrl = Url.Link("CallbackRedirect", new { to = System.Web.HttpUtility.UrlEncode(model.Redirect) }); var kycRedirect = await KycExternalProvider.GetRedirect( kycUser, ticket.ReferenceId, userTempRedirectUrl, callbackUrl ); Logger.Verbose($"{user.UserName} got kyc redirect to {kycRedirect} with callback to {callbackUrl} and middle redirect to {userTempRedirectUrl}"); return(APIResponse.Success(new VerificationKycStartView() { TicketId = ticket.Id.ToString(), Redirect = kycRedirect, })); }
public void Configure(IApplicationBuilder app, IApplicationLifetime applicationLifetime, IRuntimeConfigLoader runtimeConfigLoader) { applicationLifetime.ApplicationStopping.Register(OnServerStopRequested); applicationLifetime.ApplicationStopped.Register(OnServerStopped); // globlization app.UseRequestLocalization(new RequestLocalizationOptions { DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture(System.Globalization.CultureInfo.InvariantCulture), SupportedCultures = new List <System.Globalization.CultureInfo> { System.Globalization.CultureInfo.InvariantCulture }, SupportedUICultures = new List <System.Globalization.CultureInfo> { System.Globalization.CultureInfo.InvariantCulture }, }); // config loader _runtimeConfigHolder.SetLoader(runtimeConfigLoader); // setup ms logger // app.ApplicationServices.GetRequiredService<Microsoft.Extensions.Logging.ILoggerFactory>().AddNLog(); // nginx proxy { var forwardingOptions = new ForwardedHeadersOptions() { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, RequireHeaderSymmetry = false, ForwardLimit = null, }; forwardingOptions.KnownNetworks.Clear(); forwardingOptions.KnownProxies.Clear(); app.UseForwardedHeaders(forwardingOptions); } // 503: response on exception app.UseExceptionHandler(builder => { builder.Run(async context => { var error = context.Features.Get <IExceptionHandlerFeature>(); context.RequestServices?.GetService <ILogger>()?.ForContext(this.GetType())?.Error(error?.Error ?? new Exception("No extra data"), "Service failure"); var resp = APIResponse.GeneralInternalFailure(error?.Error, !_environment.IsProduction()); await resp.WriteResponse(context).ConfigureAwait(false); }); }); // 403: always write body if unathorized app.Use(async(context, next) => { await next(); if (context.Response.StatusCode == 403) { var resp = APIResponse.BadRequest(APIErrorCode.Unauthorized); await resp.WriteResponse(context).ConfigureAwait(false); } }); // check content type app.Use(async(context, next) => { var flatPath = context.Request.Path.ToString(); if (context.Request.Method == "POST" && flatPath.StartsWith("/api/") && !flatPath.Contains("/callback/")) { if (!(context.Request.ContentType?.StartsWith("application/json") ?? false)) { var resp = APIResponse.BadRequest(APIErrorCode.InvalidContentType, "Json format is only allowed"); await resp.WriteResponse(context).ConfigureAwait(false); return; } } await next(); }); // swagger if (!_environment.IsProduction()) { app.UseSwagger(opts => { }); app.UseSwaggerUI(opts => { opts.SwaggerEndpoint("/" + ((_appConfig.Apps.RelativeApiPath).Trim('/') + "/swagger/api/swagger.json").Trim('/'), "API"); }); } // 404: redirect to index: not found, not a file, not api request app.Use(async(context, next) => { await next(); if (context.Response.StatusCode == 404) { var resp = APIResponse.BadRequest(APIErrorCode.MethodNotFound); await resp.WriteResponse(context).ConfigureAwait(false); } }); app.UseAuthentication(); app.UseCors(opts => { opts.WithMethods("GET", "POST", "OPTIONS"); opts.AllowAnyHeader(); opts.AllowAnyOrigin(); } ); app.UseMvc(); RunServices(); }
public async Task <APIResponse> LiteWallet([FromBody] LiteWalletModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = await GetUserFromDb(); var userTier = CoreLogic.User.GetTier(user); var userLocale = GetUserLocale(); var agent = GetUserAgentInfo(); if (userTier < UserTier.Tier2) { return(APIResponse.BadRequest(APIErrorCode.AccountNotVerified)); } if (model.Amount <= 0.001m || user.UserSumusWallet.BalanceGold < model.Amount) { return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } // --- var timeNow = DateTime.UtcNow; // charge using (var scope = HttpContext.RequestServices.CreateScope()) { if (await CoreLogic.Finance.SumusWallet.Charge(scope.ServiceProvider, user.Id, model.Amount, SumusToken.Gold)) { try { var finHistory = new UserFinHistory() { Status = UserFinHistoryStatus.Processing, Type = UserFinHistoryType.GoldWithdraw, Source = "GOLD", SourceAmount = TextFormatter.FormatTokenAmountFixed(model.Amount), Destination = "", DestinationAmount = "", Comment = "", TimeCreated = timeNow, UserId = user.Id, }; DbContext.UserFinHistory.Add(finHistory); await DbContext.SaveChangesAsync(); var request = new WithdrawGold() { Status = EmissionRequestStatus.Requested, SumAddress = model.SumusAddress, Amount = model.Amount, TimeCreated = timeNow, UserId = user.Id, RelFinHistoryId = finHistory.Id, }; DbContext.WithdrawGold.Add(request); await DbContext.SaveChangesAsync(); // mint-sender service { var reply = await Bus.Request( MintSender.Subject.Sender.Request.Send, new MintSender.Sender.Request.Send() { Id = request.Id.ToString(), Amount = model.Amount.ToString("F18"), PublicKey = model.SumusAddress, Service = "core_gold_withdrawer", Token = "GOLD", }, MintSender.Sender.Request.SendReply.Parser, 3000 ); if (!reply.Success) { throw new Exception(reply.Error); } } return(APIResponse.Success( new LiteWalletView() { } )); } catch (Exception e) { try { await CoreLogic.Finance.SumusWallet.Refill(scope.ServiceProvider, user.Id, model.Amount, SumusToken.Gold); } catch { } Logger.Error(e, $"Failed to withdraw user {model.Amount} GOLD"); return(APIResponse.GeneralInternalFailure(e)); } } else { return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } } }
public async Task <APIResponse> Authenticate([FromBody] AuthenticateModel model) { var notFoundDesc = "Account not found"; // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(APIErrorCode.AccountNotFound, notFoundDesc)); } var agent = GetUserAgentInfo(); var userLocale = GetUserLocale(); var user = await UserManager.FindByNameAsync(model.Username) ?? await UserManager.FindByEmailAsync(model.Username); if (user != null) { bool isLockedOut = false; // locked out if (user.LockoutEnd != null && user.LockoutEnd.Value.UtcDateTime > DateTime.UtcNow) { // unlock before this check isLockedOut = true; user.LockoutEnd = null; } // get audience JwtAudience audience = JwtAudience.Cabinet; if (!string.IsNullOrWhiteSpace(model.Audience)) { if (Enum.TryParse(model.Audience, true, out JwtAudience aud)) { audience = aud; } } // load options await DbContext.Entry(user).Reference(_ => _.UserOptions).LoadAsync(); var sres = OnSignInResultCheck( services: HttpContext.RequestServices, result: await SignInManager.CheckPasswordSignInAsync(user, model.Password, lockoutOnFailure: true), audience: audience, user: user, tfaRequired: user.TwoFactorEnabled ); if (sres != null) { // successful result if (sres.GetHttpStatusCode() == System.Net.HttpStatusCode.OK && sres.GetErrorCode() == null) { // notification await EmailComposer.FromTemplate(await TemplateProvider.GetEmailTemplate(EmailTemplate.SignedIn, userLocale)) .ReplaceBodyTag("IP", agent.Ip) .Initiator(agent.Ip, agent.Agent, DateTime.UtcNow) .Send(user.Email, user.UserName, EmailQueue) ; // activity var userActivity = CoreLogic.User.CreateUserActivity( user: user, type: Common.UserActivityType.Auth, comment: "Signed in with password", ip: agent.Ip, agent: agent.Agent, locale: userLocale ); DbContext.UserActivity.Add(userActivity); await DbContext.SaveChangesAsync(); } return(sres); } // was locked before if (isLockedOut) { await UserManager.SetLockoutEndDateAsync(user, (DateTimeOffset)DateTime.UtcNow.AddMinutes(60)); return(APIResponse.BadRequest(APIErrorCode.AccountLocked, "Too many unsuccessful attempts. Account is locked, try to sign in later")); } } return(APIResponse.BadRequest(APIErrorCode.AccountNotFound, notFoundDesc)); }
public async Task <APIResponse> Password([FromBody] RestoreModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var agent = GetUserAgentInfo(); var userLocale = GetUserLocale(); // captcha if (!HostingEnvironment.IsDevelopment()) { if (!await Core.Recaptcha.Verify(AppConfig.Services.Recaptcha.SecretKey, model.Captcha, agent.Ip)) { return(APIResponse.BadRequest(nameof(model.Captcha), "Failed to validate captcha")); } } // try find user var user = await UserManager.FindByEmailAsync(model.Email); if (user == null || !(await UserManager.IsEmailConfirmedAsync(user))) { return(APIResponse.Success()); } // confirmation token var token = Core.Tokens.JWT.CreateSecurityToken( appConfig: AppConfig, entityId: user.UserName, audience: JwtAudience.Cabinet, area: Common.JwtArea.RestorePassword, securityStamp: "", validFor: TimeSpan.FromHours(24) ); var callbackUrl = this.MakeAppLink(JwtAudience.Cabinet, fragment: AppConfig.Apps.Cabinet.RoutePasswordRestoration.Replace(":token", token)); // restoration email await EmailComposer.FromTemplate(await TemplateProvider.GetEmailTemplate(EmailTemplate.PasswordRestoration, userLocale)) .Link(callbackUrl) .Initiator(agent.Ip, agent.Agent, DateTime.UtcNow) .Send(model.Email, user.UserName, EmailQueue) ; // activity var userActivity = CoreLogic.User.CreateUserActivity( user: user, type: Common.UserActivityType.Password, comment: "Password restoration requested", ip: agent.Ip, agent: agent.Agent, locale: userLocale ); DbContext.UserActivity.Add(userActivity); await DbContext.SaveChangesAsync(); return(APIResponse.Success()); }
public async Task <APIResponse> Confirm([FromBody] ConfirmModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var user = await GetUserFromDb(); var userTier = CoreLogic.User.GetTier(user); var userLocale = GetUserLocale(); var agent = GetUserAgentInfo(); if (userTier < UserTier.Tier2) { return(APIResponse.BadRequest(APIErrorCode.AccountNotVerified)); } // --- var request = await ( from r in DbContext.SellGoldEth where r.Status == BuySellGoldRequestStatus.Unconfirmed && r.Id == model.RequestId && r.UserId == user.Id && (DateTime.UtcNow - r.TimeCreated).TotalHours < 1.0 select r ) .Include(_ => _.RelFinHistory) .AsTracking() .FirstOrDefaultAsync() ; // request not exists if (request == null) { return(APIResponse.BadRequest(nameof(model.RequestId), "Invalid id")); } // charge using (var scope = HttpContext.RequestServices.CreateScope()) { if (!await CoreLogic.Finance.SumusWallet.Charge(scope.ServiceProvider, request.UserId, request.GoldAmount, SumusToken.Gold)) { // mark request failed request.Status = BuySellGoldRequestStatus.Failed; if (request.RelFinHistory != null) { request.RelFinHistory.Status = UserFinHistoryStatus.Failed; request.RelFinHistory.Comment = "Not enough GOLD"; } await DbContext.SaveChangesAsync(); return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } else { // mark request for processing request.Status = BuySellGoldRequestStatus.Confirmed; if (request.RelFinHistory != null) { request.RelFinHistory.Status = UserFinHistoryStatus.Processing; } await DbContext.SaveChangesAsync(); return(APIResponse.Success( new ConfirmView() { } )); } } }
public async Task <APIResponse> Transparency([FromBody] TransparencyModel model) { var sortExpression = new Dictionary <string, System.Linq.Expressions.Expression <Func <DAL.Models.Transparency, object> > >() { { "date", _ => _.TimeCreated } }; // validate if (Models.API.BasePagerModel.IsInvalid(model, sortExpression.Keys, out var errFields)) { return(APIResponse.BadRequest(errFields)); } var query = ( from a in DbContext.Transparency select a ); var page = await DalExtensions.PagerAsync(query, model.Offset, model.Limit, sortExpression.GetValueOrDefault(model.Sort), model.Ascending ); var list = from i in page.Selected select new TransparencyViewItem() { Comment = i.Comment, Amount = i.Amount, Link = string.Format("https://ipfs.io/ipfs/{0}", i.Hash), Date = ((DateTimeOffset)i.TimeCreated).ToUnixTimeSeconds(), } ; var stat = await DbContext.TransparencyStat.AsNoTracking().LastOrDefaultAsync(); var statAssets = Common.Json.Parse <TransparencyViewStatItem[]>(stat?.AssetsArray ?? "[]"); var statBonds = Common.Json.Parse <TransparencyViewStatItem[]>(stat?.BondsArray ?? "[]"); var statFiat = Common.Json.Parse <TransparencyViewStatItem[]>(stat?.FiatArray ?? "[]"); var statGold = Common.Json.Parse <TransparencyViewStatItem[]>(stat?.GoldArray ?? "[]"); var statTotalOz = stat?.TotalOz ?? ""; var statTotalUsd = stat?.TotalUsd ?? ""; var statDataTime = stat?.DataTimestamp != null? ((DateTimeOffset)stat.DataTimestamp).ToUnixTimeSeconds(): (long?)null; var statAuditTime = stat?.AuditTimestamp != null? ((DateTimeOffset)stat.AuditTimestamp).ToUnixTimeSeconds(): (long?)null; return(APIResponse.Success( new TransparencyView() { Stat = new TransparencyViewStat() { Assets = statAssets, Bonds = statBonds, Fiat = statFiat, Gold = statGold, TotalOz = statTotalOz, TotalUsd = statTotalUsd, DataTimestamp = statDataTime, AuditTimestamp = statAuditTime, }, Items = list.ToArray(), Limit = model.Limit, Offset = model.Offset, Total = page.TotalCount, } )); }
public async Task <APIResponse> AssetEth([FromBody] AssetEthModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(errFields)); } // try parse amount if (!BigInteger.TryParse(model.Amount, out var inputAmount) || inputAmount < 1) { return(APIResponse.BadRequest(nameof(model.Amount), "Invalid amount")); } // try parse fiat currency var exchangeCurrency = FiatCurrency.Usd; if (Enum.TryParse(model.Currency, true, out FiatCurrency fc)) { exchangeCurrency = fc; } // --- var rcfg = RuntimeConfigHolder.Clone(); var user = await GetUserFromDb(); var userTier = CoreLogic.User.GetTier(user); var agent = GetUserAgentInfo(); if (userTier < UserTier.Tier2) { return(APIResponse.BadRequest(APIErrorCode.AccountNotVerified)); } // --- if (!rcfg.Gold.AllowTradingEth) { return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } var limits = WithdrawalLimits(rcfg, TradableCurrency.Eth); var estimation = await Estimation(rcfg, inputAmount, TradableCurrency.Eth, exchangeCurrency, model.EthAddress, model.Reversed, limits.Min, limits.Max); if (!estimation.TradingAllowed || estimation.ResultCurrencyAmount < 1) { return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } if (estimation.IsLimitExceeded) { return(APIResponse.BadRequest(APIErrorCode.TradingExchangeLimit, estimation.View.Limits)); } // limit gold amount to max available if (estimation.ResultGoldAmount.FromSumus() > user.UserSumusWallet.BalanceGold) { estimation = await Estimation(rcfg, user.UserSumusWallet.BalanceGold.ToSumus(), TradableCurrency.Eth, exchangeCurrency, model.EthAddress, false, limits.Min, limits.Max); if (!estimation.TradingAllowed || estimation.ResultCurrencyAmount < 1) { return(APIResponse.BadRequest(APIErrorCode.TradingNotAllowed)); } if (estimation.IsLimitExceeded) { return(APIResponse.BadRequest(APIErrorCode.TradingExchangeLimit, estimation.View.Limits)); } } var timeNow = DateTime.UtcNow; // history var finHistory = new DAL.Models.UserFinHistory() { Status = UserFinHistoryStatus.Unconfirmed, Type = UserFinHistoryType.GoldSell, Source = "GOLD", SourceAmount = TextFormatter.FormatTokenAmountFixed(estimation.ResultGoldAmount, TokensPrecision.Sumus), Destination = "ETH", DestinationAmount = TextFormatter.FormatTokenAmountFixed(estimation.ResultCurrencyAmount, TokensPrecision.Ethereum), Comment = "", TimeCreated = timeNow, UserId = user.Id, }; DbContext.UserFinHistory.Add(finHistory); await DbContext.SaveChangesAsync(); // request var request = new DAL.Models.SellGoldEth() { Status = BuySellGoldRequestStatus.Unconfirmed, GoldAmount = estimation.ResultGoldAmount.FromSumus(), Destination = model.EthAddress, EthAmount = estimation.ResultCurrencyAmount.FromSumus(), ExchangeCurrency = exchangeCurrency, GoldRateCents = estimation.CentsPerGoldRate, EthRateCents = estimation.CentsPerAssetRate, TimeCreated = timeNow, RelFinHistoryId = finHistory.Id, UserId = user.Id, }; // add and save DbContext.SellGoldEth.Add(request); await DbContext.SaveChangesAsync(); var assetPerGold = CoreLogic.Finance.Estimation.AssetPerGold(TradableCurrency.Eth, estimation.CentsPerAssetRate, estimation.CentsPerGoldRate); return(APIResponse.Success( new AssetEthView() { RequestId = request.Id, EthRate = estimation.CentsPerAssetRate / 100d, GoldRate = estimation.CentsPerGoldRate / 100d, Currency = exchangeCurrency.ToString().ToUpper(), EthPerGoldRate = assetPerGold.ToString(), Estimation = estimation.View, } )); }
public async Task <APIResponse> TFAEdit([FromBody] TfaEditModel model) { // validate if (BaseValidableModel.IsInvalid(model, out var errFields)) { return(APIResponse.BadRequest(nameof(model.Code), "Invalid 2fa code")); } var user = await GetUserFromDb(); var agent = GetUserAgentInfo(); var userLocale = GetUserLocale(); var makeChange = user.TwoFactorEnabled != model.Enable; if (makeChange) { if (!Core.Tokens.GoogleAuthenticator.Validate(model.Code, user.TfaSecret)) { return(APIResponse.BadRequest(nameof(model.Code), "Invalid 2fa code")); } user.TwoFactorEnabled = model.Enable; } user.UserOptions.InitialTfaQuest = true; await DbContext.SaveChangesAsync(); // notify if (makeChange) { if (model.Enable) { // notification await EmailComposer.FromTemplate(await TemplateProvider.GetEmailTemplate(EmailTemplate.TfaEnabled, userLocale)) .Initiator(agent.Ip, agent.Agent, DateTime.UtcNow) .Send(user.Email, user.UserName, EmailQueue) ; // activity var userActivity = CoreLogic.User.CreateUserActivity( user: user, type: Common.UserActivityType.Settings, comment: "Two factor authentication enabled", ip: agent.Ip, agent: agent.Agent, locale: userLocale ); DbContext.UserActivity.Add(userActivity); await DbContext.SaveChangesAsync(); } else { // notification await EmailComposer.FromTemplate(await TemplateProvider.GetEmailTemplate(EmailTemplate.TfaDisabled, userLocale)) .Initiator(agent.Ip, agent.Agent, DateTime.UtcNow) .Send(user.Email, user.UserName, EmailQueue) ; // activity var userActivity = CoreLogic.User.CreateUserActivity( user: user, type: Common.UserActivityType.Settings, comment: "Two factor authentication disabled", ip: agent.Ip, agent: agent.Agent, locale: userLocale ); DbContext.UserActivity.Add(userActivity); await DbContext.SaveChangesAsync(); } } return(APIResponse.Success(MakeTFASetupView(user))); }