uade-mcp 0.0.0-stage → 0.1.3
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/LICENSE +21 -0
- package/README.md +196 -2
- package/SECURITY.md +62 -0
- package/dist/auth/confirm.js +59 -0
- package/dist/auth/confirm.js.map +1 -0
- package/dist/auth/login.js +153 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/permissions.js +54 -0
- package/dist/auth/permissions.js.map +1 -0
- package/dist/auth/store.js +26 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/cache/cache.js +124 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cli/doctor.js +175 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.js +114 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/login.js +20 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/net/browser.js +31 -0
- package/dist/net/browser.js.map +1 -0
- package/dist/net/http.js +109 -0
- package/dist/net/http.js.map +1 -0
- package/dist/net/open-browser.js +80 -0
- package/dist/net/open-browser.js.map +1 -0
- package/dist/net/policy.js +61 -0
- package/dist/net/policy.js.map +1 -0
- package/dist/net/session.js +111 -0
- package/dist/net/session.js.map +1 -0
- package/dist/report/contexto.js +5 -0
- package/dist/report/contexto.js.map +1 -0
- package/dist/report/errores.js +79 -0
- package/dist/report/errores.js.map +1 -0
- package/dist/sources/calendario/index.js +74 -0
- package/dist/sources/calendario/index.js.map +1 -0
- package/dist/sources/docs/leer.js +82 -0
- package/dist/sources/docs/leer.js.map +1 -0
- package/dist/sources/inscripciones/index.js +107 -0
- package/dist/sources/inscripciones/index.js.map +1 -0
- package/dist/sources/mail/draft.js +40 -0
- package/dist/sources/mail/draft.js.map +1 -0
- package/dist/sources/pia/alta.js +48 -0
- package/dist/sources/pia/alta.js.map +1 -0
- package/dist/sources/pia/index.js +74 -0
- package/dist/sources/pia/index.js.map +1 -0
- package/dist/sources/pia/parse.js +235 -0
- package/dist/sources/pia/parse.js.map +1 -0
- package/dist/sources/planificador/elegibilidad.js +50 -0
- package/dist/sources/planificador/elegibilidad.js.map +1 -0
- package/dist/sources/planificador/index.js +174 -0
- package/dist/sources/planificador/index.js.map +1 -0
- package/dist/sources/planificador/semana.js +120 -0
- package/dist/sources/planificador/semana.js.map +1 -0
- package/dist/sources/planuade/link.js +56 -0
- package/dist/sources/planuade/link.js.map +1 -0
- package/dist/sources/planuade/planes.js +93 -0
- package/dist/sources/planuade/planes.js.map +1 -0
- package/dist/sources/sharepoint/index.js +100 -0
- package/dist/sources/sharepoint/index.js.map +1 -0
- package/dist/sources/teams/index.js +110 -0
- package/dist/sources/teams/index.js.map +1 -0
- package/dist/sources/webcampus/aranceles.js +91 -0
- package/dist/sources/webcampus/aranceles.js.map +1 -0
- package/dist/sources/webcampus/client.js +11 -0
- package/dist/sources/webcampus/client.js.map +1 -0
- package/dist/sources/webcampus/correlativas.js +43 -0
- package/dist/sources/webcampus/correlativas.js.map +1 -0
- package/dist/sources/webcampus/index.js +144 -0
- package/dist/sources/webcampus/index.js.map +1 -0
- package/dist/sources/webcampus/novedades.js +72 -0
- package/dist/sources/webcampus/novedades.js.map +1 -0
- package/dist/sources/webcampus/parse.js +427 -0
- package/dist/sources/webcampus/parse.js.map +1 -0
- package/dist/sources/webcampus/previos.js +107 -0
- package/dist/sources/webcampus/previos.js.map +1 -0
- package/dist/sources/webcampus/secciones.js +137 -0
- package/dist/sources/webcampus/secciones.js.map +1 -0
- package/dist/tools/cache.js +16 -0
- package/dist/tools/cache.js.map +1 -0
- package/dist/tools/calendario.js +24 -0
- package/dist/tools/calendario.js.map +1 -0
- package/dist/tools/common.js +65 -0
- package/dist/tools/common.js.map +1 -0
- package/dist/tools/inscripciones.js +234 -0
- package/dist/tools/inscripciones.js.map +1 -0
- package/dist/tools/login.js +33 -0
- package/dist/tools/login.js.map +1 -0
- package/dist/tools/mail.js +54 -0
- package/dist/tools/mail.js.map +1 -0
- package/dist/tools/materiales.js +87 -0
- package/dist/tools/materiales.js.map +1 -0
- package/dist/tools/novedades.js +105 -0
- package/dist/tools/novedades.js.map +1 -0
- package/dist/tools/pia.js +84 -0
- package/dist/tools/pia.js.map +1 -0
- package/dist/tools/planuade.js +34 -0
- package/dist/tools/planuade.js.map +1 -0
- package/dist/tools/secciones.js +49 -0
- package/dist/tools/secciones.js.map +1 -0
- package/dist/tools/teams.js +31 -0
- package/dist/tools/teams.js.map +1 -0
- package/dist/tools/webcampus.js +95 -0
- package/dist/tools/webcampus.js.map +1 -0
- package/dist/untrusted.js +12 -0
- package/dist/untrusted.js.map +1 -0
- package/package.json +65 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 malenitaa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,197 @@
|
|
|
1
|
-
#
|
|
1
|
+
# uade-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Servidor [MCP](https://modelcontextprotocol.io) **no oficial** para alumnos de UADE: le da a tu agente de
|
|
4
|
+
IA (Claude, ChatGPT o cualquier cliente MCP) acceso de **lectura** a tu WebCampus, a las inscripciones,
|
|
5
|
+
a los apuntes de tus materias en Teams/SharePoint y a la información oficial que publica la universidad.
|
|
6
|
+
|
|
7
|
+
Corre en tu compu, sin servidores intermedios ni telemetría, y nunca ve tu contraseña.
|
|
8
|
+
|
|
9
|
+
> Proyecto de estudiantes, sin afiliación con UADE. **Beta (v0.1):** probado en macOS con una cuenta de grado; Windows y Linux, en prueba.
|
|
10
|
+
|
|
11
|
+
## Qué podés preguntarle
|
|
12
|
+
|
|
13
|
+
- "¿Qué materias puedo cursar el próximo cuatrimestre y qué finales puedo rendir?"
|
|
14
|
+
- "Armame la semana con estas 5 materias, solo de noche y sin huecos."
|
|
15
|
+
- "¿Cuándo es el final de Gestión de Personas y cuánto sale el derecho a examen?"
|
|
16
|
+
- "¿Cuánto debo? ¿Cuándo vence la próxima cuota?"
|
|
17
|
+
- "¿Cuánto sale la cuota de mi carrera a la mañana y a la noche?"
|
|
18
|
+
- "Leeme el último apunte que subió el docente de Aplicaciones Interactivas."
|
|
19
|
+
- "¿Qué hay de nuevo en UADE? ¿Dónde reservo el estacionamiento?"
|
|
20
|
+
- "Armame un mail para mi docente pidiendo revisión del parcial." (queda en borrador, lo mandás vos)
|
|
21
|
+
|
|
22
|
+
## Requisitos
|
|
23
|
+
|
|
24
|
+
- Node.js 20 o más nuevo.
|
|
25
|
+
- Chrome, Edge o Brave instalado (para iniciar sesión con tu cuenta de UADE).
|
|
26
|
+
- macOS, Windows o Linux (probado en macOS).
|
|
27
|
+
|
|
28
|
+
## Instalación
|
|
29
|
+
|
|
30
|
+
No hace falta clonar nada: `npx` baja el paquete y lo corre.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx -y uade-mcp login # se abre una ventana: iniciás sesión vos con tu cuenta de UADE
|
|
34
|
+
npx -y uade-mcp doctor # prueba todo contra tu cuenta sin mostrar datos personales
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Sumarlo a Claude Code (el MCP nunca toca la configuración de tu cliente; lo hacés vos):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
claude mcp add uade -- npx -y uade-mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
En cualquier otro cliente MCP: comando `npx`, argumentos `-y uade-mcp`, transporte stdio. Si preferís
|
|
44
|
+
tenerlo instalado: `npm i -g uade-mcp` y después `uade-mcp login`.
|
|
45
|
+
|
|
46
|
+
Desde el código (para desarrollar): `git clone`, `npm install`, `npm run build` y `node dist/index.js`.
|
|
47
|
+
|
|
48
|
+
### Sesión
|
|
49
|
+
|
|
50
|
+
Iniciás sesión una vez. WebCampus vence cada ~3 horas: el MCP la renueva solo, sin ventana, con la sesión
|
|
51
|
+
de Microsoft que recuerda su perfil de navegador. Si Microsoft pide la clave o el segundo factor, el agente
|
|
52
|
+
llama `uade_login` (o escribís `/uade:login`) y se abre la ventana para que la completes vos.
|
|
53
|
+
|
|
54
|
+
### Comandos de terminal
|
|
55
|
+
|
|
56
|
+
| Comando | Qué hace |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `login` | Iniciar sesión (o renovarla sin ventana si se puede) |
|
|
59
|
+
| `estado` | Qué sesiones hay guardadas y qué permisos están activos |
|
|
60
|
+
| `doctor` | Prueba cada función contra tu cuenta y muestra solo cantidades |
|
|
61
|
+
| `permitir <permiso>` / `revocar <permiso>` | Prende o apaga un permiso (solo desde una terminal, escribiéndolo vos) |
|
|
62
|
+
| `logout` | Borra sesiones, la copia local cifrada, su clave y el perfil del navegador |
|
|
63
|
+
|
|
64
|
+
## Herramientas
|
|
65
|
+
|
|
66
|
+
Los nombres de las tools están en inglés (los agentes los entienden mejor); el servidor le pide al agente
|
|
67
|
+
que responda en castellano rioplatense, con fechas dd/mm/aaaa y horas de 24 h.
|
|
68
|
+
|
|
69
|
+
**Tu cursada**
|
|
70
|
+
|
|
71
|
+
| Tool | Qué devuelve |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `webcampus_terms` | Tus cuatrimestres |
|
|
74
|
+
| `webcampus_current_courses` | Materias del cuatrimestre: día, horario, aula, sede, notas, fecha de final, links a Teams y apuntes |
|
|
75
|
+
| `webcampus_attendance` | Presentes, faltas y detalle por clase |
|
|
76
|
+
| `webcampus_course_calendar` | Fechas de parciales, recuperatorio y final de cada materia |
|
|
77
|
+
| `webcampus_teachers` | Nombre y mail del equipo docente (nunca datos de compañeros) |
|
|
78
|
+
| `webcampus_course_materials` | Lista, lee (PDF, Word, Excel, PowerPoint) o baja los apuntes de "Materiales de clase" |
|
|
79
|
+
| `teams_channel_news` | Lo último que se publicó en el canal General de una materia (experimental) |
|
|
80
|
+
|
|
81
|
+
**Tu carrera**
|
|
82
|
+
|
|
83
|
+
| Tool | Qué devuelve |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `webcampus_transcript` | Historial: aprobadas, finales pendientes, cursando, sin cursar y promedio |
|
|
86
|
+
| `uade_eligible_subjects` | Qué podés cursar y qué finales podés rendir, cruzando historial y correlativas del plan |
|
|
87
|
+
| `webcampus_exam_schedule` | Cronograma oficial de exámenes previos (por defecto, de tus finales pendientes) |
|
|
88
|
+
| `planuade_import_transcript` | Abre [Plan UADE](https://planuade.ar) con tu historial para importarlo; confirmás ahí |
|
|
89
|
+
|
|
90
|
+
**Inscripciones**
|
|
91
|
+
|
|
92
|
+
| Tool | Qué devuelve |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `uade_plan_next_term` | Todo el armado en un pedido: qué podés cursar (y qué se habilita si aprobás lo que cursás), qué se dicta, las mejores semanas sin superposiciones y, si querés, la vista de la semana en tu navegador (HTML) y un calendario `.ics` |
|
|
95
|
+
| `uade_enrollment_windows` | Ventanas de inscripción (materias, previos, bajas) con apertura, cierre y si están abiertas |
|
|
96
|
+
| `webcampus_class_offering` | Clases de cada materia el próximo cuatrimestre: sede, turno, días, horario, fechas |
|
|
97
|
+
| `webcampus_schedule_planner` | Combina hasta 6 materias sin solapamientos, con filtros, ordenadas por menos días y huecos |
|
|
98
|
+
| `pia_class_search` | Clases en PIA con vacantes y condiciones (en verificación: PIA rechaza el acceso fuera del navegador) |
|
|
99
|
+
| `pia_prepare_enrollment` | Resumen exacto de una inscripción a materia o a previo: qué, cuándo, cuánto y si tiene baja |
|
|
100
|
+
| `uade_open_enrollment_site` | Abre Inscripciones con tu sesión iniciada; la inscripción y el Finalizar los hacés vos |
|
|
101
|
+
|
|
102
|
+
**Plata y trámites**
|
|
103
|
+
|
|
104
|
+
| Tool | Qué devuelve |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `webcampus_account_balance` | Deuda, próximos vencimientos y pagos (no paga) |
|
|
107
|
+
| `uade_tuition` | Aranceles oficiales por carrera y turno, del PDF vigente |
|
|
108
|
+
|
|
109
|
+
**Información de UADE**
|
|
110
|
+
|
|
111
|
+
| Tool | Qué devuelve |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `webcampus_news` | Banners, novedades y agenda del inicio de WebCampus, y lo que enlazan |
|
|
114
|
+
| `webcampus_section` | Secciones informativas: normativa, guía del alumno, Wi-Fi, becas, MRI, optativas, códigos… |
|
|
115
|
+
| `uade_academic_calendar` | Calendario académico y cronogramas oficiales |
|
|
116
|
+
|
|
117
|
+
**Utilidades**
|
|
118
|
+
|
|
119
|
+
| Tool | Qué hace |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `email_compose_draft` | Abre en Outlook web un mail ya escrito a una dirección @uade.edu.ar. No envía |
|
|
122
|
+
| `uade_refresh` | Borra la copia local para releer de UADE |
|
|
123
|
+
| `uade_login` | Renueva la sesión o abre la ventana para iniciarla |
|
|
124
|
+
|
|
125
|
+
## Si UADE cambia una página
|
|
126
|
+
|
|
127
|
+
El MCP no tiene datos cargados a mano: lee en vivo la programación, las ventanas, las novedades, los PDFs,
|
|
128
|
+
los aranceles y tu historial, así que el **contenido se actualiza solo**. Lo que puede romperse es la
|
|
129
|
+
**estructura**: si UADE rediseña una página, el parser deja de entenderla. En ese caso:
|
|
130
|
+
|
|
131
|
+
- La tool no inventa: avisa que UADE cambió la página y devuelve igual su **texto plano** ("modo degradado"),
|
|
132
|
+
marcado como sin estructura, para que el agente pueda responder mientras se actualiza el parser.
|
|
133
|
+
- `doctor` lo detecta. Para enterarte antes que los alumnos, programalo:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx -y uade-mcp doctor --json
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Imprime una línea JSON por capacidad, sin datos personales, renueva la sesión sola si venció y termina con
|
|
140
|
+
código 1 si algo se rompió. En macOS/Linux, por ejemplo cada mañana con `crontab -e`:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
0 9 * * * npx -y uade-mcp doctor --json >> "$HOME/uade-mcp-doctor.log" 2>&1
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
En Windows, el Programador de tareas con el mismo comando. Si el log muestra `"estado":"error"`, hay que
|
|
147
|
+
actualizar el MCP (`npx` usa siempre la última versión publicada; con `npm i -g`, `npm update -g uade-mcp`) o, si es nuevo, abrir un issue.
|
|
148
|
+
|
|
149
|
+
## Privacidad y seguridad
|
|
150
|
+
|
|
151
|
+
- **Local.** Corre en tu compu por stdio; no hay ningún servidor del proyecto en el medio.
|
|
152
|
+
- **Sin telemetría.** Solo habla con dominios de UADE y Microsoft; cualquier otro host está bloqueado en el código (salvo el reporte de errores, si lo activás vos).
|
|
153
|
+
- **Sin contraseñas.** Iniciás sesión vos, en la página de Microsoft. Se guardan cookies de sesión en el
|
|
154
|
+
llavero del sistema y la sesión de Microsoft en un perfil de navegador propio (`~/.uade-mcp/browser-profile`).
|
|
155
|
+
- **Copia local cifrada** (AES-256-GCM, clave en el llavero) para no pedir lo mismo dos veces. Cada respuesta dice de cuándo es.
|
|
156
|
+
- **Solo lectura.** No inscribe, no paga, no da de baja, no manda mails, no toca datos personales.
|
|
157
|
+
- **Reportes de errores, apagados por defecto.** Si activás `reportes.errores`, cuando UADE cambia una página
|
|
158
|
+
o una tool falla el MCP le avisa al proyecto (`planuade.ar/api/mcp-error`, el único destino fuera de UADE y
|
|
159
|
+
Microsoft) con la versión, el nombre de la tool, el tipo de error y qué página. Nunca tu pregunta, tus datos
|
|
160
|
+
ni el contenido; a lo sumo un aviso por error por día. Lo apagás con `revocar reportes.errores`.
|
|
161
|
+
- **Tus datos y nada más.** Nunca devuelve datos de otros alumnos.
|
|
162
|
+
|
|
163
|
+
Detalle en [SECURITY.md](SECURITY.md) y en la [auditoría](docs/AUDITORIA.md).
|
|
164
|
+
|
|
165
|
+
## Estructura
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
src/
|
|
169
|
+
index.ts servidor MCP (sin argumentos) o CLI (con argumentos)
|
|
170
|
+
tools/ una tool por archivo temático: qué recibe, qué devuelve
|
|
171
|
+
sources/ cómo se lee cada sitio (WebCampus, Inscripciones, PIA, SharePoint, Teams…)
|
|
172
|
+
net/ política de red (allowlist y bloqueos), cliente HTTP, sesiones
|
|
173
|
+
auth/ login, permisos, llavero
|
|
174
|
+
cache/ copia local cifrada
|
|
175
|
+
cli/ comandos de terminal y doctor
|
|
176
|
+
test/ tests con datos sintéticos (fixtures anonimizados)
|
|
177
|
+
docs/ diseño, cobertura y auditoría
|
|
178
|
+
scripts/ revisión pre-commit y utilidades
|
|
179
|
+
experimentos/ tu espacio para probar código aparte del MCP (ver su README)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Desarrollo
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm install
|
|
186
|
+
npm test # tests
|
|
187
|
+
npm run revisar # lo mismo que corre antes de cada commit: tipos, tests, npm audit y secretos
|
|
188
|
+
npm run build
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Activá el hook una vez: `git config core.hooksPath .githooks`.
|
|
192
|
+
|
|
193
|
+
Documentación: [diseño](docs/DISENO.md) · [qué cubre](docs/COBERTURA.md) · [auditoría](docs/AUDITORIA.md).
|
|
194
|
+
|
|
195
|
+
## Licencia
|
|
196
|
+
|
|
197
|
+
[MIT](LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Seguridad
|
|
2
|
+
|
|
3
|
+
## Reportar un problema
|
|
4
|
+
|
|
5
|
+
Usá el [formulario privado de GitHub](https://github.com/malenitaa/uade-mcp/security/advisories/new).
|
|
6
|
+
No abras un issue público para temas de seguridad.
|
|
7
|
+
|
|
8
|
+
## Qué garantiza el servidor
|
|
9
|
+
|
|
10
|
+
| Garantía | Dónde | Test |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Solo HTTPS a la allowlist de UADE y Microsoft; cada redirección se revalida | `src/net/policy.ts`, `src/net/http.ts` | `test/policy.test.ts` |
|
|
13
|
+
| Bloqueos fijos (pagos, bajas, desvincularse, datos personales, buscador de personas, cerrar sesión) en URLs y postbacks | `src/net/policy.ts`, `src/net/session.ts` | `test/policy.test.ts`, `test/auditoria.test.ts` |
|
|
14
|
+
| Cookies y headers solo al host que corresponde; nada sale del origen de cada sitio | `src/net/http.ts`, `src/net/session.ts` | `test/webcampus-client.test.ts` |
|
|
15
|
+
| Nunca ve la contraseña: el login lo hace la persona en la página de Microsoft | `src/auth/login.ts` | — |
|
|
16
|
+
| Sesiones en el llavero; perfil de navegador propio en `~/.uade-mcp/browser-profile`; `logout` borra todo | `src/auth/`, `src/cli/index.ts` | `test/auditoria.test.ts` |
|
|
17
|
+
| Renovación automática de sesión: una por pedido vencido, sin bucles | `src/net/session.ts` | `test/login.test.ts` |
|
|
18
|
+
| Copia local cifrada (AES-256-GCM, clave en el llavero) | `src/cache/cache.ts` | `test/cache.test.ts` |
|
|
19
|
+
| Nunca datos de compañeros (lista blanca de roles docentes) | `src/sources/webcampus/parse.ts` | `test/auditoria.test.ts` |
|
|
20
|
+
| Texto de terceros marcado como información, no instrucciones | `src/untrusted.ts`, `src/tools/*` | `test/auditoria.test.ts` |
|
|
21
|
+
| No envía inscripciones: el resumen es de lectura y la ventana de Inscripciones la usa la persona | `src/sources/pia/alta.ts`, `src/tools/pia.ts` | `test/pia.test.ts` |
|
|
22
|
+
| Mails solo como borrador a @uade.edu.ar; adjuntos solo de carpetas de documentos | `src/sources/mail/draft.ts`, `src/net/open-browser.ts` | `test/common.test.ts` |
|
|
23
|
+
| Topes: 10 MB y 20 s por pedido, 5 redirecciones, 80 páginas por PDF, 60 MB descomprimidos por archivo | varios | `test/previos.test.ts` |
|
|
24
|
+
| Reporte de errores opt-in (`reportes.errores`): campos fijos (versión, tool, tipo, página), mails y números largos tapados, un envío por error por día, único path permitido en planuade.ar | `src/report/errores.ts`, `src/net/policy.ts` | `test/reportes.test.ts` |
|
|
25
|
+
| Sin telemetría; dependencias con versión exacta; `npm audit` en cada commit | `package.json`, `scripts/revisar.mjs` | hook pre-commit |
|
|
26
|
+
|
|
27
|
+
La auditoría completa (hallazgos, controles OWASP, NIST y ISO 27001, riesgos aceptados) está en
|
|
28
|
+
[docs/AUDITORIA.md](docs/AUDITORIA.md).
|
|
29
|
+
|
|
30
|
+
## Cadena de suministro (que lo que instalás sea lo que está en el repo)
|
|
31
|
+
|
|
32
|
+
- **Publicación solo desde GitHub Actions** (`.github/workflows/publicar.yml`), al crear un tag `vX.Y.Z`
|
|
33
|
+
que coincida con `package.json`. Usa *trusted publishing* (OIDC): no existe ningún token de npm guardado
|
|
34
|
+
que se pueda robar, y cada versión sale con **provenance** firmada que apunta a este repo y a este commit.
|
|
35
|
+
- **Nada sale al público sin una persona:** GitHub solo deja la versión *en espera* en npm; la publica quien
|
|
36
|
+
mantiene el paquete, aprobándola en npmjs.com con su segundo factor. Un repo o una cuenta de GitHub
|
|
37
|
+
comprometidos no alcanzan para publicar.
|
|
38
|
+
- **Verificar lo instalado:** `npm audit signatures` en una carpeta con `uade-mcp` instalado comprueba la
|
|
39
|
+
firma y la provenance contra el registro.
|
|
40
|
+
- **CI en cada push y PR** (`revisar.yml`): tipos, tests, `npm audit` y búsqueda de secretos en todo el
|
|
41
|
+
repo, en Node 20 y 22, sin credenciales de nadie.
|
|
42
|
+
- **Rama `main` protegida:** sin force-push ni borrado, historial lineal, y solo por PR con el CI en verde;
|
|
43
|
+
la regla aplica también a quien administra el repo.
|
|
44
|
+
- **Dependabot** avisa (y propone el PR) ante vulnerabilidades en dependencias o acciones; el escaneo de
|
|
45
|
+
secretos de GitHub, con protección al push, bloquea tokens que intenten entrar.
|
|
46
|
+
- **Dependencias con versión exacta** y `package-lock.json`; `npm ci --ignore-scripts` en CI: ninguna
|
|
47
|
+
dependencia corre código al instalarse.
|
|
48
|
+
|
|
49
|
+
## Lo que queda fuera de estas garantías
|
|
50
|
+
|
|
51
|
+
- **`experimentos/`**: el servidor no carga nada de esa carpeta, no se compila ni se testea con el resto
|
|
52
|
+
y git la ignora. Lo que corras ahí corre con tus credenciales y bajo tu responsabilidad.
|
|
53
|
+
- **Teams** se lee corriendo el Teams web real en el navegador del MCP: contacta dominios de Microsoft,
|
|
54
|
+
como si lo abrieras vos, y puede dejar de funcionar si Microsoft cambia la interfaz.
|
|
55
|
+
|
|
56
|
+
## Reglas para contribuir
|
|
57
|
+
|
|
58
|
+
- **Nunca** commitees HTML, HAR, capturas o cookies reales. Los fixtures van anonimizados en `test/fixtures/`.
|
|
59
|
+
- No se agregan dependencias que hagan llamadas de red propias (analytics, chequeo de versiones, reporte de errores).
|
|
60
|
+
- Toda tool que escriba algo necesita un permiso propio en `PERMISSIONS`, apagado por defecto, y la
|
|
61
|
+
confirmación de la persona en un diálogo que el agente no pueda contestar.
|
|
62
|
+
- `npm run revisar` tiene que pasar antes de cada commit (el hook lo corre solo).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Escrituras en dos pasos: preparar → confirmar.
|
|
2
|
+
// La confirmación la da el humano por MCP elicitation (un diálogo del cliente),
|
|
3
|
+
// así el modelo no puede contestarse a sí mismo. Sin elicitation, no hay escrituras.
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
|
+
const DRAFT_TTL_MS = 5 * 60 * 1000;
|
|
6
|
+
export class ConfirmationError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'ConfirmationError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function hashParams(permission, params) {
|
|
13
|
+
return createHash('sha256').update(permission).update(JSON.stringify(params)).digest('hex');
|
|
14
|
+
}
|
|
15
|
+
export class DraftStore {
|
|
16
|
+
now;
|
|
17
|
+
drafts = new Map();
|
|
18
|
+
constructor(now = Date.now) {
|
|
19
|
+
this.now = now;
|
|
20
|
+
}
|
|
21
|
+
preparar(permission, resumen, params) {
|
|
22
|
+
this.purge();
|
|
23
|
+
const draft = {
|
|
24
|
+
id: randomUUID(),
|
|
25
|
+
permission,
|
|
26
|
+
resumen,
|
|
27
|
+
params,
|
|
28
|
+
hash: hashParams(permission, params),
|
|
29
|
+
expiresAt: this.now() + DRAFT_TTL_MS,
|
|
30
|
+
};
|
|
31
|
+
this.drafts.set(draft.id, draft);
|
|
32
|
+
return draft;
|
|
33
|
+
}
|
|
34
|
+
// Consume el borrador (un solo uso) y pide confirmación al humano.
|
|
35
|
+
async confirmar(id, permission, elicit) {
|
|
36
|
+
const draft = this.drafts.get(id);
|
|
37
|
+
this.drafts.delete(id);
|
|
38
|
+
if (!draft || draft.expiresAt < this.now()) {
|
|
39
|
+
throw new ConfirmationError('El borrador no existe o venció. Hay que prepararlo de nuevo.');
|
|
40
|
+
}
|
|
41
|
+
if (draft.permission !== permission || draft.hash !== hashParams(permission, draft.params)) {
|
|
42
|
+
throw new ConfirmationError('El borrador no corresponde a esta acción.');
|
|
43
|
+
}
|
|
44
|
+
if (!elicit) {
|
|
45
|
+
throw new ConfirmationError('Tu cliente MCP no soporta confirmaciones (elicitation), así que las escrituras están deshabilitadas.');
|
|
46
|
+
}
|
|
47
|
+
const answer = await elicit(`${draft.resumen}\n\n¿Confirmás esta acción?`);
|
|
48
|
+
if (answer !== 'accept')
|
|
49
|
+
throw new ConfirmationError('No confirmaste la acción. No se hizo nada.');
|
|
50
|
+
return draft.params;
|
|
51
|
+
}
|
|
52
|
+
purge() {
|
|
53
|
+
const t = this.now();
|
|
54
|
+
for (const [id, d] of this.drafts)
|
|
55
|
+
if (d.expiresAt < t)
|
|
56
|
+
this.drafts.delete(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=confirm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/auth/confirm.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,gFAAgF;AAChF,qFAAqF;AACrF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGrD,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAanC,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,SAAS,UAAU,CAAC,UAAsB,EAAE,MAAe;IACzD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9F,CAAC;AAED,MAAM,OAAO,UAAU;IAGQ,GAAG;IAFf,MAAM,GAAG,IAAI,GAAG,EAA0B,CAAC;IAE5D,YAA6B,GAAG,GAAiB,IAAI,CAAC,GAAG;mBAA5B,GAAG;IAA4B,CAAC;IAE7D,QAAQ,CAAI,UAAsB,EAAE,OAAe,EAAE,MAAS;QAC5D,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,MAAM,KAAK,GAAa;YACtB,EAAE,EAAE,UAAU,EAAE;YAChB,UAAU;YACV,OAAO;YACP,MAAM;YACN,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC;YACpC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;SACrC,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mEAAmE;IACnE,KAAK,CAAC,SAAS,CAAI,EAAU,EAAE,UAAsB,EAAE,MAA0B;QAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAyB,CAAC;QAC1D,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAEvB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC3C,MAAM,IAAI,iBAAiB,CAAC,8DAA8D,CAAC,CAAC;QAC9F,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,KAAK,UAAU,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3F,MAAM,IAAI,iBAAiB,CAAC,2CAA2C,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,iBAAiB,CACzB,sGAAsG,CACvG,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;QAC3E,IAAI,MAAM,KAAK,QAAQ;YAAE,MAAM,IAAI,iBAAiB,CAAC,4CAA4C,CAAC,CAAC;QACnG,OAAO,KAAK,CAAC,MAAM,CAAC;IACtB,CAAC;IAEO,KAAK;QACX,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjF,CAAC;CACF"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Inicio de sesión en UADE con un navegador propio del MCP. La contraseña y el MFA los escribe la
|
|
2
|
+
// persona en la página de Microsoft; este código nunca los ve. Se quedan en el llavero solo las
|
|
3
|
+
// cookies de sesión de WebCampus, Inscripciones y SharePoint.
|
|
4
|
+
//
|
|
5
|
+
// El navegador usa un perfil persistente (<config>/browser-profile) para recordar la sesión de
|
|
6
|
+
// Microsoft ("¿Mantener la sesión iniciada?" → Sí). Con eso, cuando WebCampus vence (cada ~3 h)
|
|
7
|
+
// el MCP vuelve a entrar solo, sin ventana y sin pedir nada: abre el perfil en modo oculto, Microsoft
|
|
8
|
+
// lo reconoce y WebCampus entrega cookies nuevas. Si Microsoft pide algo (clave, MFA), se avisa y
|
|
9
|
+
// hace falta la ventana. `logout` borra el perfil.
|
|
10
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { chromium } from 'playwright-core';
|
|
13
|
+
import { findBrowser } from '../net/browser.js';
|
|
14
|
+
import { INSCRIPCIONES, INSCRIPCIONES_ACCOUNT } from '../sources/inscripciones/index.js';
|
|
15
|
+
import { SHAREPOINT, SHAREPOINT_ACCOUNT } from '../sources/sharepoint/index.js';
|
|
16
|
+
import { SESSION_ACCOUNT, WEBCAMPUS } from '../sources/webcampus/client.js';
|
|
17
|
+
import { configDir } from './permissions.js';
|
|
18
|
+
import { saveSession } from './store.js';
|
|
19
|
+
const INTERACTIVO_MS = 10 * 60 * 1000;
|
|
20
|
+
const SILENCIOSO_MS = 45 * 1000;
|
|
21
|
+
// Cookies de analytics que no hacen falta para la sesión y no guardamos.
|
|
22
|
+
const TRACKING = /^(_ga|_gid|_hj|__utm|_fbp|_gcl|ai_user|ai_session)/;
|
|
23
|
+
export function browserProfileDir() {
|
|
24
|
+
return join(configDir(), 'browser-profile');
|
|
25
|
+
}
|
|
26
|
+
export function browserProfileExists() {
|
|
27
|
+
return existsSync(join(browserProfileDir(), 'Default'));
|
|
28
|
+
}
|
|
29
|
+
export function borrarPerfil() {
|
|
30
|
+
rmSync(browserProfileDir(), { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
export function mensajeVentana(navegador) {
|
|
33
|
+
return [
|
|
34
|
+
`Se va a abrir una ventana de ${navegador} en WebCampus (si no la ves, buscala en el Dock o detrás de otras ventanas).`,
|
|
35
|
+
' 1. Iniciá sesión con tu cuenta de UADE (lo escribís vos, en la página de Microsoft).',
|
|
36
|
+
' 2. Si Microsoft pregunta "¿Quiere mantener la sesión iniciada?", elegí Sí: así el MCP renueva WebCampus solo cuando vence.',
|
|
37
|
+
' 3. Cuando veas tu WebCampus, la ventana se cierra sola.',
|
|
38
|
+
'Qué se guarda: las cookies de sesión de WebCampus, Inscripciones y SharePoint en el llavero, y la sesión de Microsoft en un perfil de navegador propio del MCP (~/.uade-mcp/browser-profile). Nunca tu contraseña.',
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
|
41
|
+
// Una sola renovación a la vez: si varias tools vencen juntas, comparten el mismo intento.
|
|
42
|
+
let enCurso = null;
|
|
43
|
+
export function iniciarSesion(op) {
|
|
44
|
+
if (!enCurso)
|
|
45
|
+
enCurso = correr(op).finally(() => { enCurso = null; });
|
|
46
|
+
return enCurso;
|
|
47
|
+
}
|
|
48
|
+
// Ventana visible que el MCP dejó abierta para la persona (uade_open_enrollment_site). Mientras
|
|
49
|
+
// está abierta, el perfil está en uso: la renovación usa esa misma ventana con una pestaña aparte.
|
|
50
|
+
let ventana = null;
|
|
51
|
+
// Abre inscripciones.uade.edu.ar en una ventana visible, con la sesión ya iniciada, y la deja
|
|
52
|
+
// abierta para que la persona siga. No navega más allá de la lista de ventanas de inscripción.
|
|
53
|
+
export async function abrirInscripciones() {
|
|
54
|
+
if (!ventana) {
|
|
55
|
+
ventana = await chromium.launchPersistentContext(browserProfileDir(), {
|
|
56
|
+
executablePath: findBrowser(),
|
|
57
|
+
headless: false,
|
|
58
|
+
viewport: null,
|
|
59
|
+
args: ['--no-first-run', '--no-default-browser-check', '--start-maximized'],
|
|
60
|
+
});
|
|
61
|
+
ventana.on('close', () => { ventana = null; });
|
|
62
|
+
}
|
|
63
|
+
const page = ventana.pages()[0] ?? (await ventana.newPage());
|
|
64
|
+
await page.goto(`${INSCRIPCIONES}/`);
|
|
65
|
+
if (/^\/account\/login/i.test(new URL(page.url()).pathname)) {
|
|
66
|
+
await page.getByRole('button', { name: /iniciar sesi/i }).or(page.getByRole('link', { name: /iniciar sesi/i })).first().click({ timeout: 10_000 }).catch(() => { });
|
|
67
|
+
await page.waitForURL((u) => u.origin === INSCRIPCIONES && !/^\/account\//i.test(u.pathname), { timeout: 60_000 }).catch(() => { });
|
|
68
|
+
}
|
|
69
|
+
await page.bringToFront().catch(() => { });
|
|
70
|
+
}
|
|
71
|
+
async function correr(op) {
|
|
72
|
+
const log = op.log ?? (() => { });
|
|
73
|
+
const executablePath = findBrowser();
|
|
74
|
+
if (!op.interactivo && !browserProfileExists())
|
|
75
|
+
return { ok: false, guardadas: [], motivo: 'todavía no iniciaste sesión nunca' };
|
|
76
|
+
const propia = !ventana;
|
|
77
|
+
const ctx = ventana ?? (await chromium.launchPersistentContext(browserProfileDir(), {
|
|
78
|
+
executablePath,
|
|
79
|
+
headless: !op.interactivo,
|
|
80
|
+
args: ['--no-first-run', '--no-default-browser-check'],
|
|
81
|
+
}));
|
|
82
|
+
const guardadas = [];
|
|
83
|
+
const page = propia ? (ctx.pages()[0] ?? (await ctx.newPage())) : await ctx.newPage();
|
|
84
|
+
try {
|
|
85
|
+
await page.goto(`${WEBCAMPUS}/Login.aspx`);
|
|
86
|
+
if (op.interactivo)
|
|
87
|
+
await page.bringToFront().catch(() => { });
|
|
88
|
+
// El botón "Entrar" solo redirige al login de Microsoft; no manda credenciales.
|
|
89
|
+
await page.click('#ctl00_ContentPlaceHolderMain_btnEntrar', { timeout: 10_000 }).catch(() => { });
|
|
90
|
+
try {
|
|
91
|
+
await page.waitForURL((u) => u.origin === WEBCAMPUS && /\/homewc\.aspx$/i.test(u.pathname), { timeout: op.interactivo ? INTERACTIVO_MS : SILENCIOSO_MS });
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
guardadas,
|
|
97
|
+
motivo: op.interactivo
|
|
98
|
+
? 'pasaron 10 minutos sin que termine el login; no se guardó nada'
|
|
99
|
+
: 'Microsoft pidió iniciar sesión de nuevo (clave o MFA): hace falta la ventana',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const cookies = (await ctx.cookies(WEBCAMPUS)).filter((c) => !TRACKING.test(c.name));
|
|
103
|
+
if (!cookies.some((c) => c.name === '.ASPXAUTH' || c.name === 'sessionuade'))
|
|
104
|
+
return { ok: false, guardadas, motivo: 'WebCampus no entregó la cookie de sesión' };
|
|
105
|
+
saveSession(SESSION_ACCOUNT, cookies.map((c) => `${c.name}=${c.value}`).join('; '));
|
|
106
|
+
guardadas.push('WebCampus');
|
|
107
|
+
log(`Sesión de WebCampus guardada (${cookies.length} cookies: ${cookies.map((c) => c.name).join(', ')}).`);
|
|
108
|
+
// Inscripciones y SharePoint usan el mismo login de Microsoft: con la sesión abierta entran solos.
|
|
109
|
+
await page.goto(`${INSCRIPCIONES}/`).catch(() => { });
|
|
110
|
+
// Inscripciones muestra su propia portada con "Iniciar sesión": ese botón solo manda a Microsoft,
|
|
111
|
+
// que ya reconoce la sesión y vuelve solo.
|
|
112
|
+
if (/^\/account\/login/i.test(new URL(page.url()).pathname)) {
|
|
113
|
+
await page.getByRole('button', { name: /iniciar sesi/i }).or(page.getByRole('link', { name: /iniciar sesi/i })).first().click({ timeout: 10_000 }).catch(() => { });
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
await page.waitForURL((u) => u.origin === INSCRIPCIONES && !/^\/account\//i.test(u.pathname), { timeout: 60_000 });
|
|
117
|
+
await page.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => { });
|
|
118
|
+
const ins = (await ctx.cookies(INSCRIPCIONES)).filter((c) => !TRACKING.test(c.name));
|
|
119
|
+
if (ins.length) {
|
|
120
|
+
saveSession(INSCRIPCIONES_ACCOUNT, ins.map((c) => `${c.name}=${c.value}`).join('; '));
|
|
121
|
+
guardadas.push('Inscripciones');
|
|
122
|
+
log(`Sesión de Inscripciones guardada (${ins.map((c) => c.name).join(', ')}).`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
log('No se pudo abrir Inscripciones (WebCampus quedó guardado igual).');
|
|
127
|
+
}
|
|
128
|
+
await page.goto(`${SHAREPOINT}/`).catch(() => { });
|
|
129
|
+
try {
|
|
130
|
+
await page.waitForURL((u) => u.origin === SHAREPOINT, { timeout: 60_000 });
|
|
131
|
+
await page.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => { });
|
|
132
|
+
const sp = (await ctx.cookies(SHAREPOINT)).filter((c) => /^(FedAuth|rtFa)$/.test(c.name));
|
|
133
|
+
if (sp.length) {
|
|
134
|
+
saveSession(SHAREPOINT_ACCOUNT, sp.map((c) => `${c.name}=${c.value}`).join('; '));
|
|
135
|
+
guardadas.push('SharePoint');
|
|
136
|
+
log(`Sesión de SharePoint guardada (${sp.map((c) => c.name).join(', ')}).`);
|
|
137
|
+
}
|
|
138
|
+
else
|
|
139
|
+
log('SharePoint no dejó sesión: el PDF de aranceles no va a estar disponible.');
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
log('No se pudo abrir SharePoint (lo demás quedó guardado igual).');
|
|
143
|
+
}
|
|
144
|
+
return { ok: true, guardadas };
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
if (propia)
|
|
148
|
+
await ctx.close().catch(() => { });
|
|
149
|
+
else
|
|
150
|
+
await page.close().catch(() => { });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/auth/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Permisos con nombre y alcance. Se otorgan editando la config (o con la CLI),
|
|
2
|
+
// nunca desde el chat: el agente no tiene ninguna tool que los modifique.
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
export const PERMISSIONS = {
|
|
8
|
+
'webcampus.leer': 'Leer notas, asistencia, historial, cuenta corriente y novedades de WebCampus.',
|
|
9
|
+
'pia.leer': 'Buscar asignaturas, docentes, sedes, horarios y ver tu carrito en PIA.',
|
|
10
|
+
'teams.leer_tareas': 'Leer tareas y consignas de Teams.',
|
|
11
|
+
'teams.leer_novedades': 'Leer las novedades del canal General de tus materias en Teams (con el navegador propio del MCP).',
|
|
12
|
+
'teams.entregar': 'Subir archivos como entrega en tareas de Teams en tu nombre. Cada entrega igual te pide confirmación. No permite mandar mensajes.',
|
|
13
|
+
'mail.leer': 'Leer tu mail institucional.',
|
|
14
|
+
'mail.enviar': 'Enviar mails desde tu cuenta institucional. Cada envío igual te pide confirmación.',
|
|
15
|
+
'reportes.errores': 'Avisarle al proyecto (planuade.ar) cuando UADE cambia una página o una tool falla, para que se arregle. Manda solo versión, nombre de la tool, tipo de error y qué página; nunca tu pregunta, tus datos ni el contenido. A lo sumo un aviso por error por día.',
|
|
16
|
+
};
|
|
17
|
+
// Lectura habilitada por defecto; toda escritura arranca apagada.
|
|
18
|
+
const DEFAULT_GRANTED = ['webcampus.leer', 'pia.leer', 'teams.leer_novedades'];
|
|
19
|
+
const ConfigSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
permisos: z.array(z.enum(Object.keys(PERMISSIONS))).optional(),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export function configDir() {
|
|
25
|
+
return process.env.UADE_MCP_HOME ?? join(homedir(), '.uade-mcp');
|
|
26
|
+
}
|
|
27
|
+
export async function loadGranted() {
|
|
28
|
+
let raw;
|
|
29
|
+
try {
|
|
30
|
+
raw = await readFile(join(configDir(), 'config.json'), 'utf8');
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (err.code === 'ENOENT')
|
|
34
|
+
return new Set(DEFAULT_GRANTED);
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
// Una config inválida no habilita nada extra: se falla cerrado.
|
|
38
|
+
const parsed = ConfigSchema.parse(JSON.parse(raw));
|
|
39
|
+
return new Set(parsed.permisos ?? DEFAULT_GRANTED);
|
|
40
|
+
}
|
|
41
|
+
export class PermissionError extends Error {
|
|
42
|
+
permission;
|
|
43
|
+
constructor(permission) {
|
|
44
|
+
super(`Falta el permiso "${permission}": ${PERMISSIONS[permission]} ` +
|
|
45
|
+
`Lo habilitás vos desde la terminal con: uade-mcp permitir ${permission}`);
|
|
46
|
+
this.permission = permission;
|
|
47
|
+
this.name = 'PermissionError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function requirePermission(granted, p) {
|
|
51
|
+
if (!granted.has(p))
|
|
52
|
+
throw new PermissionError(p);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/auth/permissions.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,0EAA0E;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,gBAAgB,EAAE,+EAA+E;IACjG,UAAU,EAAE,wEAAwE;IACpF,mBAAmB,EAAE,mCAAmC;IACxD,sBAAsB,EAAE,kGAAkG;IAC1H,gBAAgB,EACd,mIAAmI;IACrI,WAAW,EAAE,6BAA6B;IAC1C,aAAa,EAAE,oFAAoF;IACnG,kBAAkB,EAChB,gQAAgQ;CAC1P,CAAC;AAIX,kEAAkE;AAClE,MAAM,eAAe,GAA0B,CAAC,gBAAgB,EAAE,UAAU,EAAE,sBAAsB,CAAC,CAAC;AAEtG,MAAM,YAAY,GAAG,CAAC;KACnB,MAAM,CAAC;IACN,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAkC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAChG,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,GAAG,CAAC,eAAe,CAAC,CAAC;QACtF,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,gEAAgE;IAChE,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,eAAe,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACZ,UAAU;IAAtC,YAA4B,UAAsB;QAChD,KAAK,CACH,qBAAqB,UAAU,MAAM,WAAW,CAAC,UAAU,CAAC,GAAG;YAC7D,6DAA6D,UAAU,EAAE,CAC5E,CAAC;0BAJwB,UAAU;QAKpC,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAgC,EAAE,CAAa;IAC/E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,eAAe,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Sesiones guardadas en el llavero del sistema (Llavero de macOS, Credential Manager, libsecret).
|
|
2
|
+
// Nunca en archivos planos. Solo se guardan cookies de sesión, nunca la contraseña.
|
|
3
|
+
import { Entry } from '@napi-rs/keyring';
|
|
4
|
+
const SERVICE = 'uade-mcp';
|
|
5
|
+
function entry(account) {
|
|
6
|
+
return new Entry(SERVICE, account);
|
|
7
|
+
}
|
|
8
|
+
export function loadSession(account) {
|
|
9
|
+
const raw = entry(account).getPassword();
|
|
10
|
+
if (!raw)
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const s = JSON.parse(raw);
|
|
14
|
+
return typeof s.cookie === 'string' ? s : null;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function saveSession(account, cookie) {
|
|
21
|
+
entry(account).setPassword(JSON.stringify({ cookie, savedAt: new Date().toISOString() }));
|
|
22
|
+
}
|
|
23
|
+
export function deleteSession(account) {
|
|
24
|
+
return entry(account).deletePassword();
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/auth/store.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,oFAAoF;AACpF,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,OAAO,GAAG,UAAU,CAAC;AAO3B,SAAS,KAAK,CAAC,OAAe;IAC5B,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;QAC3C,OAAO,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,MAAc;IACzD,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAA0B,CAAC,CAAC,CAAC;AACpH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC;AACzC,CAAC"}
|