Adds facebook login.

This commit is contained in:
2025-02-17 11:40:39 -05:00
parent d784367057
commit 3f9d2fd69f
9 changed files with 733 additions and 249 deletions

View File

@@ -0,0 +1,308 @@
// <auto-generated />
using System;
using Hutopy.Web.Features.Users.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Hutopy.Web.Features.Users.Data.Migrations
{
[DbContext(typeof(IdentityDbContext))]
[Migration("20250217034117_Add_FacebookId")]
partial class Add_FacebookId
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Identity")
.HasAnnotation("ProductVersion", "8.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", "Identity");
});
modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("Address")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Alias")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<DateTime?>("BirthDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<string>("FacebookId")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Firstname")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("GoogleId")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Lastname")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("PortraitUrl")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", "Identity");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<Guid>("RoleId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", "Identity");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", "Identity");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", "Identity");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.Property<Guid>("RoleId")
.HasColumnType("uuid");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", "Identity");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", "Identity");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
{
b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
{
b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Hutopy.Web.Features.Users.Data.Migrations
{
/// <inheritdoc />
public partial class Add_FacebookId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "FacebookId",
schema: "Identity",
table: "AspNetUsers",
type: "character varying(255)",
maxLength: 255,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "FacebookId",
schema: "Identity",
table: "AspNetUsers");
}
}
}

View File

@@ -81,6 +81,10 @@ namespace Hutopy.Web.Features.Users.Data.Migrations
b.Property<bool>("EmailConfirmed") b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<string>("FacebookId")
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("Firstname") b.Property<string>("Firstname")
.HasMaxLength(255) .HasMaxLength(255)
.HasColumnType("character varying(255)"); .HasColumnType("character varying(255)");

View File

@@ -0,0 +1,132 @@
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);
}
}

View File

@@ -4,12 +4,14 @@ using Microsoft.AspNetCore.Identity;
namespace Hutopy.Web.Features.Users; namespace Hutopy.Web.Features.Users;
public class IdentityUser : IdentityUser<Guid> public class IdentityUser : IdentityUser<Guid>
{ {
[MaxLength(255)] public string? Alias { get; set; } [MaxLength(255)] public string? Alias { get; set; }
[MaxLength(255)] public string? Firstname { get; set; } [MaxLength(255)] public string? Firstname { get; set; }
[MaxLength(255)] public string? Lastname { get; set; } [MaxLength(255)] public string? Lastname { get; set; }
public DateTime? BirthDate { get; set; } public DateTime? BirthDate { get; set; }
[MaxLength(255)] public string? Address { get; set; } [MaxLength(255)] public string? Address { get; set; }
[MaxLength(255)] public string? PortraitUrl { get; set; } [MaxLength(255)] public string? PortraitUrl { get; set; }
[MaxLength(255)] public string? GoogleId { get; set; } [MaxLength(255)] public string? GoogleId { get; set; }
[MaxLength(255)] public string? FacebookId { get; set; }
} }

View File

@@ -12,6 +12,7 @@
"@stripe/stripe-js": "^3.0.10", "@stripe/stripe-js": "^3.0.10",
"@tinymce/tinymce-vue": "^6.0.1", "@tinymce/tinymce-vue": "^6.0.1",
"@vueuse/core": "^10.11.0", "@vueuse/core": "^10.11.0",
"@vueuse/head": "^2.0.0",
"@xtiannyeto/vue-auth-social": "^0.1.9", "@xtiannyeto/vue-auth-social": "^0.1.9",
"axios": "^1.6.7", "axios": "^1.6.7",
"i18n": "^0.15.1", "i18n": "^0.15.1",
@@ -1014,16 +1015,6 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
"integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==",
"dev": true,
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
}
},
"node_modules/@hapi/hoek": { "node_modules/@hapi/hoek": {
"version": "9.3.0", "version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -1611,16 +1602,6 @@
"vue": "^3.0.0" "vue": "^3.0.0"
} }
}, },
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/@types/configstore": { "node_modules/@types/configstore": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@types/configstore/-/configstore-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/configstore/-/configstore-2.1.1.tgz",
@@ -1730,6 +1711,76 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true "dev": true
}, },
"node_modules/@unhead/dom": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.19.tgz",
"integrity": "sha512-udkgITdIblEWH3hsoFQMKW+6QXNO2qFZlZ2FI37bVAplQSnK/PytTPt/5oA1GWkoVwT0DsQNGHbU6kOg/3SlNg==",
"license": "MIT",
"dependencies": {
"@unhead/schema": "1.11.19",
"@unhead/shared": "1.11.19"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/@unhead/schema": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.19.tgz",
"integrity": "sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==",
"license": "MIT",
"dependencies": {
"hookable": "^5.5.3",
"zhead": "^2.2.4"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/@unhead/shared": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.19.tgz",
"integrity": "sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==",
"license": "MIT",
"dependencies": {
"@unhead/schema": "1.11.19",
"packrup": "^0.1.2"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/@unhead/ssr": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/@unhead/ssr/-/ssr-1.11.19.tgz",
"integrity": "sha512-OH+rj6xBTdYyLsSntk4lEQyR+z57aEUZIiR2UpPl1zWGtBZPIr5zs3GY5+EyJ8t8e0zLemPR/Pu7VembTJ8o1w==",
"license": "MIT",
"dependencies": {
"@unhead/schema": "1.11.19",
"@unhead/shared": "1.11.19"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/@unhead/vue": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.19.tgz",
"integrity": "sha512-/XATTP8wVLs3+2Pkj2crvr/Z55nybVQyOwISh+sAlr/48/9n3jGNiCZHKpHgL4MpOnGT4krwzWzbfhBO/G2BSQ==",
"license": "MIT",
"dependencies": {
"@unhead/schema": "1.11.19",
"@unhead/shared": "1.11.19",
"hookable": "^5.5.3",
"unhead": "1.11.19"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
},
"peerDependencies": {
"vue": ">=2.7 || >=3"
}
},
"node_modules/@vee-validate/rules": { "node_modules/@vee-validate/rules": {
"version": "4.13.2", "version": "4.13.2",
"resolved": "https://registry.npmjs.org/@vee-validate/rules/-/rules-4.13.2.tgz", "resolved": "https://registry.npmjs.org/@vee-validate/rules/-/rules-4.13.2.tgz",
@@ -1886,6 +1937,21 @@
} }
} }
}, },
"node_modules/@vueuse/head": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@vueuse/head/-/head-2.0.0.tgz",
"integrity": "sha512-ykdOxTGs95xjD4WXE4na/umxZea2Itl0GWBILas+O4oqS7eXIods38INvk3XkJKjqMdWPcpCyLX/DioLQxU1KA==",
"license": "MIT",
"dependencies": {
"@unhead/dom": "^1.7.0",
"@unhead/schema": "^1.7.0",
"@unhead/ssr": "^1.7.0",
"@unhead/vue": "^1.7.0"
},
"peerDependencies": {
"vue": ">=2.7 || >=3"
}
},
"node_modules/@vueuse/metadata": { "node_modules/@vueuse/metadata": {
"version": "10.11.1", "version": "10.11.1",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
@@ -2935,50 +3001,6 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.0.30",
"source-map-js": "^1.0.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/cssesc": { "node_modules/cssesc": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -2990,42 +3012,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/csso": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
"integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"css-tree": "~2.2.0"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/csso/node_modules/css-tree": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.0.28",
"source-map-js": "^1.0.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/csso/node_modules/mdn-data": {
"version": "2.0.28",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
"dev": true,
"license": "CC0-1.0"
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -3306,65 +3292,6 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dot-prop": { "node_modules/dot-prop": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz",
@@ -4464,6 +4391,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
"node_modules/http-errors": { "node_modules/http-errors": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -5302,13 +5235,6 @@
"is-buffer": "~1.1.6" "is-buffer": "~1.1.6"
} }
}, },
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
"dev": true,
"license": "CC0-1.0"
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -5864,6 +5790,15 @@
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="
}, },
"node_modules/packrup": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/packrup/-/packrup-0.1.2.tgz",
"integrity": "sha512-ZcKU7zrr5GlonoS9cxxrb5HVswGnyj6jQvwFBa6p5VFw7G71VAHcUKL5wyZSU/ECtPM/9gacWxy2KFQKt1gMNA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -7143,42 +7078,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/svgo": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@trysound/sax": "0.2.0",
"commander": "^7.2.0",
"css-select": "^5.1.0",
"css-tree": "^2.3.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
"picocolors": "^1.0.0"
},
"bin": {
"svgo": "bin/svgo"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/svgo"
}
},
"node_modules/svgo/node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.10", "version": "3.4.10",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
@@ -7393,6 +7292,21 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unhead": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.19.tgz",
"integrity": "sha512-O5AYb3+xUOzBlwDmPfC/DgGp9rDMoGkB4gFkhoaz8IonQqP8W8qqetxYf5ZyEdntvXnFsMWS8lZF//5176xo6Q==",
"license": "MIT",
"dependencies": {
"@unhead/dom": "1.11.19",
"@unhead/schema": "1.11.19",
"@unhead/shared": "1.11.19",
"hookable": "^5.5.3"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/unpipe": { "node_modules/unpipe": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -7590,19 +7504,6 @@
} }
} }
}, },
"node_modules/vite-svg-loader": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/vite-svg-loader/-/vite-svg-loader-5.1.0.tgz",
"integrity": "sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"svgo": "^3.0.2"
},
"peerDependencies": {
"vue": ">=3.2.13"
}
},
"node_modules/vue": { "node_modules/vue": {
"version": "3.4.38", "version": "3.4.38",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz",
@@ -8085,6 +7986,15 @@
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
},
"node_modules/zhead": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz",
"integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
} }
} }
} }

View File

@@ -13,6 +13,7 @@
"@stripe/stripe-js": "^3.0.10", "@stripe/stripe-js": "^3.0.10",
"@tinymce/tinymce-vue": "^6.0.1", "@tinymce/tinymce-vue": "^6.0.1",
"@vueuse/core": "^10.11.0", "@vueuse/core": "^10.11.0",
"@vueuse/head": "^2.0.0",
"@xtiannyeto/vue-auth-social": "^0.1.9", "@xtiannyeto/vue-auth-social": "^0.1.9",
"axios": "^1.6.7", "axios": "^1.6.7",
"i18n": "^0.15.1", "i18n": "^0.15.1",

View File

@@ -0,0 +1,76 @@
import {onMounted, ref} from "vue";
import {useHead} from "@vueuse/head";
import {useAuthStore} from "@/stores/authStore.js";
export function useFacebookLogin() {
const isSdkLoaded = ref(false);
/* TODO: FIND THE ACTUAL HUTOPY'S APP_ID */
const FACEBOOK_APP_ID = "1076433907621883";
useHead({
script: [
{
src: "https://connect.facebook.net/en_US/sdk.js",
async: true,
defer: true,
onload: "initializeFacebookSDK()",
},
],
});
(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src = "https://connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
const initializeFacebookSDK = () => {
window.fbAsyncInit = function () {
FB.init({
appId: FACEBOOK_APP_ID,
xfbml: true,
version: 'v22.0'
});
FB.AppEvents.logPageView();
isSdkLoaded.value = true;
};
};
const loginWithFacebook = () => {
if (!isSdkLoaded.value) {
console.error("Facebook SDK non encore chargé !");
return;
}
window.FB.login(
(response) => {
if (response.authResponse) {
console.log("Utilisateur connecté :", response);
const authStore = useAuthStore();
authStore.loginWithFacebook(response.authResponse);
} else {
console.log("Connexion annulée ou échouée.");
}
},
{
scope: "public_profile,email"
}
);
};
onMounted(() => {
initializeFacebookSDK();
});
return {
loginWithFacebook,
};
}

View File

@@ -1,39 +1,13 @@
<template>
<div class="flex min-h-screen items-center">
<div class="card justify-items-center">
<img alt="hutopy login"
src="/images/hutopy-logo.png"/>
<div class="flex flex-col w-[800px] gap-10 justify-items-center">
<h1 class="text-2xl font-bold login-text text-center mt-10">
Connexion
</h1>
<google-login :callback="googleCallback"
class="w-full justify-items-center"
popup-type="TOKEN">
<button class="secondary w-full">
<v-icon left>mdi-google</v-icon>
Google
</button>
</google-login>
</div>
</div>
</div>
</template>
<script setup> <script setup>
import {ref} from 'vue'; import {ref} from 'vue';
import {GoogleLogin} from "vue3-google-login"; import {GoogleLogin} from "vue3-google-login";
import {useAuthStore} from '@/stores/authStore.js'; import {useAuthStore} from '@/stores/authStore.js';
import {useFacebookLogin} from "@/composables/useFacebookLogin";
import Facebook from "@/views/svg/Facebook.vue";
const {loginWithFacebook} = useFacebookLogin();
const authStore = useAuthStore(); const authStore = useAuthStore();
const errorSnackBar = ref(false); const errorSnackBar = ref(false);
@@ -46,3 +20,49 @@ async function googleCallback(token) {
} }
</script> </script>
<template>
<div class="flex min-h-screen items-center">
<div class="card justify-items-center">
<img alt="hutopy login"
src="/images/hutopy-logo.png"/>
<div class="flex flex-col w-[800px] gap-10 justify-items-center">
<h1 class="text-2xl font-bold login-text text-center mt-10 ">
Connexion
</h1>
<div class="w-full items-center justify-center flex flex-col gap-4">
<google-login :callback="googleCallback"
class=""
popup-type="TOKEN">
<button class="secondary w-full">
<v-icon left>mdi-google</v-icon>
Google
</button>
</google-login>
<button class="secondary w-full text-red-600"
@click="loginWithFacebook">
<facebook class="social-icon"></facebook>
Facebook
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.social-icon {
@apply w-5 h-5;
@apply text-base;
}
</style>