ar-saas 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/templates/backend/.env.example +13 -3
- package/templates/backend/README.md +22 -3
- package/templates/backend/package-lock.json +165 -2
- package/templates/backend/package.json +2 -0
- package/templates/backend/src/app.module.ts +14 -0
- package/templates/backend/src/common/guards/github-auth.guard.ts +5 -0
- package/templates/backend/src/main.ts +2 -2
- package/templates/backend/src/modules/auth/auth.controller.ts +51 -3
- package/templates/backend/src/modules/auth/auth.module.ts +2 -1
- package/templates/backend/src/modules/auth/auth.service.ts +96 -11
- package/templates/backend/src/modules/auth/strategies/github.strategy.ts +46 -0
- package/templates/backend/src/modules/auth/strategies/jwt.strategy.ts +1 -1
- package/templates/backend/src/modules/clients/clients.controller.ts +91 -0
- package/templates/backend/src/modules/clients/clients.module.ts +16 -0
- package/templates/backend/src/modules/clients/clients.repository.ts +14 -0
- package/templates/backend/src/modules/clients/clients.service.ts +52 -0
- package/templates/backend/src/modules/clients/dto/create-client.dto.ts +40 -0
- package/templates/backend/src/modules/clients/dto/query-client.dto.ts +30 -0
- package/templates/backend/src/modules/clients/dto/update-client.dto.ts +4 -0
- package/templates/backend/src/modules/clients/schemas/client.schema.ts +32 -0
- package/templates/backend/src/modules/invoices/dto/create-invoice.dto.ts +79 -0
- package/templates/backend/src/modules/invoices/dto/invoice-item.dto.ts +23 -0
- package/templates/backend/src/modules/invoices/dto/query-invoice.dto.ts +40 -0
- package/templates/backend/src/modules/invoices/dto/update-invoice.dto.ts +4 -0
- package/templates/backend/src/modules/invoices/invoices.controller.ts +91 -0
- package/templates/backend/src/modules/invoices/invoices.module.ts +18 -0
- package/templates/backend/src/modules/invoices/invoices.repository.ts +14 -0
- package/templates/backend/src/modules/invoices/invoices.service.ts +104 -0
- package/templates/backend/src/modules/invoices/schemas/invoice.schema.ts +75 -0
- package/templates/backend/src/modules/notifications/dto/create-notification.dto.ts +45 -0
- package/templates/backend/src/modules/notifications/dto/query-notification.dto.ts +30 -0
- package/templates/backend/src/modules/notifications/dto/update-notification.dto.ts +4 -0
- package/templates/backend/src/modules/notifications/notifications.controller.ts +119 -0
- package/templates/backend/src/modules/notifications/notifications.module.ts +16 -0
- package/templates/backend/src/modules/notifications/notifications.repository.ts +31 -0
- package/templates/backend/src/modules/notifications/notifications.service.ts +64 -0
- package/templates/backend/src/modules/notifications/schemas/notification.schema.ts +38 -0
- package/templates/backend/src/modules/pipeline/dto/create-deal.dto.ts +40 -0
- package/templates/backend/src/modules/pipeline/dto/query-deal.dto.ts +35 -0
- package/templates/backend/src/modules/pipeline/dto/update-deal.dto.ts +4 -0
- package/templates/backend/src/modules/pipeline/pipeline.controller.ts +91 -0
- package/templates/backend/src/modules/pipeline/pipeline.module.ts +18 -0
- package/templates/backend/src/modules/pipeline/pipeline.repository.ts +14 -0
- package/templates/backend/src/modules/pipeline/pipeline.service.ts +64 -0
- package/templates/backend/src/modules/pipeline/schemas/deal.schema.ts +39 -0
- package/templates/backend/src/modules/planner/dto/create-planner-block.dto.ts +66 -0
- package/templates/backend/src/modules/planner/dto/query-planner-block.dto.ts +48 -0
- package/templates/backend/src/modules/planner/dto/update-block-status.dto.ts +10 -0
- package/templates/backend/src/modules/planner/dto/update-planner-block.dto.ts +4 -0
- package/templates/backend/src/modules/planner/planner.controller.ts +124 -0
- package/templates/backend/src/modules/planner/planner.module.ts +16 -0
- package/templates/backend/src/modules/planner/planner.repository.ts +45 -0
- package/templates/backend/src/modules/planner/planner.service.ts +104 -0
- package/templates/backend/src/modules/planner/schemas/planner-block.schema.ts +56 -0
- package/templates/backend/src/modules/task-columns/dto/create-task-column.dto.ts +20 -0
- package/templates/backend/src/modules/task-columns/dto/reorder-columns.dto.ts +9 -0
- package/templates/backend/src/modules/task-columns/dto/update-task-column.dto.ts +4 -0
- package/templates/backend/src/modules/task-columns/schemas/task-column.schema.ts +21 -0
- package/templates/backend/src/modules/task-columns/task-columns.controller.ts +86 -0
- package/templates/backend/src/modules/task-columns/task-columns.module.ts +16 -0
- package/templates/backend/src/modules/task-columns/task-columns.repository.ts +15 -0
- package/templates/backend/src/modules/task-columns/task-columns.service.ts +49 -0
- package/templates/backend/src/modules/tasks/dto/checklist-item.dto.ts +13 -0
- package/templates/backend/src/modules/tasks/dto/create-task.dto.ts +67 -0
- package/templates/backend/src/modules/tasks/dto/label.dto.ts +12 -0
- package/templates/backend/src/modules/tasks/dto/move-task.dto.ts +15 -0
- package/templates/backend/src/modules/tasks/dto/query-task.dto.ts +40 -0
- package/templates/backend/src/modules/tasks/dto/update-task.dto.ts +4 -0
- package/templates/backend/src/modules/tasks/schemas/task.schema.ts +66 -0
- package/templates/backend/src/modules/tasks/tasks.controller.ts +104 -0
- package/templates/backend/src/modules/tasks/tasks.module.ts +18 -0
- package/templates/backend/src/modules/tasks/tasks.repository.ts +14 -0
- package/templates/backend/src/modules/tasks/tasks.service.ts +76 -0
- package/templates/backend/src/modules/users/schemas/user.schema.ts +3 -0
- package/templates/backend/src/modules/users/users.repository.ts +8 -0
- package/templates/backend/src/modules/users/users.service.ts +34 -0
- package/templates/frontend/.env.local.example +1 -1
- package/templates/frontend/README.md +43 -1
- package/templates/frontend/package.json +48 -45
- package/templates/frontend/pnpm-lock.yaml +5096 -5012
- package/templates/frontend/src/app/(auth)/layout.tsx +7 -1
- package/templates/frontend/src/app/(auth)/login/page.tsx +13 -0
- package/templates/frontend/src/app/(auth)/register/page.tsx +13 -0
- package/templates/frontend/src/app/(dashboard)/clients/page.tsx +295 -0
- package/templates/frontend/src/app/(dashboard)/invoices/page.tsx +305 -0
- package/templates/frontend/src/app/(dashboard)/notifications/page.tsx +173 -0
- package/templates/frontend/src/app/(dashboard)/pipeline/page.tsx +244 -0
- package/templates/frontend/src/app/(dashboard)/planner/page.tsx +287 -0
- package/templates/frontend/src/app/(dashboard)/settings/page.tsx +165 -128
- package/templates/frontend/src/app/(dashboard)/tasks/page.tsx +366 -0
- package/templates/frontend/src/app/auth/github/callback/page.tsx +82 -0
- package/templates/frontend/src/app/landing/page.tsx +21 -0
- package/templates/frontend/src/app/page.tsx +5 -5
- package/templates/frontend/src/app/setup/page.tsx +15 -14
- package/templates/frontend/src/components/auth/github-button.tsx +25 -0
- package/templates/frontend/src/components/dashboard/sidebar.tsx +90 -71
- package/templates/frontend/src/components/ui/alert-dialog.tsx +141 -0
- package/templates/frontend/src/components/ui/button.tsx +56 -52
- package/templates/frontend/src/components/ui/popover.tsx +31 -0
- package/templates/frontend/src/components/ui/select.tsx +160 -0
- package/templates/frontend/src/components/ui/sheet.tsx +140 -0
- package/templates/frontend/src/lib/api/auth.ts +7 -0
- package/templates/frontend/src/lib/api/clients.ts +17 -0
- package/templates/frontend/src/lib/api/invoices.ts +18 -0
- package/templates/frontend/src/lib/api/notifications.ts +27 -0
- package/templates/frontend/src/lib/api/pipeline.ts +18 -0
- package/templates/frontend/src/lib/api/planner.ts +26 -0
- package/templates/frontend/src/lib/api/task-columns.ts +17 -0
- package/templates/frontend/src/lib/api/tasks.ts +21 -0
- package/templates/frontend/src/lib/hooks/use-unread-notifications.ts +23 -0
- package/templates/frontend/src/providers/auth-provider.tsx +7 -1
- package/templates/frontend/src/types/clients.ts +38 -0
- package/templates/frontend/src/types/invoices.ts +51 -0
- package/templates/frontend/src/types/notifications.ts +30 -0
- package/templates/frontend/src/types/pipeline.ts +35 -0
- package/templates/frontend/src/types/planner.ts +49 -0
- package/templates/frontend/src/types/tasks.ts +65 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ar-saas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Generador de proyectos SaaS multi-tenant para startups argentinas. Landing page, auth, dashboard y legal listos para producción.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -51,3 +51,4 @@
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
NODE_ENV=development
|
|
11
11
|
|
|
12
12
|
# Puerto donde corre el servidor
|
|
13
|
-
PORT=
|
|
13
|
+
PORT=3001
|
|
14
14
|
|
|
15
15
|
# Prefijo para todas las rutas de la API
|
|
16
16
|
API_PREFIX=api
|
|
@@ -47,7 +47,7 @@ RESEND_FROM_NAME="Tu App"
|
|
|
47
47
|
|
|
48
48
|
# ===== App URL =====
|
|
49
49
|
# URL del frontend (para links en emails de verificación, reset, etc)
|
|
50
|
-
APP_URL=http://localhost:
|
|
50
|
+
APP_URL=http://localhost:3000
|
|
51
51
|
|
|
52
52
|
# ===== Swagger =====
|
|
53
53
|
# Habilitar documentación Swagger en /api/docs (solo development)
|
|
@@ -55,7 +55,7 @@ SWAGGER_ENABLED=true
|
|
|
55
55
|
|
|
56
56
|
# ===== CORS =====
|
|
57
57
|
# Orígenes permitidos (separados por coma)
|
|
58
|
-
CORS_ORIGINS=http://localhost:
|
|
58
|
+
CORS_ORIGINS=http://localhost:3000
|
|
59
59
|
|
|
60
60
|
# ===== Rate Limiting =====
|
|
61
61
|
# Cantidad máxima de requests por ventana de tiempo
|
|
@@ -65,3 +65,13 @@ THROTTLE_LIMIT=100
|
|
|
65
65
|
# ===== Cookies =====
|
|
66
66
|
# Secreto para firmar cookies (usar openssl rand -hex 32 para generar)
|
|
67
67
|
COOKIE_SECRET=cambiar-por-secreto-seguro
|
|
68
|
+
|
|
69
|
+
# ===== GitHub OAuth =====
|
|
70
|
+
# Crear en https://github.com/settings/applications/new
|
|
71
|
+
# Authorization callback URL: http://localhost:3001/api/auth/github/callback
|
|
72
|
+
GITHUB_CLIENT_ID=cambiar-por-client-id
|
|
73
|
+
GITHUB_CLIENT_SECRET=cambiar-por-client-secret
|
|
74
|
+
GITHUB_CALLBACK_URL=http://localhost:3001/api/auth/github/callback
|
|
75
|
+
|
|
76
|
+
# URL del frontend (para redirigir después del OAuth)
|
|
77
|
+
FRONTEND_URL=http://localhost:3000
|
|
@@ -39,7 +39,7 @@ todo lo genérico ya resuelto, más integraciones reales para operar en Argentin
|
|
|
39
39
|
| ODM | Mongoose | 9 |
|
|
40
40
|
| Auth (tokens) | JWT + cookies HttpOnly | — |
|
|
41
41
|
| Auth (passwords) | bcrypt | 5.x |
|
|
42
|
-
| Auth (passport) | passport + passport-jwt | 0.7 / 4.x |
|
|
42
|
+
| Auth (passport) | passport + passport-jwt + passport-github2 | 0.7 / 4.x / 0.1.x |
|
|
43
43
|
| Validación | class-validator | 0.14 |
|
|
44
44
|
| Transformación | class-transformer | 0.5 |
|
|
45
45
|
| Emails | Resend | 4.x |
|
|
@@ -53,7 +53,7 @@ todo lo genérico ya resuelto, más integraciones reales para operar en Argentin
|
|
|
53
53
|
|
|
54
54
|
- **Multi-tenancy real** — Aislamiento por `workspaceId` garantizado en cada query. El `workspaceId` se extrae del JWT verificado del usuario autenticado. `BaseRepository` fuerza el filtro en toda operación. Imposible leakear datos entre workspaces por error humano.
|
|
55
55
|
|
|
56
|
-
- **Auth completo** — Registro, login, refresh token rotativo, email verification, password reset
|
|
56
|
+
- **Auth completo** — Registro, login, refresh token rotativo, email verification, password reset y **OAuth con GitHub**. Access + refresh tokens en cookies `HttpOnly`, `Secure`. `SameSite=none` + `Partitioned` en producción para soporte cross-domain. Con rate limiting en endpoints sensibles y Helmet para headers de seguridad. Nunca en `localStorage` ni en el body.
|
|
57
57
|
|
|
58
58
|
- **BaseRepository genérico** — Soft delete, paginación, filtros dinámicos, agregaciones, conteo, upsert. Manejo automático de errores MongoDB (duplicate key, cast error). 12 métodos heredados por todos los repositorios.
|
|
59
59
|
|
|
@@ -124,6 +124,23 @@ Instalá MongoDB Community Edition siguiendo las instrucciones oficiales:
|
|
|
124
124
|
| `RESEND_FROM_EMAIL` | Email remitente verificado en Resend |
|
|
125
125
|
| `APP_URL` | URL del frontend: `http://localhost:3001` |
|
|
126
126
|
| `CORS_ORIGINS` | URL del frontend (misma que APP_URL): `http://localhost:3001` |
|
|
127
|
+
| `GITHUB_CLIENT_ID` | Client ID de la GitHub OAuth App |
|
|
128
|
+
| `GITHUB_CLIENT_SECRET` | Client Secret de la GitHub OAuth App |
|
|
129
|
+
| `GITHUB_CALLBACK_URL` | `http://localhost:3001/api/auth/github/callback` en desarrollo |
|
|
130
|
+
| `FRONTEND_URL` | URL del frontend para redirigir después del OAuth: `http://localhost:3000` |
|
|
131
|
+
|
|
132
|
+
**Configurar GitHub OAuth:**
|
|
133
|
+
|
|
134
|
+
1. Ir a [github.com/settings/applications/new](https://github.com/settings/applications/new)
|
|
135
|
+
2. Completar:
|
|
136
|
+
- **Application name**: nombre de tu app
|
|
137
|
+
- **Homepage URL**: `http://localhost:3000`
|
|
138
|
+
- **Authorization callback URL**: `http://localhost:3001/api/auth/github/callback`
|
|
139
|
+
3. Hacer click en **Register application**
|
|
140
|
+
4. Copiar el **Client ID** → `GITHUB_CLIENT_ID`
|
|
141
|
+
5. Generar un **Client Secret** → `GITHUB_CLIENT_SECRET`
|
|
142
|
+
|
|
143
|
+
En producción, crear una segunda OAuth App con las URLs de producción, o actualizar la existente.
|
|
127
144
|
|
|
128
145
|
**Generar JWT secrets:**
|
|
129
146
|
|
|
@@ -167,6 +184,8 @@ El servidor corre en `http://localhost:3000/api`. Swagger en `http://localhost:3
|
|
|
167
184
|
| `secretOrPrivateKey must have a value` | `JWT_ACCESS_SECRET` o `JWT_REFRESH_SECRET` vacíos en `.env` |
|
|
168
185
|
| Error de CORS en el frontend | `CORS_ORIGINS` en `.env` no incluye `http://localhost:3001` |
|
|
169
186
|
| Emails no llegan | Verificar `RESEND_API_KEY` y que `RESEND_FROM_EMAIL` esté verificado en Resend |
|
|
187
|
+
| GitHub OAuth: `redirect_uri_mismatch` | La callback URL en la GitHub App no coincide con `GITHUB_CALLBACK_URL` en `.env` |
|
|
188
|
+
| GitHub OAuth: `No public email on GitHub account` | El usuario de GitHub tiene el email en privado — debe hacerlo público en [github.com/settings/profile](https://github.com/settings/profile) |
|
|
170
189
|
|
|
171
190
|
## Comandos disponibles
|
|
172
191
|
|
|
@@ -199,7 +218,7 @@ src/
|
|
|
199
218
|
│ └── interceptors/
|
|
200
219
|
│ └── workspace-tenant.interceptor.ts # Extrae workspaceId del JWT verificado
|
|
201
220
|
├── modules/
|
|
202
|
-
│ ├── auth/ # Registro, login, refresh, email verification, password reset
|
|
221
|
+
│ ├── auth/ # Registro, login, refresh, email verification, password reset, GitHub OAuth
|
|
203
222
|
│ ├── users/ # CRUD de usuarios
|
|
204
223
|
│ ├── workspaces/ # CRUD de workspaces
|
|
205
224
|
│ └── mail/ # Envío de emails con Resend (@Global)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "ar-saas-backend",
|
|
3
3
|
"version": "0.0.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
|
-
"name": "
|
|
8
|
+
"name": "ar-saas-backend",
|
|
9
9
|
"version": "0.0.1",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"dependencies": {
|
|
@@ -18,12 +18,16 @@
|
|
|
18
18
|
"@nestjs/platform-express": "^11.0.1",
|
|
19
19
|
"@nestjs/schedule": "^6.0.1",
|
|
20
20
|
"@nestjs/swagger": "^11.2.1",
|
|
21
|
+
"@nestjs/throttler": "^6.0.0",
|
|
21
22
|
"bcryptjs": "^2.4.3",
|
|
22
23
|
"class-transformer": "^0.5.1",
|
|
23
24
|
"class-validator": "^0.14.2",
|
|
24
25
|
"cookie-parser": "^1.4.7",
|
|
26
|
+
"helmet": "^8.0.0",
|
|
27
|
+
"joi": "^17.13.3",
|
|
25
28
|
"mongoose": "^9.0.1",
|
|
26
29
|
"passport": "^0.7.0",
|
|
30
|
+
"passport-github2": "^0.1.12",
|
|
27
31
|
"passport-jwt": "^4.0.1",
|
|
28
32
|
"reflect-metadata": "^0.2.2",
|
|
29
33
|
"resend": "^4.1.2",
|
|
@@ -40,6 +44,7 @@
|
|
|
40
44
|
"@types/express": "^5.0.0",
|
|
41
45
|
"@types/jest": "^30.0.0",
|
|
42
46
|
"@types/node": "^22.10.7",
|
|
47
|
+
"@types/passport-github2": "^1.2.9",
|
|
43
48
|
"@types/passport-jwt": "^4.0.1",
|
|
44
49
|
"@types/supertest": "^6.0.2",
|
|
45
50
|
"eslint": "^9.18.0",
|
|
@@ -954,6 +959,21 @@
|
|
|
954
959
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
955
960
|
}
|
|
956
961
|
},
|
|
962
|
+
"node_modules/@hapi/hoek": {
|
|
963
|
+
"version": "9.3.0",
|
|
964
|
+
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
|
965
|
+
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
|
|
966
|
+
"license": "BSD-3-Clause"
|
|
967
|
+
},
|
|
968
|
+
"node_modules/@hapi/topo": {
|
|
969
|
+
"version": "5.1.0",
|
|
970
|
+
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
|
|
971
|
+
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
|
|
972
|
+
"license": "BSD-3-Clause",
|
|
973
|
+
"dependencies": {
|
|
974
|
+
"@hapi/hoek": "^9.0.0"
|
|
975
|
+
}
|
|
976
|
+
},
|
|
957
977
|
"node_modules/@humanfs/core": {
|
|
958
978
|
"version": "0.19.2",
|
|
959
979
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
|
@@ -2595,6 +2615,17 @@
|
|
|
2595
2615
|
}
|
|
2596
2616
|
}
|
|
2597
2617
|
},
|
|
2618
|
+
"node_modules/@nestjs/throttler": {
|
|
2619
|
+
"version": "6.5.0",
|
|
2620
|
+
"resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.5.0.tgz",
|
|
2621
|
+
"integrity": "sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==",
|
|
2622
|
+
"license": "MIT",
|
|
2623
|
+
"peerDependencies": {
|
|
2624
|
+
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
2625
|
+
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
2626
|
+
"reflect-metadata": "^0.1.13 || ^0.2.0"
|
|
2627
|
+
}
|
|
2628
|
+
},
|
|
2598
2629
|
"node_modules/@noble/hashes": {
|
|
2599
2630
|
"version": "1.8.0",
|
|
2600
2631
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
|
@@ -2696,6 +2727,27 @@
|
|
|
2696
2727
|
"url": "https://ko-fi.com/killymxi"
|
|
2697
2728
|
}
|
|
2698
2729
|
},
|
|
2730
|
+
"node_modules/@sideway/address": {
|
|
2731
|
+
"version": "4.1.5",
|
|
2732
|
+
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
|
2733
|
+
"integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
|
|
2734
|
+
"license": "BSD-3-Clause",
|
|
2735
|
+
"dependencies": {
|
|
2736
|
+
"@hapi/hoek": "^9.0.0"
|
|
2737
|
+
}
|
|
2738
|
+
},
|
|
2739
|
+
"node_modules/@sideway/formula": {
|
|
2740
|
+
"version": "3.0.1",
|
|
2741
|
+
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
|
|
2742
|
+
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
|
|
2743
|
+
"license": "BSD-3-Clause"
|
|
2744
|
+
},
|
|
2745
|
+
"node_modules/@sideway/pinpoint": {
|
|
2746
|
+
"version": "2.0.0",
|
|
2747
|
+
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
|
|
2748
|
+
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
|
|
2749
|
+
"license": "BSD-3-Clause"
|
|
2750
|
+
},
|
|
2699
2751
|
"node_modules/@sinclair/typebox": {
|
|
2700
2752
|
"version": "0.34.49",
|
|
2701
2753
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz",
|
|
@@ -3019,6 +3071,16 @@
|
|
|
3019
3071
|
"undici-types": "~6.21.0"
|
|
3020
3072
|
}
|
|
3021
3073
|
},
|
|
3074
|
+
"node_modules/@types/oauth": {
|
|
3075
|
+
"version": "0.9.6",
|
|
3076
|
+
"resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz",
|
|
3077
|
+
"integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==",
|
|
3078
|
+
"dev": true,
|
|
3079
|
+
"license": "MIT",
|
|
3080
|
+
"dependencies": {
|
|
3081
|
+
"@types/node": "*"
|
|
3082
|
+
}
|
|
3083
|
+
},
|
|
3022
3084
|
"node_modules/@types/passport": {
|
|
3023
3085
|
"version": "1.0.17",
|
|
3024
3086
|
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz",
|
|
@@ -3029,6 +3091,18 @@
|
|
|
3029
3091
|
"@types/express": "*"
|
|
3030
3092
|
}
|
|
3031
3093
|
},
|
|
3094
|
+
"node_modules/@types/passport-github2": {
|
|
3095
|
+
"version": "1.2.9",
|
|
3096
|
+
"resolved": "https://registry.npmjs.org/@types/passport-github2/-/passport-github2-1.2.9.tgz",
|
|
3097
|
+
"integrity": "sha512-/nMfiPK2E6GKttwBzwj0Wjaot8eHrM57hnWxu52o6becr5/kXlH/4yE2v2rh234WGvSgEEzIII02Nc5oC5xEHA==",
|
|
3098
|
+
"dev": true,
|
|
3099
|
+
"license": "MIT",
|
|
3100
|
+
"dependencies": {
|
|
3101
|
+
"@types/express": "*",
|
|
3102
|
+
"@types/passport": "*",
|
|
3103
|
+
"@types/passport-oauth2": "*"
|
|
3104
|
+
}
|
|
3105
|
+
},
|
|
3032
3106
|
"node_modules/@types/passport-jwt": {
|
|
3033
3107
|
"version": "4.0.1",
|
|
3034
3108
|
"resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz",
|
|
@@ -3040,6 +3114,18 @@
|
|
|
3040
3114
|
"@types/passport-strategy": "*"
|
|
3041
3115
|
}
|
|
3042
3116
|
},
|
|
3117
|
+
"node_modules/@types/passport-oauth2": {
|
|
3118
|
+
"version": "1.8.0",
|
|
3119
|
+
"resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
|
|
3120
|
+
"integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==",
|
|
3121
|
+
"dev": true,
|
|
3122
|
+
"license": "MIT",
|
|
3123
|
+
"dependencies": {
|
|
3124
|
+
"@types/express": "*",
|
|
3125
|
+
"@types/oauth": "*",
|
|
3126
|
+
"@types/passport": "*"
|
|
3127
|
+
}
|
|
3128
|
+
},
|
|
3043
3129
|
"node_modules/@types/passport-strategy": {
|
|
3044
3130
|
"version": "0.2.38",
|
|
3045
3131
|
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz",
|
|
@@ -4319,6 +4405,15 @@
|
|
|
4319
4405
|
],
|
|
4320
4406
|
"license": "MIT"
|
|
4321
4407
|
},
|
|
4408
|
+
"node_modules/base64url": {
|
|
4409
|
+
"version": "3.0.1",
|
|
4410
|
+
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
|
|
4411
|
+
"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==",
|
|
4412
|
+
"license": "MIT",
|
|
4413
|
+
"engines": {
|
|
4414
|
+
"node": ">=6.0.0"
|
|
4415
|
+
}
|
|
4416
|
+
},
|
|
4322
4417
|
"node_modules/baseline-browser-mapping": {
|
|
4323
4418
|
"version": "2.10.34",
|
|
4324
4419
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz",
|
|
@@ -6416,6 +6511,18 @@
|
|
|
6416
6511
|
"node": ">= 0.4"
|
|
6417
6512
|
}
|
|
6418
6513
|
},
|
|
6514
|
+
"node_modules/helmet": {
|
|
6515
|
+
"version": "8.2.0",
|
|
6516
|
+
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.2.0.tgz",
|
|
6517
|
+
"integrity": "sha512-DRgTIUgnWcJ62KyarxxziuqYxKGnR6Rgg19BlbucN/dpmJbl1XOit6qvoOX0ZT+HhWe5OUVhU/a1zpGyc1xA0Q==",
|
|
6518
|
+
"license": "MIT",
|
|
6519
|
+
"engines": {
|
|
6520
|
+
"node": ">=18.0.0"
|
|
6521
|
+
},
|
|
6522
|
+
"funding": {
|
|
6523
|
+
"url": "https://github.com/sponsors/EvanHahn"
|
|
6524
|
+
}
|
|
6525
|
+
},
|
|
6419
6526
|
"node_modules/html-escaper": {
|
|
6420
6527
|
"version": "2.0.2",
|
|
6421
6528
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
|
@@ -7558,6 +7665,19 @@
|
|
|
7558
7665
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
|
7559
7666
|
}
|
|
7560
7667
|
},
|
|
7668
|
+
"node_modules/joi": {
|
|
7669
|
+
"version": "17.13.3",
|
|
7670
|
+
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
|
|
7671
|
+
"integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
|
|
7672
|
+
"license": "BSD-3-Clause",
|
|
7673
|
+
"dependencies": {
|
|
7674
|
+
"@hapi/hoek": "^9.3.0",
|
|
7675
|
+
"@hapi/topo": "^5.1.0",
|
|
7676
|
+
"@sideway/address": "^4.1.5",
|
|
7677
|
+
"@sideway/formula": "^3.0.1",
|
|
7678
|
+
"@sideway/pinpoint": "^2.0.0"
|
|
7679
|
+
}
|
|
7680
|
+
},
|
|
7561
7681
|
"node_modules/js-tokens": {
|
|
7562
7682
|
"version": "4.0.0",
|
|
7563
7683
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
@@ -8406,6 +8526,12 @@
|
|
|
8406
8526
|
"node": ">=8"
|
|
8407
8527
|
}
|
|
8408
8528
|
},
|
|
8529
|
+
"node_modules/oauth": {
|
|
8530
|
+
"version": "0.10.2",
|
|
8531
|
+
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz",
|
|
8532
|
+
"integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==",
|
|
8533
|
+
"license": "MIT"
|
|
8534
|
+
},
|
|
8409
8535
|
"node_modules/object-assign": {
|
|
8410
8536
|
"version": "4.1.1",
|
|
8411
8537
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
|
@@ -8627,6 +8753,17 @@
|
|
|
8627
8753
|
"url": "https://github.com/sponsors/jaredhanson"
|
|
8628
8754
|
}
|
|
8629
8755
|
},
|
|
8756
|
+
"node_modules/passport-github2": {
|
|
8757
|
+
"version": "0.1.12",
|
|
8758
|
+
"resolved": "https://registry.npmjs.org/passport-github2/-/passport-github2-0.1.12.tgz",
|
|
8759
|
+
"integrity": "sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==",
|
|
8760
|
+
"dependencies": {
|
|
8761
|
+
"passport-oauth2": "1.x.x"
|
|
8762
|
+
},
|
|
8763
|
+
"engines": {
|
|
8764
|
+
"node": ">= 0.8.0"
|
|
8765
|
+
}
|
|
8766
|
+
},
|
|
8630
8767
|
"node_modules/passport-jwt": {
|
|
8631
8768
|
"version": "4.0.1",
|
|
8632
8769
|
"resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz",
|
|
@@ -8637,6 +8774,26 @@
|
|
|
8637
8774
|
"passport-strategy": "^1.0.0"
|
|
8638
8775
|
}
|
|
8639
8776
|
},
|
|
8777
|
+
"node_modules/passport-oauth2": {
|
|
8778
|
+
"version": "1.8.0",
|
|
8779
|
+
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
|
|
8780
|
+
"integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==",
|
|
8781
|
+
"license": "MIT",
|
|
8782
|
+
"dependencies": {
|
|
8783
|
+
"base64url": "3.x.x",
|
|
8784
|
+
"oauth": "0.10.x",
|
|
8785
|
+
"passport-strategy": "1.x.x",
|
|
8786
|
+
"uid2": "0.0.x",
|
|
8787
|
+
"utils-merge": "1.x.x"
|
|
8788
|
+
},
|
|
8789
|
+
"engines": {
|
|
8790
|
+
"node": ">= 0.4.0"
|
|
8791
|
+
},
|
|
8792
|
+
"funding": {
|
|
8793
|
+
"type": "github",
|
|
8794
|
+
"url": "https://github.com/sponsors/jaredhanson"
|
|
8795
|
+
}
|
|
8796
|
+
},
|
|
8640
8797
|
"node_modules/passport-strategy": {
|
|
8641
8798
|
"version": "1.0.0",
|
|
8642
8799
|
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
|
@@ -10405,6 +10562,12 @@
|
|
|
10405
10562
|
"node": ">=8"
|
|
10406
10563
|
}
|
|
10407
10564
|
},
|
|
10565
|
+
"node_modules/uid2": {
|
|
10566
|
+
"version": "0.0.4",
|
|
10567
|
+
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
|
|
10568
|
+
"integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==",
|
|
10569
|
+
"license": "MIT"
|
|
10570
|
+
},
|
|
10408
10571
|
"node_modules/uint8array-extras": {
|
|
10409
10572
|
"version": "1.5.0",
|
|
10410
10573
|
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"joi": "^17.13.3",
|
|
39
39
|
"mongoose": "^9.0.1",
|
|
40
40
|
"passport": "^0.7.0",
|
|
41
|
+
"passport-github2": "^0.1.12",
|
|
41
42
|
"passport-jwt": "^4.0.1",
|
|
42
43
|
"reflect-metadata": "^0.2.2",
|
|
43
44
|
"resend": "^4.1.2",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"@types/express": "^5.0.0",
|
|
55
56
|
"@types/jest": "^30.0.0",
|
|
56
57
|
"@types/node": "^22.10.7",
|
|
58
|
+
"@types/passport-github2": "^1.2.9",
|
|
57
59
|
"@types/passport-jwt": "^4.0.1",
|
|
58
60
|
"@types/supertest": "^6.0.2",
|
|
59
61
|
"eslint": "^9.18.0",
|
|
@@ -8,7 +8,14 @@ import { AppController } from './app.controller';
|
|
|
8
8
|
import { AppService } from './app.service';
|
|
9
9
|
import { GlobalExceptionFilter } from './common/filters/global-exception.filter';
|
|
10
10
|
import { AuthModule } from './modules/auth/auth.module';
|
|
11
|
+
import { ClientsModule } from './modules/clients/clients.module';
|
|
12
|
+
import { InvoicesModule } from './modules/invoices/invoices.module';
|
|
11
13
|
import { MailModule } from './modules/mail/mail.module';
|
|
14
|
+
import { NotificationsModule } from './modules/notifications/notifications.module';
|
|
15
|
+
import { PipelineModule } from './modules/pipeline/pipeline.module';
|
|
16
|
+
import { PlannerModule } from './modules/planner/planner.module';
|
|
17
|
+
import { TaskColumnsModule } from './modules/task-columns/task-columns.module';
|
|
18
|
+
import { TasksModule } from './modules/tasks/tasks.module';
|
|
12
19
|
import { UsersModule } from './modules/users/users.module';
|
|
13
20
|
import { WorkspacesModule } from './modules/workspaces/workspaces.module';
|
|
14
21
|
|
|
@@ -51,6 +58,13 @@ import { WorkspacesModule } from './modules/workspaces/workspaces.module';
|
|
|
51
58
|
UsersModule,
|
|
52
59
|
WorkspacesModule,
|
|
53
60
|
AuthModule,
|
|
61
|
+
ClientsModule,
|
|
62
|
+
NotificationsModule,
|
|
63
|
+
InvoicesModule,
|
|
64
|
+
PipelineModule,
|
|
65
|
+
TaskColumnsModule,
|
|
66
|
+
TasksModule,
|
|
67
|
+
PlannerModule,
|
|
54
68
|
],
|
|
55
69
|
controllers: [AppController],
|
|
56
70
|
providers: [
|
|
@@ -15,7 +15,7 @@ async function bootstrap() {
|
|
|
15
15
|
app.use(cookieParser(process.env.COOKIE_SECRET));
|
|
16
16
|
|
|
17
17
|
app.enableCors({
|
|
18
|
-
origin: process.env.CORS_ORIGINS?.split(',') ?? ['http://localhost:
|
|
18
|
+
origin: process.env.CORS_ORIGINS?.split(',') ?? ['http://localhost:3000'],
|
|
19
19
|
credentials: true,
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -42,7 +42,7 @@ async function bootstrap() {
|
|
|
42
42
|
SwaggerModule.setup(`${apiPrefix}/docs`, app, document);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const port = process.env.PORT ??
|
|
45
|
+
const port = process.env.PORT ?? 3001;
|
|
46
46
|
await app.listen(port);
|
|
47
47
|
console.log(`🚀 Servidor corriendo en http://localhost:${port}/${apiPrefix}`);
|
|
48
48
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
HttpStatus,
|
|
7
7
|
Post,
|
|
8
8
|
Query,
|
|
9
|
+
Req,
|
|
9
10
|
Res,
|
|
10
11
|
UnauthorizedException,
|
|
11
12
|
UseGuards,
|
|
@@ -13,11 +14,12 @@ import {
|
|
|
13
14
|
import { ConfigService } from '@nestjs/config';
|
|
14
15
|
import { Throttle } from '@nestjs/throttler';
|
|
15
16
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
|
16
|
-
import type { Response } from 'express';
|
|
17
|
+
import type { Request, Response } from 'express';
|
|
17
18
|
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
|
18
19
|
import type { TokenPayload } from '../../common/decorators/current-user.decorator';
|
|
19
20
|
import { Cookie } from '../../common/decorators/cookie.decorator';
|
|
20
21
|
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
|
22
|
+
import { GithubAuthGuard } from '../../common/guards/github-auth.guard';
|
|
21
23
|
import { AuthService } from './auth.service';
|
|
22
24
|
import { ForgotPasswordDto } from './dto/forgot-password.dto';
|
|
23
25
|
import { LoginDto } from './dto/login.dto';
|
|
@@ -25,6 +27,11 @@ import { RefreshTokenDto } from './dto/refresh-token.dto';
|
|
|
25
27
|
import { RegisterDto } from './dto/register.dto';
|
|
26
28
|
import { ResetPasswordDto } from './dto/reset-password.dto';
|
|
27
29
|
import { VerifyEmailDto } from './dto/verify-email.dto';
|
|
30
|
+
import { GithubProfile } from './strategies/github.strategy';
|
|
31
|
+
|
|
32
|
+
interface RequestWithGithubUser extends Request {
|
|
33
|
+
user: GithubProfile;
|
|
34
|
+
}
|
|
28
35
|
|
|
29
36
|
@ApiTags('Auth')
|
|
30
37
|
@Controller('auth')
|
|
@@ -121,6 +128,45 @@ export class AuthController {
|
|
|
121
128
|
return this.authService.getMe(user.userId);
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
@Get('github')
|
|
132
|
+
@UseGuards(GithubAuthGuard)
|
|
133
|
+
@ApiOperation({ summary: 'Iniciar autenticación con GitHub' })
|
|
134
|
+
githubLogin(): void {}
|
|
135
|
+
|
|
136
|
+
@Get('github/callback')
|
|
137
|
+
@UseGuards(GithubAuthGuard)
|
|
138
|
+
@ApiOperation({ summary: 'Callback de GitHub OAuth' })
|
|
139
|
+
async githubCallback(
|
|
140
|
+
@Req() req: RequestWithGithubUser,
|
|
141
|
+
@Res() res: Response,
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
const frontendUrl = this.configService.getOrThrow<string>('FRONTEND_URL');
|
|
144
|
+
try {
|
|
145
|
+
const { code, alreadyExisted } = await this.authService.githubLogin(req.user);
|
|
146
|
+
const info = alreadyExisted ? '&info=already_exists' : '';
|
|
147
|
+
res.redirect(`${frontendUrl}/auth/github/callback?code=${code}${info}`);
|
|
148
|
+
} catch {
|
|
149
|
+
res.redirect(`${frontendUrl}/auth/github/callback?error=github_failed`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@Post('github/exchange')
|
|
154
|
+
@HttpCode(HttpStatus.OK)
|
|
155
|
+
@Throttle({ default: { limit: 10, ttl: 60000 } })
|
|
156
|
+
@ApiOperation({ summary: 'Intercambiar código OAuth por sesión' })
|
|
157
|
+
async githubExchange(
|
|
158
|
+
@Body('code') code: string,
|
|
159
|
+
@Res({ passthrough: true }) res: Response,
|
|
160
|
+
) {
|
|
161
|
+
if (!code) {
|
|
162
|
+
throw new UnauthorizedException('Código requerido.');
|
|
163
|
+
}
|
|
164
|
+
const { alreadyExisted, accessToken, refreshToken } =
|
|
165
|
+
await this.authService.exchangeGithubCode(code);
|
|
166
|
+
this.setTokenCookies(res, accessToken, refreshToken);
|
|
167
|
+
return { success: true, alreadyExisted };
|
|
168
|
+
}
|
|
169
|
+
|
|
124
170
|
private setTokenCookies(
|
|
125
171
|
res: Response,
|
|
126
172
|
accessToken: string,
|
|
@@ -130,7 +176,8 @@ export class AuthController {
|
|
|
130
176
|
const base = {
|
|
131
177
|
httpOnly: true,
|
|
132
178
|
secure: isProd,
|
|
133
|
-
sameSite: '
|
|
179
|
+
sameSite: (isProd ? 'none' : 'lax') as 'none' | 'lax',
|
|
180
|
+
...(isProd && { partitioned: true }),
|
|
134
181
|
};
|
|
135
182
|
|
|
136
183
|
res.cookie('access_token', accessToken, {
|
|
@@ -150,7 +197,8 @@ export class AuthController {
|
|
|
150
197
|
const base = {
|
|
151
198
|
httpOnly: true,
|
|
152
199
|
secure: isProd,
|
|
153
|
-
sameSite: '
|
|
200
|
+
sameSite: (isProd ? 'none' : 'lax') as 'none' | 'lax',
|
|
201
|
+
...(isProd && { partitioned: true }),
|
|
154
202
|
};
|
|
155
203
|
|
|
156
204
|
res.clearCookie('access_token', base);
|
|
@@ -6,6 +6,7 @@ import { WorkspacesModule } from '../workspaces/workspaces.module';
|
|
|
6
6
|
import { AuthController } from './auth.controller';
|
|
7
7
|
import { AuthService } from './auth.service';
|
|
8
8
|
import { JwtStrategy } from './strategies/jwt.strategy';
|
|
9
|
+
import { GithubStrategy } from './strategies/github.strategy';
|
|
9
10
|
|
|
10
11
|
@Module({
|
|
11
12
|
imports: [
|
|
@@ -15,6 +16,6 @@ import { JwtStrategy } from './strategies/jwt.strategy';
|
|
|
15
16
|
WorkspacesModule,
|
|
16
17
|
],
|
|
17
18
|
controllers: [AuthController],
|
|
18
|
-
providers: [AuthService, JwtStrategy],
|
|
19
|
+
providers: [AuthService, JwtStrategy, GithubStrategy],
|
|
19
20
|
})
|
|
20
21
|
export class AuthModule {}
|