Files
social-media/backend/src/Web/Features/Users/Handlers/LoginWithFacebook.cs
2025-02-17 11:45:17 -05:00

133 lines
4.4 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
using Hutopy.Web.Common.Security;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
namespace Hutopy.Web.Features.Users.Handlers;
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; }
}
public class FacebookPictureData
{
[JsonPropertyName("data")] public required FacebookPicture Picture { get; init; }
}
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,
SignInManager<IdentityUser> signInManager,
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.GeneratePassword(10, 12);
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;
}
await signInManager.SignInAsync(user, isPersistent: false);
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,
alias: user.Alias,
firstname: user.Firstname,
lastname: user.Lastname,
portraitUrl: user.PortraitUrl);
await SendOkAsync(
new LoginWithFacebookResponse(accessToken, string.Empty),
cancellation: ct);
}
}