From ab9b80b74f517549b3a578f90b5dd300582e7a80 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:17:01 +0300 Subject: [PATCH] Inject roles on token validation --- docker-compose.yml | 3 +- .../Database/ApplicationDbContext.cs | 2 +- ...> 20250607211109_InitialSetup.Designer.cs} | 4 +- ...etup.cs => 20250607211109_InitialSetup.cs} | 14 +++--- .../ApplicationDbContextModelSnapshot.cs | 2 +- .../Repositories/UserRoleRepository.cs | 14 +++--- .../Controllers/Users/UserController.cs | 17 ++----- src/Imprink.WebApi/Startup.cs | 29 ++++++++++++ webui/package-lock.json | 8 ++-- webui/package.json | 2 +- webui/src/app/page.js | 46 ++++++++++++++++++- webui/src/app/token/route.js | 34 ++++++++++++++ 12 files changed, 137 insertions(+), 38 deletions(-) rename src/Imprink.Infrastructure/Migrations/{20250606173957_InitialSetup.Designer.cs => 20250607211109_InitialSetup.Designer.cs} (99%) rename src/Imprink.Infrastructure/Migrations/{20250606173957_InitialSetup.cs => 20250607211109_InitialSetup.cs} (98%) create mode 100644 webui/src/app/token/route.js diff --git a/docker-compose.yml b/docker-compose.yml index bf9ceb4..492f7c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT} - ConnectionStrings__DefaultConnection=Server=${SQL_SERVER};Database=${SQL_DATABASE};User Id=${SQL_USER_ID};Password=${SQL_PASSWORD};Encrypt=false;TrustServerCertificate=true;MultipleActiveResultSets=true; - ASPNETCORE_URLS=${ASPNETCORE_URLS} - - Auth0__Authority=${AUTH0_DOMAIN} + - Auth0__Authority=${AUTH0_AUTHORITY} - Auth0__Audience=${AUTH0_AUDIENCE} - Logging__LogLevel__Default=${ASPNETCORE_LOGGING_LEVEL_DEFAULT} - Logging__LogLevel__Microsoft.AspNetCore=${ASPNETCORE_LOGGING_LEVEL} @@ -37,6 +37,7 @@ services: - AUTH0_CLIENT_SECRET=${AUTH0_CLIENT_SECRET} - AUTH0_AUDIENCE=${AUTH0_AUDIENCE} - AUTH0_SCOPE=${AUTH0_SCOPE} + - COOKIE_DOMAIN=${COOKIE_DOMAIN} - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} - NEXT_PUBLIC_AUTH0_CLIENT_ID=${NEXT_PUBLIC_AUTH0_CLIENT_ID} - NEXT_PUBLIC_AUTH0_DOMAIN=${NEXT_PUBLIC_AUTH0_DOMAIN} diff --git a/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs b/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs index 0896482..4fe7a92 100644 --- a/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs +++ b/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs @@ -21,7 +21,7 @@ public class ApplicationDbContext(DbContextOptions options public DbSet OrderStatuses { get; set; } public DbSet ShippingStatuses { get; set; } public DbSet Users { get; set; } - public DbSet UserRoles { get; set; } + public DbSet UserRole { get; set; } public DbSet Roles { get; set; } public DbSet Categories { get; set; } diff --git a/src/Imprink.Infrastructure/Migrations/20250606173957_InitialSetup.Designer.cs b/src/Imprink.Infrastructure/Migrations/20250607211109_InitialSetup.Designer.cs similarity index 99% rename from src/Imprink.Infrastructure/Migrations/20250606173957_InitialSetup.Designer.cs rename to src/Imprink.Infrastructure/Migrations/20250607211109_InitialSetup.Designer.cs index 4ce7e76..dd22c86 100644 --- a/src/Imprink.Infrastructure/Migrations/20250606173957_InitialSetup.Designer.cs +++ b/src/Imprink.Infrastructure/Migrations/20250607211109_InitialSetup.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Imprink.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20250606173957_InitialSetup")] + [Migration("20250607211109_InitialSetup")] partial class InitialSetup { /// @@ -825,7 +825,7 @@ namespace Imprink.Infrastructure.Migrations b.HasIndex("UserId") .HasDatabaseName("IX_UserRole_UserId"); - b.ToTable("UserRoles"); + b.ToTable("UserRole"); }); modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => diff --git a/src/Imprink.Infrastructure/Migrations/20250606173957_InitialSetup.cs b/src/Imprink.Infrastructure/Migrations/20250607211109_InitialSetup.cs similarity index 98% rename from src/Imprink.Infrastructure/Migrations/20250606173957_InitialSetup.cs rename to src/Imprink.Infrastructure/Migrations/20250607211109_InitialSetup.cs index 770da5f..0fee439 100644 --- a/src/Imprink.Infrastructure/Migrations/20250606173957_InitialSetup.cs +++ b/src/Imprink.Infrastructure/Migrations/20250607211109_InitialSetup.cs @@ -193,7 +193,7 @@ namespace Imprink.Infrastructure.Migrations }); migrationBuilder.CreateTable( - name: "UserRoles", + name: "UserRole", columns: table => new { UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), @@ -201,15 +201,15 @@ namespace Imprink.Infrastructure.Migrations }, constraints: table => { - table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId }); + table.PrimaryKey("PK_UserRole", x => new { x.UserId, x.RoleId }); table.ForeignKey( - name: "FK_UserRoles_Roles_RoleId", + name: "FK_UserRole_Roles_RoleId", column: x => x.RoleId, principalTable: "Roles", principalColumn: "Id", onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "FK_UserRoles_Users_UserId", + name: "FK_UserRole_Users_UserId", column: x => x.UserId, principalTable: "Users", principalColumn: "Id", @@ -630,12 +630,12 @@ namespace Imprink.Infrastructure.Migrations migrationBuilder.CreateIndex( name: "IX_UserRole_RoleId", - table: "UserRoles", + table: "UserRole", column: "RoleId"); migrationBuilder.CreateIndex( name: "IX_UserRole_UserId", - table: "UserRoles", + table: "UserRole", column: "UserId"); migrationBuilder.CreateIndex( @@ -663,7 +663,7 @@ namespace Imprink.Infrastructure.Migrations name: "OrderItems"); migrationBuilder.DropTable( - name: "UserRoles"); + name: "UserRole"); migrationBuilder.DropTable( name: "Orders"); diff --git a/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index 060d38d..2741704 100644 --- a/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -822,7 +822,7 @@ namespace Imprink.Infrastructure.Migrations b.HasIndex("UserId") .HasDatabaseName("IX_UserRole_UserId"); - b.ToTable("UserRoles"); + b.ToTable("UserRole"); }); modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => diff --git a/src/Imprink.Infrastructure/Repositories/UserRoleRepository.cs b/src/Imprink.Infrastructure/Repositories/UserRoleRepository.cs index 1cb2a27..c253a16 100644 --- a/src/Imprink.Infrastructure/Repositories/UserRoleRepository.cs +++ b/src/Imprink.Infrastructure/Repositories/UserRoleRepository.cs @@ -9,7 +9,7 @@ public class UserRoleRepository(ApplicationDbContext context) : IUserRoleReposit { public async Task> GetUserRolesAsync(string userId, CancellationToken cancellationToken = default) { - return await context.UserRoles + return await context.UserRole .AsNoTracking() .Where(ur => ur.UserId == userId) .Select(ur => ur.Role) @@ -18,7 +18,7 @@ public class UserRoleRepository(ApplicationDbContext context) : IUserRoleReposit public async Task> GetUsersInRoleAsync(Guid roleId, CancellationToken cancellationToken = default) { - return await context.UserRoles + return await context.UserRole .AsNoTracking() .Where(ur => ur.RoleId == roleId) .Select(ur => ur.User) @@ -27,32 +27,32 @@ public class UserRoleRepository(ApplicationDbContext context) : IUserRoleReposit public async Task IsUserInRoleAsync(string userId, Guid roleId, CancellationToken cancellationToken = default) { - return await context.UserRoles + return await context.UserRole .AnyAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken); } public async Task GetUserRoleAsync(string userId, Guid roleId, CancellationToken cancellationToken = default) { - return await context.UserRoles + return await context.UserRole .AsNoTracking() .FirstOrDefaultAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken); } public async Task AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default) { - context.UserRoles.Add(userRole); + context.UserRole.Add(userRole); await context.SaveChangesAsync(cancellationToken); } public async Task RemoveUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default) { - context.UserRoles.Remove(userRole); + context.UserRole.Remove(userRole); await context.SaveChangesAsync(cancellationToken); } public async Task> GetUserRolesByUserIdAsync(string userId, CancellationToken cancellationToken = default) { - return await context.UserRoles + return await context.UserRole .AsNoTracking() .Where(ur => ur.UserId == userId) .ToListAsync(cancellationToken); diff --git a/src/Imprink.WebApi/Controllers/Users/UserController.cs b/src/Imprink.WebApi/Controllers/Users/UserController.cs index 4c9e1d6..ee104b8 100644 --- a/src/Imprink.WebApi/Controllers/Users/UserController.cs +++ b/src/Imprink.WebApi/Controllers/Users/UserController.cs @@ -1,5 +1,3 @@ -using System.Security.Claims; -using Imprink.Domain.Common.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -15,16 +13,11 @@ public class UserController : ControllerBase { var claims = User.Claims; - var enumerable = claims as Claim[] ?? claims.ToArray(); - var user = new Auth0User + foreach (var claim in claims) { - Sub = enumerable.FirstOrDefault(c => c.Type == "sub")?.Value ?? "", - Name = enumerable.FirstOrDefault(c => c.Type == "name")?.Value ?? "", - Nickname = enumerable.FirstOrDefault(c => c.Type == "nickname")?.Value ?? "", - Email = enumerable.FirstOrDefault(c => c.Type == "email")?.Value ?? "", - EmailVerified = enumerable.FirstOrDefault(c => c.Type == "email_verified")?.Value == "true" - }; - - return Ok(user); + Console.WriteLine($"Claim Type: {claim.Type}, Claim Value: {claim.Value}"); + } + + return Ok("Claims logged to console."); } } \ No newline at end of file diff --git a/src/Imprink.WebApi/Startup.cs b/src/Imprink.WebApi/Startup.cs index c0431db..b3070bd 100644 --- a/src/Imprink.WebApi/Startup.cs +++ b/src/Imprink.WebApi/Startup.cs @@ -1,3 +1,4 @@ +using System.Security.Claims; using Imprink.Application; using Imprink.Application.Products.Create; using Imprink.Domain.Repositories; @@ -41,6 +42,34 @@ public static class Startup { options.Authority = builder.Configuration["Auth0:Authority"]; options.Audience = builder.Configuration["Auth0:Audience"]; + + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var token = context.Request.Cookies["access_token"]; + if (!string.IsNullOrEmpty(token)) context.Token = token; + return Task.CompletedTask; + }, + OnTokenValidated = context => + { + var dbContext = context.HttpContext.RequestServices.GetService(); + var userId = context.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value + ?? context.Principal?.FindFirst("sub")?.Value; + + if (string.IsNullOrEmpty(userId)) return Task.CompletedTask; + var identity = context.Principal!.Identity as ClaimsIdentity; + + var roles = (from ur in dbContext?.UserRole + join r in dbContext?.Roles on ur.RoleId equals r.Id + where ur.UserId == userId + select r.RoleName).ToList(); + + foreach (var role in roles) identity!.AddClaim(new Claim(ClaimTypes.Role, role)); + + return Task.CompletedTask; + } + }; }); services.AddAuthorization(); diff --git a/webui/package-lock.json b/webui/package-lock.json index 5dfcd57..3fc5eb5 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -8,7 +8,7 @@ "name": "webui", "version": "0.1.0", "dependencies": { - "@auth0/nextjs-auth0": "^4.6.0", + "@auth0/nextjs-auth0": "^4.6.1", "next": "15.3.3", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -46,9 +46,9 @@ } }, "node_modules/@auth0/nextjs-auth0": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@auth0/nextjs-auth0/-/nextjs-auth0-4.6.0.tgz", - "integrity": "sha512-HK+fcUW6P8/qUDQfOfntftMg6yzeZLtyfTxL/lyeOub1o/xTL9SZ2fF39nH0H6w1loB5SCAbyN1vD8xxBwINqQ==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@auth0/nextjs-auth0/-/nextjs-auth0-4.6.1.tgz", + "integrity": "sha512-eSYLCPBzROheJL0gdI0hHCbV468yqyz/sBcuag7cm3dx6LMhRzzFmComPs8p+Y7OCblzblGfk/Hju8A1BkjZxw==", "license": "MIT", "dependencies": { "@edge-runtime/cookies": "^5.0.1", diff --git a/webui/package.json b/webui/package.json index eaf30ad..6db78c1 100644 --- a/webui/package.json +++ b/webui/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@auth0/nextjs-auth0": "^4.6.0", + "@auth0/nextjs-auth0": "^4.6.1", "next": "15.3.3", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/webui/src/app/page.js b/webui/src/app/page.js index 40de756..e3e8199 100644 --- a/webui/src/app/page.js +++ b/webui/src/app/page.js @@ -5,9 +5,31 @@ import {useEffect, useState} from "react"; export default function Home() { const { user, error, isLoading } = useUser(); + const [accessToken, setAccessToken] = useState(null); + + useEffect(() => { + const fetchAccessToken = async () => { + if (user) { + try { + const response = await fetch('/auth/access-token'); + const v = await fetch('/token'); + if (response.ok) { + const tokenData = await response.text(); + setAccessToken(tokenData); + } else { + setAccessToken('Token not available'); + } + } catch (error) { + setAccessToken('Error fetching token'); + } + } + }; + + fetchAccessToken().then(r => console.log(r)); + }, [user]); async function checkValidity() { - const check = await fetch('https://impr.ink/auth/sync', {method: 'POST'}); + const check = await fetch('https://impr.ink/api/api/User', {method: 'POST'}); } if (isLoading) { @@ -39,6 +61,16 @@ export default function Home() { Sign In + checkValidity()} + className="group relative px-6 py-3 bg-gradient-to-r from-red-500 to-pink-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-red-500/25 transition-all duration-300 hover:scale-105 active:scale-95" + > +
+ + Check + +
@@ -52,7 +84,8 @@ export default function Home() { {user ? (
-
+
{user.picture ? (
)} +
+ +
+ {accessToken} +
+
diff --git a/webui/src/app/token/route.js b/webui/src/app/token/route.js new file mode 100644 index 0000000..bfe4df4 --- /dev/null +++ b/webui/src/app/token/route.js @@ -0,0 +1,34 @@ +import { cookies } from 'next/headers'; +import { NextResponse } from 'next/server'; +import {auth0} from "@/lib/auth0"; + +export async function GET() { + try { + const session = await auth0.getSession(); + const accessToken = session.tokenSet.accessToken; + if (!accessToken) { + return NextResponse.json({ error: 'No access token found' }, { status: 401 }); + } + + const response = NextResponse.json({ message: 'Access token set in cookie' }); + + const cookieDomain = process.env.COOKIE_DOMAIN || undefined; + + const cookieStore = await cookies(); + cookieStore.set({ + name: 'access_token', + value: accessToken, + httpOnly: true, + secure: true, + sameSite: 'strict', + path: '/', + domain: cookieDomain, + maxAge: 3600, + }); + + return response; + } catch (error) { + console.error('Error in /api/set-token:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +} \ No newline at end of file