143 lines
4.7 KiB
C#
143 lines
4.7 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Hutopy.Web.Common.Security;
|
|
using Hutopy.Web.Features.Users.Data;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.Extensions.Options;
|
|
using IdentityUser = Hutopy.Web.Features.Users.Data.IdentityUser;
|
|
|
|
namespace Hutopy.Web.Features.Users.Handlers;
|
|
|
|
[PublicAPI]
|
|
public class FacebookUserInfo
|
|
{
|
|
[JsonPropertyName("id")] public required string Id { get; init; }
|
|
[JsonPropertyName("email")] public string? Email { get; init; } // Email might be null if not granted
|
|
[JsonPropertyName("name")] public required string Name { get; init; }
|
|
[JsonPropertyName("picture")] public required FacebookPictureData Picture { get; init; }
|
|
}
|
|
|
|
[PublicAPI]
|
|
public class FacebookPictureData
|
|
{
|
|
[JsonPropertyName("data")] public required FacebookPicture Picture { get; init; }
|
|
}
|
|
|
|
[PublicAPI]
|
|
public class FacebookPicture
|
|
{
|
|
[JsonPropertyName("url")] public required string Url { get; init; }
|
|
}
|
|
|
|
[PublicAPI]
|
|
public record LoginWithFacebookRequest(
|
|
string Token);
|
|
|
|
[PublicAPI]
|
|
public record LoginWithFacebookResponse(
|
|
string AccessToken,
|
|
string RefreshToken);
|
|
|
|
[PublicAPI]
|
|
public class LoginWithFacebookHandler(
|
|
IHttpClientFactory httpClientFactory,
|
|
IdentityUserManager userManager,
|
|
IOptionsSnapshot<JwtOptions> jwtOptions)
|
|
: Endpoint<LoginWithFacebookRequest, LoginWithFacebookResponse>
|
|
{
|
|
public override void Configure()
|
|
{
|
|
AllowAnonymous();
|
|
Post("/api/users/login-with-facebook");
|
|
Options(o => o.WithTags("Users"));
|
|
}
|
|
|
|
public override async Task HandleAsync(
|
|
LoginWithFacebookRequest request,
|
|
CancellationToken ct)
|
|
{
|
|
// Verify the token with Facebook
|
|
using var httpClient = httpClientFactory.CreateClient();
|
|
using var response = await httpClient.GetAsync(
|
|
$"https://graph.facebook.com/me?access_token={request.Token}&fields=id,name,email,picture.width(200).height(200)",
|
|
ct);
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
await SendStringAsync(
|
|
"The token is not valid",
|
|
400,
|
|
cancellation: ct);
|
|
return;
|
|
}
|
|
|
|
// Extract the user info (email, name, profile picture)
|
|
var content = await response.Content.ReadAsStringAsync(ct);
|
|
var userInfo = JsonSerializer.Deserialize<FacebookUserInfo>(content);
|
|
if (userInfo is null || string.IsNullOrEmpty(userInfo.Id))
|
|
{
|
|
await SendStringAsync(
|
|
"Failed to retrieve user information from Facebook",
|
|
400,
|
|
cancellation: ct);
|
|
return;
|
|
}
|
|
|
|
// Check if user exists or create a new one
|
|
var user = await userManager.FindByEmailAsync(userInfo.Email!);
|
|
|
|
if (user is null)
|
|
{
|
|
var generatedPassword = PasswordGenerator.Next();
|
|
var generatedUser = new IdentityUser
|
|
{
|
|
UserName = userInfo.Email ?? $"fb_{userInfo.Id}",
|
|
Email = userInfo.Email,
|
|
Firstname = userInfo.Name.Split(' ').FirstOrDefault() ?? "",
|
|
Lastname = userInfo.Name.Split(' ').Skip(1).FirstOrDefault() ?? "",
|
|
Alias = userInfo.Name,
|
|
PortraitUrl = userInfo.Picture.Picture.Url,
|
|
FacebookId = userInfo.Id, // Storing Facebook ID
|
|
};
|
|
|
|
var result = await userManager.CreateAsync(
|
|
generatedUser,
|
|
generatedPassword);
|
|
|
|
if (!result.Succeeded)
|
|
{
|
|
await SendStringAsync(
|
|
result.Errors.First().Description,
|
|
400,
|
|
cancellation: ct);
|
|
return;
|
|
}
|
|
|
|
user = generatedUser;
|
|
}
|
|
|
|
// Generate refresh token
|
|
var refreshToken = RefreshTokenGenerator.Next();
|
|
|
|
// Store refresh token in user's properties
|
|
user.RefreshToken = refreshToken;
|
|
user.RefreshTokenExpiryTime = DateTime.UtcNow.Add(jwtOptions.Value.RefreshTokenLifetime);
|
|
await userManager.UpdateAsync(user);
|
|
|
|
var accessToken = JwtTokenHelper.GenerateJwtToken(
|
|
expiresIn: jwtOptions.Value.Lifetime,
|
|
issuer: jwtOptions.Value.Issuer,
|
|
audience: jwtOptions.Value.Audience,
|
|
key: jwtOptions.Value.Key,
|
|
userId: user.Id.ToString(),
|
|
email: user.Email ?? string.Empty,
|
|
alias: user.Alias,
|
|
firstname: user.Firstname ?? string.Empty,
|
|
lastname: user.Lastname ?? string.Empty,
|
|
portraitUrl: user.PortraitUrl);
|
|
|
|
await SendOkAsync(
|
|
new LoginWithFacebookResponse(accessToken, refreshToken),
|
|
cancellation: ct);
|
|
}
|
|
}
|