forked from xiongyuxing/tiku-backend.net
feat: add wechat authentication
This commit is contained in:
@@ -152,9 +152,94 @@ public sealed class AuthServiceTests
|
||||
|
||||
await Assert.ThrowsAsync<SessionRevokedException>(() =>
|
||||
service.RefreshAsync(new RefreshSessionRequest(
|
||||
login.Tokens.RefreshToken,
|
||||
null,
|
||||
null)));
|
||||
login.Tokens.RefreshToken,
|
||||
null,
|
||||
null)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Wechat_miniapp_login_creates_user_identity_membership_and_session()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
var tenantId = await SeedTenantWithWechatProviderAsync(context, "wechat-miniapp");
|
||||
var service = CreateAuthService(
|
||||
context,
|
||||
new FakeWechatOAuthClient(
|
||||
MiniAppIdentity: new WechatIdentity(
|
||||
"mini-open-id",
|
||||
"union-id",
|
||||
null,
|
||||
null,
|
||||
"session-key",
|
||||
"""{"openid":"mini-open-id","unionid":"union-id","session_key":"session-key"}""")));
|
||||
|
||||
var result = await service.LoginWithWechatMiniAppAsync(new WechatLoginRequest(
|
||||
tenantId,
|
||||
"wx-code",
|
||||
null,
|
||||
null));
|
||||
|
||||
Assert.Equal(tenantId, result.Tenant.TenantId);
|
||||
Assert.Single(context.Users);
|
||||
Assert.Contains(context.UserIdentities, identity =>
|
||||
identity.Provider == "wechat-miniapp" &&
|
||||
identity.ProviderSubject == "wx-app-id:mini-open-id" &&
|
||||
identity.OpenId == "mini-open-id" &&
|
||||
identity.UnionId == "union-id");
|
||||
Assert.Contains(context.TenantMemberships, membership =>
|
||||
membership.TenantId == tenantId &&
|
||||
membership.UserId == result.UserId &&
|
||||
membership.Status == MembershipStatus.Active);
|
||||
Assert.Single(context.AuthSessions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Wechat_union_id_reuses_existing_user_across_providers()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
var tenantId = await SeedTenantWithWechatProviderAsync(context, "wechat-miniapp");
|
||||
context.TenantAuthProviders.Add(new TenantAuthProvider
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Provider = "wechat_web",
|
||||
Status = TenantAuthProviderStatus.Testing,
|
||||
ConfigPublic = WechatProviderConfig()
|
||||
});
|
||||
var user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Existing"
|
||||
};
|
||||
context.Users.Add(user);
|
||||
context.UserIdentities.Add(new UserIdentity
|
||||
{
|
||||
UserId = user.Id,
|
||||
Provider = "wechat_web",
|
||||
ProviderSubject = "wx-app-id:web-open-id",
|
||||
OpenId = "web-open-id",
|
||||
UnionId = "same-union"
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
var service = CreateAuthService(
|
||||
context,
|
||||
new FakeWechatOAuthClient(
|
||||
MiniAppIdentity: new WechatIdentity(
|
||||
"mini-open-id",
|
||||
"same-union",
|
||||
null,
|
||||
null,
|
||||
"session-key",
|
||||
"""{"openid":"mini-open-id","unionid":"same-union","session_key":"session-key"}""")));
|
||||
|
||||
var result = await service.LoginWithWechatMiniAppAsync(new WechatLoginRequest(
|
||||
tenantId,
|
||||
"wx-code",
|
||||
null,
|
||||
null));
|
||||
|
||||
Assert.Equal(user.Id, result.UserId);
|
||||
Assert.Single(context.Users);
|
||||
Assert.Equal(2, context.UserIdentities.Count());
|
||||
}
|
||||
|
||||
private static TikuDbContext CreateContext()
|
||||
@@ -166,7 +251,9 @@ public sealed class AuthServiceTests
|
||||
return new TikuDbContext(options);
|
||||
}
|
||||
|
||||
private static IAuthService CreateAuthService(TikuDbContext context)
|
||||
private static IAuthService CreateAuthService(
|
||||
TikuDbContext context,
|
||||
IWechatOAuthClient? wechatOAuthClient = null)
|
||||
{
|
||||
var tokenService = new TokenService(Options.Create(JwtOptions));
|
||||
var sessionService = new SessionService(context, tokenService, Options.Create(JwtOptions));
|
||||
@@ -176,7 +263,40 @@ public sealed class AuthServiceTests
|
||||
context,
|
||||
new PasswordHasher(),
|
||||
smsService,
|
||||
sessionService);
|
||||
sessionService,
|
||||
wechatOAuthClient ?? new FakeWechatOAuthClient());
|
||||
}
|
||||
|
||||
private static async Task<Guid> SeedTenantWithWechatProviderAsync(
|
||||
TikuDbContext context,
|
||||
string provider)
|
||||
{
|
||||
var tenant = new Tenant
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Slug = Guid.NewGuid().ToString("N"),
|
||||
Name = "Wechat Tenant"
|
||||
};
|
||||
context.Tenants.Add(tenant);
|
||||
context.TenantAuthProviders.Add(new TenantAuthProvider
|
||||
{
|
||||
TenantId = tenant.Id,
|
||||
Provider = provider,
|
||||
Status = TenantAuthProviderStatus.Testing,
|
||||
ConfigPublic = WechatProviderConfig()
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return tenant.Id;
|
||||
}
|
||||
|
||||
private static JsonElement WechatProviderConfig()
|
||||
{
|
||||
return JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
appId = "wx-app-id",
|
||||
appSecret = "wx-app-secret"
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedUserAsync(
|
||||
@@ -226,4 +346,37 @@ public sealed class AuthServiceTests
|
||||
$$"""{"passwordHash":{{JsonSerializer.Serialize(passwordHash)}}}""");
|
||||
return document.RootElement.Clone();
|
||||
}
|
||||
|
||||
private sealed class FakeWechatOAuthClient(
|
||||
WechatIdentity? WebIdentity = null,
|
||||
WechatIdentity? MiniAppIdentity = null) : IWechatOAuthClient
|
||||
{
|
||||
public Task<WechatIdentity> ExchangeWebCodeAsync(
|
||||
WechatProviderOptions options,
|
||||
string code,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(WebIdentity ?? new WechatIdentity(
|
||||
"web-open-id",
|
||||
"union-id",
|
||||
"Wechat User",
|
||||
"https://example.test/avatar.png",
|
||||
null,
|
||||
"""{"openid":"web-open-id","unionid":"union-id"}"""));
|
||||
}
|
||||
|
||||
public Task<WechatIdentity> ExchangeMiniAppCodeAsync(
|
||||
WechatProviderOptions options,
|
||||
string code,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(MiniAppIdentity ?? new WechatIdentity(
|
||||
"mini-open-id",
|
||||
"union-id",
|
||||
null,
|
||||
null,
|
||||
"session-key",
|
||||
"""{"openid":"mini-open-id","unionid":"union-id","session_key":"session-key"}"""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user