Initial commit of io8 project
This commit is contained in:
parent
4ba91e7860
commit
ea55ce3756
56
.sureai/.code_tree.txt
Normal file
56
.sureai/.code_tree.txt
Normal 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
|
||||
@ -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.
|
||||
196
.sureai/.directory_structure.txt
Normal file
196
.sureai/.directory_structure.txt
Normal 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
|
||||
@ -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
31
.sureai/sprint_plan.md
Normal 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
92
.sureai/tasks_list.md
Normal 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.
|
||||
@ -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.");
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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> {
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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 */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
@ -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');
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user