Removed setUserLockout from PartnerUI
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
@@ -13,12 +13,18 @@ public sealed class SetUserLockoutEndpoint : IEndpoint
|
||||
{
|
||||
builder.MapPut("api/users/{userId:Guid}/session/lockout/{disable:bool}", async (Guid userId, bool disable, HttpContext context,
|
||||
IUserService service, CancellationToken cancellationToken = default) =>
|
||||
await service.SetUserLockoutAsync(userId, context.GetUserId(), disable, cancellationToken) switch
|
||||
{
|
||||
var (_, role) = context.User.GetUserRole();
|
||||
if (role != null && role.Equals("Partner", StringComparison.OrdinalIgnoreCase))
|
||||
return Results.Forbid();
|
||||
|
||||
return await service.SetUserLockoutAsync(userId, context.GetUserId(), disable, cancellationToken) switch
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
|
||||
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
|
||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||
};
|
||||
})
|
||||
.WithDescription("Lock user session")
|
||||
.RequireAuthorization(Constants.MainScope)
|
||||
@@ -26,6 +32,7 @@ public sealed class SetUserLockoutEndpoint : IEndpoint
|
||||
.MapToApiVersion(new ApiVersion(1))
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces<Problems>(StatusCodes.Status404NotFound)
|
||||
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
|
||||
@@ -29,7 +29,7 @@ builder.Services.AddApiVersioning(options =>
|
||||
|
||||
builder.Services.AddScoped<ActiveUserFilter>();
|
||||
builder.Services.AddScoped<UserRoleFilter>();
|
||||
builder.Services.AddTransient<ITenantContextService, TenantContextService>();
|
||||
builder.Services.AddScoped<ITenantContextService, TenantContextService>();
|
||||
|
||||
builder.Services.AddEndpoints(Assembly.GetExecutingAssembly());
|
||||
builder.Services.AddApiServices(builder.Configuration);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PostFundManagement.Application.Extensions;
|
||||
namespace PostFundManagement.Application.Extensions;
|
||||
|
||||
public static class General
|
||||
{
|
||||
@@ -16,7 +16,8 @@ public static class General
|
||||
|
||||
public static (bool Decoded, Guid userId) GetUserId(this ClaimsPrincipal principal)
|
||||
{
|
||||
var subClaim = principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value ?? principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
var subClaim = principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value
|
||||
?? principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
var decoded = Guid.TryParse(subClaim, out var userId);
|
||||
|
||||
@@ -25,20 +26,34 @@ public static class General
|
||||
|
||||
public static (bool Decoded, string email) GetUserEmail(this ClaimsPrincipal principal)
|
||||
{
|
||||
var emailClaim = principal.FindFirst("email")?.Value ?? principal.FindFirst(ClaimTypes.Email)?.Value;
|
||||
var emailClaim = principal.FindFirst("email")?.Value
|
||||
?? principal.FindFirst(ClaimTypes.Email)?.Value
|
||||
?? principal.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")?.Value
|
||||
?? principal.FindFirst("preferred_username")?.Value
|
||||
?? principal.FindFirst("name")?.Value
|
||||
?? principal.FindFirst(ClaimTypes.Name)?.Value;
|
||||
|
||||
var decoded = !string.IsNullOrWhiteSpace(emailClaim);
|
||||
|
||||
return (decoded, emailClaim!);
|
||||
return (decoded, emailClaim ?? string.Empty);
|
||||
}
|
||||
|
||||
public static (bool Decoded, string role) GetUserRole(this ClaimsPrincipal principal)
|
||||
{
|
||||
var roleClaim = principal.FindFirst("role")?.Value ?? principal.FindFirst(ClaimTypes.Role)?.Value;
|
||||
var roleClaims = principal.FindAll("role")
|
||||
.Concat(principal.FindAll(ClaimTypes.Role))
|
||||
.Concat(principal.FindAll("http://schemas.microsoft.com/ws/2008/06/identity/claims/role"))
|
||||
.Concat(principal.FindAll("roles"))
|
||||
.Select(c => c.Value)
|
||||
.Where(v => !string.IsNullOrWhiteSpace(v))
|
||||
.ToList();
|
||||
|
||||
var decoded = !string.IsNullOrWhiteSpace(roleClaim);
|
||||
var partnerRole = roleClaims.FirstOrDefault(r => r.Equals("Partner", StringComparison.OrdinalIgnoreCase));
|
||||
var role = partnerRole ?? roleClaims.FirstOrDefault();
|
||||
|
||||
return (decoded, roleClaim!);
|
||||
var decoded = !string.IsNullOrWhiteSpace(role);
|
||||
|
||||
return (decoded, role ?? string.Empty);
|
||||
}
|
||||
|
||||
public static Microsoft.AspNetCore.Mvc.ProblemDetails ToProblems(this IReadOnlyList<IError> errors, int statusCode = StatusCodes.Status400BadRequest) => new()
|
||||
|
||||
@@ -17,7 +17,6 @@ import { AccountActivationModal } from '../components/auth/AccountActivationModa
|
||||
import { NonPartnerAccessDeniedModal } from '../components/auth/NonPartnerAccessDeniedModal';
|
||||
import { SessionExpiredModal } from '../components/auth/SessionExpiredModal';
|
||||
import { encodeHashIdSync } from '../services/toolsService';
|
||||
import { verifyTenantLockout, clearTenantLockoutCache } from '../services/tenantLockoutService';
|
||||
import {
|
||||
onSessionExpired,
|
||||
triggerSessionExpired,
|
||||
@@ -510,35 +509,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Verify tenant lockout using the HashId-encoded tenant ID
|
||||
const effectiveTenantId =
|
||||
parsedTenantId ??
|
||||
syncResult.data?.tenantId ??
|
||||
syncResult.data?.organisationId ??
|
||||
tenantClaimValues.tenantId;
|
||||
|
||||
const tenantLockoutResult = await verifyTenantLockout(effectiveTenantId, tenantClaim);
|
||||
|
||||
if (!tenantLockoutResult.isActive) {
|
||||
setIsPendingActivation(true);
|
||||
setIsSynced(false);
|
||||
setActivationError(tenantLockoutResult.errorMessage || 'Tenant is inactive or not found');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tenantLockoutResult.tenant?.name) {
|
||||
profile.tenant = tenantLockoutResult.tenant.name;
|
||||
setTenantName(tenantLockoutResult.tenant.name);
|
||||
}
|
||||
if (tenantLockoutResult.tenant?.id !== undefined && !profile.tenant_id) {
|
||||
const numericTid = Number(tenantLockoutResult.tenant.id);
|
||||
if (!isNaN(numericTid) && numericTid > 0) {
|
||||
profile.tenant_id = numericTid;
|
||||
setTenantId(numericTid);
|
||||
}
|
||||
}
|
||||
|
||||
setUser({ ...profile });
|
||||
localStorage.setItem(LOCAL_STORAGE_USER_KEY, JSON.stringify(profile));
|
||||
|
||||
@@ -669,39 +639,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Verify tenant lockout using HashId-encoded tenant ID
|
||||
const effectiveTenantId =
|
||||
storedTenantId ??
|
||||
syncResult.data?.tenantId ??
|
||||
syncResult.data?.organisationId ??
|
||||
tenantClaimValues.tenantId;
|
||||
|
||||
const tenantLockoutResult = await verifyTenantLockout(
|
||||
effectiveTenantId,
|
||||
storedTenantClaim,
|
||||
hadPendingActivation
|
||||
);
|
||||
|
||||
if (!tenantLockoutResult.isActive) {
|
||||
setIsPendingActivation(true);
|
||||
setIsSynced(false);
|
||||
setActivationError(tenantLockoutResult.errorMessage || 'Tenant is inactive or not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (tenantLockoutResult.tenant?.name) {
|
||||
if (resolvedUser) {
|
||||
resolvedUser.tenant = tenantLockoutResult.tenant.name;
|
||||
}
|
||||
setTenantName(tenantLockoutResult.tenant.name);
|
||||
}
|
||||
if (tenantLockoutResult.tenant?.id !== undefined && resolvedUser && !resolvedUser.tenant_id) {
|
||||
const numericTid = Number(tenantLockoutResult.tenant.id);
|
||||
if (!isNaN(numericTid) && numericTid > 0) {
|
||||
resolvedUser.tenant_id = numericTid;
|
||||
setTenantId(numericTid);
|
||||
}
|
||||
}
|
||||
if (resolvedUser) {
|
||||
setUser({ ...resolvedUser });
|
||||
localStorage.setItem(LOCAL_STORAGE_USER_KEY, JSON.stringify(resolvedUser));
|
||||
@@ -746,7 +683,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
const syncUserSession = async (force = true): Promise<UserSyncResult> => {
|
||||
try {
|
||||
await loadRuntimeConfig();
|
||||
clearTenantLockoutCache();
|
||||
if (sub) {
|
||||
clearCachedUserRbac(sub);
|
||||
}
|
||||
@@ -793,41 +729,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
localStorage.setItem(LOCAL_STORAGE_CLAIMS_KEY, JSON.stringify(resolved.claims));
|
||||
} catch {}
|
||||
|
||||
const tenantClaimValues = extractTenantClaimValues(claims, accessToken);
|
||||
const effectiveTenantId =
|
||||
currentTenantId ??
|
||||
result.data?.tenantId ??
|
||||
result.data?.organisationId ??
|
||||
tenantClaimValues.tenantId;
|
||||
const effectiveTenantClaim = currentTenantClaim || tenantClaimValues.tenant || undefined;
|
||||
|
||||
// Step 3: Verify tenant lockout using HashId-encoded tenant ID
|
||||
const tenantLockoutResult = await verifyTenantLockout(
|
||||
effectiveTenantId,
|
||||
effectiveTenantClaim,
|
||||
true
|
||||
);
|
||||
if (!tenantLockoutResult.isActive) {
|
||||
const msg = tenantLockoutResult.errorMessage || 'Tenant is not Active. Lockout active.';
|
||||
setIsPendingActivation(true);
|
||||
setIsSynced(false);
|
||||
setActivationError(msg);
|
||||
return {
|
||||
success: false,
|
||||
isActivationPending: true,
|
||||
errorMessage: msg,
|
||||
};
|
||||
}
|
||||
|
||||
if (tenantLockoutResult.tenant?.name) {
|
||||
setTenantName(tenantLockoutResult.tenant.name);
|
||||
}
|
||||
if (tenantLockoutResult.tenant?.id !== undefined && !tenantId) {
|
||||
const numericTid = Number(tenantLockoutResult.tenant.id);
|
||||
if (!isNaN(numericTid) && numericTid > 0) {
|
||||
setTenantId(numericTid);
|
||||
}
|
||||
}
|
||||
// Partner user verified successfully
|
||||
|
||||
setActivationPending(false);
|
||||
setIsPendingActivation(false);
|
||||
@@ -873,7 +775,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
localStorage.removeItem(LOCAL_STORAGE_SUB_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_CLAIMS_KEY);
|
||||
clearUserSyncInBrowser();
|
||||
clearTenantLockoutCache();
|
||||
setUser(null);
|
||||
setSub('');
|
||||
setClaims({});
|
||||
|
||||
@@ -1585,13 +1585,7 @@ export const deleteUserClaim = async (targetUserId: string, claimId: string | nu
|
||||
await apiClient.delete(`/v1/api/users/${targetUserId}/claims/${claimId}`);
|
||||
};
|
||||
|
||||
export const forceLogoutUser = async (userId: string): Promise<void> => {
|
||||
await apiClient.put(`/v1/api/users/${userId}/session/logout`);
|
||||
};
|
||||
|
||||
export const setUserLockout = async (userId: string, disable: boolean): Promise<void> => {
|
||||
await apiClient.put(`/v1/api/users/${userId}/session/lockout/${disable}`);
|
||||
};
|
||||
// Administrative functions (forceLogoutUser, setUserLockout) removed from PartnerUI
|
||||
|
||||
import {
|
||||
syncUserAccount,
|
||||
|
||||
@@ -189,19 +189,7 @@ async function executeBackendSync(
|
||||
userSub?: string,
|
||||
profile?: UserSyncProfile
|
||||
): Promise<{ endpoint: string; data?: any }> {
|
||||
const rawTenantId = profile?.tenant_id ?? (profile as any)?.tenantId;
|
||||
const parsedTenantId =
|
||||
rawTenantId !== undefined && rawTenantId !== null && rawTenantId !== ''
|
||||
? Number(rawTenantId)
|
||||
: undefined;
|
||||
|
||||
const resolvedOrgId =
|
||||
parsedTenantId !== undefined
|
||||
? parsedTenantId
|
||||
: profile?.organisationId !== undefined && profile?.organisationId !== null && profile?.organisationId !== ''
|
||||
? Number(profile.organisationId)
|
||||
: undefined;
|
||||
|
||||
const endpoint = '/v1/api/integration/sync';
|
||||
const payload = {
|
||||
id: userSub || profile?.id || profile?.sub,
|
||||
sub: userSub || profile?.sub,
|
||||
@@ -209,40 +197,25 @@ async function executeBackendSync(
|
||||
name: profile?.name || undefined,
|
||||
role: profile?.role || undefined,
|
||||
tenant: profile?.tenant || undefined,
|
||||
tenant_id: parsedTenantId,
|
||||
organisationId: resolvedOrgId,
|
||||
tenant_id: profile?.tenant_id,
|
||||
organisationId: profile?.organisationId,
|
||||
};
|
||||
|
||||
let lastError: any = null;
|
||||
|
||||
for (const endpoint of USER_SYNC_CANDIDATE_ENDPOINTS) {
|
||||
try {
|
||||
let res;
|
||||
if (endpoint === '/v1/api/integration/sync') {
|
||||
try {
|
||||
res = await apiClient.post(endpoint, payload);
|
||||
} catch (postErr: any) {
|
||||
const errStatus = postErr?.response?.status;
|
||||
if (errStatus === 400 || errStatus === 415 || errStatus === 422) {
|
||||
res = await apiClient.post(endpoint, {});
|
||||
} else {
|
||||
throw postErr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = await apiClient.post(endpoint, payload);
|
||||
}
|
||||
const res = await apiClient.post(endpoint, payload);
|
||||
return { endpoint, data: res?.data };
|
||||
} catch (err: any) {
|
||||
lastError = err;
|
||||
const status = err?.response?.status;
|
||||
if (status === 401 || status === 403 || status === 423) {
|
||||
if (status === 400 || status === 415 || status === 422) {
|
||||
try {
|
||||
const resFallback = await apiClient.post(endpoint, {});
|
||||
return { endpoint, data: resFallback?.data };
|
||||
} catch (fallbackErr) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw lastError || new Error('Failed to reach user sync endpoint');
|
||||
}
|
||||
|
||||
export async function syncUserAccount(
|
||||
|
||||
Reference in New Issue
Block a user