Example #1
0
        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);
        }
Example #3
0
        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,
                }
            });
        }
Example #4
0
        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,
                    }
                });
            }
        }
Example #5
0
        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,
                    }
                });
            }
        }
Example #6
0
        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,
                    }
                });
            }
        }
Example #7
0
        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,
                    }
                });
            }
        }
Example #8
0
        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,
                    }
                });
            }
        }
Example #9
0
        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,
                    }
                });
            }
        }
Example #10
0
        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,
                    }
                });
            }
        }
Example #11
0
        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));
            }
        }
Example #12
0
 public static bool NotValidData(MilvanethProtocol mp)
 {
     return(mp.Context == null || !(mp.Data is PackedResult));
 }
Example #13
0
        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,
                    }
                });
            }
        }
Example #14
0
        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,
                    }
                });
            }
        }
Example #15
0
        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,
                    }
                });
            }
        }