2026-04-10 22:06:39 +08:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Brisk 认证模块。
|
|
|
|
|
/// </summary>
|
2026-04-10 22:06:39 +08:00
|
|
|
public sealed class BriskAuthModule
|
|
|
|
|
: BriskModuleBase
|
|
|
|
|
{
|
2026-04-10 22:38:28 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// 通过稳定的第三方用户 ID 换取 Brisk 登录态。
|
|
|
|
|
/// </summary>
|
2026-04-10 22:06:39 +08:00
|
|
|
public async Task<BriskLoginResult> LoginWithUserIdAsync(string loginProvider, string loginUserId, BriskProfile profile = null)
|
|
|
|
|
{
|
|
|
|
|
RequireNotEmpty(loginProvider, nameof(loginProvider));
|
|
|
|
|
RequireNotEmpty(loginUserId, nameof(loginUserId));
|
|
|
|
|
|
|
|
|
|
return await LoginInternalAsync(CreateLoginBody(loginProvider, profile, loginUserId, null), loginProvider, loginUserId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// 通过第三方返回的 code 换取 Brisk 登录态。
|
|
|
|
|
/// </summary>
|
2026-04-10 22:06:39 +08:00
|
|
|
public async Task<BriskLoginResult> LoginWithCodeAsync(string loginProvider, string code, BriskProfile profile = null)
|
|
|
|
|
{
|
|
|
|
|
RequireNotEmpty(loginProvider, nameof(loginProvider));
|
|
|
|
|
RequireNotEmpty(code, nameof(code));
|
|
|
|
|
|
|
|
|
|
return await LoginInternalAsync(CreateLoginBody(loginProvider, profile, null, code), loginProvider, null);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 22:38:28 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// 登出当前账号并清理本地会话。
|
|
|
|
|
/// </summary>
|
2026-04-10 22:06:39 +08:00
|
|
|
public async Task LogoutAsync()
|
|
|
|
|
{
|
|
|
|
|
var context = GetContext();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (context.Session.HasAccessToken)
|
|
|
|
|
{
|
|
|
|
|
await context.HttpClient.PostJsonAsync("/auth/logout", new Dictionary<string, object>(), true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (BriskAuthExpiredException)
|
|
|
|
|
{
|
|
|
|
|
// Local logout should still succeed even if the remote token has already expired.
|
|
|
|
|
}
|
|
|
|
|
catch (BriskBlockingException exception)
|
|
|
|
|
{
|
|
|
|
|
Brisk.NotifyBlockingError(exception);
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
context.Session.Clear();
|
|
|
|
|
await context.TokenStore.ClearAsync();
|
|
|
|
|
Brisk.NotifyLoggedOut();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<BriskLoginResult> LoginInternalAsync(Dictionary<string, object> body, string requestedLoginProvider, string requestedLoginUserId)
|
|
|
|
|
{
|
|
|
|
|
return await ExecutePublicAsync(async context =>
|
|
|
|
|
{
|
|
|
|
|
var data = await context.HttpClient.PostJsonAsync("/auth/login/exchange", body, false);
|
|
|
|
|
var result = BriskModelMapper.ToLoginResult(data);
|
|
|
|
|
if (string.IsNullOrWhiteSpace(result.LoginProvider))
|
|
|
|
|
{
|
|
|
|
|
result.LoginProvider = requestedLoginProvider;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(result.LoginUserId))
|
|
|
|
|
{
|
|
|
|
|
result.LoginUserId = requestedLoginUserId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await UpdateSessionAsync(context, result);
|
|
|
|
|
Brisk.NotifyLoggedIn();
|
|
|
|
|
return result;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Dictionary<string, object> CreateLoginBody(string loginProvider, BriskProfile profile, string loginUserId, string code)
|
|
|
|
|
{
|
|
|
|
|
var context = GetContext();
|
|
|
|
|
var body = new Dictionary<string, object>
|
|
|
|
|
{
|
|
|
|
|
{ "game_key", context.Options.GameKey },
|
|
|
|
|
{ "login_provider", loginProvider }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AddIfNotEmpty(body, "login_user_id", loginUserId);
|
|
|
|
|
AddIfNotEmpty(body, "code", code);
|
|
|
|
|
AddIfNotEmpty(body, "device_id", context.Options.DeviceId);
|
|
|
|
|
AddIfNotEmpty(body, "client_version", context.Options.ClientVersion);
|
|
|
|
|
|
|
|
|
|
if (profile != null)
|
|
|
|
|
{
|
|
|
|
|
AddIfNotEmpty(body, "nickname", profile.Nickname);
|
|
|
|
|
AddIfNotEmpty(body, "avatar_url", profile.AvatarUrl);
|
|
|
|
|
if (profile.ProfileJson != null)
|
|
|
|
|
{
|
|
|
|
|
body["profile_json"] = profile.ProfileJson;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return body;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void AddIfNotEmpty(Dictionary<string, object> body, string key, string value)
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(value))
|
|
|
|
|
{
|
|
|
|
|
body[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task UpdateSessionAsync(BriskContext context, BriskLoginResult result)
|
|
|
|
|
{
|
|
|
|
|
var expiresAt = result.ExpiresIn > 0
|
|
|
|
|
? DateTimeOffset.UtcNow.AddSeconds(result.ExpiresIn)
|
|
|
|
|
: (DateTimeOffset?)null;
|
|
|
|
|
|
|
|
|
|
context.Session.Update(
|
|
|
|
|
result.AccessToken,
|
|
|
|
|
expiresAt,
|
|
|
|
|
result.PlayerId,
|
|
|
|
|
result.ProjectAccountId,
|
|
|
|
|
result.LoginProvider,
|
|
|
|
|
result.LoginUserId);
|
|
|
|
|
|
|
|
|
|
var storedSession = new BriskStoredSession
|
|
|
|
|
{
|
|
|
|
|
AccessToken = result.AccessToken,
|
|
|
|
|
ExpiresAt = expiresAt,
|
|
|
|
|
PlayerId = result.PlayerId,
|
|
|
|
|
ProjectAccountId = result.ProjectAccountId,
|
|
|
|
|
LoginProvider = result.LoginProvider,
|
|
|
|
|
LoginUserId = result.LoginUserId
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await context.TokenStore.SaveAsync(storedSession);
|
|
|
|
|
}
|
|
|
|
|
}
|