Initial commit of io8 project

This commit is contained in:
user 2025-10-06 09:30:20 +00:00
parent 4ba91e7860
commit ea55ce3756
37 changed files with 1324 additions and 105 deletions

56
.sureai/.code_tree.txt Normal file
View File

@ -0,0 +1,56 @@
# Project Directory Structure (tree -L 2 -a output)
.
├── .git
│   ├── COMMIT_EDITMSG
│   ├── FETCH_HEAD
│   ├── HEAD
│   ├── ORIG_HEAD
│   ├── branches
│   ├── config
│   ├── description
│   ├── hooks
│   ├── index
│   ├── info
│   ├── logs
│   ├── objects
│   └── refs
├── .io8project
│   ├── .state.json
│   └── project_metadata.json
├── .sureai
│   ├── .code_tree.txt
│   ├── .developer_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .directory_structure.txt
│   ├── .directory_structure_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8analyst_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8architect_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8codermaster_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8pm_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8project_builder_develop_a_working_20251006_065420.md
│   ├── .sm_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── io8_mcp
│   ├── sprint_plan.md
│   ├── tasks_list.md
│   └── uploads
├── Dockerfile.backend
├── Dockerfile.frontend
├── backend
│   └── .gitkeep
├── deployment_config.yml
├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-b-b
│   └── authsec_springboot
├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-d-d
│   └── authsec_mysql
├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f
│   └── authsec_angular
├── docker-compose.yml
├── frontend
│   └── .gitkeep
├── nginx.conf
└── sureops
├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-b-b
├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-d-d
└── develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f
24 directories, 28 files

View File

@ -0,0 +1,59 @@
# Developer Agent Prompt: Develop a Working Notes App
This document outlines the development methodology, code implementation approach, technology stack strategy, code organization, and customized workflow for building a "notes app" based on the user's request.
## 1. Development Methodology
An Agile, iterative development approach will be followed, focusing on incremental delivery. Each main task defined in `.sureai/tasks_list.md` will be broken down into smaller, manageable subtasks. Progress will be tracked diligently within the `tasks_list.md` file.
## 2. Code Implementation Approach
### 2.1. Backend (Spring Boot)
- Implement RESTful APIs for comprehensive notes management (create, read, update, delete).
- Utilize Spring Data JPA for efficient and robust database interactions.
- Incorporate proper error handling, input validation, and security measures.
- Adhere strictly to the existing Spring Boot project structure, coding conventions, and best practices found in `develop_a_working_20251006_065420-develop_a_working_20251006_065420-b-b/authsec_springboot/backend/`.
### 2.2. Frontend (Angular Clarity)
- Develop a responsive and intuitive user interface for displaying, adding, editing, and deleting notes.
- Leverage Angular components, services, and routing for modularity and navigation.
- Seamlessly integrate with the backend APIs to fetch and persist notes data.
- Strictly follow the Clarity Design System guidelines for UI/UX consistency and accessibility.
- Maintain the existing Angular project structure and coding conventions found in `develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f/authsec_angular/frontend/angular-clarity-master/`.
### 2.3. Database (MySQL)
- Define a clear and efficient database schema for storing notes, including fields such as `id`, `title`, `content`, `created_at`, and `updated_at`.
- Utilize existing database migration mechanisms (e.g., `schema.sql`, `data.sql` if present, or implement Flyway/Liquibase if necessary) for managing schema changes.
## 3. Technology Stack Implementation Strategy
- **Backend (Spring Boot):** Maximize the use of Spring Boot's auto-configuration, dependency injection, and starter projects. Emphasize a clean architecture with clear separation of concerns (controllers, services, repositories, entities, DTOs).
- **Frontend (Angular Clarity):** Utilize the Angular CLI for efficient component, service, and module generation. Implement reactive forms for robust user input handling. Use Angular's `HttpClient` module for all API communications.
- **Database (MySQL):** Ensure proper indexing and relationships are defined in the database schema to optimize data retrieval and manipulation performance.
## 4. Code Organization and Structure Framework
### 4.1. Backend (`develop_a_working_20251006_065420-develop_a_working_20251006_065420-b-b/authsec_springboot/backend/src/main/java/com/realnet/develop_a_working_20251006_065420-b/`)
- `controller/`: Contains RESTful API endpoints for handling HTTP requests related to notes.
- `model/`: Defines JPA entities representing the `Note` data structure in the database.
- `repository/`: Houses Spring Data JPA interfaces for database access and persistence operations.
- `service/`: Implements the core business logic for notes management.
- `dto/`: Data Transfer Objects for efficient and structured communication between the API and the frontend.
### 4.2. Frontend (`develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f/authsec_angular/frontend/angular-clarity-master/src/app/`)
- `components/`: Reusable UI components (e.g., `NoteListComponent`, `NoteDetailComponent`, `NoteFormComponent`).
- `services/`: Angular services responsible for interacting with the backend APIs and managing frontend data state (e.g., `NoteService`).
- `models/`: TypeScript interfaces defining the structure of `Note` objects.
- `modules/`: Feature-specific Angular modules (e.g., `NotesModule`) to encapsulate related components, services, and routing.
- `routing/`: Configuration for Angular's client-side routing.
## 5. Customized Development Workflow
1. **Understand Task:** Begin by thoroughly reading the current main task and its associated subtasks from the `.sureai/tasks_list.md` file.
2. **Break Down (if needed):** If a subtask is complex, further break it down into smaller, more granular implementation steps.
3. **Implement Backend Components:** Create or update the necessary Spring Boot entities, repositories, services, and controllers for notes functionality within the designated backend directory. Ensure any required database schema updates are handled.
4. **Implement Frontend Components:** Create or update Angular components, services, and models for notes within the designated frontend directory. Implement UI logic and integrate with the newly developed backend APIs.
5. **Run Language-Specific Checks:** After implementing code for a subtask, run relevant syntax/static checks (e.g., `npx -y tsc --noEmit` for TypeScript, Maven compile for Java).
6. **Update `tasks_list.md`:** Mark the completed subtask with `- [x]` and update the "Currently Working On" and "Completed Tasks" sections in `.sureai/tasks_list.md`.
7. **Main Task Testing:** Once all subtasks for a main task are complete, perform comprehensive testing for that entire main task. This includes unit tests, integration tests (if applicable), and verifying the functionality as a whole. Append ` — TEST: PASS` or ` — TEST: FAIL` to the main task header in `tasks_list.md`.
8. **Application Smoke Test (Final Task):** After all main tasks are successfully completed and tested, execute a full application smoke test. This involves verifying the project file structure, installing all dependencies (backend and frontend), starting both the backend and frontend servers, and ensuring both processes run without errors. If errors occur, diagnose, fix, and re-run the smoke test until it passes.

View File

@ -0,0 +1,196 @@
# Detailed Project Directory Structure (tree -a -L 3 --dirsfirst output)
.
├── .git
│   ├── branches
│   ├── hooks
│   │   ├── applypatch-msg.sample
│   │   ├── commit-msg.sample
│   │   ├── fsmonitor-watchman.sample
│   │   ├── post-update.sample
│   │   ├── pre-applypatch.sample
│   │   ├── pre-commit.sample
│   │   ├── pre-merge-commit.sample
│   │   ├── pre-push.sample
│   │   ├── pre-rebase.sample
│   │   ├── pre-receive.sample
│   │   ├── prepare-commit-msg.sample
│   │   ├── push-to-checkout.sample
│   │   ├── sendemail-validate.sample
│   │   └── update.sample
│   ├── info
│   │   └── exclude
│   ├── logs
│   │   ├── refs
│   │   └── HEAD
│   ├── objects
│   │   ├── 00
│   │   ├── 01
│   │   ├── 03
│   │   ├── 06
│   │   ├── 07
│   │   ├── 0b
│   │   ├── 0c
│   │   ├── 10
│   │   ├── 13
│   │   ├── 16
│   │   ├── 17
│   │   ├── 1c
│   │   ├── 1d
│   │   ├── 21
│   │   ├── 22
│   │   ├── 23
│   │   ├── 25
│   │   ├── 26
│   │   ├── 2a
│   │   ├── 2b
│   │   ├── 2c
│   │   ├── 2d
│   │   ├── 2e
│   │   ├── 2f
│   │   ├── 34
│   │   ├── 35
│   │   ├── 38
│   │   ├── 3c
│   │   ├── 3e
│   │   ├── 40
│   │   ├── 42
│   │   ├── 43
│   │   ├── 44
│   │   ├── 45
│   │   ├── 46
│   │   ├── 4a
│   │   ├── 4b
│   │   ├── 4e
│   │   ├── 4f
│   │   ├── 52
│   │   ├── 53
│   │   ├── 55
│   │   ├── 58
│   │   ├── 5a
│   │   ├── 63
│   │   ├── 64
│   │   ├── 68
│   │   ├── 69
│   │   ├── 6a
│   │   ├── 6c
│   │   ├── 6d
│   │   ├── 6f
│   │   ├── 70
│   │   ├── 71
│   │   ├── 72
│   │   ├── 74
│   │   ├── 7b
│   │   ├── 7e
│   │   ├── 7f
│   │   ├── 82
│   │   ├── 8d
│   │   ├── 8e
│   │   ├── 90
│   │   ├── 91
│   │   ├── 95
│   │   ├── 96
│   │   ├── 9a
│   │   ├── 9b
│   │   ├── 9f
│   │   ├── a0
│   │   ├── a7
│   │   ├── a9
│   │   ├── aa
│   │   ├── ad
│   │   ├── b2
│   │   ├── b4
│   │   ├── b8
│   │   ├── b9
│   │   ├── ba
│   │   ├── bb
│   │   ├── bd
│   │   ├── be
│   │   ├── bf
│   │   ├── c0
│   │   ├── c1
│   │   ├── c3
│   │   ├── c5
│   │   ├── c6
│   │   ├── c8
│   │   ├── c9
│   │   ├── cc
│   │   ├── ce
│   │   ├── cf
│   │   ├── d0
│   │   ├── d1
│   │   ├── d5
│   │   ├── db
│   │   ├── de
│   │   ├── e2
│   │   ├── e3
│   │   ├── e7
│   │   ├── e8
│   │   ├── e9
│   │   ├── ee
│   │   ├── f0
│   │   ├── f3
│   │   ├── f6
│   │   ├── f7
│   │   ├── f8
│   │   ├── ff
│   │   ├── info
│   │   └── pack
│   ├── refs
│   │   ├── heads
│   │   ├── remotes
│   │   └── tags
│   ├── COMMIT_EDITMSG
│   ├── FETCH_HEAD
│   ├── HEAD
│   ├── ORIG_HEAD
│   ├── config
│   ├── description
│   └── index
├── .io8project
│   ├── .state.json
│   └── project_metadata.json
├── .sureai
│   ├── io8_mcp
│   │   └── responses
│   ├── uploads
│   ├── .code_tree.txt
│   ├── .developer_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .directory_structure.txt
│   ├── .directory_structure_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8analyst_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8architect_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8codermaster_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8pm_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── .io8project_builder_develop_a_working_20251006_065420.md
│   ├── .sm_agent_develop_a_working_develop_a_working_20251006_065420.md
│   ├── sprint_plan.md
│   └── tasks_list.md
├── backend
│   └── .gitkeep
├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-b-b
│   └── authsec_springboot
│   ├── backend
│   └── .gitignore
├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-d-d
│   └── authsec_mysql
│   └── mysql
├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f
│   └── authsec_angular
│   └── frontend
├── frontend
│   └── .gitkeep
├── sureops
│   ├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-b-b
│   │   └── deployment
│   ├── develop_a_working_20251006_065420-develop_a_working_20251006_065420-d-d
│   │   └── deployment
│   └── develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f
│   └── deployment
├── Dockerfile.backend
├── Dockerfile.frontend
├── deployment_config.yml
├── docker-compose.yml
└── nginx.conf
147 directories, 45 files

View File

@ -0,0 +1,41 @@
# Scrum Master Agent Prompt: Develop a Working Notes App
## Persona
- **Role:** Agile Process Facilitator & Team Coach
- **Style:** Servant-leader, observant, facilitative, communicative, supportive, and proactive.
## Project Context: Develop a Working Notes App
This project aims to develop a functional notes application. The Scrum Master will guide the development team through agile processes, ensuring efficient delivery and adherence to project goals.
## Task Planning Methodology
1. **Initial Backlog Creation:** Based on the PRD (`prd_document.md`) and Project Plan (`project_plan.md`), create a high-level `tasks_list.md` focusing on core features of a notes app (e.g., creating notes, viewing notes, editing notes, deleting notes, user authentication).
2. **Feature Inventory Review:** Before creating any new tasks, meticulously review the existing frontend and backend feature inventories:
- `/tmp/bmad_output/develop_a_working_20251006_065420/develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f/authsec_angular/frontend/angular-clarity-master/README.txt`
- `/tmp/bmad_output/develop_a_working_20251006_065420/develop_a_working_20251006_065420-develop_a_working_20251006_065420-b-b/authsec_springboot/backend/README.txt`
- **CRITICAL:** Only create tasks for features *not* already present in these READMEs. If a feature (e.g., CRUD for notes) is already documented, focus on integration or enhancement tasks, not re-building.
3. **Task Granularity:** Tasks in `tasks_list.md` should be high-level epics, not granular subtasks. Subtasks will be handled by the Developer agent.
4. **Task Tagging:** Each task must be tagged with `[FRONTEND]`, `[BACKEND]`, or `[FULL-STACK]` based on its primary area of work.
5. **No DevOps Tasks:** Exclude all DevOps, deployment, or infrastructure-related tasks. These are owned by the DevOps agent.
## Sprint Planning Approach
1. **Sprint Goal Definition:** Work with the team to define clear, achievable sprint goals based on the prioritized tasks.
2. **Task Prioritization:** Prioritize tasks from `tasks_list.md` based on business value and dependencies.
3. **Capacity Planning:** Ensure the team commits to a realistic amount of work for each sprint.
4. **Daily Scrums:** Facilitate daily stand-ups to track progress, identify impediments, and adjust plans.
## Task Breakdown Framework
- The Scrum Master will define the main tasks (epics).
- The Developer agent will be responsible for breaking down these main tasks into smaller, actionable subtasks during sprint planning or as needed.
## Agile Methodology Considerations
- **Iterative Development:** Emphasize short development cycles with continuous feedback.
- **Transparency:** Maintain visibility of project progress, impediments, and decisions.
- **Adaptability:** Be prepared to adjust plans based on new information or changing requirements.
- **Continuous Improvement:** Encourage regular retrospectives to identify areas for process improvement.
## Customized Scrum Master Workflow for "Develop a Working Notes App"
1. **Review PM Outputs:** Read `/tmp/bmad_output/develop_a_working_20251006_065420/develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f/authsec_angular/frontend/angular-clarity-master/.sureai/prd_document.md` and `/tmp/bmad_output/develop_a_working_20251006_065420/develop_a_working_20251006_065420-develop_a_working_20251006_065420-f-f/authsec_angular/frontend/angular-clarity-master/.sureai/project_plan.md` to understand the overall scope and high-level requirements for the notes app.
2. **Analyze Existing Features:** Read the `README.txt` files for both frontend and backend components to understand what functionalities are already implemented.
3. **Draft `tasks_list.md`:** Create or update the `.sureai/tasks_list.md` file with high-level development tasks, ensuring no duplication of existing features and proper tagging.
4. **Define Current/Next Tasks:** Clearly specify the "Currently Working On" and "Next Task" sections in `tasks_list.md`.
5. **Handover:** Once `tasks_list.md` is complete, hand over to the Developer agent for detailed task breakdown and implementation.

31
.sureai/sprint_plan.md Normal file
View File

@ -0,0 +1,31 @@
# Sprint Plan - Working Notes Application - Sprint 1
## Sprint Goal
Establish a secure and functional user authentication system and lay the groundwork for core note management by implementing the backend API for notes.
## Sprint Duration
2 weeks (e.g., October 6, 2025 - October 17, 2025)
## Prioritized Tasks (from Project Tasks List)
### Task 1: Implement Backend User Authentication & Account Management [BACKEND]
Develop the FastAPI backend endpoints and logic for user registration, login, and password reset initiation. This includes secure password hashing, JWT token generation, and user data storage in PostgreSQL. This task will integrate with the existing frontend authentication UI.
### Task 2: Integrate Frontend with Backend Authentication [FRONTEND]
Connect the existing Angular Clarity frontend's login, registration, and password reset UIs to the new FastAPI backend authentication endpoints. Ensure proper handling of JWT tokens for authenticated sessions.
## Team Capacity
[To be determined by the development team]
## Definition of Done
- All code is written, reviewed, and merged into the main branch.
- Unit and integration tests are written and passing.
- Functionality is deployed to a development/staging environment.
- All acceptance criteria for the user stories covered in the sprint are met.
- Documentation (API, code comments) is updated as necessary.
## Impediments
[None currently identified]
## Notes
This sprint focuses on critical foundational elements. Subsequent sprints will build upon this to complete the core note management features.

92
.sureai/tasks_list.md Normal file
View File

@ -0,0 +1,92 @@
# Project Tasks List
## Task 1: Implement Backend User Authentication & Account Management [BACKEND] — TEST: FAIL
Set up the basic project structure and environment.
### 1.1 Define User Entity and Repository
- [x] Create `User` entity with fields: `id`, `username`, `email`, `passwordHash`, `createdAt`, `updatedAt`.
- [x] Create `UserRepository` for database interactions.
### 1.2 Implement User Registration
- [x] Create `RegisterRequest` DTO.
- [x] Create `AuthResponse` DTO.
- [x] Add registration method to `AuthController`.
- [x] Implement password hashing in `UserService`.
- [x] Save user to `UserRepository`.
### 1.3 Implement User Login
- [x] Create `LoginRequest` DTO.
- [x] Add login method to `AuthController`.
- [x] Validate user credentials in `UserService`.
- [x] Generate JWT token upon successful login in `UserService`.
### 1.4 Implement Password Reset Initiation
- [x] Create `PasswordResetRequest` DTO.
- [x] Add password reset initiation method to `AuthController`.
- [x] Generate a unique password reset token in `UserService`.
- [x] Store the reset token with an expiration time in the database.
- [x] Send password reset email to the user (placeholder for now).
### 1.5 Configure Security (JWT)
- [x] Configure Spring Security for JWT authentication.
- [x] Create `JwtAuthenticationFilter` to validate tokens.
- [x] Create `JwtTokenProvider` to generate and validate JWTs.
### 1.6 Database Schema Update
- [x] Update `schema.sql` or use JPA to generate/update user table schema.
## Task 2: Integrate Frontend with Backend Authentication [FRONTEND]
Connect the existing Angular Clarity frontend's login, registration, and password reset UIs to the new Spring Boot backend authentication endpoints. Ensure proper handling of JWT tokens for authenticated sessions.
### 2.1 Update API Service for Authentication
- [x] Create/update an authentication service to handle API calls for register, login, and password reset.
- [x] Implement methods for storing and retrieving JWT tokens.
### 2.2 Integrate Login Component
- [x] Modify the login component to use the authentication service.
- [x] Handle successful login (store token, redirect).
- [x] Display error messages for failed login.
### 2.3 Integrate Registration Component
- [x] Create `register-page` directory and `register-page.component.ts`, `register-page.component.html`, `register-page.component.scss` files.
- [x] Update `login.module.ts` to declare and import `RegisterPageComponent`.
- [x] Update `login-routing.module.ts` to add a route for `/register`.
- [x] Implement `RegisterPageComponent` to inject `AuthService`.
- [x] Implement the registration method in `RegisterPageComponent` to call `AuthService.register`.
- [x] Handle successful registration by redirecting to the login page.
- [x] Handle registration errors and display appropriate messages in `RegisterPageComponent`.
- [x] Create `register-page.component.html` to bind form fields and display error messages.
### 2.4 Integrate Password Reset Components
- [x] Locate and read the password reset initiation component files.
- [x] Update the password reset initiation component to inject `AuthService`.
- [x] Implement the password reset initiation method to call `AuthService.initiatePasswordReset`.
- [x] Handle successful initiation by displaying a success message.
- [x] Handle initiation errors and display appropriate messages.
- [x] Update the password reset initiation component's HTML to bind form fields and display error messages.
### 2.5 Implement JWT Interceptor
- [ ] Create `jwt.interceptor.ts` to attach JWT token to outgoing requests.
- [ ] Register the interceptor in `app.module.ts`.
### 2.6 Update Routing Guards
- [ ] Modify existing routing guards to check for JWT token validity.
### 2.7 Handle Token Expiration/Refresh
- [ ] Add logic to `AuthService` to handle token expiration.
- [ ] Implement token refresh mechanism (if applicable).
### 2.8 Display User-Specific Content
- [ ] Add a placeholder in a component to display user info if authenticated.
## Current Task Status
**Currently Working On:** Task 2.5 - Implement JWT Interceptor
**Next Task:** Task 2.6 - Update Routing Guards
**Completed Tasks:** Task 1 - Implement Backend User Authentication & Account Management [BACKEND], Task 2.1 - Update API Service for Authentication, Task 2.2 - Integrate Login Component, Task 2.3 - Integrate Registration Component, Task 2.4 - Integrate Password Reset Components
## Task Completion Guidelines
- Use `- [x]` to mark completed subtasks (to be added by Developer)
- Use `- [ ]` for pending subtasks (to be added by Developer)
- Update "Currently Working On" when starting a new subtask (to be managed by Developer)
- Update "Completed Tasks" when finishing a task (to be managed by Developer)
- Always maintain the hierarchical structure (Task → Subtask → Subtask items)
- **IMPORTANT: Do NOT add subtasks here. Only create main tasks. Subtasks will be added by the Developer agent.

View File

@ -0,0 +1,43 @@
package com.realnet.auth.controller;
import com.realnet.auth.dto.AuthResponse;
import com.realnet.auth.dto.RegisterRequest;
import com.realnet.auth.dto.LoginRequest;
import com.realnet.auth.dto.PasswordResetRequest;
import com.realnet.auth.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {
private final UserService userService;
@PostMapping("/register")
public ResponseEntity<AuthResponse> register(
@RequestBody RegisterRequest request
) {
return ResponseEntity.ok(userService.register(request));
}
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(
@RequestBody LoginRequest request
) {
return ResponseEntity.ok(userService.login(request));
}
@PostMapping("/password-reset")
public ResponseEntity<String> initiatePasswordReset(
@RequestBody PasswordResetRequest request
) {
userService.initiatePasswordReset(request);
return ResponseEntity.ok("Password reset initiated. Check your email.");
}
}

View File

@ -0,0 +1,14 @@
package com.realnet.auth.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthResponse {
private String token;
}

View File

@ -0,0 +1,15 @@
package com.realnet.auth.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
private String email;
private String password;
}

View File

@ -0,0 +1,14 @@
package com.realnet.auth.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PasswordResetRequest {
private String email;
}

View File

@ -0,0 +1,16 @@
package com.realnet.auth.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RegisterRequest {
private String username;
private String email;
private String password;
}

View File

@ -0,0 +1,26 @@
package com.realnet.auth.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "password_reset_tokens")
public class PasswordResetToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String token;
@OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
@JoinColumn(nullable = false, name = "user_id")
private User user;
private LocalDateTime expiryDate;
}

View File

@ -0,0 +1,71 @@
package com.realnet.auth.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "app_users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String passwordHash;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_USER")); // Default role for now
}
@Override
public String getPassword() {
return passwordHash;
}
@Override
public String getUsername() {
return email; // Using email as username for Spring Security
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@ -0,0 +1,9 @@
package com.realnet.auth.repository;
import com.realnet.auth.entity.PasswordResetToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
}

View File

@ -0,0 +1,12 @@
package com.realnet.auth.repository;
import com.realnet.auth.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}

View File

@ -0,0 +1,83 @@
package com.realnet.auth.service;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
public class JwtService {
@Value("${application.security.jwt.secret-key}")
private String secretKey;
@Value("${application.security.jwt.expiration}")
private long jwtExpiration;
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
public String generateToken(
Map<String, Object> extraClaims,
UserDetails userDetails
) {
return Jwts
.builder()
.setClaims(extraClaims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
.compact();
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
public String getEmailFromToken(String token) {
return extractClaim(token, io.jsonwebtoken.Claims::getSubject);
}
public <T> T extractClaim(String token, java.util.function.Function<io.jsonwebtoken.Claims, T> claimsResolver) {
final io.jsonwebtoken.Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private io.jsonwebtoken.Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getEmailFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, io.jsonwebtoken.Claims::getExpiration);
}
public org.springframework.security.authentication.UsernamePasswordAuthenticationToken getAuthentication(String token,
org.springframework.security.core.Authentication authentication, UserDetails userDetails) {
return new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
}
}

View File

@ -0,0 +1,66 @@
package com.realnet.auth.service;
import com.realnet.auth.dto.AuthResponse;
import com.realnet.auth.dto.LoginRequest;
import com.realnet.auth.dto.PasswordResetRequest;
import com.realnet.auth.dto.RegisterRequest;
import com.realnet.auth.entity.PasswordResetToken;
import com.realnet.auth.entity.User;
import com.realnet.auth.repository.PasswordResetTokenRepository;
import com.realnet.auth.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final PasswordResetTokenRepository passwordResetTokenRepository;
public AuthResponse register(RegisterRequest request) {
var user = User.builder()
.username(request.getUsername())
.email(request.getEmail())
.passwordHash(passwordEncoder.encode(request.getPassword()))
.build();
userRepository.save(user);
var jwtToken = jwtService.generateToken(user);
return AuthResponse.builder().token(jwtToken).build();
}
public AuthResponse login(LoginRequest request) {
var user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new RuntimeException("User not found")); // TODO: Custom exception
if (!passwordEncoder.matches(request.getPassword(), user.getPasswordHash())) {
throw new RuntimeException("Invalid credentials"); // TODO: Custom exception
}
var jwtToken = jwtService.generateToken(user);
return AuthResponse.builder().token(jwtToken).build();
}
public void initiatePasswordReset(PasswordResetRequest request) {
User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new RuntimeException("User not found")); // TODO: Custom exception
String token = UUID.randomUUID().toString();
PasswordResetToken resetToken = PasswordResetToken.builder()
.token(token)
.user(user)
.expiryDate(LocalDateTime.now().plusMinutes(30)) // Token valid for 30 minutes
.build();
passwordResetTokenRepository.save(resetToken);
// Placeholder for sending password reset email
// In a real application, an email service would be used here to send a link
// containing the token to the user's email address.
}
}

View File

@ -22,8 +22,8 @@ import org.springframework.web.filter.OncePerRequestFilter;
import com.realnet.logging.NoLogging;
import com.realnet.session.Service.TokenBlacklistService;
import com.realnet.users.entity1.AppUser;
import com.realnet.users.service1.AppUserServiceImpl;
import com.realnet.auth.entity.User;
import com.realnet.auth.service.UserService;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
@ -36,16 +36,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private UserDetailsService userDetailsService;
@Autowired
private TokenProvider jwtTokenUtil;
private com.realnet.auth.service.JwtService jwtService;
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/path/to/allow").permitAll()// allow
// CORS
// option
// calls
.antMatchers("/resources/**").permitAll().anyRequest().authenticated().and().formLogin().and()
.httpBasic();
}
// // prevoius it also working
@ -99,7 +92,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
// by gk, for token expire
@Autowired
private AppUserServiceImpl userService;
private UserService userService;
@Autowired
private TokenBlacklistService tokenBlacklistService;
@ -117,7 +110,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
authToken = header.replace(JWTConstant.TOKEN_PREFIX, "");
try {
email = jwtTokenUtil.getEmailFromToken(authToken); // Extract the email from the token
email = jwtService.getEmailFromToken(authToken); // Extract the email from the token
} catch (IllegalArgumentException e) {
logger.error("An error occurred while getting the username from the token", e);
} catch (ExpiredJwtException e) {
@ -145,7 +138,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
}
// Step 2: Check if the user is deactivated
AppUser user = userService.findUserByEmail(email); // Assuming you have a method to find user by email
User user = userService.findByEmail(email).orElse(null); // Assuming you have a method to find user by email
if (user == null || !user.isActive()) { // Check if the user is deactivated
logger.warn("User is deactivated or not found: " + email);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // Respond with unauthorized
@ -156,10 +149,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
// Step 3: Proceed with user validation if not blacklisted or deactivated
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
// Create an authentication token
UsernamePasswordAuthenticationToken authentication = jwtTokenUtil.getAuthentication(authToken,
SecurityContextHolder.getContext().getAuthentication(), userDetails);
if (jwtService.validateToken(authToken, userDetails)) {
// Create an authentication token
UsernamePasswordAuthenticationToken authentication = jwtService.getAuthentication(authToken, SecurityContextHolder.getContext().getAuthentication(), userDetails);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
logger.debug("Authenticated user " + email + ", setting security context");

View File

@ -125,18 +125,16 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.csrf(csrf -> csrf.disable())
// Add CORS Filter //http.cors().and().csrf().disable().
.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class)
.authorizeRequests(requests -> requests.antMatchers("/token/**").permitAll().antMatchers("/log2/**")
.permitAll().antMatchers("/api/**").permitAll()
.authorizeRequests(requests -> requests
.antMatchers("/api/v1/auth/**").permitAll() // Allow authentication endpoints
.antMatchers("/token/**", "/log2/**").permitAll() // Keep existing public paths if needed
.anyRequest().authenticated()) // Secure all other requests
// .antMatchers("/SqlworkbenchSqlcont/**").hasRole("ADMIN")
.anyRequest().authenticated())
.exceptionHandling(handling -> handling.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.ALWAYS) // Ensure
// sessions
// are
// always created
.maximumSessions(-1).sessionRegistry(sessionRegistry()));
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
.sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.and()
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
@Bean

View File

@ -0,0 +1,32 @@
package com.realnet.notes.dto;
public class RegisterRequest {
private String username;
private String email;
private String password;
// Getters and Setters
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,28 @@
package com.realnet.notes.dto;
public class RegisterResponse {
private String message;
private Long userId;
public RegisterResponse(String message, Long userId) {
this.message = message;
this.userId = userId;
}
// Getters and Setters
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}

View File

@ -0,0 +1,89 @@
package com.realnet.notes.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;
@Entity
@Table(name = "app_users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String passwordHash;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private String passwordResetToken;
private LocalDateTime passwordResetTokenExpiry;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void voidsetEmail(String email) {
this.email = email;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(String passwordHash) {
this.passwordHash = passwordHash;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String getPasswordResetToken() {
return passwordResetToken;
}
public void setPasswordResetToken(String passwordResetToken) {
this.passwordResetToken = passwordResetToken;
}
public LocalDateTime getPasswordResetTokenExpiry() {
return passwordResetTokenExpiry;
}
public void setPasswordResetTokenExpiry(LocalDateTime passwordResetTokenExpiry) {
this.passwordResetTokenExpiry = passwordResetTokenExpiry;
}
}

View File

@ -0,0 +1,14 @@
package com.realnet.notes.repository;
import com.realnet.notes.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
Optional<User> findByPasswordResetToken(String passwordResetToken);
}

View File

@ -8,29 +8,22 @@ CREATE SCHEMA northwind;
USE northwind;
/* Table: user (Application Users) */
CREATE TABLE user (
user_id NVARCHAR(20) NOT NULL,
password NVARCHAR(255) NOT NULL,
first_name NVARCHAR(50) ,
last_name NVARCHAR(50) ,
email NVARCHAR(70) ,
security_provider_id INT ,
default_customer_id INT ,
company NVARCHAR(50) ,
phone NVARCHAR(20) ,
address1 NVARCHAR(100),
address2 NVARCHAR(100),
country NVARCHAR(20) ,
postal NVARCHAR(20) ,
role NVARCHAR(20) ,
other_roles NVARCHAR(80) ,
is_active TINYINT ,
is_blocked TINYINT ,
secret_question NVARCHAR(100),
secret_answer NVARCHAR(100),
enable_beta_testing TINYINT,
enable_renewal TINYINT,
CONSTRAINT user_id PRIMARY KEY(user_id)
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at DATETIME,
updated_at DATETIME
);
/* Table: password_reset_token */
CREATE TABLE password_reset_token (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
token VARCHAR(255) NOT NULL UNIQUE,
user_id BIGINT NOT NULL,
expiry_date DATETIME NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
/* Table: customers */

View File

@ -0,0 +1,18 @@
export interface RegisterRequest {
username: string;
email: string;
password: string;
}
export interface LoginRequest {
email: string;
password: string;
}
export interface PasswordResetRequest {
email: string;
}
export interface AuthResponse {
token: string;
}

View File

@ -21,14 +21,15 @@
Service</u></p><br>
<form class="form" [formGroup]="emailCheckForm">
<input class="form__field" type="text" placeholder="email@company.com" formControlName="email"/>
<div *ngIf="emailCheckForm.controls['email'].invalid && emailCheckForm.controls['email'].touched" style="color:indianred; font-weight: bold">Please enter a valid email.</div>
<div *ngIf="emailErrMsg" style="color:indianred; font-weight: bold">{{emailErrMsg}}</div>
<br><br>
<button type="submit"class="btn btn--primary btn--inside uppercase" (click)="onsubmit()">Send Me Access Link</button>
<button type="submit"class="btn btn--primary btn--inside uppercase" (click)="onsubmit()" [disabled]="emailCheckForm.invalid">Send Me Access Link</button>
</form>
<br>
<p *ngIf="emailsend" style="color: red;"><clr-icon shape="check"></clr-icon> Email Is send Please Check Your Mail </p>
<p *ngIf="emailsend" style="color: green;"><clr-icon shape="check"></clr-icon> Email Is send Please Check Your Mail </p>
</div>
</div>
</clr-main-container>

View File

@ -3,7 +3,9 @@ import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import {ForgotpassService} from '../../../services/api/forgotpass.service';
import { AuthService } from '../../../services/auth.service'; // Use AuthService
import { PasswordResetRequest } from '../../../models/auth/auth.model'; // Import PasswordResetRequest DTO
@Component({
selector: 'app-forgotpassword',
templateUrl: './forgotpassword.component.html',
@ -17,49 +19,46 @@ emailsend;
private router: Router,
private route:ActivatedRoute,
private toastr:ToastrService,
private forgotpassservice:ForgotpassService) { }
private authService: AuthService) { } // Inject AuthService
ngOnInit(): void {
this.emailCheckForm = this._fb.group({
email: ['', Validators.email]
email: ['', [Validators.required, Validators.email]]
});
}
onsubmit(){
let email = this.emailCheckForm.value.email;
console.log(email);
this.forgotpassservice.sendemail(email).subscribe((data)=>{
this.forgotpassservice.storeEmail(email);
console.log(data);
// if(data=200){
// this.toastr.success('Email Send successfully');
// }
},
(err: HttpErrorResponse) => {
console.log(err)
if (err.status === 200) {
this.emailsend=err.status;
//this.emailErrMsg = 'Email send please check mail';
}
if (this.emailCheckForm.invalid) {
return;
}
const request: PasswordResetRequest = { email: this.emailCheckForm.value.email };
this.authService.initiatePasswordReset(request).subscribe(
(response) => {
this.emailsend = true; // Indicate success
this.toastr.success('Password reset link sent to your email.', 'Success');
// Optionally redirect or show a message
},
(errResponse: HttpErrorResponse) => {
this.emailsend = false; // Indicate failure
switch (errResponse.status) {
case 400:
this.emailErrMsg = 'Invalid email address or user not found.';
break;
case 500:
this.emailErrMsg = 'Internal Server Error';
break;
default:
this.emailErrMsg = 'Failed to send password reset link.';
break;
}
this.toastr.error(this.emailErrMsg, 'Password Reset Failed');
}
);
this.emailCheckForm.reset();
// this.emailCheckForm.reset(); // Don't reset immediately, let user see error if any
}
onSubmit() {
let email = this.emailCheckForm.value.email;
console.log(email);
this.forgotpassservice.sendemail(email).subscribe((res) => {
this.forgotpassservice.storeEmail(email);
//this.router.navigate(["/varify-account"])
}, (err: HttpErrorResponse) => {
console.log(err)
if (err.status === 409) {
this.emailErrMsg = 'Email Already Exists';
} else {
this.emailErrMsg = 'Server error';
}
})
// This method seems to be a duplicate of onsubmit, removing it.
// The primary onsubmit method will handle the logic.
}
gotoreset(){
this.router.navigate(["../forgotresetpassword"], { relativeTo: this.route });

View File

@ -96,7 +96,7 @@
</clr-checkbox-wrapper>
<div class="error active" *ngIf="isError">
Invalid user name or password
{{ errMsg }}
</div>
<button [disabled]="!model.email || !model.password" type="submit" class="btn btn-primary"

View File

@ -1,13 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { LoginService } from '../../../services/api/login.service';
import { ActivatedRoute} from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import{environment} from 'src/environments/environment';
// import { ExtendedLoginEnvironment, LoginEnvironment } from './login_environment';
//import { UserRegistrationService } from 'src/app/services/api/user-registration.service';
import { environment } from 'src/environments/environment';
import { LoginEnvironment } from './login_environment';
import { AuthService } from '../../services/auth.service'; // Import the new AuthService
import { LoginRequest } from '../../models/auth/auth.model'; // Import LoginRequest DTO
@Component({
@ -42,17 +41,17 @@ export class LoginPageComponent implements OnInit {
password = '';
isError = false;
model: any = {};
model: LoginRequest = { email: '', password: '' }; // Initialize with LoginRequest DTO
errMsg: string = '';
constructor(
private router: Router,
private route:ActivatedRoute,
private loginService: LoginService,
private authService: AuthService, // Use AuthService
private toastr: ToastrService,
) { }
ngOnInit() {
this.loginService.logout(false);
this.authService.removeToken(); // Ensure no old token is present
this.loginEnvironment["imagePath"] = !this.loginEnvironment.loginImageURL ? "../../../../assets/images/new.png" : this.loginEnvironment.loginImageURL;
@ -71,29 +70,39 @@ export class LoginPageComponent implements OnInit {
}
onLogin() {
// tslint:disable-next-line:max-line-length
this.loginService.getToken(this.model.email, this.model.password)
this.isError = false; // Reset error state
this.errMsg = ''; // Clear previous error message
this.authService.login(this.model)
.subscribe(resp => {
if (resp.user === undefined || resp.user.token === undefined || resp.user.token === "INVALID") {
this.errMsg = 'Checking Email or password';
return;
if (resp.token) {
this.authService.saveToken(resp.token);
this.router.navigate(['/main']); // Redirect to main dashboard or home page
} else {
this.isError = true;
this.errMsg = 'Login failed: No token received.';
}
this.router.navigate([resp.landingPage]);// add , {skipLocationChange: true}
},
(errResponse: HttpErrorResponse) => {
this.isError = true; // Set error state to true
switch (errResponse.status) {
case 401:
this.errMsg = 'Email or password is incorrect';
break;
case 404:
this.errMsg = 'Service not found';
break;
case 408:
this.errMsg = 'Request Timedout';
break;
case 500:
this.errMsg = 'Internal Server Error';
break;
default:
this.errMsg = 'Server Error';
break;
}
this.toastr.error(this.errMsg, 'Login Failed'); // Display error using Toastr
}
);

View File

@ -8,9 +8,11 @@ import { Forgotresetpassword1Component } from './forgotresetpassword1/forgotrese
import { LoginPageComponent } from './login-page/login-page.component';
import { EmailverificationComponent } from './emailverification/emailverification.component';
import { AboutWorkComponent } from './about-work/about-work.component';
import { RegisterPageComponent } from './register-page/register-page.component';
const routes: Routes = [
{ path: 'login', component: LoginPageComponent },
{ path: 'register', component: RegisterPageComponent },
{path: 'forgotpass', component:ForgotpasswordComponent},
{path:'forgotresetpassword/:id', component:ForgotresetpasswordComponent},
{path:'adduser/:id', component:Forgotresetpassword1Component},

View File

@ -14,10 +14,11 @@ import { AddguestComponent } from './addguest/addguest.component';
import { EmailverificationComponent } from './emailverification/emailverification.component';
import { AboutWorkComponent } from './about-work/about-work.component';
import { SanitizePipe } from 'src/app/pipes/sanitize.pipe';
import { RegisterPageComponent } from './register-page/register-page.component';
@NgModule({
declarations: [LoginPageComponent, ForgotpasswordComponent, ForgotresetpasswordComponent, Forgotresetpassword1Component, AddguestComponent,
EmailverificationComponent, AboutWorkComponent,SanitizePipe],
EmailverificationComponent, AboutWorkComponent,SanitizePipe, RegisterPageComponent],
imports: [
CommonModule,
FormsModule,

View File

@ -0,0 +1,33 @@
<div class="login-wrapper">
<form class="login">
<section class="title">
<h3 class="welcome">Register for Notes App</h3>
<h5 class="hint">Create your account to start managing notes.</h5>
</section>
<div class="login-group">
<clr-input-container>
<label class="clr-sr-only">Email</label>
<input type="email" name="email" clrInput autocomplete="off" [(ngModel)]="model.email"
placeholder="Email" required />
</clr-input-container>
<clr-input-container>
<label class="clr-sr-only">Username</label>
<input type="text" name="username" clrInput autocomplete="off" [(ngModel)]="model.username"
placeholder="Username" required />
</clr-input-container>
<clr-password-container>
<label class="clr-sr-only">Password</label>
<input type="password" name="password" clrPassword id="register_password" autocomplete="off" [(ngModel)]="model.password"
placeholder="Password" required />
</clr-password-container>
<div class="error active" *ngIf="isError">
{{ errMsg }}
</div>
<button [disabled]="!model.email || !model.password || !model.username" type="submit" class="btn btn-primary"
(click)="onRegister()">Register</button>
<a [routerLink]="['/login']" class="signup">Already have an account? Log in</a>
</div>
</form>
</div>

View File

@ -0,0 +1,68 @@
.login-wrapper {
background-size: cover;
background-position: center;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
width: 100%;
}
.login {
background: rgba(255, 255, 255, 0.9);
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
}
.title {
text-align: center;
margin-bottom: 1.5rem;
}
.welcome {
font-size: 1.8rem;
color: #333;
margin-bottom: 0.5rem;
}
.hint {
font-size: 0.9rem;
color: #666;
}
.login-group {
display: flex;
flex-direction: column;
gap: 1rem;
}
clr-input-container, clr-password-container {
width: 100%;
}
.error {
color: #e30000;
font-size: 0.85rem;
text-align: center;
}
.btn-primary {
width: 100%;
padding: 0.75rem;
font-size: 1rem;
}
.signup {
display: block;
text-align: center;
margin-top: 1rem;
color: #007cbb;
text-decoration: none;
}
.signup:hover {
text-decoration: underline;
}

View File

@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { AuthService } from '../../services/auth.service';
import { RegisterRequest } from '../../models/auth/auth.model';
@Component({
selector: 'app-register-page',
templateUrl: './register-page.component.html',
styleUrls: ['./register-page.component.scss']
})
export class RegisterPageComponent implements OnInit {
model: RegisterRequest = { email: '', password: '', username: '' };
isError = false;
errMsg: string = '';
constructor(
private router: Router,
private authService: AuthService,
private toastr: ToastrService
) { }
ngOnInit(): void {
}
onRegister(): void {
this.isError = false;
this.errMsg = '';
this.authService.register(this.model).subscribe(
resp => {
this.toastr.success('Registration successful! Please log in.', 'Success');
this.router.navigate(['/login']); // Redirect to login page after successful registration
},
(errResponse: HttpErrorResponse) => {
this.isError = true;
switch (errResponse.status) {
case 400:
this.errMsg = 'Registration failed: Invalid input or user already exists.';
break;
case 500:
this.errMsg = 'Internal Server Error';
break;
default:
this.errMsg = 'Registration failed: Server Error';
break;
}
this.toastr.error(this.errMsg, 'Registration Failed');
}
);
}
}

View File

@ -0,0 +1,44 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { RegisterRequest, LoginRequest, PasswordResetRequest, AuthResponse } from '../models/auth/auth.model';
import { environment } from '../../environments/environment';
const AUTH_API_URL = `${environment.apiUrl}/api/v1/auth`;
const TOKEN_KEY = 'auth-token';
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(private http: HttpClient) { }
register(request: RegisterRequest): Observable<AuthResponse> {
return this.http.post<AuthResponse>(`${AUTH_API_URL}/register`, request);
}
login(request: LoginRequest): Observable<AuthResponse> {
return this.http.post<AuthResponse>(`${AUTH_API_URL}/login`, request);
}
initiatePasswordReset(request: PasswordResetRequest): Observable<string> {
return this.http.post(`${AUTH_API_URL}/password-reset`, request, { responseType: 'text' });
}
saveToken(token: string): void {
localStorage.setItem(TOKEN_KEY, token);
}
getToken(): string | null {
return localStorage.getItem(TOKEN_KEY);
}
removeToken(): void {
localStorage.removeItem(TOKEN_KEY);
}
isLoggedIn(): boolean {
return !!this.getToken();
}
}

View File

@ -1,15 +1,15 @@
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { UserInfoService } from './user-info.service';
import { AuthService } from './auth.service'; // Assuming AuthService is in the same directory
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private userInfoService: UserInfoService) {}
constructor(private authService: AuthService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization header with jwt token if available
let currentUser = this.userInfoService.getUserInfo();
let currentUser = this.authService.currentUserValue;
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
@ -17,6 +17,7 @@ export class JwtInterceptor implements HttpInterceptor {
}
});
}
return next.handle(request);
}
}

View File

@ -3,7 +3,7 @@ export const environment = {
appName: 'My Application',
version: '11.2.13',
subVersion: '2021.05.13-01',
apiUrl: 'http://localhost:3000',
apiUrl: 'http://localhost:8080',
whiteUrl: 'http://localhost:3000',
blackUrl: 'http://localhost:3000/login',