initial commit
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user