286 lines
11 KiB
C#
286 lines
11 KiB
C#
using Indotalent.ConfigFrontEnd.Service;
|
|
using Indotalent.Data.Entities;
|
|
using Indotalent.Features.Account.ForgotPassword.Cqrs;
|
|
using Indotalent.Features.Account.Login.Cqrs;
|
|
using Indotalent.Features.Account.Register.Cqrs;
|
|
using Indotalent.Features.Account.ResetPassword.Cqrs;
|
|
using Indotalent.Features.Account.TenantSelection.Cqrs;
|
|
using Indotalent.Infrastructure.Authentication.Identity;
|
|
using MediatR;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Security.Claims;
|
|
|
|
namespace Indotalent.Features.Account;
|
|
|
|
public static class AccountEndPoint
|
|
{
|
|
public static IEndpointConventionBuilder MapAccountEndpoints(this IEndpointRouteBuilder endpoints)
|
|
{
|
|
var group = endpoints.MapGroup("/account").WithTags("Account");
|
|
|
|
group.MapPost("/signin-sso", async ([FromQuery] string email, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IOptions<JwtSettingsModel> jwtOptions, HttpContext context) =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(email)) return Results.Json(new { status = 401, title = "Email is required" }, statusCode: 401);
|
|
|
|
var user = await userManager.FindByEmailAsync(email);
|
|
|
|
if (user == null || !user.IsActive)
|
|
{
|
|
return Results.Json(new { status = 401, title = "Account not found or inactive" }, statusCode: 401);
|
|
}
|
|
|
|
var jwtSettings = jwtOptions.Value;
|
|
var token = JwtService.GenerateNewJwt(user, jwtSettings);
|
|
var refreshToken = Guid.NewGuid().ToString().Replace("-", "");
|
|
|
|
user.RefreshToken = refreshToken;
|
|
user.LastLoginAt = DateTime.Now;
|
|
await userManager.UpdateAsync(user);
|
|
|
|
await signInManager.SignInAsync(user, isPersistent: true);
|
|
|
|
context.Response.Cookies.Append("X-Auth-Token", token, new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = DateTimeOffset.UtcNow.AddDays(1)
|
|
});
|
|
|
|
context.Response.Cookies.Append("X-Refresh-Token", refreshToken, new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
|
});
|
|
|
|
return Results.Ok(new { status = 200, title = "SSO Login successful" });
|
|
});
|
|
|
|
group.MapGet("/active-user-exists", async ([FromQuery] string email, UserManager<ApplicationUser> userManager) =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(email))
|
|
{
|
|
return Results.Ok(new { exists = false, isActive = false });
|
|
}
|
|
|
|
var user = await userManager.FindByEmailAsync(email);
|
|
|
|
if (user == null)
|
|
{
|
|
return Results.Ok(new { exists = false, isActive = false });
|
|
}
|
|
|
|
return Results.Ok(new { exists = true, isActive = user.IsActive });
|
|
});
|
|
|
|
group.MapPost("/signin", async ([FromBody] LoginRequest request, IMediator mediator, HttpContext context, UserManager<ApplicationUser> userManager) =>
|
|
{
|
|
var command = new LoginCommand(request);
|
|
var response = await mediator.Send(command);
|
|
|
|
if (response.Succeeded)
|
|
{
|
|
var user = await userManager.FindByEmailAsync(request.Email);
|
|
|
|
if (user != null)
|
|
{
|
|
context.Response.Cookies.Append("X-Auth-Token", response.Token!, new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = DateTimeOffset.UtcNow.AddDays(1)
|
|
});
|
|
|
|
context.Response.Cookies.Append("X-Refresh-Token", user.RefreshToken!, new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
|
});
|
|
}
|
|
|
|
return Results.Ok(new { status = 200, title = response.Message });
|
|
}
|
|
return Results.Json(new { status = 401, title = response.Message }, statusCode: 401);
|
|
});
|
|
|
|
|
|
|
|
group.MapPost("/refresh-token", async (HttpContext context) =>
|
|
{
|
|
var userManager = context.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
|
|
var jwtSettings = context.RequestServices.GetRequiredService<IOptions<JwtSettingsModel>>().Value;
|
|
|
|
var refreshToken = context.Request.Headers["X-Refresh-Token"].ToString();
|
|
|
|
if (string.IsNullOrEmpty(refreshToken)) return Results.Unauthorized();
|
|
|
|
var user = await userManager.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken);
|
|
|
|
if (user == null || !user.IsActive) return Results.Unauthorized();
|
|
|
|
var newToken = JwtService.GenerateNewJwt(user, jwtSettings);
|
|
|
|
context.Response.Cookies.Append("X-Auth-Token", newToken, new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = DateTimeOffset.UtcNow.AddDays(1)
|
|
});
|
|
|
|
return Results.Ok(new LoginResponse { Succeeded = true, Token = newToken });
|
|
});
|
|
|
|
group.MapPost("/signup", async ([FromBody] CreateUserRequest request, IMediator mediator) =>
|
|
{
|
|
try
|
|
{
|
|
var command = new CreateUserCommand(request);
|
|
var response = await mediator.Send(command);
|
|
return Results.Ok(response);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Results.BadRequest(new { errors = new[] { ex.Message } });
|
|
}
|
|
});
|
|
|
|
group.MapPost("/signout", async (
|
|
SignInManager<ApplicationUser> signInManager,
|
|
UserManager<ApplicationUser> userManager,
|
|
HttpContext context) =>
|
|
{
|
|
var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
|
|
if (!string.IsNullOrEmpty(userId))
|
|
{
|
|
var user = await userManager.FindByIdAsync(userId);
|
|
if (user != null)
|
|
{
|
|
user.RefreshToken = null;
|
|
await userManager.UpdateAsync(user);
|
|
}
|
|
}
|
|
|
|
await signInManager.SignOutAsync();
|
|
|
|
context.Response.Cookies.Delete("X-Auth-Token");
|
|
context.Response.Cookies.Delete("X-Refresh-Token");
|
|
|
|
return Results.Ok(new { status = 200, title = "Successfully signed out" });
|
|
|
|
});
|
|
|
|
group.MapPost("/forgot-password", async ([FromBody] ForgotPasswordRequest request, IMediator mediator) =>
|
|
{
|
|
try
|
|
{
|
|
var command = new ForgotPasswordCommand(request);
|
|
var response = await mediator.Send(command);
|
|
return Results.Ok(response);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Results.BadRequest(new { errors = new[] { ex.Message } });
|
|
}
|
|
});
|
|
|
|
group.MapPost("/logout", async (SignInManager<ApplicationUser> signInManager) =>
|
|
{
|
|
await signInManager.SignOutAsync();
|
|
return Results.Ok(new { title = "Success user logout" });
|
|
}).RequireAuthorization();
|
|
|
|
group.MapPost("/reset-password", async ([FromBody] ResetPasswordRequest request, IMediator mediator) =>
|
|
{
|
|
try
|
|
{
|
|
var command = new ResetPasswordCommand(request);
|
|
var response = await mediator.Send(command);
|
|
return Results.Ok(response);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Results.BadRequest(new { errors = new[] { ex.Message } });
|
|
}
|
|
});
|
|
|
|
group.MapGet("/my-tenants", async (IMediator mediator, HttpContext context) =>
|
|
{
|
|
var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrEmpty(userId)) return Results.Unauthorized();
|
|
|
|
var query = new GetTenantsQuery(userId);
|
|
var response = await mediator.Send(query);
|
|
return Results.Ok(response);
|
|
}).RequireAuthorization();
|
|
|
|
group.MapPost("/confirm-tenant", async (
|
|
[FromBody] ConfirmTenantRequest request,
|
|
IMediator mediator,
|
|
HttpContext context,
|
|
SignInManager<ApplicationUser> signInManager,
|
|
UserManager<ApplicationUser> userManager) =>
|
|
{
|
|
var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrEmpty(userId)) return Results.Unauthorized();
|
|
|
|
var command = new ConfirmTenantCommand(userId, request);
|
|
var response = await mediator.Send(command);
|
|
|
|
if (!response.Success)
|
|
{
|
|
return Results.Json(new { success = false, title = response.Message }, statusCode: 400);
|
|
}
|
|
|
|
var user = await userManager.FindByIdAsync(userId);
|
|
if (user == null) return Results.Unauthorized();
|
|
|
|
var principal = await signInManager.CreateUserPrincipalAsync(user);
|
|
var identity = (ClaimsIdentity?)principal.Identity;
|
|
|
|
if (identity != null)
|
|
{
|
|
identity.AddClaim(new Claim(ClaimTypes.GroupSid, request.TenantId));
|
|
var jwtClaims = new List<Claim>
|
|
{
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id ?? string.Empty),
|
|
new Claim(ClaimTypes.Name, user.UserName ?? string.Empty),
|
|
new Claim(ClaimTypes.Email, user.Email ?? string.Empty),
|
|
new Claim(ClaimTypes.GivenName, user.FullName ?? string.Empty),
|
|
new Claim(ClaimTypes.GroupSid, request.TenantId)
|
|
};
|
|
|
|
var jwtSettings = context.RequestServices.GetRequiredService<IOptions<JwtSettingsModel>>().Value;
|
|
var newToken = JwtService.GenerateNewJwtWithClaims(user, jwtClaims, jwtSettings);
|
|
var newRefreshToken = Guid.NewGuid().ToString().Replace("-", "");
|
|
|
|
user.RefreshToken = newRefreshToken;
|
|
await userManager.UpdateAsync(user);
|
|
|
|
context.Response.Cookies.Append("X-Auth-Token", newToken, new CookieOptions
|
|
{
|
|
HttpOnly = true,
|
|
Secure = true,
|
|
SameSite = SameSiteMode.Strict,
|
|
Expires = DateTimeOffset.UtcNow.AddDays(1)
|
|
});
|
|
|
|
return Results.Ok(new { success = true, token = newToken });
|
|
}
|
|
|
|
return Results.Json(new { success = false, title = "Failed to reconstruct security claims" }, statusCode: 400);
|
|
}).RequireAuthorization();
|
|
|
|
return group;
|
|
}
|
|
} |