Skip to content

Commit f22208c

Browse files
Merge pull request #4 from HasanJaved-Developer/phase-5-webapp
Phase 5 webapp
2 parents 4374b9b + cb03b2e commit f22208c

File tree

159 files changed

+85458
-133
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+85458
-133
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\CentralizedLogging.Sdk\CentralizedLogging.Sdk.csproj" />
11+
<ProjectReference Include="..\UserManagement.Sdk\UserManagement.Sdk.csproj" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<Folder Include="Areas\Admin\Models\" />
16+
<Folder Include="Areas\Home\Models\" />
17+
<Folder Include="Controllers\" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
22+
</ItemGroup>
23+
24+
</Project>
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using ApiIntegrationMvc.Areas.Account.Models;
2+
using CentralizedLogging.Contracts.DTO;
3+
using CentralizedLogging.Sdk.Abstractions;
4+
using Microsoft.AspNetCore.Authentication;
5+
using Microsoft.AspNetCore.Authentication.Cookies;
6+
using Microsoft.AspNetCore.Mvc;
7+
using System.Security.Claims;
8+
using System.Text.Json;
9+
using UserManagement.Contracts.Auth;
10+
using UserManagement.Sdk.Abstractions;
11+
12+
namespace ApiIntegrationMvc.Areas.Account.Controllers
13+
{
14+
[Area("Account")]
15+
public class LoginController : Controller
16+
{
17+
private readonly IUserManagementClient _users;
18+
private readonly UserManagement.Sdk.Abstractions.IAccessTokenProvider _cache;
19+
private readonly IHttpContextAccessor _http;
20+
private readonly ICentralizedLoggingClient _centralizedlogs;
21+
public LoginController(IUserManagementClient users, ICentralizedLoggingClient centralizedlogs,
22+
UserManagement.Sdk.Abstractions.IAccessTokenProvider cache, IHttpContextAccessor http) => (_users, _centralizedlogs, _cache, _http) = (users, centralizedlogs, cache, http);
23+
24+
[HttpGet]
25+
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
26+
public IActionResult Index()
27+
{
28+
ViewBag.Error = TempData["Error"]; // one-time error
29+
return View(new LoginViewModel()); // empty fields
30+
}
31+
32+
[HttpPost]
33+
[ValidateAntiForgeryToken]
34+
public async Task<IActionResult> Index(LoginViewModel model, CancellationToken ct)
35+
{
36+
try
37+
{
38+
if (!ModelState.IsValid)
39+
{
40+
return View(model);
41+
}
42+
43+
var req = new LoginRequest(model.Username, model.Password);
44+
var result = await _users.LoginAsync(req, ct);
45+
46+
if (result == null || string.IsNullOrWhiteSpace(result.Token))
47+
{
48+
TempData["Error"] = "Invalid username or password.";
49+
return RedirectToAction(nameof(Index)); // ← PRG on failure
50+
}
51+
52+
53+
// Build claims (at least a stable user id + name)
54+
var claims = new List<Claim>
55+
{
56+
new(ClaimTypes.NameIdentifier, result.UserId.ToString()),
57+
new(ClaimTypes.Name, result.UserName),
58+
59+
};
60+
61+
var identity = new ClaimsIdentity(
62+
claims, CookieAuthenticationDefaults.AuthenticationScheme);
63+
64+
// Sign-in (creates auth cookie)
65+
await HttpContext.SignInAsync(
66+
CookieAuthenticationDefaults.AuthenticationScheme,
67+
new ClaimsPrincipal(identity),
68+
new AuthenticationProperties
69+
{
70+
IsPersistent = false,
71+
// keep cookie a bit shorter than token
72+
ExpiresUtc = result.ExpiresAtUtc.AddMinutes(-5)
73+
});
74+
75+
76+
_cache.SetAccessToken(result.Token, result.UserId, result.ExpiresAtUtc);
77+
78+
return RedirectToAction("Index", "Home", new { area = "Home" });
79+
}
80+
catch (HttpRequestException hx)
81+
{
82+
await _centralizedlogs.LogErrorAsync(BuildDto(hx, requestPath: "GET api/users"), ct);
83+
TempData["Error"] = hx.Message;
84+
return RedirectToAction(nameof(Index)); // ← PRG on failure
85+
}
86+
catch (JsonException jx)
87+
{
88+
TempData["Error"] = jx.Message;
89+
return RedirectToAction(nameof(Index)); // ← PRG on failure
90+
}
91+
catch(Exception ex)
92+
{
93+
TempData["Error"] = "Internal Error. Please contact administrator.";
94+
return RedirectToAction(nameof(Index)); // ← PRG on failure
95+
}
96+
}
97+
98+
private CreateErrorLogDto BuildDto(HttpRequestException ex, string requestPath)
99+
{
100+
//=>
101+
CreateErrorLogDto obj = new CreateErrorLogDto
102+
{
103+
ApplicationId = 4,
104+
Severity = "Error",
105+
Message = ex.Message,
106+
StackTrace = ex.ToString(),
107+
Source = ex.Source,
108+
RequestId = Guid.NewGuid().ToString(), // or pass one down
109+
};
110+
return obj;
111+
}
112+
113+
private static string? Claim(ClaimsPrincipal? u, string t) => u?.FindFirst(t)?.Value;
114+
115+
private string? CurrentUserId() =>
116+
Claim(_http.HttpContext?.User, "sub")
117+
?? Claim(_http.HttpContext?.User, ClaimTypes.NameIdentifier);
118+
119+
120+
[HttpPost]
121+
[ValidateAntiForgeryToken]
122+
public async Task<IActionResult> Logout(CancellationToken ct)
123+
{
124+
var uid = CurrentUserId();
125+
if (string.IsNullOrEmpty(uid))
126+
{
127+
return RedirectToAction("Index", "Login", new { area = "Account" });
128+
}
129+
// IMPORTANT: remove tokens while the user is still authenticated
130+
await _cache.RemoveAsync(uid, ct);
131+
132+
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
133+
return RedirectToAction("Index", "Login", new { area = "Account" });
134+
}
135+
136+
137+
}
138+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace ApiIntegrationMvc.Areas.Account.Models
4+
{
5+
public class LoginViewModel
6+
{
7+
[Required(ErrorMessage = "Username is required")]
8+
[StringLength(50)]
9+
public string Username { get; set; }
10+
11+
[Required(ErrorMessage = "Password is required")]
12+
[DataType(DataType.Password)]
13+
[StringLength(100)]
14+
public string Password { get; set; }
15+
}
16+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
@using ApiIntegrationMvc.Areas.Account.Models
2+
@model LoginViewModel
3+
@{
4+
ViewData["Title"] = "Login";
5+
}
6+
7+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
8+
@section Styles{
9+
<style>
10+
/* Page-specific centering */
11+
.login-container {
12+
height: 100vh;
13+
display: flex;
14+
justify-content: center; /* horizontal */
15+
align-items: center; /* vertical */
16+
17+
}
18+
19+
body {
20+
margin-bottom: 60px;
21+
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
22+
}
23+
24+
.login-card {
25+
width: 100%;
26+
max-width: 400px;
27+
background: #fff;
28+
border-radius: 12px;
29+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
30+
padding: 2rem;
31+
}
32+
33+
.login-card h2 {
34+
font-weight: 600;
35+
color: #2c3e50;
36+
}
37+
38+
.alert {
39+
border-radius: 8px;
40+
margin-top: 1rem;
41+
}
42+
</style>
43+
}
44+
<div class="login-container">
45+
<div class="login-card text-center">
46+
<h2 class="mb-4">🔗 Integration Portal</h2>
47+
48+
<form asp-action="Index" asp-controller="Login" method="post" novalidate>
49+
@Html.AntiForgeryToken()
50+
51+
<div asp-validation-summary="ModelOnly" class="alert alert-danger text-start"></div>
52+
53+
<!-- Username with icon -->
54+
<div class="input-group mb-3">
55+
<span class="input-group-text"><i class="bi bi-person"></i></span>
56+
<input asp-for="Username" class="form-control" placeholder="Username" />
57+
</div>
58+
<span asp-validation-for="Username" class="text-danger"></span>
59+
60+
<!-- Password with icon -->
61+
<div class="input-group mb-4">
62+
<span class="input-group-text"><i class="bi bi-lock"></i></span>
63+
<input asp-for="Password" class="form-control" placeholder="Password" />
64+
</div>
65+
<span asp-validation-for="Password" class="text-danger"></span>
66+
67+
<button type="submit" class="btn btn-primary w-100">
68+
<i class="bi bi-box-arrow-in-right"></i> Login
69+
</button>
70+
</form>
71+
72+
@if (ViewBag.Error != null)
73+
{
74+
<div class="alert alert-danger mt-3">@ViewBag.Error</div>
75+
}
76+
</div>
77+
</div>
78+
79+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@using ApiIntegrationMvc
2+
@using ApiIntegrationMvc.Models
3+
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4+
@using ApiIntegrationMvc.Areas.Account.Models
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@{
2+
Layout = "~/Views/Shared/_LoginLayout.cshtml"; // or area-specific layout if you want
3+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using CentralizedLogging.Sdk.Abstractions;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
namespace ApiIntegrationMvc.Areas.Admin.Controllers
5+
{
6+
[Area("Admin")]
7+
public class LogsController : Controller
8+
{
9+
private readonly ICentralizedLoggingClient _centralizedlogs;
10+
private readonly IAccessTokenProvider _cache;
11+
private readonly IHttpContextAccessor _http;
12+
public LogsController(ICentralizedLoggingClient centralizedlogs, IAccessTokenProvider cache, IHttpContextAccessor http) => (_centralizedlogs, _cache, _http) = (centralizedlogs, cache, http);
13+
14+
public async Task<IActionResult> Index(CancellationToken ct)
15+
{
16+
string token = await _cache.GetAccessTokenAsync(ct);
17+
18+
var result = await _centralizedlogs.GetAllErrorAsync(ct);
19+
20+
21+
return View(result.OrderByDescending(v => v.Id));
22+
}
23+
}
24+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
namespace ApiIntegrationMvc.Areas.Admin.Controllers
4+
{
5+
[Area("Admin")]
6+
public class RolesController : Controller
7+
{
8+
public IActionResult Index()
9+
{
10+
return RedirectToAction("Index", "Home", new { area = "Home" });
11+
//return View();
12+
}
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
namespace ApiIntegrationMvc.Areas.Admin.Controllers
4+
{
5+
[Area("Admin")]
6+
public class UsersController : Controller
7+
{
8+
public IActionResult Index()
9+
{
10+
return RedirectToAction("Index", "Home", new { area = "Home" });
11+
//return View();
12+
}
13+
}
14+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@using ApiIntegrationMvc
2+
@using ApiIntegrationMvc.Models
3+
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4+
@using ApiIntegrationMvc.Areas.Account.Models

0 commit comments

Comments
 (0)