From 1b47fbdcf0ff769479085f14f3cb088a0b5235af Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 12 Aug 2025 10:03:28 +0530 Subject: [PATCH 1/5] Testing the firebase notification added in login API --- .../Dtos/Authentication/LoginDto.cs | 2 ++ .../Controllers/AttendanceController.cs | 12 +------ .../Controllers/AuthController.cs | 31 ++++++++++++++++--- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Marco.Pms.Model/Dtos/Authentication/LoginDto.cs b/Marco.Pms.Model/Dtos/Authentication/LoginDto.cs index 00bef12..711113b 100644 --- a/Marco.Pms.Model/Dtos/Authentication/LoginDto.cs +++ b/Marco.Pms.Model/Dtos/Authentication/LoginDto.cs @@ -4,5 +4,7 @@ { public string? Username { get; set; } public string? Password { get; set; } + public required string DeviceToken { get; set; } + } } diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index e54fca0..bc78ed7 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -808,17 +808,7 @@ namespace MarcoBMS.Services.Controllers PreSignedUrl = string.Empty }; } - var message = new Message() - { - Token = recordAttendanceDot.DeviceToken, - Notification = new Notification - { - Title = "Hello from .NET", - Body = "This is a test message" - } - }; - string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); - _logger.LogInfo("Firebase push notification messageId: {MessageId}", response); + _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty); diff --git a/Marco.Pms.Services/Controllers/AuthController.cs b/Marco.Pms.Services/Controllers/AuthController.cs index 1b45eb7..6d226d0 100644 --- a/Marco.Pms.Services/Controllers/AuthController.cs +++ b/Marco.Pms.Services/Controllers/AuthController.cs @@ -1,7 +1,4 @@ -using System.Net; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text; +using FirebaseAdmin.Messaging; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Authentication; using Marco.Pms.Model.Dtos.Authentication; @@ -15,6 +12,10 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using System.Net; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; namespace MarcoBMS.Services.Controllers { @@ -51,6 +52,17 @@ namespace MarcoBMS.Services.Controllers { try { + var message = new Message() + { + Token = "ewjsd9zGTh6aS6Vg3Z_uxP:APA91bFZi1KzZdxlHUfBa_dX3PEJnDhX4R2dvFjD9Zf3WPSm957Hb53JPim7jrpjhpeOY61I9rfc11c3wpqWfW_06aSx-Yb8UfWpygV2YgZ8gbHtSku_PSQ", + Notification = new Notification + { + Title = "Hello from .NET", + Body = "This is a test message" + } + }; + string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); + _logger.LogInfo("Firebase push notification messageId: {MessageId}", response); // Find user by email or phone number var user = await _context.ApplicationUsers .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username); @@ -118,6 +130,17 @@ namespace MarcoBMS.Services.Controllers [HttpPost("login-mobile")] public async Task LoginMobile([FromBody] LoginDto loginDto) { + var message = new Message() + { + Token = loginDto.DeviceToken, + Notification = new Notification + { + Title = "Hello from .NET", + Body = "This is a test message" + } + }; + string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); + _logger.LogInfo("Firebase push notification messageId: {MessageId}", response); // Validate input DTO if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password)) { From f502adb6c6fe1212359870396f90a0e00d56245a Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 12 Aug 2025 10:15:24 +0530 Subject: [PATCH 2/5] added notofication in attendance --- .../Controllers/AttendanceController.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index bc78ed7..c0ffa5e 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using FirebaseAdmin.Messaging; +using FirebaseAdmin.Messaging; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.AttendanceModule; using Marco.Pms.Model.Dtos.Attendance; @@ -15,6 +14,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; +using System.Globalization; using Document = Marco.Pms.Model.DocumentManager.Document; namespace MarcoBMS.Services.Controllers @@ -808,7 +808,17 @@ namespace MarcoBMS.Services.Controllers PreSignedUrl = string.Empty }; } - + var message = new Message() + { + Token = "ewjsd9zGTh6aS6Vg3Z_uxP:APA91bFZi1KzZdxlHUfBa_dX3PEJnDhX4R2dvFjD9Zf3WPSm957Hb53JPim7jrpjhpeOY61I9rfc11c3wpqWfW_06aSx-Yb8UfWpygV2YgZ8gbHtSku_PSQ", + Notification = new Notification + { + Title = "Hello from .NET", + Body = "This is a test message" + } + }; + string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); + _logger.LogInfo("Firebase push notification messageId: {MessageId}", response); _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty); From 6017d87793a9d39a84ce1d78c6e16f3662af2cd9 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 12 Aug 2025 10:19:31 +0530 Subject: [PATCH 3/5] Taking token from model --- Marco.Pms.Services/Controllers/AttendanceController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index c0ffa5e..040029b 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -810,7 +810,7 @@ namespace MarcoBMS.Services.Controllers } var message = new Message() { - Token = "ewjsd9zGTh6aS6Vg3Z_uxP:APA91bFZi1KzZdxlHUfBa_dX3PEJnDhX4R2dvFjD9Zf3WPSm957Hb53JPim7jrpjhpeOY61I9rfc11c3wpqWfW_06aSx-Yb8UfWpygV2YgZ8gbHtSku_PSQ", + Token = recordAttendanceDot.DeviceToken, Notification = new Notification { Title = "Hello from .NET", From c5385c0b06b10b88b7a5f132330d173b7719be42 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 12 Aug 2025 10:54:40 +0530 Subject: [PATCH 4/5] Added logs in login-mobile --- .../Dtos/Authentication/LoginDto.cs | 2 +- .../Controllers/AuthController.cs | 48 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Marco.Pms.Model/Dtos/Authentication/LoginDto.cs b/Marco.Pms.Model/Dtos/Authentication/LoginDto.cs index 711113b..536ac5d 100644 --- a/Marco.Pms.Model/Dtos/Authentication/LoginDto.cs +++ b/Marco.Pms.Model/Dtos/Authentication/LoginDto.cs @@ -4,7 +4,7 @@ { public string? Username { get; set; } public string? Password { get; set; } - public required string DeviceToken { get; set; } + public string? DeviceToken { get; set; } } } diff --git a/Marco.Pms.Services/Controllers/AuthController.cs b/Marco.Pms.Services/Controllers/AuthController.cs index 6d226d0..b0f86f4 100644 --- a/Marco.Pms.Services/Controllers/AuthController.cs +++ b/Marco.Pms.Services/Controllers/AuthController.cs @@ -52,18 +52,36 @@ namespace MarcoBMS.Services.Controllers { try { + var deviceToken = "ewjsd9zGTh6aS6Vg3Z_uxP:APA91bFZi1KzZdxlHUfBa_dX3PEJnDhX4R2dvFjD9Zf3WPSm957Hb53JPim7jrpjhpeOY61I9rfc11c3wpqWfW_06aSx-Yb8UfWpygV2YgZ8gbHtSku_PSQ"; var message = new Message() { - Token = "ewjsd9zGTh6aS6Vg3Z_uxP:APA91bFZi1KzZdxlHUfBa_dX3PEJnDhX4R2dvFjD9Zf3WPSm957Hb53JPim7jrpjhpeOY61I9rfc11c3wpqWfW_06aSx-Yb8UfWpygV2YgZ8gbHtSku_PSQ", + Token = deviceToken, Notification = new Notification { Title = "Hello from .NET", Body = "This is a test message" } }; - string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); - _logger.LogInfo("Firebase push notification messageId: {MessageId}", response); - // Find user by email or phone number + try + { + string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); + _logger.LogInfo("Successfully sent message: {MessageId}", response); + } + catch (FirebaseMessagingException ex) + { + _logger.LogError("Error sending push notification. : {Error}", ex.Message); + + // Check for the specific error codes that indicate an invalid token + if (ex.MessagingErrorCode == MessagingErrorCode.Unregistered || + ex.MessagingErrorCode == MessagingErrorCode.InvalidArgument) + { + _logger.LogWarning("FCM token is invalid and should be deleted: {Token}", deviceToken); + + // Add your logic here to remove the invalid token from your database + // await YourTokenService.DeleteTokenAsync(recordAttendanceDot.DeviceToken); + } + } + var user = await _context.ApplicationUsers .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username); @@ -139,11 +157,29 @@ namespace MarcoBMS.Services.Controllers Body = "This is a test message" } }; - string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); - _logger.LogInfo("Firebase push notification messageId: {MessageId}", response); + try + { + string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); + _logger.LogInfo("Successfully sent message: {MessageId}", response); + } + catch (FirebaseMessagingException ex) + { + _logger.LogError("Error sending push notification. : {Error}", ex.Message); + + // Check for the specific error codes that indicate an invalid token + if (ex.MessagingErrorCode == MessagingErrorCode.Unregistered || + ex.MessagingErrorCode == MessagingErrorCode.InvalidArgument) + { + _logger.LogWarning("FCM token is invalid and should be deleted: {Token}", loginDto.DeviceToken); + + // Add your logic here to remove the invalid token from your database + // await YourTokenService.DeleteTokenAsync(recordAttendanceDot.DeviceToken); + } + } // Validate input DTO if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password)) { + _logger.LogWarning("Login failed: User not found for input {Username}", loginDto.Username ?? string.Empty); return BadRequest(ApiResponse.ErrorResponse("Username or password is missing.", "Invalid request", 400)); } From 7335ad23ce89b8e4a7fb214658df91cb30e3a6e5 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 12 Aug 2025 11:31:04 +0530 Subject: [PATCH 5/5] added the logs --- .../Controllers/AuthController.cs | 192 +++++++++++------- 1 file changed, 122 insertions(+), 70 deletions(-) diff --git a/Marco.Pms.Services/Controllers/AuthController.cs b/Marco.Pms.Services/Controllers/AuthController.cs index b0f86f4..cbc17da 100644 --- a/Marco.Pms.Services/Controllers/AuthController.cs +++ b/Marco.Pms.Services/Controllers/AuthController.cs @@ -146,8 +146,20 @@ namespace MarcoBMS.Services.Controllers } [HttpPost("login-mobile")] + /// + /// Handles mobile user login, validates credentials, sends a test push notification, + /// and generates JWT, Refresh, and MPIN tokens upon successful authentication. + /// + /// Data Transfer Object containing the user's login credentials and device token. + /// An IActionResult containing the authentication tokens or an error response. public async Task LoginMobile([FromBody] LoginDto loginDto) { + // Log the start of the login attempt for traceability. + _logger.LogInfo("Login attempt initiated for user: {Username}", loginDto.Username ?? "N/A"); + + // --- Push Notification Section --- + // This section attempts to send a test push notification to the user's device. + // It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens. var message = new Message() { Token = loginDto.DeviceToken, @@ -157,95 +169,135 @@ namespace MarcoBMS.Services.Controllers Body = "This is a test message" } }; + try { + // Attempt to send the message via Firebase. string response = await FirebaseMessaging.DefaultInstance.SendAsync(message); - _logger.LogInfo("Successfully sent message: {MessageId}", response); + _logger.LogInfo("Successfully sent test push notification. MessageId: {MessageId}", response); } catch (FirebaseMessagingException ex) { - _logger.LogError("Error sending push notification. : {Error}", ex.Message); + // Log the specific Firebase error. + _logger.LogError("Error sending push notification: {Error}", ex.Message); - // Check for the specific error codes that indicate an invalid token + // Check for specific error codes that indicate an invalid or unregistered token. if (ex.MessagingErrorCode == MessagingErrorCode.Unregistered || ex.MessagingErrorCode == MessagingErrorCode.InvalidArgument) { - _logger.LogWarning("FCM token is invalid and should be deleted: {Token}", loginDto.DeviceToken); + _logger.LogWarning("FCM token is invalid and should be deleted from the database: {Token}", loginDto.DeviceToken ?? string.Empty); - // Add your logic here to remove the invalid token from your database - // await YourTokenService.DeleteTokenAsync(recordAttendanceDot.DeviceToken); + // TODO: Implement the logic here to remove the invalid token from your database. + // Example: await YourTokenService.DeleteTokenAsync(loginDto.DeviceToken); } } - // Validate input DTO - if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password)) + + try { - _logger.LogWarning("Login failed: User not found for input {Username}", loginDto.Username ?? string.Empty); - return BadRequest(ApiResponse.ErrorResponse("Username or password is missing.", "Invalid request", 400)); + // --- Input Validation --- + // Ensure that the request body and essential fields are not null or empty. + if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password)) + { + _logger.LogWarning("Login failed due to missing username or password."); + return BadRequest(ApiResponse.ErrorResponse("Username or password is missing.", "Invalid request", 400)); + } + + // --- User Retrieval --- + // Find the user in the database by their email or phone number. + _logger.LogInfo("Searching for user: {Username}", loginDto.Username); + var user = await _context.ApplicationUsers + .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username); + + // If no user is found, return an unauthorized response. + if (user == null || string.IsNullOrWhiteSpace(user.UserName)) + { + _logger.LogWarning("Login failed: User not found for username {Username}", loginDto.Username); + return Unauthorized(ApiResponse.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401)); + } + + // --- User Status Checks --- + // Check if the user's account is marked as inactive. + if (!user.IsActive) + { + _logger.LogWarning("Login failed: User '{Username}' account is inactive.", user.UserName); + return BadRequest(ApiResponse.ErrorResponse("User is inactive", "User is inactive", 400)); + } + + // Check if the user has confirmed their email address. + if (!user.EmailConfirmed) + { + _logger.LogWarning("Login failed: User '{Username}' email is not verified.", user.UserName); + return BadRequest(ApiResponse.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400)); + } + + // --- Password Validation --- + // Use ASP.NET Identity's UserManager to securely check the password. + _logger.LogInfo("Validating password for user: {Username}", user.UserName); + var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password); + if (!isPasswordValid) + { + _logger.LogWarning("Login failed: Invalid password for user {Username}", user.UserName); + return Unauthorized(ApiResponse.ErrorResponse("Invalid username or password.", "Invalid credentials", 401)); + } + _logger.LogInfo("Password validation successful for user: {Username}", user.UserName); + + // Check if the username property on the user object is populated. + if (string.IsNullOrWhiteSpace(user.UserName)) + { + // This is an unlikely edge case, but good to handle. + _logger.LogError("Login failed: User object for ID {UserId} is missing a UserName.", user.Id); + return NotFound(ApiResponse.ErrorResponse("UserName not found", "Username is missing", 404)); + } + + // --- Employee and Tenant Context Retrieval --- + // Fetch associated employee details to get tenant context for token generation. + _logger.LogInfo("Fetching employee details for user ID: {UserId}", user.Id); + var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id); + if (emp == null) + { + _logger.LogError("Login failed: Could not find associated employee record for user ID {UserId}", user.Id); + return NotFound(ApiResponse.ErrorResponse("Employee not found", "Employee details missing", 404)); + } + _logger.LogInfo("Successfully found employee details for tenant ID: {TenantId}", emp.TenantId); + + // --- Token Generation --- + // Generate the primary JWT access token. + _logger.LogInfo("Generating JWT for user: {Username}", user.UserName); + var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings); + + // Generate a new refresh token and store it in the database. + _logger.LogInfo("Generating and storing Refresh Token for user: {Username}", user.UserName); + var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings); + + // Fetch the user's MPIN token if it exists. + _logger.LogInfo("Fetching MPIN token for user: {Username}", user.UserName); + var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id) && p.TenantId == emp.TenantId); + + // --- Response Assembly --- + // Combine all tokens into a single response object. + var responseData = new + { + token, + refreshToken, + mpinToken = mpinToken?.MPINToken // Safely access the MPIN token, will be null if not found. + }; + + // Return a successful response with the generated tokens. + _logger.LogInfo("User {Username} logged in successfully.", user.UserName); + return Ok(ApiResponse.SuccessResponse(responseData, "User logged in successfully.", 200)); } - - // Find user by email or phone number - var user = await _context.ApplicationUsers - .FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username); - - // If user not found, return unauthorized - if (user == null) + catch (Exception ex) { - return Unauthorized(ApiResponse.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401)); + // --- Global Exception Handling --- + // Catch any unexpected exceptions during the login process. + _logger.LogError("An unexpected error occurred during the LoginMobile process for user: {Username} : {Error}", loginDto?.Username ?? "N/A", ex.Message); + + // Return a generic 500 Internal Server Error to avoid leaking implementation details. + return StatusCode(500, ApiResponse.ErrorResponse("An internal server error occurred.", "Server Error", 500)); } - - // Check if user is inactive - if (!user.IsActive) - { - return BadRequest(ApiResponse.ErrorResponse("User is inactive", "User is inactive", 400)); - } - - // Check if user email is not confirmed - if (!user.EmailConfirmed) - { - return BadRequest(ApiResponse.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400)); - } - - // Validate password using ASP.NET Identity - var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password); - if (!isPasswordValid) - { - return Unauthorized(ApiResponse.ErrorResponse("Invalid username or password.", "Invalid credentials", 401)); - } - - // Check if username is missing - if (string.IsNullOrWhiteSpace(user.UserName)) - { - return NotFound(ApiResponse.ErrorResponse("UserName not found", "Username is missing", 404)); - } - - // Get employee information for tenant context - var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id); - if (emp == null) - { - return NotFound(ApiResponse.ErrorResponse("Employee not found", "Employee details missing", 404)); - } - - // Generate JWT token - var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings); - - // Generate Refresh Token and store in DB - var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings); - - // Fetch MPIN Token - var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id) && p.TenantId == emp.TenantId); - - // Combine all tokens in response - var responseData = new - { - token, - refreshToken, - mpinToken = mpinToken?.MPINToken - }; - - // Return success response - return Ok(ApiResponse.SuccessResponse(responseData, "User logged in successfully.", 200)); } + [HttpPost("login-mpin")] public async Task VerifyMPIN([FromBody] VerifyMPINDto verifyMPIN) {