Browse Source

restructuring and added tree service

Dr-Swopt 2 months ago
parent
commit
990ec9ed07

+ 44 - 74
README.md

@@ -1,98 +1,68 @@
-<p align="center">
-  <a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
-</p>
-
-[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
-[circleci-url]: https://circleci.com/gh/nestjs/nest
-
-  <p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
-    <p align="center">
-<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
-<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
-<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
-<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
-<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
-<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
-<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
-  <a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
-    <a href="https://opencollective.com/nest#sponsor"  target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
-  <a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
-</p>
-  <!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
-  [![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
-
-## Description
-
-[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
-
-## Project setup
+# 🔐 WebAuthn Passkey Demo (Angular + NestJS)
 
-```bash
-$ npm install
-```
+This is a full-stack demonstration of **passwordless authentication using WebAuthn passkeys**, built with:
 
-## Compile and run the project
+- **Frontend**: Angular 20 (Standalone Components + Material)
+- **Backend**: NestJS
+- **Authentication**: WebAuthn via [@simplewebauthn](https://github.com/MasterKale/SimpleWebAuthn)
 
-```bash
-# development
-$ npm run start
+---
 
-# watch mode
-$ npm run start:dev
+## ✨ Features
 
-# production mode
-$ npm run start:prod
-```
+- 🔐 Passkey-only login — no email input required
+- ✅ Discoverable credentials (platform or cross-device)
+- 📲 Cross-device support using synced passkeys
+- 🎨 Clean Angular Material UI
+- 🧪 Ideal for learning, demos, or prototyping WebAuthn
 
-## Run tests
+---
 
-```bash
-# unit tests
-$ npm run test
+## 🧰 Tech Stack
 
-# e2e tests
-$ npm run test:e2e
+| Layer     | Tech                          |
+|-----------|-------------------------------|
+| Frontend  | Angular 20, Angular Material  |
+| Backend   | NestJS                        |
+| Auth      | @simplewebauthn/server + browser |
+| Hosting   | NestJS serves Angular build   |
+| Tunnel    | [ngrok](https://ngrok.com/) for HTTPS development
 
-# test coverage
-$ npm run test:cov
-```
-
-## Deployment
+---
 
-When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
+## 🚀 Getting Started
 
-If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
+### 1. Clone or Download the Project
 
+Download this project from Google Drive or clone it if hosted on Git later.
+https://chat.google.com/dm/hDsHMQAAAAE/79WHykPDGEQ/79WHykPDGEQ?cls=10
 ```bash
-$ npm install -g mau
-$ mau deploy
+cd your-project-directory
 ```
 
-With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
+### 2. Clone or Download the Project
+dependencies already installed in this case. just extract the program.
+
+
+### 3. Expose via ngrok or preferred method
+setup: https://dashboard.ngrok.com/get-started/setup/windows
+npx ngrok http 3000
+Copy the exposed Url
 
-## Resources
+### 4. Update Config Files
+Update the URLs ..\Mobile Authentication Sample\sample-auth-backend\src\config.ts
+<RPID must match>
+Update the URL ..\Mobile Authentication Sample\web-app\src\app\config.ts
 
-Check out a few resources that may come in handy when working with NestJS:
 
-- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
-- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
-- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
-- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
-- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
-- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
-- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
-- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
+### 5. Angular Build 
+npm run nest
+This will enable NestJS to serve the frontend statically
 
-## Support
 
-Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
+### 5. Start the app
+in  ..\Mobile Authentication Sample\sample-auth-backend run npm run start
 
-## Stay in touch
 
-- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
-- Website - [https://nestjs.com](https://nestjs.com/)
-- Twitter - [@nestframework](https://twitter.com/nestframework)
 
-## License
 
-Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

+ 28 - 0
certs/192-168-100-100.nip.io-key.pem

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDkww6zT/7O+Fgy
+w14zsgR4XYStNJHTxoyBYbyqhp6yepClEOe+4a2k5N/gSqU98o/sfpQRZNvQ3Mla
+jxPUEn9J/Os1uwT04XCDLhpwV+MEHUAD6Et5JmuHCmh3XmjlOpupyVCsQZ+3M63Y
+DnWagPzx6K88CeWXLP36Z9xcCjlAL88hWB8zpqbdC/xqr8TS5xT6+WVnLQMe8Gni
+sfrk9um41uYs8ZLKTETlMJWANopoyJfFPgB4Cp8/tsackGn90/TbnBeey9kSqD10
+Y5zg5vQ6iCMHBY9sXCRE487Rngub2CDZIxgchS+fRMOW2DDYt60RDn5L0meb5TAM
+6uRcZov9AgMBAAECggEAEkF2tNyXaxDqiPKKW/nC3ZAo/GGLmajooZKZKcqgc4HQ
+dTvKIrOD1frl2tJaKX1n+umHC3RPDnXwI6fzXyMywUbLxnfsR1aOUgml1n1NApEL
+AMTS8RId1qL//zarjE+HCJ+mLnj2M/qNzwYoYHIWvX9iLJpllhDjG86aCUZPl02S
+vOF2W4RFgEKFLiSoEarMHo8waB/JlKkHIYNarI3Ji3GWwtMmByLIb9kIv+B0ZtEU
+vef2+SBuKCwr2rgM8Z07LEobvEyQP6DCfAOPR3O36zVy+MuW+hD0WQH914wgMT0N
+FmhzKuimoNkh57Tlbs9oLAgh6GTQ4u2r38A55Ip0gQKBgQD/0qzCUmgSn/vSZPGA
+oZYA8luqp98l3g+TCBnrKZBVSB1MYkto2eAde6f9i+aiz66A5JxupUmr/PNCYG8Y
+X91K2VdrQtwnc6W6s/jAcCOCFfqeVDCaiAqkUHm9ayV/YfduNbd7WK4sCIZHfwJI
+GzEViPSIWVkDKISfcfTzxjWU0QKBgQDk65aMT9ZE6oj/A2bVjbBXnSjLITD/ygB4
+W2cBiqAjaEAZ0f7qGLdcDMDa3wJ0aMQoWkg+p85lrC62UH9Q8rvKJHgqbiFmCRSd
+u7daYlLlOVZ91XW215hbV9xeqdNPj6wcfcC0ldZZ9VL1DWtXedaGhmwZXCrRB9v5
+L/RkETr/bQKBgQDxYUpKzFCQik2kdkR813pfCE6/1keyRjWxyOoTvpMGNNYYNYLr
+uvBBQWT/0lpa8tS3n32O1PyPgEvTLISGniWCM1LNzTdHLlKIuLmUcfq/Cpf/fnah
+yI0p/xjvRkMKT1iUr7ChfMpSl/SDT27KtIV6yU/Zu1xGo5uHipHS1gnGkQKBgQDZ
+K3qGIBNZC9rtem+rNqDNbWXO1u0iLKOniqa8uqNTE8qD9ElOEiaZJ+wpd5lfkChP
+U97nudA/ekpSYSl3RN/MVC6/qfz7TLAHN5cpItAbb/rSo5fp3rxGI6MHFztFZjpU
+SSRw7RkN/IM3wQRNV2Oh+ZJBuMNRzI/t5YWHDSWYPQKBgGUJWicKY2cBBT6sYwEt
+NEaH6kRJZGkLZxnG8COz4i892lDfU9jas0bXfwR+SivATfE/LLaGPhH2nOZZN7il
+AGcXJwVPS6CkRFs9gm+EctIsqoPQ6BQcy2We1ByFQjFUocgdvV5Cg4+FuemUr/jv
+SPlhWXSfcjfH3IhuquYfIEdv
+-----END PRIVATE KEY-----

+ 25 - 0
certs/192-168-100-100.nip.io.pem

@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEQTCCAqmgAwIBAgIRAIEsETnDfwerdPk7WobrAw4wDQYJKoZIhvcNAQELBQAw
+eTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMScwJQYDVQQLDB5TT0RD
+MTZcZW56b0BTT1BDLTIzRDEwMSAoRW56bykxLjAsBgNVBAMMJW1rY2VydCBTT0RD
+MTZcZW56b0BTT1BDLTIzRDEwMSAoRW56bykwHhcNMjUwOTI2MDMyMTI5WhcNMjcx
+MjI2MDMyMTI5WjBSMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlm
+aWNhdGUxJzAlBgNVBAsMHlNPREMxNlxlbnpvQFNPUEMtMjNEMTAxIChFbnpvKTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOTDDrNP/s74WDLDXjOyBHhd
+hK00kdPGjIFhvKqGnrJ6kKUQ577hraTk3+BKpT3yj+x+lBFk29DcyVqPE9QSf0n8
+6zW7BPThcIMuGnBX4wQdQAPoS3kma4cKaHdeaOU6m6nJUKxBn7czrdgOdZqA/PHo
+rzwJ5Zcs/fpn3FwKOUAvzyFYHzOmpt0L/GqvxNLnFPr5ZWctAx7waeKx+uT26bjW
+5izxkspMROUwlYA2imjIl8U+AHgKnz+2xpyQaf3T9NucF57L2RKoPXRjnODm9DqI
+IwcFj2xcJETjztGeC5vYINkjGByFL59Ew5bYMNi3rREOfkvSZ5vlMAzq5Fxmi/0C
+AwEAAaNrMGkwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8G
+A1UdIwQYMBaAFEkrR51nmFN4NAN3UzC81h1OHyV6MCEGA1UdEQQaMBiCFjE5Mi0x
+NjgtMTAwLTEwMC5uaXAuaW8wDQYJKoZIhvcNAQELBQADggGBAG66LuEMZQJVrGgL
+pk0L9UoU7dHk21Z8rGC2bdcYlhkQj/2RchZvncy37ZSA2oFCLUmqRBZ6vhrHOmi2
+cCIFrx0f2VZEHTks1z5zWomNJ0m/NFy5Q1+JRibcuj+HT1lVnHk1t2Kaj+MsY8cA
+wOXNdckHvGvOap7HmuIE7MewQPKUKNuImjJu36+L0yV8wwcCXvVcgX8AV7CEJDT1
+mK5CcHScM8TS8FobtZg/kQaqsICSj6oMrMcDmXFZuzvDZQ29u7RrMepSmk76t7kx
+oQdtuW9na8S/RHS8JqSVAysOxsCf1+R7f8suamfcmIQhFhtwt96DAvnFut8AY4P/
+nSVtWXS8sVTXzhN0LJ3NWGRSjE4mxVUHgnZynwNd0xoiKQdHybrmz3O5vcyp8NU9
+0JZPYlas5gVYJcZi19q21WpLo88UFhrY3IV2qnYUpXQrhhG419zq2U7vR8M9JnGa
+4BzVkDB8+ISbaz7wyJOA9PpSYzWiyhSHlZBBSGb0FdHiDwB+8Q==
+-----END CERTIFICATE-----

+ 62 - 11
package-lock.json

@@ -18,11 +18,13 @@
         "@nestjs/websockets": "^11.1.3",
         "@simplewebauthn/server": "^13.1.1",
         "bcrypt": "^6.0.0",
+        "express-list-endpoints": "^7.1.1",
         "express-session": "^1.18.1",
         "passport": "^0.7.0",
         "passport-jwt": "^4.0.1",
         "reflect-metadata": "^0.2.2",
-        "rxjs": "^7.8.2"
+        "rxjs": "^7.8.2",
+        "tree-model": "^1.0.7"
       },
       "devDependencies": {
         "@eslint/eslintrc": "^3.3.1",
@@ -241,6 +243,7 @@
       "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
         "@babel/code-frame": "^7.27.1",
@@ -2444,6 +2447,7 @@
       "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.3",
         "fast-uri": "^3.0.1",
@@ -2612,6 +2616,7 @@
       "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.3.tgz",
       "integrity": "sha512-ogEK+GriWodIwCw6buQ1rpcH4Kx+G7YQ9EwuPySI3rS05pSdtQ++UhucjusSI9apNidv+QURBztJkRecwwJQXg==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "file-type": "21.0.0",
         "iterare": "1.2.1",
@@ -2644,6 +2649,7 @@
       "integrity": "sha512-5lTni0TCh8x7bXETRD57pQFnKnEg1T6M+VLE7wAmyQRIecKQU+2inRGZD+A4v2DC1I04eA0WffP0GKLxjOKlzw==",
       "hasInstallScript": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@nuxt/opencollective": "0.4.1",
         "fast-safe-stringify": "2.1.1",
@@ -2707,6 +2713,7 @@
       "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.3.tgz",
       "integrity": "sha512-hEDNMlaPiBO72fxxX/CuRQL3MEhKRc/sIYGVoXjrnw6hTxZdezvvM6A95UaLsYknfmcZZa/CdG1SMBZOu9agHQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "cors": "2.8.5",
         "express": "5.1.0",
@@ -2728,6 +2735,7 @@
       "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.3.tgz",
       "integrity": "sha512-jQ+ccprmh3kKolBp+bb97zoaS3vKaiyeNqyctGqV4CSG8P6mXSaaUObWxAsw6Jdgn5YQAVEBWJ6FhvF4s6QZbg==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "socket.io": "4.8.1",
         "tslib": "2.8.1"
@@ -2873,6 +2881,7 @@
       "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.3.tgz",
       "integrity": "sha512-IjhWKfRf0D247JxYIEs8USblJJbcxUsKJpzbCPaZ7TrVy4LrpG3IRQDlSTOw599TRIYP5ixyH9C0+v5DyaI9uA==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "iterare": "1.2.1",
         "object-hash": "3.0.0",
@@ -3127,6 +3136,7 @@
       "integrity": "sha512-j4yYm9bx3pxWofaJKX1BFwj/3ngUDynN4UIQ2Xd2h0h/7Gt7zkReBTpDN7g5S13mgAYxacaTHTOUsz18097E8w==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@swc/counter": "^0.1.3",
         "@xhmikosr/bin-wrapper": "^13.0.5",
@@ -3199,6 +3209,7 @@
       "dev": true,
       "hasInstallScript": true,
       "license": "Apache-2.0",
+      "peer": true,
       "dependencies": {
         "@swc/counter": "^0.1.3",
         "@swc/types": "^0.1.23"
@@ -3582,6 +3593,7 @@
       "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@types/estree": "*",
         "@types/json-schema": "*"
@@ -3717,6 +3729,7 @@
       "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz",
       "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "undici-types": "~7.8.0"
       }
@@ -3852,6 +3865,7 @@
       "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@typescript-eslint/scope-manager": "8.35.0",
         "@typescript-eslint/types": "8.35.0",
@@ -5093,6 +5107,7 @@
       "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -5129,6 +5144,7 @@
       "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
         "fast-json-stable-stringify": "^2.0.0",
@@ -5642,6 +5658,7 @@
         }
       ],
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "caniuse-lite": "^1.0.30001718",
         "electron-to-chromium": "^1.5.160",
@@ -5884,6 +5901,7 @@
       "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "readdirp": "^4.0.1"
       },
@@ -6727,6 +6745,7 @@
       "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.12.1",
@@ -6788,6 +6807,7 @@
       "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "bin": {
         "eslint-config-prettier": "bin/cli.js"
       },
@@ -7057,6 +7077,15 @@
         "url": "https://opencollective.com/express"
       }
     },
+    "node_modules/express-list-endpoints": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/express-list-endpoints/-/express-list-endpoints-7.1.1.tgz",
+      "integrity": "sha512-SA6YHH1r6DrioJ4fFJNqiwu1FweGFqJZO9KBApMzwPosoSGPOX2AW0wiMepOXjojjEXDuP9whIvckomheErbJA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/express-session": {
       "version": "1.18.1",
       "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
@@ -7401,6 +7430,12 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/find-insert-index": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/find-insert-index/-/find-insert-index-0.0.1.tgz",
+      "integrity": "sha512-eIqFuQzY7XwpAJ3sHWKFNGLx1nm3w/IhmFASETcx5sUuCaOUd3xDqRK/376SzXMVVJQaJUCPlS7L841T0xpFjQ==",
+      "license": "MIT"
+    },
     "node_modules/find-up": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -8333,6 +8368,7 @@
       "integrity": "sha512-Uy8xfeE/WpT2ZLGDXQmaYNzw2v8NUKuYeKGtkS6sDxwsdQihdgYCXaKIYnph1h95DN5H35ubFDm0dfmsQnjn4Q==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@jest/core": "30.0.3",
         "@jest/types": "30.0.1",
@@ -9541,6 +9577,12 @@
         "node": ">= 8"
       }
     },
+    "node_modules/mergesort": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/mergesort/-/mergesort-0.0.1.tgz",
+      "integrity": "sha512-WKghTBzqAvTt9rG5TWS78Dmk2kCCL9VkkX8Zi9kKfJ4iqYpvcGGpeYtkhPHa9NZAPLivZiZsdO/LBG3ENayDmQ==",
+      "license": "MIT"
+    },
     "node_modules/methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -10138,6 +10180,7 @@
       "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
       "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "passport-strategy": "1.x.x",
         "pause": "0.0.1",
@@ -10406,6 +10449,7 @@
       "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "bin": {
         "prettier": "bin/prettier.cjs"
       },
@@ -10646,7 +10690,8 @@
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
       "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
-      "license": "Apache-2.0"
+      "license": "Apache-2.0",
+      "peer": true
     },
     "node_modules/repeat-string": {
       "version": "1.6.1",
@@ -10811,6 +10856,7 @@
       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
       "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
       "license": "Apache-2.0",
+      "peer": true,
       "dependencies": {
         "tslib": "^2.1.0"
       }
@@ -11728,6 +11774,7 @@
       "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.3",
         "fast-uri": "^3.0.1",
@@ -11958,6 +12005,16 @@
         "tree-kill": "cli.js"
       }
     },
+    "node_modules/tree-model": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/tree-model/-/tree-model-1.0.7.tgz",
+      "integrity": "sha512-oP4LUbCVtD2gcjcRaeI4L5hY60tHzB+AK/bthIJ2Pq1EUUOio5/xFzPWnGoBZlhtqpqbOkhFDzKIwKLOn0kccQ==",
+      "license": "MIT",
+      "dependencies": {
+        "find-insert-index": "0.0.1",
+        "mergesort": "0.0.1"
+      }
+    },
     "node_modules/ts-api-utils": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -12064,6 +12121,7 @@
       "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
       "dev": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@cspotcode/source-map-support": "^0.8.0",
         "@tsconfig/node10": "^1.0.7",
@@ -12211,6 +12269,7 @@
       "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
       "dev": true,
       "license": "Apache-2.0",
+      "peer": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -12576,7 +12635,6 @@
       "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "ajv": "^8.0.0"
       },
@@ -12595,7 +12653,6 @@
       "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.3"
       },
@@ -12609,7 +12666,6 @@
       "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
       "dev": true,
       "license": "BSD-2-Clause",
-      "peer": true,
       "dependencies": {
         "esrecurse": "^4.3.0",
         "estraverse": "^4.1.1"
@@ -12624,7 +12680,6 @@
       "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
       "dev": true,
       "license": "BSD-2-Clause",
-      "peer": true,
       "engines": {
         "node": ">=4.0"
       }
@@ -12634,8 +12689,7 @@
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
       "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
       "dev": true,
-      "license": "MIT",
-      "peer": true
+      "license": "MIT"
     },
     "node_modules/webpack/node_modules/mime-db": {
       "version": "1.52.0",
@@ -12643,7 +12697,6 @@
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">= 0.6"
       }
@@ -12654,7 +12707,6 @@
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "mime-db": "1.52.0"
       },
@@ -12668,7 +12720,6 @@
       "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@types/json-schema": "^7.0.9",
         "ajv": "^8.9.0",

+ 7 - 2
package.json

@@ -29,11 +29,13 @@
     "@nestjs/websockets": "^11.1.3",
     "@simplewebauthn/server": "^13.1.1",
     "bcrypt": "^6.0.0",
+    "express-list-endpoints": "^7.1.1",
     "express-session": "^1.18.1",
     "passport": "^0.7.0",
     "passport-jwt": "^4.0.1",
     "reflect-metadata": "^0.2.2",
-    "rxjs": "^7.8.2"
+    "rxjs": "^7.8.2",
+    "tree-model": "^1.0.7"
   },
   "devDependencies": {
     "@eslint/eslintrc": "^3.3.1",
@@ -77,6 +79,9 @@
       "**/*.(t|j)s"
     ],
     "coverageDirectory": "../coverage",
-    "testEnvironment": "node"
+    "testEnvironment": "node",
+    "moduleNameMapper": {
+      "^src/(.*)$": "<rootDir>/$1"
+    }
   }
 }

+ 0 - 22
src/app.controller.spec.ts

@@ -1,22 +0,0 @@
-import { Test, TestingModule } from '@nestjs/testing';
-import { AppController } from './app.controller';
-import { AppService } from './app.service';
-
-describe('AppController', () => {
-  let appController: AppController;
-
-  beforeEach(async () => {
-    const app: TestingModule = await Test.createTestingModule({
-      controllers: [AppController],
-      providers: [AppService],
-    }).compile();
-
-    appController = app.get<AppController>(AppController);
-  });
-
-  describe('root', () => {
-    it('should return "Hello World!"', () => {
-      expect(appController.getHello()).toBe('Hello World!');
-    });
-  });
-});

+ 2 - 4
src/app.controller.ts

@@ -1,12 +1,10 @@
 import { Controller, Get, Logger, UseGuards } from '@nestjs/common';
-import { GeneralService } from './services/general.service';
-import { serverConfig } from './config';
-import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
+import { AppService } from './app.service';
 
 @Controller()
 export class AppController {
   private logger: Logger = new Logger(`AppController`)
-  constructor(private readonly service: GeneralService) { }
+  constructor(private readonly service: AppService) { }
 
   @Get()
   getHello(): string {

+ 5 - 4
src/app.module.ts

@@ -1,13 +1,14 @@
 import { Module } from '@nestjs/common';
 import { AppController } from './app.controller';
-import { GeneralService } from './services/general.service';
 import { AuthModule } from './auth/auth.module';
 import { AttendanceModule } from './attendance/attendance.module';
 import { PaymentModule } from './payment/payment.module';
+import { PlantationTreeModule } from './plantation/plantation-tree.module';
+import { AppService } from './app.service';
 
 @Module({
-  imports: [AuthModule, AttendanceModule, PaymentModule],
+  imports: [AuthModule, AttendanceModule, PaymentModule, PlantationTreeModule],
   controllers: [AppController],
-  providers: [GeneralService],
+  providers: [AppService],
 })
-export class AppModule {}
+export class AppModule { }

+ 1 - 1
src/services/general.service.ts → src/app.service.ts

@@ -1,7 +1,7 @@
 import { Injectable } from '@nestjs/common';
 
 @Injectable()
-export class GeneralService {
+export class AppService {
   getHello(): string {
     return 'Hello World!';
   }

+ 1 - 1
src/attendance/attendance.controller.ts

@@ -8,7 +8,7 @@ import {
 } from '@nestjs/common';
 import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';
 import { AttendancePayload } from 'src/interface/interface';
-import { AttendanceService } from 'src/services/attendance.service';
+import { AttendanceService } from 'src/attendance/attendance.service';
 
 @Controller('attendance')
 export class AttendanceController {

+ 1 - 1
src/attendance/attendance.module.ts

@@ -1,6 +1,6 @@
 import { Module } from '@nestjs/common';
 import { AttendanceController } from './attendance.controller';
-import { AttendanceService } from 'src/services/attendance.service';
+import { AttendanceService } from 'src/attendance/attendance.service';
 import { SocketGateway } from 'src/gateway/socket.gateway';
 import { SocketModule } from 'src/gateway/socket.module';
 

+ 0 - 0
src/services/attendance.service.ts → src/attendance/attendance.service.ts


+ 84 - 0
src/auth/auth.controller.spec.ts

@@ -0,0 +1,84 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { AuthController } from './auth.controller';
+import { UsersService } from 'src/users/user.service';
+import { WebauthnService } from 'src/auth/webauthn.service';
+import { JwtService } from '@nestjs/jwt';
+import type { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/server';
+
+describe('AuthController', () => {
+  let controller: AuthController;
+  let usersService: jest.Mocked<UsersService>;
+  let webauthnService: jest.Mocked<WebauthnService>;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [AuthController],
+      providers: [
+        { provide: UsersService, useValue: { findByEmail: jest.fn(), createUser: jest.fn() } },
+        { provide: WebauthnService, useValue: { generateRegistrationOptions: jest.fn() } },
+        { provide: JwtService, useValue: { sign: jest.fn().mockReturnValue('mock-jwt') } },
+      ],
+    }).compile();
+
+    controller = module.get<AuthController>(AuthController);
+    usersService = module.get(UsersService);
+    webauthnService = module.get(WebauthnService);
+  });
+
+  describe('webauthn-register-options', () => {
+    it('should return registration options for a valid user', async () => {
+      const session: Record<string, any> = {};
+      const mockUser = { id: 'u123', username: 'u123@user.com', devices: [] };
+
+      // Mock DB + WebAuthn
+      jest.spyOn(controller, 'getUserFromDb').mockResolvedValue(mockUser);
+      webauthnService.generateRegistrationOptions.mockResolvedValueOnce(
+        createMockRegistrationOptions({ challenge: 'abc123' }),
+      );
+
+      const result = await controller.getRegistrationOptions({ username: 'u123' }, session);
+
+      expect(result.challenge).toBe('abc123');
+      expect(session.challenge).toBe('abc123');
+      expect(webauthnService.generateRegistrationOptions).toHaveBeenCalledWith(mockUser);
+    });
+  });
+});
+
+
+
+
+/**
+ * Factory for mock registration options
+ */
+export function createMockRegistrationOptions(
+  overrides: Partial<PublicKeyCredentialCreationOptionsJSON> = {},
+): PublicKeyCredentialCreationOptionsJSON {
+  return {
+    challenge: 'mock-challenge',
+    rp: { name: 'Mock RP' },
+    user: {
+      id: 'user-id',
+      name: 'test@example.com',
+      displayName: 'Test User',
+    },
+    pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
+    ...overrides,
+  };
+}
+
+/**
+ * Factory for mock authentication (login) options
+ */
+export function createMockAuthenticationOptions(
+  overrides: Partial<PublicKeyCredentialRequestOptionsJSON> = {},
+): PublicKeyCredentialRequestOptionsJSON {
+  return {
+    challenge: 'mock-challenge',
+    timeout: 60000,
+    rpId: 'localhost',
+    allowCredentials: [],
+    userVerification: 'preferred',
+    ...overrides,
+  };
+}

+ 36 - 34
src/auth/auth.controller.ts

@@ -4,39 +4,20 @@ import { AuthenticatorTransportFuture } from '@simplewebauthn/server';
 import { isoBase64URL } from '@simplewebauthn/server/helpers';
 import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';
 import { serverConfig } from 'src/config';
-import { LoginPayload, User } from 'src/interface/interface';
-import { AuthService } from 'src/services/auth.service';
-import { UsersService } from 'src/services/user.service';
-import { WebauthnService } from 'src/services/webauthn.service';
+import { UsersService } from 'src/users/user.service';
+import { WebauthnService } from 'src/auth/webauthn.service';
 
 @Controller('auth')
 export class AuthController {
     private logger: Logger = new Logger(`Auth Controller`)
-    constructor(private authService: AuthService, private webauthnService: WebauthnService, private usersService: UsersService, private jwtService: JwtService) { }
-
-    @Post('register')
-    async register(@Body() body: { name: string, email: string; password: string }) {
-        this.logger.log(`Registering ${body.name}`)
-        let user: User = body
-        return this.authService.register(user);
-    }
-
-    @Post('login')
-    login(@Body() body: { email?: string; password?: string }) {
-        this.logger.log(`logging in ${body.email}`)
-        if (!body.email || !body.password) {
-            this.logger.error('Missing email or password');
-        } else {
-            return this.authService.login(body as LoginPayload);
-        }
-    }
+    constructor(private webauthnService: WebauthnService, private usersService: UsersService, private jwtService: JwtService) { }
 
     @Post('webauthn-register-options')
     async getRegistrationOptions(
         @Body() body: { username: string },
         @Session() session: Record<string, any>,
     ) {
-        this.logger.log(`Registring options...`)
+        this.logger.log(`Registering options...`)
         if (!body.username?.trim()) {
             throw new BadRequestException('Missing or empty username');
         }
@@ -57,7 +38,7 @@ export class AuthController {
         @Body() body: any,
         @Session() session: Record<string, any>,
     ) {
-        this.logger.log(`Registring ${body.name}`)
+        this.logger.log(`Registering ${body.name}`)
         const expectedChallenge = session.challenge;
         if (!expectedChallenge) {
             throw new BadRequestException('Missing challenge in session');
@@ -126,8 +107,23 @@ export class AuthController {
     }
 
     @Post('webauthn-login-options')
-    async getLoginOptions(@Body() body: { email: string }, @Session() session: Record<string, any>) {
-        if (!body.email) throw new BadRequestException('Missing email');
+    async getLoginOptions(
+        @Body() body: { email?: string; passkey?: boolean },
+        @Session() session: Record<string, any>,
+    ) {
+        if (body.passkey) {
+            this.logger.log(`Generating passkey login options...`);
+
+            const options = await this.webauthnService.generateAuthenticationOptions(); // No allowCredentials
+            session.challenge = options.challenge;
+            return options;
+        }
+
+        if (!body.email) {
+            throw new BadRequestException('Missing email');
+        }
+
+        this.logger.log(`Generating login options for ${body.email}...`);
 
         const user = await this.usersService.findByEmail(body.email);
         if (!user || !user.devices?.length) {
@@ -140,23 +136,31 @@ export class AuthController {
         return options;
     }
 
-
     @Post('webauthn-login')
     async verifyLogin(@Body() body: any, @Session() session: Record<string, any>) {
         const expectedChallenge = session.challenge;
-        const email = session.email;
+        if (!expectedChallenge) {
+            throw new BadRequestException('Missing challenge in session');
+        }
+
+        let user;
 
-        if (!expectedChallenge || !email) {
-            throw new BadRequestException('Missing challenge or email in session');
+        // Case 1: email-based login
+        if (session.email) {
+            user = await this.usersService.findByEmail(session.email);
+        } else {
+            // Case 2: passkey login (discoverable credential)
+            const credentialId = body.id || body.rawId;
+            user = await this.usersService.findByCredentialId(credentialId);
         }
 
-        const user = await this.usersService.findByEmail(email);
         if (!user || !user.devices?.length) {
             throw new NotFoundException('User or devices not found');
         }
 
+        const credentialId = body.id || body.rawId;
         const device = user.devices.find(
-            d => isoBase64URL.fromBuffer(d.credentialID) === body.id || body.rawId === isoBase64URL.fromBuffer(d.credentialID),
+            d => isoBase64URL.fromBuffer(d.credentialID) === credentialId,
         );
 
         if (!device) {
@@ -175,9 +179,7 @@ export class AuthController {
             throw new BadRequestException('Authentication failed');
         }
 
-        // Optional: update device counter
         device.counter = verification.authenticationInfo.newCounter;
-
         await this.usersService.updateUser(user);
 
         const payload = { sub: user.id, name: user.name, email: user.email };

+ 3 - 4
src/auth/auth.module.ts

@@ -1,11 +1,10 @@
 import { Module } from '@nestjs/common';
 import { AuthController } from './auth.controller';
 import { JwtModule } from '@nestjs/jwt';
-import { AuthService } from 'src/services/auth.service';
 import { PassportModule } from '@nestjs/passport';
 import { JwtStrategy } from './jwt.strategy';
 import { UsersModule } from 'src/users/users.module';
-import { WebauthnService } from 'src/services/webauthn.service';
+import { WebauthnService } from 'src/auth/webauthn.service';
 
 @Module({
   imports: [
@@ -17,6 +16,6 @@ import { WebauthnService } from 'src/services/webauthn.service';
     }),
   ],
   controllers: [AuthController],
-  providers: [AuthService, JwtStrategy, WebauthnService],
+  providers: [JwtStrategy, WebauthnService],
 })
-export class AuthModule {}
+export class AuthModule { }

+ 14 - 9
src/services/webauthn.service.ts → src/auth/webauthn.service.ts

@@ -11,13 +11,14 @@ import {
 import { Injectable, Logger } from '@nestjs/common';
 import { isoUint8Array } from '@simplewebauthn/server/helpers';
 import { isoBase64URL } from '@simplewebauthn/server/helpers';
+import { serverConfig } from 'src/config';
 
 @Injectable()
 export class WebauthnService {
     private logger: Logger = new Logger(`WebAuthn`)
-    private rpName = 'MyApp';
-    private rpID = 'b8c8-115-132-229-66.ngrok-free.app'; // replace with your domain in production
-    private origin = 'https://b8c8-115-132-229-66.ngrok-free.app'; // your frontend origin
+    private rpName = serverConfig.rpName
+    private rpID = serverConfig.rpId
+    private origin = serverConfig.origin
 
     public devices: any[] = []; // In-memory device store
 
@@ -67,19 +68,23 @@ export class WebauthnService {
         });
     }
 
-    async generateAuthenticationOptions(registeredDevices: {
+    async generateAuthenticationOptions(registeredDevices?: {
         credentialID: Uint8Array;
         transports?: AuthenticatorTransport[];
     }[]) {
-        return generateAuthenticationOptions({
-            rpID: this.rpID,
-            timeout: 60000,
-            allowCredentials: registeredDevices.map(dev => ({
+        const allowCredentials = registeredDevices?.length
+            ? registeredDevices.map(dev => ({
                 id: isoBase64URL.fromBuffer(dev.credentialID),
                 type: 'public-key',
                 transports: dev.transports,
-            })),
+            }))
+            : undefined; // 💡 omit if no devices or we want discoverable login
+
+        return generateAuthenticationOptions({
+            rpID: this.rpID,
+            timeout: 60000,
             userVerification: 'preferred',
+            allowCredentials, // ✅ undefined = allow browser to try passkeys
         });
     }
 

+ 11 - 2
src/config.ts

@@ -1,3 +1,12 @@
+// export const serverConfig = {
+//   exposedUrl: 'https://36a90a13a4c7.ngrok-free.app',
+//   rpName: 'My App',
+//   rpId: '36a90a13a4c7.ngrok-free.app',
+//   origin: 'https://36a90a13a4c7.ngrok-free.app',
+// };
 export const serverConfig = {
-    exposedUrl: `https://b8c8-115-132-229-66.ngrok-free.app`
-}
+  exposedUrl: 'https://192-168-100-100.nip.io:3000',
+  rpName: 'My App',
+  rpId: '192-168-100-100.nip.io',
+  origin: 'https://192-168-100-100.nip.io:3000',
+};

+ 23 - 28
src/main.ts

@@ -1,53 +1,48 @@
 import { NestFactory } from '@nestjs/core';
 import { AppModule } from './app.module';
 import { serverConfig } from './config';
-import * as session from 'express-session';
 import { join } from 'path';
-import { NestExpressApplication } from '@nestjs/platform-express';
 import * as fs from 'fs';
+import { NestExpressApplication } from '@nestjs/platform-express';
+import session from 'express-session';
 
 async function bootstrap() {
-  const app = await NestFactory.create<NestExpressApplication>(AppModule);
-  app.enableCors();
+  const certsDir = join(__dirname, '..', 'certs');
+  const httpsOptions = {
+    key: fs.readFileSync(join(certsDir, '192-168-100-100.nip.io-key.pem')),
+    cert: fs.readFileSync(join(certsDir, '192-168-100-100.nip.io.pem')),
+  };
+
+  // const app = await NestFactory.create<NestExpressApplication>(AppModule);
+  const app = await NestFactory.create<NestExpressApplication>(AppModule, {
+    httpsOptions, // <-- Let Nest bind HTTPS
+  });
 
-  // ✅ API prefix
-  app.setGlobalPrefix('api'); // <<==== All controllers now under /api/*
+  app.enableCors();
+  app.setGlobalPrefix('api');
 
   const angularDistPath = join(__dirname, '..', '..', 'web-app', 'dist', 'mobile-auth-web-app', 'browser');
   const indexPath = join(angularDistPath, 'index.html');
 
-  console.log('✅ Angular path:', angularDistPath);
-  console.log('✅ index.html exists:', fs.existsSync(indexPath));
-
-  // ✅ Now serve Angular static files
   app.useStaticAssets(angularDistPath);
   app.setBaseViewsDir(angularDistPath);
   app.setViewEngine('html');
 
-  app.use(
-    session({
-      secret: 'your-secret',
-      resave: false,
-      saveUninitialized: false,
-    }),
-  );
+  app.use(session({
+    secret: 'your-secret',
+    resave: false,
+    saveUninitialized: false,
+  }));
 
-  // ✅ Angular fallback: only for non-API, non-static requests
   app.use((req, res, next) => {
     const isStaticAsset = req.url.includes('.');
     const isApiCall = req.url.startsWith('/api') || req.method !== 'GET';
-
-    if (isStaticAsset || isApiCall) {
-      return next();
-    }
-
-    console.log('📦 Angular fallback hit for:', req.url);
+    if (isStaticAsset || isApiCall) return next();
     res.sendFile(indexPath);
   });
 
-  await app.listen(process.env.PORT ?? 3000);
-  console.log(`🚀 Server running at http://localhost:3000`);
-  console.log(`🌐 Exposed at: ${serverConfig.exposedUrl}`);
-}
+  await app.listen(3000, '0.0.0.0');
+  console.log(`🚀 HTTPS server running at ${serverConfig.exposedUrl}`);
 
+}
 bootstrap();

+ 1 - 1
src/payment/payment.controller.ts

@@ -8,7 +8,7 @@ import {
 } from '@nestjs/common';
 import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';
 import { PaymentPayload } from 'src/interface/interface';
-import { PaymentService } from 'src/services/payment.service';
+import { PaymentService } from 'src/payment/payment.service';
 
 @Controller('payment')
 export class PaymentController {

+ 1 - 1
src/payment/payment.module.ts

@@ -1,6 +1,6 @@
 import { Module } from '@nestjs/common';
 import { PaymentController } from './payment.controller';
-import { PaymentService } from 'src/services/payment.service';
+import { PaymentService } from 'src/payment/payment.service';
 import { SocketModule } from 'src/gateway/socket.module';
 
 @Module({

+ 0 - 0
src/services/payment.service.ts → src/payment/payment.service.ts


+ 5 - 0
src/plantation/plantation-node-data.interface.ts

@@ -0,0 +1,5 @@
+// plantation-node-data.interface.ts
+export interface PlantationNodeData {
+  name: string;
+  // other fields, e.g., type, status, assignedTo
+}

+ 34 - 0
src/plantation/plantation-tree.controller.ts

@@ -0,0 +1,34 @@
+// plantation-tree.controller.ts
+import { Controller, Get, Post, Delete, Body, Param } from '@nestjs/common';
+import { PlantationTreeService } from './plantation-tree.service';
+import { PlantationNodeData } from './plantation-node-data.interface';
+import { TreeNode } from 'src/services/tree.service';
+
+@Controller('plantation-tree')
+export class PlantationTreeController {
+  constructor(private readonly treeService: PlantationTreeService) {}
+
+  @Get()
+  getTree() {
+    return this.treeService.getTree();
+  }
+
+  @Post('add/:parentId')
+  addNode(
+    @Param('parentId') parentId: string,
+    @Body() node: TreeNode<PlantationNodeData>,
+  ) {
+    return this.treeService.addNode(parentId, node);
+  }
+
+  @Delete('remove/:nodeId')
+  removeNode(@Param('nodeId') nodeId: string) {
+    this.treeService.removeNode(nodeId);
+    return { success: true };
+  }
+
+  @Get('find/:nodeId')
+  findNode(@Param('nodeId') nodeId: string) {
+    return this.treeService.findNode(nodeId);
+  }
+}

+ 11 - 0
src/plantation/plantation-tree.module.ts

@@ -0,0 +1,11 @@
+// plantation-tree.module.ts
+import { Module } from '@nestjs/common';
+import { PlantationTreeService } from './plantation-tree.service';
+import { PlantationTreeController } from './plantation-tree.controller';
+
+@Module({
+  providers: [PlantationTreeService],
+  controllers: [PlantationTreeController],
+  exports: [PlantationTreeService], // export if other modules need to use it
+})
+export class PlantationTreeModule {}

+ 20 - 0
src/plantation/plantation-tree.service.ts

@@ -0,0 +1,20 @@
+// plantation-tree.service.ts
+import { Injectable } from '@nestjs/common';
+import { PlantationNodeData } from './plantation-node-data.interface';
+import { TreeNode, TreeService } from 'src/services/tree.service';
+
+@Injectable()
+export class PlantationTreeService extends TreeService<PlantationNodeData> {
+    constructor() {
+        super({ id: 'root', data: { name: 'Plantation' } });
+    }
+
+    // Example of a domain-specific helper
+    findZoneByName(name: string): TreeNode<PlantationNodeData> | undefined {
+        let found: TreeNode<PlantationNodeData> | undefined;
+        this.traverseTree(node => {
+            if (node.data.name === name) found = node;
+        });
+        return found;
+    }
+}

+ 0 - 37
src/services/auth.service.ts

@@ -1,37 +0,0 @@
-import { Injectable, Logger } from '@nestjs/common';
-import { JwtService } from '@nestjs/jwt';
-import { UsersService } from './user.service';
-import { LoginPayload, RegisteredUser, User } from 'src/interface/interface';
-
-@Injectable()
-export class AuthService {
-    private logger: Logger = new Logger(`AuthService`)
-    constructor(
-        private usersService: UsersService,
-        private jwtService: JwtService
-    ) { }
-
-    async register(user: User): Promise<any> {
-        return new Promise(async (resolve, reject) => {
-            const registeredUser: RegisteredUser = await this.usersService.createUser(user);
-            this.logger.log(`${registeredUser.name} registered`)
-            const payload = { name: registeredUser.name, email: registeredUser.email, sub: registeredUser.id };
-            let response = { access_token: this.jwtService.sign(payload), name: payload.name }
-            resolve(response)
-        })
-    }
-
-    async login(login: LoginPayload) {
-        return new Promise((resolve, reject) => {
-            this.usersService.validateUser(login).then((user) => {
-                const payload = { name: user.name, email: user.email, sub: user.id };
-                let response = { access_token: this.jwtService.sign(payload), name: payload.name }
-                resolve(response)
-            }).catch((error) => {
-                this.logger.error('Invalid credentials')
-                reject(error)
-            })
-        })
-    }
-
-}

+ 57 - 0
src/services/tree.service.ts

@@ -0,0 +1,57 @@
+// tree.service.ts
+import TreeModel = require('tree-model');
+
+export interface TreeNode<T> {
+  id: string; // node identifier
+  data: T;    // domain-specific data
+}
+
+export class TreeService<T> {
+  protected treeModel = new TreeModel();
+  protected root: TreeModel.Node<TreeNode<T>>;
+
+  constructor(rootNode: TreeNode<T>) {
+    this.root = this.treeModel.parse<TreeNode<T>>({
+      ...rootNode,
+      children: [],
+    });
+  }
+
+  getTree() {
+    return this.serializeNode(this.root);
+  }
+
+  addNode(parentId: string, node: TreeNode<T>) {
+    const parent = this.root.first(n => n.model.id === parentId);
+    if (!parent) throw new Error('Parent not found');
+
+    const treeNode = this.treeModel.parse<TreeNode<T>>(node);
+    parent.addChild(treeNode);
+    return treeNode.model;
+  }
+
+  removeNode(nodeId: string) {
+    const node = this.root.first(n => n.model.id === nodeId);
+    if (!node) throw new Error('Node not found');
+    node.drop();
+  }
+
+  findNode(nodeId: string): TreeNode<T> | undefined {
+    const node = this.root.first(n => n.model.id === nodeId);
+    return node?.model;
+  }
+
+  traverseTree(fn: (node: TreeNode<T>) => void) {
+    this.root.walk(n => {
+      fn(n.model);
+      return true; // required for TreeModel.walk
+    });
+  }
+
+  protected serializeNode(node: TreeModel.Node<TreeNode<T>>): any {
+    return {
+      ...node.model,
+      children: node.children.map(child => this.serializeNode(child)),
+    };
+  }
+}

+ 17 - 9
src/services/user.service.ts → src/users/user.service.ts

@@ -1,11 +1,12 @@
 import { Injectable, Logger } from '@nestjs/common';
+import { isoBase64URL } from '@simplewebauthn/server/helpers';
 import * as bcrypt from 'bcrypt';
 import { LoginPayload, RegisteredUser, User } from 'src/interface/interface';
 
 
 @Injectable()
 export class UsersService {
-    private logger: Logger = new Logger(`UsersService`)
+    private logger: Logger = new Logger(`UsersService`);
     private users: RegisteredUser[] = [];
     private idCounter = 1;
 
@@ -21,12 +22,13 @@ export class UsersService {
                     name: user.name,
                     email: user.email,
                     password: hashedPassword,
+                    devices: [], // Make sure this is initialized
                 };
                 this.users.push(newUser);
-                this.logger.log(`Current users count: ${this.users.length}`)
-                resolve(newUser)
+                this.logger.log(`Current users count: ${this.users.length}`);
+                resolve(newUser);
             }
-        })
+        });
     }
 
     public async findByEmail(email: string): Promise<RegisteredUser | undefined> {
@@ -39,10 +41,6 @@ export class UsersService {
                 .then(user => {
                     if (!user) return reject('No such user');
 
-                    // console.log('Comparing passwords...');
-                    // console.log('Login.password:', login.password, typeof login.password);
-                    // console.log('User.password:', user.password, typeof user.password);
-
                     if (typeof login.password !== 'string' || typeof user.password !== 'string') {
                         return reject('Password and hash must be strings');
                     }
@@ -78,4 +76,14 @@ export class UsersService {
         this.logger.log(`User ${updatedUser.id} updated successfully`);
     }
 
-}
+    // ✅ New method for passkey login
+    public async findByCredentialId(credentialId: string): Promise<RegisteredUser | null> {
+        return (
+            this.users.find(user =>
+                user.devices?.some(
+                    dev => isoBase64URL.fromBuffer(dev.credentialID) === credentialId,
+                )
+            ) ?? null
+        );
+    }
+}

+ 1 - 1
src/users/users.controller.ts

@@ -1,5 +1,5 @@
 import { Controller, Get, Param, NotFoundException } from '@nestjs/common';
-import { UsersService } from 'src/services/user.service';
+import { UsersService } from 'src/users/user.service';
 import { RegisteredUser } from 'src/interface/interface';
 
 @Controller('users')

+ 1 - 1
src/users/users.module.ts

@@ -1,6 +1,6 @@
 // src/services/users.module.ts
 import { Module } from '@nestjs/common';
-import { UsersService } from 'src/services/user.service';
+import { UsersService } from 'src/users/user.service';
 import { UsersController } from 'src/users/users.controller';
 
 @Module({

+ 1 - 0
tsconfig.json

@@ -5,6 +5,7 @@
     "removeComments": true,
     "emitDecoratorMetadata": true,
     "experimentalDecorators": true,
+    "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
     "target": "ES2023",
     "sourceMap": true,