public ActionResult AccountUpdate([FromQuery] string token, MilvanethProtocol data) { if (!_token.TryDecode(token, out var payload)) { return(StatusCode(401)); } if (!(data?.Data is AccountUpdate update) || !update.Check()) { return(StatusCode(400)); } try { if (payload.ValidTo < _time.UtcNow) { return(StatusCode(511)); } _auth.EnsureToken(payload, TokenPurpose.AccessToken, GlobalOperation.ACCOUNT_STATUS, 0, out var account); account.Trace = account.Trace.Union(update.Trace).ToArray(); account.DisplayName = update.DisplayName; _context.AccountData.Update(account); _context.SaveChanges(); return(StatusCode(200)); } catch (Exception e) { Log.Error(e, "Error in ACCOUNT/UPDATE"); return(StatusCode(500)); } }
public int Step1(MilvanethProtocol mp) { if (!ApiVendor.HasToken()) { return(02_0008); } if (mp == null) // as we have karma, this is not too serious { return(00_0001); } if (CheckVendor.NotValidData(mp)) { return(02_0008); } var result = ApiCall.DataUpload.Call(null, mp); return(00_0000); }
public ActionResult <MilvanethProtocol> AccountRecovery(MilvanethProtocol data) { if (data?.Data is RecoveryEmail) { return(AccountRecoveryEmail(data)); } if (data?.Data is RecoveryGame) { return(AccountRecoveryGame(data)); } return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); }
public ActionResult <MilvanethProtocol> SessionCreate(MilvanethProtocol data) { if (!(data?.Data is AuthRequest request) || !request.Check()) { return(new MilvanethProtocol { Context = null, Data = new AuthResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } try { var key = _context.KeyStore .Include(x => x.HoldingAccountNavigation) .ThenInclude(x => x.PrivilegeLevelNavigation) .Include(x => x.UsageNavigation) .Single(x => x.Key.SequenceEqual(request.AuthToken)); _auth.EnsureKey(key, new KeyUsage { CreateSession = true }, GlobalOperation.SESSION_CREATE, 0, "Create session via session/create", _accessor.GetIp()); var account = key.HoldingAccountNavigation; _auth.EnsureAccount(account, new PrivilegeConfig { AccessData = true }, GlobalOperation.SESSION_CREATE, 0, "Create session via session/create", _accessor.GetIp()); var renew = _api.Sign(_renewToken, 1, account, _time.UtcNow, _time.UtcNow.AddSeconds(GlobalConfig.TOKEN_RENEW_LIFE_TIME)); var access = _token.Sign(new TokenPayload(_time.UtcNow.AddSeconds(GlobalConfig.TOKEN_DATA_LIFE_TIME), account.AccountId, TokenPurpose.AccessToken, renew.KeyId)); _context.SaveChanges(); return(new MilvanethProtocol { Context = null, Data = new AuthResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, RenewToken = renew.Key, SessionToken = access } }); } catch (Exception e) { Log.Error(e, "Error in SESSION/CREATE"); return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }
public ActionResult <MilvanethProtocol> SessionRenew([FromQuery] string token, MilvanethProtocol data) { if (!(data?.Data is AuthRenew renew) || !renew.Check()) { return(new MilvanethProtocol { Context = null, Data = new AuthResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } try { _token.TryDecode(token, out var payload); var key = _context.KeyStore .Include(x => x.HoldingAccountNavigation) .ThenInclude(x => x.PrivilegeLevelNavigation) .Include(x => x.UsageNavigation) .Single(x => x.Key.SequenceEqual(renew.RenewToken)); _auth.EnsureKey(key, new KeyUsage { RenewSession = true, CreateSession = payload == null }, GlobalOperation.SESSION_RENEW, 0, "Renew session via session/renew", _accessor.GetIp()); var account = key.HoldingAccountNavigation; _auth.EnsureAccount(account, new PrivilegeConfig { AccessData = true }, GlobalOperation.SESSION_RENEW, 0, "Create session via session/renew", _accessor.GetIp()); var newRenew = _api.Sign(_renewToken, 1, account, _time.UtcNow, _time.UtcNow.AddSeconds(GlobalConfig.TOKEN_RENEW_LIFE_TIME)); var access = _token.Sign(new TokenPayload(_time.UtcNow.AddSeconds(GlobalConfig.TOKEN_DATA_LIFE_TIME), account.AccountId, TokenPurpose.AccessToken, newRenew.KeyId)); if (payload != null) { _context.TokenRevocationList.Add(new TokenRevocationList { Reason = GlobalOperation.SESSION_RENEW, RevokeSince = _time.UtcNow, TokenSerial = payload.TokenId, }); } _context.SaveChanges(); return(new MilvanethProtocol { Context = null, Data = new AuthResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, RenewToken = newRenew.Key, SessionToken = access } }); } catch (Exception e) { Log.Error(e, "Error in SESSION/RENEW"); return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }
public ActionResult <MilvanethProtocol> SessionChange(MilvanethProtocol data) { if (!(data?.Data is AuthRequest request) || !request.Check()) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } try { var key = _context.KeyStore .Include(x => x.HoldingAccountNavigation) .ThenInclude(x => x.PrivilegeLevelNavigation) .Include(x => x.UsageNavigation) .Single(x => x.Key.SequenceEqual(request.AuthToken)); _auth.EnsureKey(key, new KeyUsage { GetChangeToken = true }, GlobalOperation.SESSION_CHANGE, 0, "Create change token via session/change", _accessor.GetIp()); var account = key.HoldingAccountNavigation; _auth.EnsureAccount(account, new PrivilegeConfig { AccountOperation = true }, GlobalOperation.SESSION_CHANGE, 0, "Create change token via session/change", _accessor.GetIp()); var recovery = _api.Sign(_changeToken, 1, account, _time.UtcNow, _time.UtcNow.AddSeconds(GlobalConfig.TOKEN_ACCOUNT_LIFE_TIME)); _context.SaveChanges(); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, AuthToken = recovery.Key, } }); } catch (Exception e) { Log.Error(e, "Error in SESSION/CHANGE"); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }
public ActionResult <MilvanethProtocol> AuthStart(MilvanethProtocol data) { if (!(data?.Data is ClientChallenge challenge) || !challenge.Check()) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } if (!_pow.Verify(challenge.ProofOfWork) && _pow.ConditionalGenerate(out var requirement)) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.RATE_POW_REQUIRED, ProofOfWork = requirement, ReportTime = _time.SafeNow, } }); } try { var accountData = _context.AccountData.Include(x => x.PrivilegeLevelNavigation).SingleOrDefault(x => x.AccountName == challenge.Username); if (accountData == null) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.DATA_NO_SUCH_USER, ReportTime = _time.SafeNow, } }); } if (accountData.PrivilegeLevel == GlobalConfig.ACCOUNT_BLOCKED_LEVEL) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.OP_SANCTION, ReportTime = _time.SafeNow, } }); } if (accountData.HasSuspended() && accountData.SuspendUntil >= _time.UtcNow) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.OP_ACCOUNT_SUSPENDED, ReportTime = _time.SafeNow, } }); } if (accountData.PasswordRetry > GlobalConfig.ACCOUNT_PASSWORD_RETRY_TOLERANCE && accountData.LastRetry.HasValue && (_time.UtcNow - accountData.LastRetry.Value).Seconds < GlobalConfig.ACCOUNT_PASSWORD_RETRY_COOLDOWN) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.OP_PASSWORD_RETRY_TOO_MUCH, ReportTime = _time.SafeNow, } }); } _auth.EnsureAccount(accountData, new PrivilegeConfig { Login = true }, GlobalOperation.AUTH_START, 0, "Start login via auth/start", _accessor.GetIp()); var session = _srp.DoServerResponse(accountData.AccountId, accountData.GroupParam, accountData.Verifier, out var token); _context.AccountData.Update(accountData); _context.SaveChanges(); return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { GroupParam = accountData.GroupParam, Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, Salt = accountData.Salt, ServerToken = token, SessionId = session } }); } catch (Exception e) { Log.Error(e, "Error in AUTH/START"); return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }
public ActionResult <MilvanethProtocol> AuthReset(MilvanethProtocol data) { if (!(data?.Data is RecoveryRequest request) || !request.Check()) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } try { var key = _context.KeyStore .Include(x => x.HoldingAccountNavigation) .ThenInclude(x => x.PrivilegeLevelNavigation) .Include(x => x.UsageNavigation) .Single(x => x.Key.SequenceEqual(request.OperationToken)); _auth.EnsureKey(key, new KeyUsage { ChangePassword = true }, GlobalOperation.AUTH_RESET, 0, "Change password via auth/reset", _accessor.GetIp()); var account = key.HoldingAccountNavigation; if (!string.IsNullOrWhiteSpace(request.Email)) { account.Email = request.Email; } account.GroupParam = (short)request.GroupParam; account.Salt = request.Salt; account.Verifier = request.Verifier; var auth = _api.Sign(_authToken, 1, account, _time.UtcNow, _time.UtcNow.AddSeconds(GlobalConfig.TOKEN_ACCOUNT_LIFE_TIME)); _context.AccountData.Update(account); _context.SaveChanges(); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, AuthToken = auth.Key } }); } catch (Exception e) { Log.Error(e, "Error in AUTH/RESET"); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }
public ActionResult <MilvanethProtocol> AuthFinish(MilvanethProtocol data) { if (!(data?.Data is ClientResponse response) || !response.Check()) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } try { if (!_srp.DoServerValidate(response.SessionId, response.ClientToken, response.ClientEvidence, out var accountId)) { if (accountId != 0) { var accountData = _context.AccountData.Single(x => x.AccountId == accountId); if (accountData.PasswordRetry > GlobalConfig.ACCOUNT_PASSWORD_RETRY_TOLERANCE && accountData.LastRetry.HasValue && (_time.UtcNow - accountData.LastRetry.Value).Seconds < GlobalConfig.ACCOUNT_PASSWORD_RETRY_COOLDOWN) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_PASSWORD_RETRY_TOO_MUCH, ReportTime = _time.SafeNow, } }); } if (accountData.PasswordRetry == null) { accountData.PasswordRetry = 1; } accountData.PasswordRetry += 1; accountData.LastRetry = _time.UtcNow; _context.AccountData.Update(accountData); _context.SaveChanges(); } return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_RECORD_MISMATCH, ReportTime = _time.SafeNow, } }); } var account = _context.AccountData.Include(x => x.PrivilegeLevelNavigation).Single(x => x.AccountId == accountId); if (account.PasswordRetry > GlobalConfig.ACCOUNT_PASSWORD_RETRY_TOLERANCE && account.LastRetry.HasValue && (_time.UtcNow - account.LastRetry.Value).Seconds < GlobalConfig.ACCOUNT_PASSWORD_RETRY_COOLDOWN) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_PASSWORD_RETRY_TOO_MUCH, ReportTime = _time.SafeNow, } }); } account.PasswordRetry = 0; _context.AccountData.Update(account); _context.AccountLog.Add(new AccountLog { ReportTime = _time.UtcNow, AccountId = account.AccountId, Message = GlobalOperation.AUTH_FINISH, Detail = "Finish login via auth/finish", IpAddress = _accessor.GetIp() }); _context.SaveChanges(); var key = _api.Sign(_authToken, 1, account, _time.UtcNow, _time.UtcNow.AddSeconds(GlobalConfig.TOKEN_ACCOUNT_LIFE_TIME)); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, AuthToken = key.Key } }); } catch (Exception e) { Log.Error(e, "Error in AUTH/FINISH"); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }
public ActionResult <MilvanethProtocol> DataOverview([FromQuery] string token, int part, MilvanethProtocol data) { if (!_token.TryDecode(token, out var payload)) { return(new MilvanethProtocol { Context = null, Data = new OverviewResponse { Message = GlobalMessage.OP_TOKEN_NOT_FOUND, ReportTime = _time.SafeNow, } }); } if (!(data?.Data is OverviewRequest request) || !request.Check()) { return(new MilvanethProtocol { Context = null, Data = new OverviewResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } try { if (payload.ValidTo < _time.UtcNow) { return(new MilvanethProtocol { Context = null, Data = new OverviewResponse { Message = GlobalMessage.OP_TOKEN_RENEW_REQUIRED, ReportTime = _time.SafeNow, } }); } _auth.EnsureToken(payload, TokenPurpose.AccessToken, GlobalOperation.DATA_OVERVIEW, -15, out _); var query = request.QueryItems.OrderBy(x => x).Skip(part * GlobalConfig.DATA_OVERVIEW_QUERY_LIMIT) .Take(GlobalConfig.DATA_OVERVIEW_QUERY_LIMIT).ToArray(); var param = new NpgsqlParameter <int[]>("query", query); var result = _context.OverviewData.AsNoTracking() .FromSql( $"select record_id, bucket_id, report_time, world, reporter_id, item_id, open_listing, demand from (select *, rank() over (partition by item_id, world order by report_time desc) as ranking from overview_data where item_id = any(@query)) as result_table where ranking = 1", param).Select(x => x.FromDb()).ToList(); return(new MilvanethProtocol { Context = null, Data = new OverviewResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, EstiTotalParts = request.QueryItems.Length / GlobalConfig.DATA_OVERVIEW_QUERY_LIMIT + 1, FinalPart = part * GlobalConfig.DATA_OVERVIEW_QUERY_LIMIT + query.Count() >= request.QueryItems.Length, PartId = part, Results = result } }); } catch (Exception e) { Log.Error(e, $"Error in DATA/OVERVIEW/{part}"); return(new MilvanethProtocol { Context = null, Data = new OverviewResponse { Message = GlobalMessage.OP_TOKEN_NOT_FOUND, ReportTime = _time.SafeNow, } }); } }
public ActionResult DataUpload([FromQuery] string token, MilvanethProtocol data) { if (!_token.TryDecode(token, out var payload)) { return(StatusCode(401)); } if (!(data?.Context).Check() || !(data?.Data is PackedResult result) || !result.Check()) { return(StatusCode(400)); } try { if (payload.ValidTo < _time.UtcNow) { return(StatusCode(511)); } _auth.EnsureToken(payload, TokenPurpose.AccessToken, GlobalOperation.DATA_UPLOAD, 0, out var account); // virtual deduction for token validation operation var karmaBefore = account.Karma; account.Karma -= 20; switch (result.Type) { case PackedResultType.Inventory: var inventory = (InventoryResult)result.Result; if (inventory.Context != data.Context.CharacterId) { break; } _repo.Character.Commit(account.AccountId, new CharacterData { CharacterId = inventory.Context, ServiceId = data.Context.ServiceId, AccountId = account.AccountId, Inventory = LZ4MessagePackSerializer.Serialize(data.Data) }, false); account.Karma += 20; break; case PackedResultType.InventoryNetwork: var inventoryNetwork = (InventoryResult)result.Result; _repo.Retainer.Commit(account.AccountId, new RetainerData { RetainerId = inventoryNetwork.Context, Character = data.Context.CharacterId, World = data.Context.World, Inventory = LZ4MessagePackSerializer.Serialize(data.Data) }, false); account.Karma += 20; break; case PackedResultType.Artisan: var artisan = (ArtisanResult)result.Result; _repo.Character.CommitRange(account.AccountId, artisan.ArtisanList.Select(x => new CharacterData { CharacterId = x.CharacterId, CharacterName = x.IsValid ? x.CharacterName : null, HomeWorld = x.IsValid && !x.FromMemory ? data.Context.World : 0, }), false); account.Karma += 20 + artisan.ArtisanList.Count(x => x.IsValid) * 10 + artisan.ArtisanList.Count(x => !x.IsValid) * 3; break; case PackedResultType.MarketHistory: var marketHistory = (MarketHistoryResult)result.Result; _repo.History.CommitRange(data.Context.CharacterId, marketHistory.HistoryItems.Select(x => x.ToDb(result.ReportTime, data.Context.World)), false); account.Karma += 20 + 30; break; case PackedResultType.MarketListing: var marketListing = (MarketListingResult)result.Result; var artisanFk = marketListing.ListingItems.Select(x => x.ArtisanId); var ownerFk = marketListing.ListingItems.Select(x => new { x.OwnerId, x.PlayerName }).ToImmutableList(); _repo.Character.CommitRange(account.AccountId, ownerFk.Where(x => x.OwnerId != 0).GroupBy(x => x.OwnerId).Select(y => { var x = y.First(); return(new CharacterData { CharacterId = x.OwnerId, HomeWorld = data.Context.World, CharacterName = x.PlayerName }); }), false); _repo.Character.CommitRange(account.AccountId, artisanFk.Where(x => x > 0 && ownerFk.All(y => y.OwnerId != x)).Distinct().Select(x => new CharacterData { CharacterId = x }), true); _repo.Retainer.CommitRange(account.AccountId, marketListing.ListingItems.GroupBy(x => x.RetainerId).Select(y => { var x = y.First(); return(new RetainerData { RetainerId = x.RetainerId, Character = x.OwnerId, RetainerName = x.RetainerName, Location = x.RetainerLocation, World = data.Context.World }); }), true); _repo.Listing.CommitRange(data.Context.CharacterId, marketListing.ListingItems.Select(x => x.ToDb(result.ReportTime, data.Context.World)), false); foreach (var x in marketListing.ListingItems.GroupBy(x => x.ItemId)) { _repo.Overview.Commit(data.Context.CharacterId, new OverviewData { ReportTime = result.ReportTime, World = data.Context.World, ItemId = x.Key, OpenListing = (short)x.Count(), }, false); } account.Karma += 20 + 100; break; case PackedResultType.MarketOverview: var marketOverview = (MarketOverviewResult)result.Result; _repo.Overview.CommitRange(data.Context.CharacterId, marketOverview.ResultItems.Select(x => x.ToDb(result.ReportTime, data.Context.World)), false); account.Karma += 20 + 30 + marketOverview.ResultItems.Count; break; case PackedResultType.RetainerHistory: // todo break; case PackedResultType.RetainerList: var retainerList = (RetainerInfoResult)result.Result; _repo.Retainer.CommitRange(account.AccountId, retainerList.RetainerInfo.Select(x => x.ToDb(data.Context.CharacterId, data.Context.World)), false); account.Karma += 20 + retainerList.RetainerInfo.Count * 4; break; case PackedResultType.RetainerUpdate: #region NotFinishedCode // todo // 目前有两种方案,直接更新和Copy-on-Update,前者的主要问题在于并发条件下Time-Bucket体系可能会出现问题,后者则在于性能开销,故目前暂不对此数据进行处理 //var retainerUpdate = (RetainerUpdateResult)result.Result; //foreach (var item in retainerUpdate.UpdateItems) //{ // var record = _context.ListingData // .Where(x => x.RetainerId == item.RetainerId && x.ContainerId == (short)item.ContainerId && x.SlotId == item.ContainerSlot) // .OrderByDescending(x => x.ReportTime).Take(1); // if (!record.Any()) // { // var retInfo = // _context.RetainerData.SingleOrDefault(x => x.RetainerId == item.RetainerId); // var record2 = _context.ListingData.Where(x => // x.World == data.Context.World && x.ItemId == item.ItemInfo.ItemId && // x.ReportTime <= result.ReportTime) // .OrderByDescending(x => x.ReportTime).Take(1); // _repo.Commit(data.Context.CharacterId, new ListingData // { // BucketId = record2.Any() ? record2.First().BucketId : Guid.Empty, // ReportTime = result.ReportTime, // World = data.Context.World, // ReporterId = data.Context.CharacterId, // ListingId = 0, // RetainerId = item.RetainerId, // OwnerId = data.Context.CharacterId, // ArtisanId = item.ItemInfo.ArtisanId, // UnitPrice = item.NewPrice, // TotalTax = 0, // Quantity = item.ItemInfo.Amount, // ItemId = item.ItemInfo.ItemId, // UpdateTime = Helper.DateTimeToUnixTimeStamp(result.ReportTime), // ContainerId = (short)item.ContainerId, // SlotId = (short)item.ContainerSlot, // Condition = (short)item.ItemInfo.Durability, // SpiritBond = (short)item.ItemInfo.SpiritBond, // Materia1 = item.ItemInfo.Materia1, // Materia2 = item.ItemInfo.Materia2, // Materia3 = item.ItemInfo.Materia3, // Materia4 = item.ItemInfo.Materia4, // Materia5 = item.ItemInfo.Materia5, // RetainerName = retInfo?.RetainerName, // PlayerName = item.ContainerId == InventoryContainerId.HIRE_LISTING ? null : _context.CharacterData.SingleOrDefault(x => x.CharacterId == data.Context.CharacterId)?.CharacterName, // IsHq = item.ItemInfo.IsHq, // MateriaCount = 0, //todo // OnMannequin = item.ContainerId != InventoryContainerId.HIRE_LISTING, // RetainerLoc = 0,//todo // DyeId = item.ItemInfo.DyeId // }, true); // account.Karma += 25; // continue; // } // var recordEntity = record.First(); // if (recordEntity.ReportTime >= result.ReportTime) // { // account.Karma -= 25; // continue; // } // recordEntity. //} //account.Karma += 20 + retainerUpdate.UpdateItems.Count * 25; #endregion account.Karma += 20; break; case PackedResultType.Status: var status = (StatusResult)result.Result; if (status.CharacterId != data.Context.CharacterId) { break; } if (account.PlayedCharacter == null) { account.PlayedCharacter = new long[0]; } if (!account.PlayedCharacter.Contains(status.CharacterId)) { var temp = new long[account.PlayedCharacter.Length + 1]; Array.Copy(account.PlayedCharacter, temp, account.PlayedCharacter.Length); temp[temp.Length - 1] = status.CharacterId; account.PlayedCharacter = temp; } _repo.Character.Commit(account.AccountId, new CharacterData { CharacterId = status.CharacterId, CharacterName = status.CharacterName, ServiceId = data.Context.ServiceId, AccountId = account.AccountId, HomeWorld = status.CharacterHomeWorld, JobLevels = status.LevelInfo.ToDb(), GilHold = status.CharaInfo.GilHold }, false); account.Karma += 20 + 40; break; case PackedResultType.LobbyService: var lobbyService = (LobbyServiceResult)result.Result; if (lobbyService.ServiceId != data.Context.ServiceId) { break; } if (account.RelatedService == null) { account.RelatedService = new long[0]; } if (!account.RelatedService.Contains(lobbyService.ServiceId)) { var temp = new long[account.RelatedService.Length + 1]; Array.Copy(account.RelatedService, temp, account.RelatedService.Length); temp[temp.Length - 1] = lobbyService.ServiceId; account.RelatedService = temp; } account.Karma += 20; break; case PackedResultType.LobbyCharacter: var lobbyCharacter = (LobbyCharacterResult)result.Result; if (!DataChecker.CheckOnlineCharacterBinding(data.Context.ServiceId, lobbyCharacter.CharacterItems)) { #warning api availability is not checked. account.Karma -= 180; break; } if (account.RelatedService == null) { account.RelatedService = new long[0]; } if (!account.RelatedService.Contains(data.Context.ServiceId)) { var temp = new long[account.RelatedService.Length + 1]; Array.Copy(account.RelatedService, temp, account.RelatedService.Length); temp[temp.Length - 1] = data.Context.ServiceId; account.RelatedService = temp; } _repo.Character.CommitRange(account.AccountId, lobbyCharacter.CharacterItems.Select(x => x.ToDb(data.Context.ServiceId)), false); account.Karma += 20 + 20 * lobbyCharacter.CharacterItems.Count; break; default: // do nothing break; } _context.KarmaLog.Add(new KarmaLog { ReportTime = _time.UtcNow, AccountId = account.AccountId, Reason = GlobalOperation.DATA_UPLOAD + (int)result.Type, Before = karmaBefore, After = account.Karma }); _context.AccountData.Update(account); _context.SaveChanges(); return(StatusCode(200)); } catch (Exception e) { Log.Error(e, "Error in DATA/UPLOAD"); return(StatusCode(500)); } }
public static bool NotValidData(MilvanethProtocol mp) { return(mp.Context == null || !(mp.Data is PackedResult)); }
public ActionResult <MilvanethProtocol> AccountCreate(MilvanethProtocol data) { if (!(data?.Data is RegisterForm form) || !form.Check()) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } if (!_pow.Verify(form.ProofOfWork)) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.RATE_POW_REQUIRED, ProofOfWork = _pow.Generate((byte)Math.Max(GlobalConfig.POW_SENSITIVE_OPERATION, _pow.Difficulty)), ReportTime = _time.SafeNow, } }); } try { if (_context.AccountData.Any(x => x.AccountName == form.Username)) { return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.DATA_USERNAME_OCCUPIED, ReportTime = _time.SafeNow, } }); } //var accountData = _context.AccountData.CreateProxy(); var accountData = new AccountData(); { accountData.AccountName = form.Username; accountData.DisplayName = form.DisplayName; accountData.Email = form.Email; accountData.EmailConfirmed = false; accountData.Salt = form.Salt; accountData.Verifier = form.Verifier; accountData.GroupParam = (short)form.GroupParam; accountData.RegisterService = form.Service.ServiceId; accountData.RelatedService = new long[] { form.Service.ServiceId }; accountData.PlayedCharacter = null; accountData.Trace = form.Trace; accountData.Karma = 0; accountData.PrivilegeLevelNavigation = _userPrivilege; accountData.SuspendUntil = null; accountData.PasswordRetry = 0; accountData.LastRetry = DateTime.UtcNow; } _context.AccountData.Add(accountData); _context.SaveChanges(); _repo.Character.CommitRange(accountData.AccountId, form.Character.CharacterItems.Select(x => x.ToDb(accountData.RegisterService)), true); _auth.EnsureAccount(accountData, new PrivilegeConfig { Login = true }, GlobalOperation.ACCOUNT_CREATE, GlobalConfig.USER_INITIAL_KARMA, "Registered new account via account/create", _accessor.GetIp()); var session = _srp.DoServerResponse(accountData.AccountId, accountData.GroupParam, accountData.Verifier, out var token); return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { GroupParam = accountData.GroupParam, Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, Salt = accountData.Salt, ServerToken = token, SessionId = session } }); } catch (Exception e) { Log.Error(e, "Error in ACCOUNT/CREATE"); return(new MilvanethProtocol { Context = null, Data = new ServerChallenge { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }
public ActionResult <MilvanethProtocol> AccountRecoveryGame(MilvanethProtocol data) { if (!(data?.Data is RecoveryGame game) || !game.Check()) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } if (!_pow.Verify(game.ProofOfWork)) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.RATE_POW_REQUIRED, AuthToken = _pow.Generate((byte)Math.Max(GlobalConfig.POW_SENSITIVE_OPERATION, _pow.Difficulty)), ReportTime = _time.SafeNow, } }); } try { var user = _context.AccountData.Include(x => x.PrivilegeLevelNavigation).Single(x => x.AccountName == game.Username); _auth.EnsureAccount(user, new PrivilegeConfig { AccountOperation = true }, GlobalOperation.ACCOUNT_RECOVERY_GAME, 0, "Recovery account via account/recovery with game", _accessor.GetIp()); var success = game.Trace.Count(t => user.Trace.Contains(t)); if (!DataChecker.WeightedSuccess(success, game.Trace.Length, user.Trace.Length)) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_TRACE_MISMATCH, ReportTime = _time.SafeNow, } }); } if (user.RegisterService != game.Service.ServiceId) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_RECORD_MISMATCH, ReportTime = _time.SafeNow, } }); } var recovery = _api.Sign(_changeToken, 1, user, _time.UtcNow, _time.UtcNow.AddSeconds(GlobalConfig.TOKEN_ACCOUNT_LIFE_TIME)); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, AuthToken = recovery.Key, } }); } catch (Exception e) { Log.Error(e, "Error in ACCOUNT/RECOVERYGAME"); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }
public ActionResult <MilvanethProtocol> AccountRecoveryEmail(MilvanethProtocol data) { if (!(data?.Data is RecoveryEmail email) || !email.Check()) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } try { var user = _context.AccountData.Include(x => x.PrivilegeLevelNavigation).SingleOrDefault(x => x.AccountName == email.Username); if (user == null) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_NO_SUCH_USER, ReportTime = _time.SafeNow, } }); } _auth.EnsureAccount(user, new PrivilegeConfig { AccountOperation = true }, GlobalOperation.ACCOUNT_RECOVERY_EMAIL, 0, "Recovery account via account/recovery with email", _accessor.GetIp()); if (user.Email == null) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_NO_EMAIL_RECORDED, ReportTime = _time.SafeNow, } }); } var record = _context.EmailVerifyCode.OrderByDescending(x => x.SendTime).FirstOrDefault(); if (string.IsNullOrEmpty(email.Code)) { if (record != null && record.SendTime.AddSeconds(GlobalConfig.ACCOUNT_VERIFY_CODE_COOLDOWN) > _time.UtcNow) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.RATE_LIMIT, ReportTime = _time.SafeNow, } }); } if (user.Email.Equals(email.Email, StringComparison.InvariantCultureIgnoreCase)) { string code; if (record != null && record.ValidTo > _time.UtcNow.AddSeconds(3 * GlobalConfig.ACCOUNT_VERIFY_CODE_COOLDOWN)) { code = record.Code; record.SendTime = _time.UtcNow; _context.EmailVerifyCode.Update(record); } else { using (var cryptoRng = new RNGCryptoServiceProvider()) { var seed = new byte[5]; cryptoRng.GetBytes(seed); code = Helper.ToCode(seed); } _context.EmailVerifyCode.Add(new EmailVerifyCode { AccountId = user.AccountId, Email = user.Email, FailedRetry = 0, ValidTo = _time.UtcNow.AddSeconds(GlobalConfig.ACCOUNT_VERIFY_CODE_LIFE_TIME), Code = code, SendTime = _time.UtcNow }); } _context.SaveChanges(); _mail.SendCode(user.Email, user.DisplayName, code); } else { var rand = new Random(); // prevent timing attack Thread.Sleep(1000 + rand.Next(3000)); } return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, } }); } if (record == null || record.ValidTo < _time.UtcNow || record.FailedRetry >= 5) { return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_INPUT, ReportTime = _time.SafeNow, } }); } if (record.Code != email.Code.ToUpperInvariant()) { record.FailedRetry += 1; _context.EmailVerifyCode.Update(record); _context.SaveChanges(); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.DATA_INVALID_CAPTCHA, ReportTime = _time.SafeNow, } }); } var recovery = _api.Sign(_changeToken, 1, user, _time.UtcNow, _time.UtcNow.AddSeconds(GlobalConfig.TOKEN_ACCOUNT_LIFE_TIME)); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OK_SUCCESS, ReportTime = _time.SafeNow, AuthToken = recovery.Key, } }); } catch (Exception e) { Log.Error(e, "Error in ACCOUNT/RECOVERYMAIL"); return(new MilvanethProtocol { Context = null, Data = new ServerResponse { Message = GlobalMessage.OP_INVALID, ReportTime = _time.SafeNow, } }); } }