login by password

This commit is contained in:
DismissedLight
2022-11-16 19:48:07 +08:00
parent d8b77369aa
commit f1dda029e8
9 changed files with 175 additions and 15 deletions

View File

@@ -3,10 +3,12 @@
using Microsoft.Win32;
using Snap.Hutao.Core.Convert;
using Snap.Hutao.Core.Json;
using Snap.Hutao.Extension;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System.Collections.Immutable;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Windows.ApplicationModel;
namespace Snap.Hutao.Core;
@@ -70,7 +72,7 @@ internal static class CoreEnvironment
public static readonly JsonSerializerOptions JsonOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Encoder = new InboxTextEncoder(), //JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
};

View File

@@ -0,0 +1,49 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace Snap.Hutao.Core.Json;
/// <summary>
/// 替换 =
/// </summary>
internal class InboxTextEncoder : JavaScriptEncoder
{
/// <inheritdoc/>
public override int MaxOutputCharactersPerInputCharacter { get => 6; }
/// <inheritdoc/>
public override unsafe int FindFirstCharacterToEncode(char* text, int textLength)
{
for (int i = 0; i < textLength; i++)
{
char c = text[i];
if (c == '=')
{
return i;
}
}
return -1;
}
/// <inheritdoc/>
public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten)
{
string encoded = FormattableString.Invariant($"\\u{(uint)unicodeScalar:x4}");
numberOfCharactersWritten = (encoded.Length <= (uint)bufferLength) ? encoded.Length : 0;
return encoded.AsSpan().TryCopyTo(new Span<char>(buffer, bufferLength));
}
/// <inheritdoc/>
public override bool WillEncode(int unicodeScalar)
{
return true;
}
}

View File

@@ -12,11 +12,12 @@ using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Web.Hoyolab.Passport;
namespace Snap.Hutao.ViewModel;
/// <summary>
/// 测试视图模型
/// 设置视图模型
/// </summary>
[Injection(InjectAs.Scoped)]
internal class SettingViewModel : ObservableObject
@@ -37,7 +38,7 @@ internal class SettingViewModel : ObservableObject
private NamedValue<BackdropType> selectedBackdropType;
/// <summary>
/// 构造一个新的测试视图模型
/// 构造一个新的设置视图模型
/// </summary>
/// <param name="appDbContext">数据库上下文</param>
/// <param name="gameService">游戏服务</param>
@@ -153,10 +154,13 @@ internal class SettingViewModel : ObservableObject
}
}
private void DebugThrowException()
private async void DebugThrowException()
{
#if DEBUG
throw new InvalidOperationException("测试用异常");
PassportClient2 passportClient2 = Ioc.Default.GetRequiredService<PassportClient2>();
LoginResult? data = await passportClient2.LoginByPasswordAsync("phoneNunmber", "password", CancellationToken.None).ConfigureAwait(false);
_ = data;
#endif
}
}

View File

@@ -189,6 +189,11 @@ internal static class ApiEndpoints
/// </summary>
public const string AccountCookieAccountInfoBySToken = $"{PassportApi}/account/auth/api/getCookieAccountInfoBySToken";
/// <summary>
/// 验证 Ltoken 有效性
/// </summary>
public const string AccountLoginByPassword = $"{PassportApi}/account/ma-cn-passport/app/loginByPassword";
/// <summary>
/// 验证 Ltoken 有效性
/// </summary>
@@ -244,8 +249,8 @@ internal static class ApiEndpoints
private const string Hk4eApiAnnouncementApi = $"{Hk4eApi}/common/hk4e_cn/announcement/api";
private const string Hk4eApiGachaInfoApi = $"{Hk4eApi}/event/gacha_info/api";
private const string PassportApi = "passport-api.mihoyo.com";
private const string PassportApiV4 = "passport-api-v4.mihoyo.com";
private const string PassportApi = "https://passport-api.mihoyo.com";
private const string PassportApiV4 = "https://passport-api-v4.mihoyo.com";
private const string SdkStatic = "https://sdk-static.mihoyo.com";
private const string SdkStaticLauncherApi = $"{SdkStatic}/hk4e_cn/mdk/launcher/api";

View File

@@ -98,4 +98,28 @@ internal static class HttpClientExtensions
httpClient.DefaultRequestHeaders.Set("Referer", referer);
return httpClient;
}
/// <summary>
/// 设置AppId
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <returns>客户端</returns>
internal static HttpClient SetAppId(this HttpClient httpClient)
{
httpClient.DefaultRequestHeaders.Set("x-rpc-app_id", "bll8iq97cem8");
return httpClient;
}
/// <summary>
/// 设置头
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns>客户端</returns>
internal static HttpClient SetHeader(this HttpClient httpClient, string key, string value)
{
httpClient.DefaultRequestHeaders.Set(key, value);
return httpClient;
}
}

View File

@@ -40,11 +40,11 @@ internal class PassportClient
/// <param name="token">取消令牌</param>
/// <returns>验证信息</returns>
[ApiInformation(Cookie = CookieType.All)]
public async Task<VerifyInformation?> VerifyLtokenAsync(User user, CancellationToken token)
public async Task<UserInformation?> VerifyLtokenAsync(User user, CancellationToken token)
{
Response<VerifyInformation>? response = await httpClient
Response<UserInformation>? response = await httpClient
.SetUser(user, CookieType.All)
.TryCatchPostAsJsonAsync<Timestamp, Response<VerifyInformation>>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token)
.TryCatchPostAsJsonAsync<Timestamp, Response<UserInformation>>(ApiEndpoints.AccountVerifyLtoken, new(), options, logger, token)
.ConfigureAwait(false);
return response?.Data;

View File

@@ -33,6 +33,34 @@ internal class PassportClient2
this.logger = logger;
}
/// <summary>
/// 异步账密登录
/// </summary>
/// <param name="account">用户</param>
/// <param name="password">密码</param>
/// <param name="token">取消令牌</param>
/// <returns>登录数据</returns>
public async Task<LoginResult?> LoginByPasswordAsync(string account, string password, CancellationToken token)
{
Dictionary<string, string> data = new()
{
{ "account", RSAEncryptedString.Encrypt(account) },
{ "password", RSAEncryptedString.Encrypt(password) },
};
Response<LoginResult>? resp = await httpClient
.SetAppId()
.SetHeader("x-rpc-aigis", string.Empty)
.SetHeader("x-rpc-client_type", "2")
.SetHeader("x-rpc-game_biz", "bbs_cn")
.SetHeader("x-rpc-sdk_version", "1.3.1.2")
.UsingDynamicSecret2(SaltType.PROD, options, ApiEndpoints.AccountLoginByPassword, data)
.TryCatchPostAsJsonAsync<Response<LoginResult>>(logger, token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 异步获取 CookieToken
/// </summary>
@@ -50,4 +78,38 @@ internal class PassportClient2
return resp?.Data;
}
}
public class LoginResult
{
[JsonPropertyName("token")]
public TokenWrapper Token { get; set; }
[JsonPropertyName("user_info")]
public UserInformation UserInfo { get; set; }
[JsonPropertyName("reactivate_info")]
public ReactivateInfo ReactivateInfo { get; set; }
[JsonPropertyName("login_ticket")]
public string LoginTicket { get; set; }
}
public class ReactivateInfo
{
[JsonPropertyName("required")]
public bool Required { get; set; }
[JsonPropertyName("ticket")]
public string Ticket { get; set; }
}
public class TokenWrapper
{
[JsonPropertyName("token_type")]
public int TokenType { get; set; }
[JsonPropertyName("token")]
public string Token { get; set; }
}

View File

@@ -9,20 +9,34 @@ namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 支持RSA转换
/// </summary>
internal static class RSAUtil
internal class RSAEncryptedString
{
private const string RsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDvekdPMHN3AYhm/vktJT+YJr7cI5DcsNKqdsx5DZX0gDuWFuIjzdwButrIYPNmRJ1G8ybDIF7oDW2eEpm5sMbL9zs\n9ExXCdvqrn51qELbqj0XxtMTIpaCHFSI50PfPpTFV9Xt/hmyVwokoOXFlAEgCn+Q\nCgGs52bFoYMtyi+xEQIDAQAB\n";
private readonly string encryptedSource;
private RSAEncryptedString(string encryptedSource)
{
this.encryptedSource = encryptedSource;
}
public static implicit operator string(RSAEncryptedString encryptedString)
{
return encryptedString.encryptedSource.Replace("=", @"\u003d");
}
/// <summary>
/// 加密
/// </summary>
/// <param name="source">源</param>
/// <returns>字节数组</returns>
public static string Encrypt(string source)
public static RSAEncryptedString Encrypt(string source)
{
RSA rsa = RSA.Create();
rsa.ImportFromPem($"-----BEGIN PUBLIC KEY-----\n{RsaPublicKey}\n-----END PUBLIC KEY-----");
rsa.ImportFromPem($"-----BEGIN PUBLIC KEY-----\n{RsaPublicKey}-----END PUBLIC KEY-----");
byte[] bytes = Encoding.UTF8.GetBytes(source);
return Convert.ToBase64String(rsa.Encrypt(bytes, RSAEncryptionPadding.Pkcs1));
string encrypted = Convert.ToBase64String(rsa.Encrypt(bytes, RSAEncryptionPadding.Pkcs1));
return new(encrypted);
}
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Web.Hoyolab.Passport;
/// <summary>
/// 验证返回信息
/// </summary>
public class VerifyInformation
public class UserInformation
{
/// <summary>
/// 账户Id