En esta lección se amplía el ejemplo de la lección anterior para controlar el acceso a una app.
Puedes probar la app en https://replit.com/@GilbertoPachec5/srvaut?v=1. Hazle fork al proyecto y córrelo.
Prueba con los siguientes usarios y contraseñas:
Este ejmplo se puede complementar usando reCaptcha..
Este ejemplo usa sesión en el navegador, que es un archivo en el servidor que permite guardar la información del usuario. Este mecanismo está cayendo en desuso, porque limita la veocidad del servidor y lo expone a fallas de seguridad.
Hoy en día se prefiere usar servidores de OAuth 2 para autenticar usuarios. Los principales servidores para este tipo de autenticación son Google, Facebook y Microsoft.
Algunos navegadores web bloquean las interfaces que muestran etiquetas como
contraseña
, usuario
, id
, email
, etcétera,
suponiendo que es un
intento de engañar al usuario; para evitar este problema, al campo con el
nombre de usuario se le llama cue
y a la conreaseña match
.
Para este ejemplo se utilizan algunos principios de arquitecturas limpias.
Cada uno de los paquetes apunta con una flecha use
a los que utiliza
para realizar sus funciones.
Cada paquete oculta los detalles de su implementación y tecnología.
Los detalles de la base de datos, así como de su configuración, se mantienen
dentro del paquete bd
y no se exponen fuera de dicho paquete.
Los detalles de la interfaz gráfica, por ejemplo las api del navegador web,
o de las interfaces en Android, se mantienen dentro del paquete
access
y no se exponen fuera de dicho paquete.
El intercambio de datos entre los paquetes access
y service
se realiza de acuerdo al contenido de las lecciones anteriores.
El intercambio de datos entre los paquetes service
y bd
se realiza con el contenido del paquete modelo
.
Revisa el proyecto en Replit con la URL https://replit.com/@GilbertoPachec5/srvaut?v=1. Hazle fork al proyecto y córrelo. En el ambiente de desarrollo tienes la opción de descargar el proyecto en un zip.
Crea un proyecto PHP Web Server en Replit y edita o sube los archivos de este proyecto.
Haz clic en los triángulos para expandir las carpetas
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Autenticación</title> |
10 | |
11 | <script type="module" src="lib/js/muestraError.js"></script> |
12 | <script type="module" src="./js/protege.js"></script> |
13 | <script type="module" src="./js/custom/mi-nav.js"></script> |
14 | |
15 | </head> |
16 | |
17 | <body onload="protege('srv/srvSesion.php') |
18 | .then(sesion => nav.sesion = sesion) |
19 | .catch(muestraError)"> |
20 | |
21 | <mi-nav id="nav"></mi-nav> |
22 | |
23 | <h1>Autenticación</h1> |
24 | |
25 | <p>Bienvenid@.</p> |
26 | |
27 | </body> |
28 | |
29 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Perfil</title> |
10 | |
11 | <script type="module" src="lib/js/invocaServicio.js"></script> |
12 | <script type="module" src="lib/js/muestraError.js"></script> |
13 | <script type="module" src="./js/protege.js"></script> |
14 | <script type="module" src="./js/custom/mi-nav.js"></script> |
15 | |
16 | </head> |
17 | |
18 | <body onload="protege('srv/srvSesion.php') |
19 | .then(sesion => { |
20 | nav.sesion = sesion |
21 | const cue = sesion.cue |
22 | if (cue === '') { |
23 | login.hidden = false |
24 | outputCue.value = 'No ha iniciado sesión.' |
25 | outputRoles.value = '' |
26 | } else { |
27 | logout.hidden = false |
28 | outputCue.value = cue |
29 | const rolIds = sesion.rolIds |
30 | outputRoles.value = rolIds.size === 0 |
31 | ? 'Sin roles' |
32 | : Array.from(rolIds).join(', ') |
33 | } |
34 | }) |
35 | .catch(muestraError)"> |
36 | |
37 | <mi-nav id="nav"></mi-nav> |
38 | |
39 | <h1>Perfil</h1> |
40 | |
41 | <p> |
42 | <output id="outputCue"> |
43 | <progress max="100">Cargando…</progress> |
44 | </output> |
45 | </p> |
46 | |
47 | <p> |
48 | <output id="outputRoles"> |
49 | <progress max="100">Cargando…</progress> |
50 | </output> |
51 | </p> |
52 | |
53 | <p> |
54 | |
55 | <a id="login" hidden href="login.html">Iniciar sesión</a> |
56 | |
57 | <button type="button" id="logout" hidden |
58 | onclick="invocaServicio('srv/srvLogout.php') |
59 | .then(json => location.reload()) |
60 | .catch(muestraError)"> |
61 | Terminar sesión |
62 | </button> |
63 | |
64 | </p> |
65 | |
66 | </body> |
67 | |
68 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Iniciar sesión</title> |
10 | |
11 | <script type="module" src="lib/js/submitForm.js"></script> |
12 | <script type="module" src="lib/js/muestraError.js"></script> |
13 | <script type="module" src="./js/protege.js"></script> |
14 | |
15 | </head> |
16 | |
17 | <body onload="protege('srv/srvSesion.php') |
18 | .then(sesion => { |
19 | if (sesion.cue !== '') { |
20 | location.href = 'perfil.html' |
21 | } |
22 | }) |
23 | .catch(muestraError)"> |
24 | |
25 | <form id="login" onsubmit="submitForm('srv/srvLogin.php', event) |
26 | .then(sesion => location.href = 'perfil.html') |
27 | .catch(muestraError)"> |
28 | |
29 | <h1>Iniciar Sesión</h1> |
30 | |
31 | <p> |
32 | <label> |
33 | Cue |
34 | <input name="cue"> |
35 | </label> |
36 | </p> |
37 | |
38 | <p> |
39 | <label> |
40 | Match |
41 | <input type="password" name="match"> |
42 | </label> |
43 | </p> |
44 | |
45 | <p> |
46 | <a href="perfil.html">Cancelar</a> |
47 | <button type="submit">Iniciar sesión</button> |
48 | </p> |
49 | |
50 | </form> |
51 | |
52 | </body> |
53 | |
54 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Solo Administradores</title> |
10 | |
11 | <script type="module" src="lib/js/invocaServicio.js"></script> |
12 | <script type="module" src="lib/js/muestraError.js"></script> |
13 | <script type="module" src="./js/const/ROL_ADMINISTRADOR.js"></script> |
14 | <script type="module" src="./js/protege.js"></script> |
15 | <script type="module" src="./js/custom/mi-nav.js"></script> |
16 | |
17 | </head> |
18 | |
19 | <body onload="protege('srv/srvSesion.php', [ROL_ADMINISTRADOR], 'index.html') |
20 | .then(sesion => { |
21 | nav.sesion = sesion |
22 | main.hidden = false |
23 | }) |
24 | .catch(muestraError)"> |
25 | |
26 | <mi-nav id="nav"></mi-nav> |
27 | |
28 | <main id="main" hidden> |
29 | |
30 | <h1>Solo Administradores</h1> |
31 | |
32 | <p>Hola.</p> |
33 | |
34 | <p> |
35 | <button type="button" onclick="invocaServicio('srv/srvSaludoCliente.php') |
36 | .then(saludo => alert(saludo.body)) |
37 | .catch(muestraError)"> |
38 | Ejecuta servicio |
39 | </button> |
40 | </p> |
41 | |
42 | </main> |
43 | |
44 | </body> |
45 | |
46 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Solo Clientes</title> |
10 | |
11 | <script type="module" src="lib/js/invocaServicio.js"></script> |
12 | <script type="module" src="lib/js/muestraError.js"></script> |
13 | <script type="module" src="./js/const/ROL_CLIENTE.js"></script> |
14 | <script type="module" src="./js/protege.js"></script> |
15 | <script type="module" src="./js/custom/mi-nav.js"></script> |
16 | |
17 | </head> |
18 | |
19 | <body onload="protege('srv/srvSesion.php', [ROL_CLIENTE], 'index.html') |
20 | .then(sesion => { |
21 | nav.sesion = sesion |
22 | main.hidden = false |
23 | }) |
24 | .catch(muestraError)"> |
25 | |
26 | <mi-nav id="nav"></mi-nav> |
27 | |
28 | <main id="main" hidden> |
29 | |
30 | <h1>Solo Clientes</h1> |
31 | |
32 | <p>Hola.</p> |
33 | |
34 | <p> |
35 | <button type="button" onclick="invocaServicio('srv/srvSaludoCliente.php') |
36 | .then(saludo => alert(saludo.body)) |
37 | .catch(muestraError)"> |
38 | Ejecuta servicio |
39 | </button> |
40 | </p> |
41 | |
42 | </main> |
43 | |
44 | </body> |
45 | |
46 | </html> |
1 | export const CUE = "cue" |
1 | export const ROL_ADMINISTRADOR = "Administrador" |
2 | |
3 | // Permite que los eventos de html usen la constante. |
4 | window["ROL_ADMINISTRADOR"] = ROL_ADMINISTRADOR |
1 | export const ROL_CLIENTE = "Cliente" |
2 | |
3 | // Permite que los eventos de html usen la constante. |
4 | window["ROL_CLIENTE"] = ROL_CLIENTE |
1 | export const ROL_IDS = "rolIds" |
1 | import { CUE } from "./const/CUE.js" |
2 | import { ROL_IDS } from "./const/ROL_IDS.js" |
3 | |
4 | export class Sesion { |
5 | |
6 | /** |
7 | * @param { any } objeto |
8 | */ |
9 | constructor(objeto) { |
10 | |
11 | /** @readonly */ |
12 | this.cue = objeto[CUE] |
13 | if (typeof this.cue !== "string") |
14 | throw new Error("cue debe ser string.") |
15 | |
16 | /** @readonly */ |
17 | const rolIds = objeto[ROL_IDS] |
18 | if (!Array.isArray(rolIds)) |
19 | throw new Error("rolIds debe ser arreglo.") |
20 | /** @readonly */ |
21 | this.rolIds = new Set(rolIds) |
22 | |
23 | } |
24 | |
25 | } |
26 | |
27 | // Permite que los eventos de html usen la clase. |
28 | window["Sesion"] = Sesion |
1 | import { invocaServicio } from "../lib/js/invocaServicio.js" |
2 | import { Sesion } from "./Sesion.js" |
3 | |
4 | /** |
5 | * @param {string} servicio |
6 | * @param {string[]} [rolIdsPermitidos] |
7 | * @param {string} [urlDeProtección] |
8 | */ |
9 | export async function protege(servicio, rolIdsPermitidos, urlDeProtección) { |
10 | const respuesta = await invocaServicio('srv/srvSesion.php') |
11 | const sesion = new Sesion(respuesta.body) |
12 | if (rolIdsPermitidos === undefined) { |
13 | return sesion |
14 | } else { |
15 | const rolIds = sesion.rolIds |
16 | for (const rolId of rolIdsPermitidos) { |
17 | if (rolIds.has(rolId)) { |
18 | return sesion |
19 | } |
20 | } |
21 | if (urlDeProtección !== undefined) { |
22 | location.href = urlDeProtección |
23 | } |
24 | throw new Error("No autorizado.") |
25 | } |
26 | } |
27 | |
28 | // Permite que los eventos de html usen la función. |
29 | window["protege"] = protege |
1 | import { htmlentities } from "../../lib/js/htmlentities.js" |
2 | import { Sesion } from "../Sesion.js" |
3 | import { ROL_ADMINISTRADOR } from "../const/ROL_ADMINISTRADOR.js" |
4 | import { ROL_CLIENTE } from "../const/ROL_CLIENTE.js" |
5 | |
6 | export class MiNav extends HTMLElement { |
7 | |
8 | connectedCallback() { |
9 | |
10 | this.style.display = "block" |
11 | |
12 | this.innerHTML = /* html */ |
13 | `<nav> |
14 | <ul style="display: flex; |
15 | flex-wrap: wrap; |
16 | padding:0; |
17 | gap: 0.5em; |
18 | list-style-type: none"> |
19 | <li><progress max="100">Cargando…</progress></li> |
20 | </ul> |
21 | </nav>` |
22 | |
23 | } |
24 | |
25 | /** |
26 | * @param {Sesion} sesion |
27 | */ |
28 | set sesion(sesion) { |
29 | const cue = sesion.cue |
30 | const rolIds = sesion.rolIds |
31 | let innerHTML = /* html */ `<li><a href="index.html">Inicio</a></li>` |
32 | innerHTML += this.hipervinculosAdmin(rolIds) |
33 | innerHTML += this.hipervinculosCliente(rolIds) |
34 | const cueHtml = htmlentities(cue) |
35 | if (cue !== "") { |
36 | innerHTML += /* html */ `<li>${cueHtml}</li>` |
37 | } |
38 | innerHTML += /* html */ `<li><a href="perfil.html">Perfil</a></li>` |
39 | const ul = this.querySelector("ul") |
40 | if (ul !== null) { |
41 | ul.innerHTML = innerHTML |
42 | } |
43 | } |
44 | |
45 | /** |
46 | * @param {Set<string>} rolIds |
47 | */ |
48 | hipervinculosAdmin(rolIds) { |
49 | return rolIds.has(ROL_ADMINISTRADOR) ? |
50 | /* html */ `<li><a href="admin.html">Para administradores</a></li>` |
51 | : "" |
52 | } |
53 | |
54 | /** |
55 | * @param {Set<string>} rolIds |
56 | */ |
57 | hipervinculosCliente(rolIds) { |
58 | return rolIds.has(ROL_CLIENTE) ? |
59 | /* html */ `<li><a href="cliente.html">Para clientes</a></li>` |
60 | : "" |
61 | } |
62 | } |
63 | |
64 | customElements.define("mi-nav", MiNav) |
1 | <?php |
2 | |
3 | const CUE = "cue"; |
1 | <?php |
2 | |
3 | const ROL_ADMINISTRADOR = "Administrador"; |
4 |
1 | <?php |
2 | |
3 | const ROL_CLIENTE = "Cliente"; |
1 | <?php |
2 | |
3 | const ROL_IDS = "rolIds"; |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../../lib/php/ProblemDetails.php"; |
4 | |
5 | class Rol |
6 | { |
7 | |
8 | public string $id; |
9 | public string $descripcion; |
10 | |
11 | public function __construct(string $descripcion = "", string $id = "") |
12 | { |
13 | $this->id = $id; |
14 | $this->descripcion = $descripcion; |
15 | } |
16 | |
17 | public function valida() |
18 | { |
19 | |
20 | if ($this->id === "") |
21 | throw new ProblemDetails( |
22 | status: ProblemDetails::BadRequest, |
23 | type: "/error/faltaid.html", |
24 | title: "Falta el id.", |
25 | ); |
26 | |
27 | if ($this->descripcion === "") |
28 | throw new ProblemDetails( |
29 | status: ProblemDetails::BadRequest, |
30 | type: "/error/faltadescripcion.html", |
31 | title: "Falta la descripción.", |
32 | ); |
33 | } |
34 | } |
35 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../../lib/php/ProblemDetails.php"; |
4 | require_once __DIR__ . "/Rol.php"; |
5 | |
6 | class Usuario |
7 | { |
8 | |
9 | public int $id; |
10 | public string $cue; |
11 | public string $match; |
12 | /** @var Rol[] */ |
13 | public array $roles; |
14 | |
15 | public function __construct( |
16 | string $cue = "", |
17 | string $match = "", |
18 | array $roles = [], |
19 | int $id = 0 |
20 | ) { |
21 | $this->id = $id; |
22 | $this->cue = $cue; |
23 | $this->match = $match; |
24 | $this->roles = $roles; |
25 | } |
26 | |
27 | public function valida() |
28 | { |
29 | |
30 | if ($this->cue === "") |
31 | throw new ProblemDetails( |
32 | status: ProblemDetails::BadRequest, |
33 | type: "/error/faltacue.html", |
34 | title: "Falta el cue.", |
35 | ); |
36 | |
37 | if ($this->match === "") |
38 | throw new ProblemDetails( |
39 | status: ProblemDetails::BadRequest, |
40 | type: "/error/faltamatch.html", |
41 | title: "Falta el match.", |
42 | ); |
43 | |
44 | foreach ($this->roles as $rol) { |
45 | if (!($rol instanceof Rol)) |
46 | throw new ProblemDetails( |
47 | status: ProblemDetails::BadRequest, |
48 | type: "/error/rolincorrecto.html", |
49 | title: "Tipo incorrecto para un rol.", |
50 | ); |
51 | } |
52 | } |
53 | } |
54 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/../lib/php/ProblemDetails.php"; |
5 | require_once __DIR__ . "/const/CUE.php"; |
6 | require_once __DIR__ . "/const/ROL_IDS.php"; |
7 | require_once __DIR__ . "/const/ROL_CLIENTE.php"; |
8 | require_once __DIR__ . "/Sesion.php"; |
9 | |
10 | const NO_AUTORIZADO = 401; |
11 | |
12 | function protege(?array $rolIdsPermitidos = null) |
13 | { |
14 | session_start(); |
15 | $cue = isset($_SESSION[CUE]) |
16 | ? $_SESSION[CUE] |
17 | : ""; |
18 | $rolIds = isset($_SESSION[ROL_IDS]) |
19 | ? $_SESSION[ROL_IDS] |
20 | : []; |
21 | $sesion = new Sesion($cue, $rolIds); |
22 | if ($rolIdsPermitidos === null) { |
23 | return $sesion; |
24 | } else { |
25 | foreach ($rolIdsPermitidos as $rolId) { |
26 | if (array_search($rolId, $rolIds, true) !== false) { |
27 | return $sesion; |
28 | } |
29 | } |
30 | throw new ProblemDetails( |
31 | status: NO_AUTORIZADO, |
32 | type: "/error/noautorizado.html", |
33 | title: "No autorizado.", |
34 | detail: "No está autorizado para usar este recurso.", |
35 | ); |
36 | } |
37 | } |
38 |
1 | <?php |
2 | |
3 | class Sesion |
4 | { |
5 | |
6 | public string $cue; |
7 | public array $rolIds; |
8 | |
9 | public function __construct(string $cue, array $rolIds) |
10 | { |
11 | $this->cue = $cue; |
12 | $this->rolIds = $rolIds; |
13 | } |
14 | } |
15 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/../lib/php/ProblemDetails.php"; |
5 | require_once __DIR__ . "/../lib/php/leeTexto.php"; |
6 | require_once __DIR__ . "/const/CUE.php"; |
7 | require_once __DIR__ . "/const/ROL_IDS.php"; |
8 | require_once __DIR__ . "/modelo/Rol.php"; |
9 | require_once __DIR__ . "/bd/usuarioVerifica.php"; |
10 | require_once __DIR__ . "/protege.php"; |
11 | |
12 | ejecutaServicio(function () { |
13 | $sesion = protege(); |
14 | if ($sesion->cue !== "") { |
15 | throw new ProblemDetails( |
16 | status: NO_AUTORIZADO, |
17 | type: "/error/sesioniniciada.html", |
18 | title: "Sesión iniciada.", |
19 | detail: "La sesión ya está iniciada.", |
20 | ); |
21 | } |
22 | $cue = leeTexto("cue"); |
23 | $match = leeTexto("match"); |
24 | if ($cue === null || $cue === "") |
25 | throw new ProblemDetails( |
26 | status: ProblemDetails::BadRequest, |
27 | type: "/error/faltacue.html", |
28 | title: "Falta el cue.", |
29 | ); |
30 | |
31 | if ($match === null || $match === "") |
32 | throw new ProblemDetails( |
33 | status: ProblemDetails::BadRequest, |
34 | type: "/error/faltamatch.html", |
35 | title: "Falta el match.", |
36 | ); |
37 | |
38 | $usuario = usuarioVerifica(trim($cue), trim($match)); |
39 | if ($usuario === false) { |
40 | throw new ProblemDetails( |
41 | status: ProblemDetails::BadRequest, |
42 | type: "/error/datosincorrectos.html", |
43 | title: "Datos incorrectos.", |
44 | detail: "El cue y/o el match proporcionados son incorrectos.", |
45 | ); |
46 | } else { |
47 | $rolIds = []; |
48 | foreach ($usuario->roles as $rol) { |
49 | $rolIds[] = $rol->id; |
50 | } |
51 | $_SESSION[CUE] = $cue; |
52 | $_SESSION[ROL_IDS] = $rolIds; |
53 | return [ |
54 | CUE => $cue, |
55 | ROL_IDS => $rolIds |
56 | ]; |
57 | } |
58 | }); |
59 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/const/CUE.php"; |
5 | require_once __DIR__ . "/const/ROL_IDS.php"; |
6 | |
7 | ejecutaServicio(function () { |
8 | session_start(); |
9 | if (isset($_SESSION[CUE])) { |
10 | unset($_SESSION[CUE]); |
11 | } |
12 | if (isset($_SESSION[ROL_IDS])) { |
13 | unset($_SESSION[ROL_IDS]); |
14 | } |
15 | session_destroy(); |
16 | return []; |
17 | }); |
18 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/const/ROL_CLIENTE.php"; |
5 | require_once __DIR__ . "/protege.php"; |
6 | |
7 | ejecutaServicio(function () { |
8 | $sesion = protege([ROL_CLIENTE]); |
9 | return "Hola " . $sesion->cue; |
10 | }); |
11 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | require_once __DIR__ . "/protege.php"; |
5 | |
6 | ejecutaServicio(function () { |
7 | return protege(); |
8 | }); |
9 |
1 | <?php |
2 | |
3 | function bdCrea(PDO $con) |
4 | { |
5 | $con->exec( |
6 | 'CREATE TABLE IF NOT EXISTS USUARIO ( |
7 | USU_ID INTEGER, |
8 | USU_CUE TEXT NOT NULL, |
9 | USU_MATCH TEXT NOT NULL, |
10 | CONSTRAINT USU_PK |
11 | PRIMARY KEY(USU_ID), |
12 | CONSTRAINT USU_CUE_UNQ |
13 | UNIQUE(USU_CUE) |
14 | )' |
15 | ); |
16 | $con->exec( |
17 | 'CREATE TABLE IF NOT EXISTS ROL ( |
18 | ROL_ID TEXT, |
19 | ROL_DESCRIPCION TEXT NOT NULL, |
20 | CONSTRAINT ROL_PK |
21 | PRIMARY KEY(ROL_ID), |
22 | CONSTRAINT ROL_DESCR_UNQ |
23 | UNIQUE(ROL_DESCRIPCION) |
24 | )' |
25 | ); |
26 | $con->exec( |
27 | 'CREATE TABLE IF NOT EXISTS USU_ROL ( |
28 | USU_ID INTEGER NOT NULL, |
29 | ROL_ID TEXT NOT NULL, |
30 | CONSTRAINT USU_ROL_PK |
31 | PRIMARY KEY(USU_ID, ROL_ID), |
32 | CONSTRAINT USU_ROL_USU_FK |
33 | FOREIGN KEY (USU_ID) REFERENCES USUARIO(USU_ID), |
34 | CONSTRAINT USU_ROL_ROL_FK |
35 | FOREIGN KEY (ROL_ID) REFERENCES ROL(ROL_ID) |
36 | )' |
37 | ); |
38 | } |
39 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../const/ROL_CLIENTE.php"; |
4 | require_once __DIR__ . "/../const/ROL_ADMINISTRADOR.php"; |
5 | require_once __DIR__ . "/../modelo/Rol.php"; |
6 | require_once __DIR__ . "/../modelo/Usuario.php"; |
7 | require_once __DIR__ . "/bdCrea.php"; |
8 | require_once __DIR__ . "/usuarioBuscaCue.php"; |
9 | require_once __DIR__ . "/usuarioAgrega.php"; |
10 | require_once __DIR__ . "/rolConsulta.php"; |
11 | require_once __DIR__ . "/rolAgrega.php"; |
12 | require_once __DIR__ . "/rolBusca.php"; |
13 | |
14 | class Bd |
15 | { |
16 | |
17 | private static ?PDO $conexion = null; |
18 | |
19 | static function getConexion(): PDO |
20 | { |
21 | if (self::$conexion === null) { |
22 | |
23 | self::$conexion = new PDO( |
24 | // cadena de conexión |
25 | "sqlite:srvaut.db", |
26 | // usuario |
27 | null, |
28 | // contraseña |
29 | null, |
30 | // Opciones: conexiones persistentes y lanza excepciones. |
31 | [PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] |
32 | ); |
33 | |
34 | bdCrea(self::$conexion); |
35 | |
36 | if (rolBusca(ROL_ADMINISTRADOR) === false) { |
37 | $administrador = new Rol( |
38 | id: ROL_ADMINISTRADOR, |
39 | descripcion: "Administra el sistema." |
40 | ); |
41 | rolAgrega($administrador); |
42 | } |
43 | |
44 | if (rolBusca("Cliente") === false) { |
45 | $cliente = new Rol( |
46 | id: "Cliente", |
47 | descripcion: "Realiza compras." |
48 | ); |
49 | rolAgrega($cliente); |
50 | } |
51 | |
52 | if (usuarioBuscaCue("pepito") === false) { |
53 | $usuario = new Usuario( |
54 | cue: "pepito", |
55 | match: "cuentos", |
56 | roles: [$cliente] |
57 | ); |
58 | usuarioAgrega($usuario); |
59 | } |
60 | |
61 | if (usuarioBuscaCue("susana") === false) { |
62 | $usuario = new Usuario( |
63 | cue: "susana", |
64 | match: "alegria", |
65 | roles: [$administrador] |
66 | ); |
67 | usuarioAgrega($usuario); |
68 | } |
69 | |
70 | if (usuarioBuscaCue("bebe") === false) { |
71 | $usuario = new Usuario( |
72 | cue: "bebe", |
73 | match: "saurio", |
74 | roles: [$administrador, $cliente] |
75 | ); |
76 | usuarioAgrega($usuario); |
77 | } |
78 | } |
79 | |
80 | return self::$conexion; |
81 | } |
82 | } |
83 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Rol.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | |
6 | function rolAgrega(Rol $modelo) |
7 | { |
8 | $modelo->valida(); |
9 | $con = Bd::getConexion(); |
10 | $stmt = $con->prepare( |
11 | "INSERT INTO ROL |
12 | (ROL_ID, ROL_DESCRIPCION) |
13 | VALUES |
14 | (:id, :descripcion)" |
15 | ); |
16 | $stmt->execute([ |
17 | ":id" => $modelo->id, |
18 | ":descripcion" => $modelo->descripcion |
19 | ]); |
20 | } |
21 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Rol.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | require_once __DIR__ . "/usuRolConsulta.php"; |
6 | |
7 | function rolBusca(string $id) : false|Rol |
8 | { |
9 | $con = Bd::getConexion(); |
10 | $stmt = $con->prepare( |
11 | "SELECT |
12 | ROL_ID as id, |
13 | ROL_DESCRIPCION as descripcion |
14 | FROM ROL |
15 | WHERE ROL_ID = :id" |
16 | ); |
17 | $stmt->execute([":id" => $id]); |
18 | $stmt->setFetchMode( |
19 | PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, |
20 | Rol::class |
21 | ); |
22 | return $stmt->fetch(); |
23 | } |
24 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../../lib/php/recibeFetchAll.php"; |
4 | require_once __DIR__ . "/../modelo/Rol.php"; |
5 | require_once __DIR__ . "/Bd.php"; |
6 | |
7 | /** @return Rol[] */ |
8 | function rolConsulta() |
9 | { |
10 | $con = Bd::getConexion(); |
11 | $stmt = $con->query( |
12 | "SELECT |
13 | ROL_ID as id, |
14 | ROL_DESCRIPCION as descripcion |
15 | FROM ROL |
16 | ORDER BY ROL_ID" |
17 | ); |
18 | $resultado = $stmt->fetchAll( |
19 | PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, |
20 | Rol::class |
21 | ); |
22 | return recibeFetchAll($resultado); |
23 | } |
24 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Usuario.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | require_once __DIR__ . "/usuRolAgrega.php"; |
6 | |
7 | function usuarioAgrega(Usuario $modelo) |
8 | { |
9 | $modelo->valida(); |
10 | $con = Bd::getConexion(); |
11 | $con->beginTransaction(); |
12 | $stmt = $con->prepare( |
13 | "INSERT INTO USUARIO |
14 | (USU_CUE, USU_MATCH) |
15 | VALUES |
16 | (:cue, :match)" |
17 | ); |
18 | $stmt->execute([ |
19 | ":cue" => $modelo->cue, |
20 | ":match" => password_hash($modelo->match, PASSWORD_DEFAULT) |
21 | ]); |
22 | /* Si usas una secuencia para generar el id, |
23 | * pasa como parámetro de lastInsertId el |
24 | * nombre de dicha secuencia. */ |
25 | $modelo->id = $con->lastInsertId(); |
26 | usuRolAgrega($modelo); |
27 | $con->commit(); |
28 | } |
29 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Usuario.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | require_once __DIR__ . "/usuRolConsulta.php"; |
6 | |
7 | function usuarioBuscaCue(string $cue) |
8 | { |
9 | $con = Bd::getConexion(); |
10 | $stmt = $con->prepare( |
11 | "SELECT |
12 | USU_ID as id, |
13 | USU_CUE as cue, |
14 | USU_MATCH as match |
15 | FROM USUARIO |
16 | WHERE USU_CUE = :cue" |
17 | ); |
18 | $stmt->execute([":cue" => $cue]); |
19 | $stmt->setFetchMode( |
20 | PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, |
21 | Usuario::class |
22 | ); |
23 | /** @var false|Usuario */ |
24 | $usuario = $stmt->fetch(); |
25 | if ($usuario === false) { |
26 | return false; |
27 | } else { |
28 | $usuario->roles = usuRolConsulta($usuario->id); |
29 | return $usuario; |
30 | } |
31 | } |
32 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/usuarioBuscaCue.php"; |
4 | |
5 | function usuarioVerifica(string $cue, string $match) |
6 | { |
7 | $usuario = usuarioBuscaCue($cue); |
8 | if ($usuario !== false && password_verify($match, $usuario->match)) { |
9 | return $usuario; |
10 | } else { |
11 | return false; |
12 | } |
13 | } |
14 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../modelo/Usuario.php"; |
4 | require_once __DIR__ . "/Bd.php"; |
5 | |
6 | function usuRolAgrega(Usuario $usuario) { |
7 | $roles = $usuario->roles; |
8 | if (sizeof($roles) > 0) { |
9 | $con = Bd::getConexion(); |
10 | $stmt = $con->prepare( |
11 | "INSERT INTO USU_ROL |
12 | (USU_ID, ROL_ID) |
13 | VALUES |
14 | (:usuId, :rolId)" |
15 | ); |
16 | foreach ($roles as $rol) { |
17 | $stmt->execute( |
18 | [ |
19 | ":usuId" => $usuario->id, |
20 | ":rolId" => $rol->id |
21 | ] |
22 | ); |
23 | } |
24 | } |
25 | } |
26 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../../lib/php/recibeFetchAll.php"; |
4 | require_once __DIR__ . "/../modelo/Rol.php"; |
5 | require_once __DIR__ . "/Bd.php"; |
6 | |
7 | /** @return Rol[] */ |
8 | function usuRolConsulta(int $usuId) |
9 | { |
10 | $con = Bd::getConexion(); |
11 | $stmt = $con->query( |
12 | "SELECT |
13 | UR.ROL_ID AS id, |
14 | R.ROL_DESCRIPCION AS descripcion |
15 | FROM USU_ROL UR, ROL R |
16 | WHERE |
17 | UR.ROL_ID = R.ROL_ID |
18 | AND UR.USU_ID = :usuId |
19 | ORDER BY UR.ROL_ID" |
20 | ); |
21 | $stmt->execute([":usuId" => $usuId]); |
22 | $resultado = $stmt->fetchAll( |
23 | PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, |
24 | Rol::class |
25 | ); |
26 | return recibeFetchAll($resultado); |
27 | } |
28 |
1 | /** |
2 | * Codifica un texto para que cambie los caracteres |
3 | * especiales y no se pueda interpretar como |
4 | * etiiqueta HTML. Esta técnica evita la inyección |
5 | * de código. |
6 | * @param { string } texto |
7 | * @returns { string } un texto que no puede |
8 | * interpretarse como HTML. */ |
9 | export function htmlentities(texto) { |
10 | return texto.replace(/[<>"']/g, textoDetectado => { |
11 | switch (textoDetectado) { |
12 | case "<": return "<" |
13 | case ">": return ">" |
14 | case '"': return """ |
15 | case "'": return "'" |
16 | default: return textoDetectado |
17 | } |
18 | }) |
19 | } |
20 | // Permite que los eventos de html usen la función. |
21 | window["htmlentities"] = htmlentities |
1 | import { |
2 | JsonResponse, JsonResponse_Created, JsonResponse_NoContent, JsonResponse_OK |
3 | } from "./JsonResponse.js" |
4 | import { |
5 | ProblemDetails, ProblemDetails_InternalServerError |
6 | } from "./ProblemDetails.js" |
7 | |
8 | /** |
9 | * Espera a que la promesa de un fetch termine. Si |
10 | * hay error, lanza una excepción. Si no hay error, |
11 | * interpreta la respuesta del servidor como JSON y |
12 | * la convierte en una literal de objeto. |
13 | * @param { string | Promise<Response> } servicio |
14 | */ |
15 | export async function invocaServicio(servicio) { |
16 | let f = servicio |
17 | if (typeof servicio === "string") { |
18 | f = fetch(servicio, { |
19 | headers: { "Accept": "application/json, application/problem+json" } |
20 | }) |
21 | } else if (!(f instanceof Promise)) { |
22 | throw new Error("Servicio de tipo incorrecto.") |
23 | } |
24 | const respuesta = await f |
25 | if (respuesta.ok) { |
26 | if (respuesta.status === JsonResponse_NoContent) { |
27 | return new JsonResponse(JsonResponse_NoContent) |
28 | } |
29 | const texto = await respuesta.text() |
30 | try { |
31 | const body = JSON.parse(texto) |
32 | if (respuesta.status === JsonResponse_Created) { |
33 | const location = respuesta.headers.get("location") |
34 | return new JsonResponse(JsonResponse_Created, body, |
35 | location === null ? undefined : location) |
36 | } else { |
37 | return new JsonResponse(JsonResponse_OK, body) |
38 | } |
39 | } catch (error) { |
40 | // El contenido no es JSON. Probablemente sea texto. |
41 | throw new ProblemDetails(ProblemDetails_InternalServerError, |
42 | "Problema interno en el servidor.", texto) |
43 | } |
44 | } else { |
45 | const texto = await respuesta.text() |
46 | try { |
47 | const { type, title, detail } = JSON.parse(texto) |
48 | throw new ProblemDetails(respuesta.status, |
49 | typeof title === "string" ? title : "", |
50 | typeof detail === "string" ? detail : undefined, |
51 | typeof type === "string" ? type : undefined) |
52 | } catch (error) { |
53 | if (error instanceof ProblemDetails) { |
54 | throw error |
55 | } else { |
56 | // El contenido no es JSON. Probablemente sea texto. |
57 | throw new ProblemDetails(respuesta.status, respuesta.statusText, texto) |
58 | } |
59 | } |
60 | } |
61 | } |
62 | |
63 | // Permite que los eventos de html usen la función. |
64 | window["invocaServicio"] = invocaServicio |
1 | export const JsonResponse_OK = 200 |
2 | export const JsonResponse_Created = 201 |
3 | export const JsonResponse_NoContent = 204 |
4 | |
5 | export class JsonResponse { |
6 | |
7 | /** |
8 | * @param {number} status |
9 | * @param {any} [body] |
10 | * @param {string} [location] |
11 | */ |
12 | constructor(status, body, location) { |
13 | /** @readonly */ |
14 | this.status = status |
15 | /** @readonly */ |
16 | this.body = body |
17 | /** @readonly */ |
18 | this.location = location |
19 | } |
20 | |
21 | } |
1 | import { ProblemDetails } from "./ProblemDetails.js" |
2 | |
3 | /** |
4 | * Muestra un error en la consola y en un cuadro de |
5 | * alerta el mensaje de una excepción. |
6 | * @param { ProblemDetails | Error | null } error descripción del error. |
7 | */ |
8 | export function muestraError(error) { |
9 | if (error === null) { |
10 | console.log("Error") |
11 | alert("Error") |
12 | } else if (error instanceof ProblemDetails) { |
13 | let mensaje = error.title |
14 | if (error.detail) { |
15 | mensaje += `\n\n${error.detail}` |
16 | } |
17 | mensaje += `\n\nCódigo: ${error.status}` |
18 | if (error.type) { |
19 | mensaje += ` ${error.type}` |
20 | } |
21 | console.error(mensaje) |
22 | console.error(error) |
23 | alert(mensaje) |
24 | } else { |
25 | console.error(error) |
26 | alert(error.message) |
27 | } |
28 | } |
29 | |
30 | // Permite que los eventos de html usen la función. |
31 | window["muestraError"] = muestraError |
1 | export const ProblemDetails_BadRequest = 400 |
2 | export const ProblemDetails_NotFound = 404 |
3 | export const ProblemDetails_InternalServerError = 500 |
4 | |
5 | export class ProblemDetails extends Error { |
6 | |
7 | /** |
8 | * @param {number} status |
9 | * @param {string} title |
10 | * @param {string} [detail] |
11 | * @param {string} [type] |
12 | */ |
13 | constructor(status, title, detail, type) { |
14 | super(title) |
15 | /** @readonly */ |
16 | this.status = status |
17 | /** @readonly */ |
18 | this.type = type |
19 | /** @readonly */ |
20 | this.title = title |
21 | /** @readonly */ |
22 | this.detail = detail |
23 | } |
24 | |
25 | } |
1 | import { invocaServicio } from "./invocaServicio.js" |
2 | |
3 | /** |
4 | * Envía los datos de la forma a la url usando la codificación |
5 | * multipart/form-data. |
6 | * @param {string} url |
7 | * @param {Event} event |
8 | * @param { "GET" | "POST"| "PUT" | "PATCH" | "DELETE" | "TRACE" | "OPTIONS" |
9 | * | "CONNECT" | "HEAD" } metodoHttp |
10 | */ |
11 | export function submitForm(url, event, metodoHttp = "POST") { |
12 | event.preventDefault() |
13 | const form = event.target |
14 | if (!(form instanceof HTMLFormElement)) |
15 | throw new Error("event.target no es una form.") |
16 | return invocaServicio(fetch(url, { |
17 | method: metodoHttp, |
18 | headers: { "Accept": "application/json, application/problem+json" }, |
19 | body: new FormData(form) |
20 | })) |
21 | } |
22 | |
23 | // Permite que los eventos de html usen la función. |
24 | window["submitForm"] = submitForm |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/JsonResponse.php"; |
4 | require_once __DIR__ . "/ProblemDetails.php"; |
5 | |
6 | /** |
7 | * Ejecuta una funcion que implementa un servicio. |
8 | */ |
9 | function ejecutaServicio($servicio) |
10 | { |
11 | try { |
12 | $resultado = $servicio(); |
13 | if (!($resultado instanceof JsonResponse)) { |
14 | $resultado = JsonResponse::ok($resultado); |
15 | } |
16 | procesa_json_response($resultado); |
17 | } catch (ProblemDetails $details) { |
18 | procesa_problem_details($details); |
19 | } catch (Throwable $throwable) { |
20 | procesa_problem_details(new ProblemDetails( |
21 | status: ProblemDetails::InternalServerError, |
22 | type: "/error/errorinterno.html", |
23 | title: "Error interno del servidor.", |
24 | detail: $throwable->getMessage() |
25 | )); |
26 | } |
27 | } |
28 | |
29 | function procesa_json_response(JsonResponse $response) |
30 | { |
31 | $json = ""; |
32 | $body = $response->body; |
33 | if ($response->status !== JsonResponse_NoContent) { |
34 | $json = json_encode($body); |
35 | if ($json === false) { |
36 | no_puede_generar_json(); |
37 | return; |
38 | } |
39 | } |
40 | http_response_code($response->status); |
41 | if ($response->location !== null) { |
42 | header("Location: {$response->location}"); |
43 | } |
44 | if ($response->status !== JsonResponse_NoContent) { |
45 | header("Content-Type: application/json"); |
46 | echo $json; |
47 | } |
48 | } |
49 | |
50 | function procesa_problem_details(ProblemDetails $details) |
51 | { |
52 | $body = ["title" => $details->title]; |
53 | if ($details->type !== null) { |
54 | $body["type"] = $details->type; |
55 | } |
56 | if ($details->detail !== null) { |
57 | $body["detail"] = $details->detail; |
58 | } |
59 | $json = json_encode($body); |
60 | if ($json === false) { |
61 | no_puede_generar_json(); |
62 | } else { |
63 | http_response_code($details->status); |
64 | header("Content-Type: application/problem+json"); |
65 | echo $json; |
66 | } |
67 | } |
68 | |
69 | function no_puede_generar_json() |
70 | { |
71 | http_response_code(ProblemDetails::InternalServerError); |
72 | header("Content-Type: application/problem+json"); |
73 | echo '{"type":"/error/nojson.html"' |
74 | . ',"title":"El valor devuelto no puede representarse como JSON."}'; |
75 | } |
76 |
1 | <?php |
2 | |
3 | const JsonResponse_OK = 200; |
4 | const JsonResponse_Created = 201; |
5 | const JsonResponse_NoContent = 204; |
6 | |
7 | class JsonResponse |
8 | { |
9 | |
10 | public int $status; |
11 | public $body; |
12 | public ?string $location; |
13 | |
14 | public function __construct( |
15 | int $status = JsonResponse_OK, |
16 | $body = null, |
17 | ?string $location = null |
18 | ) { |
19 | $this->status = $status; |
20 | $this->body = $body; |
21 | $this->location = $location; |
22 | } |
23 | |
24 | public static function ok($body) |
25 | { |
26 | return new JsonResponse(body: $body); |
27 | } |
28 | |
29 | public static function created(string $location, $body) |
30 | { |
31 | return new JsonResponse(JsonResponse_Created, $body, $location); |
32 | } |
33 | |
34 | public static function noContent() |
35 | { |
36 | return new JsonResponse(JsonResponse_NoContent, null); |
37 | } |
38 | } |
39 |
1 | <?php |
2 | |
3 | /** |
4 | * Recupera el texto de un parámetro enviado al |
5 | * servidor por medio de GET, POST o cookie. |
6 | * Si el parámetro no se recibe, devuelve null. |
7 | */ |
8 | function leeTexto(string $parametro): ?string |
9 | { |
10 | /* Si el parámetro está asignado en $_REQUEST, |
11 | * devuelve su valor; de lo contrario, |
12 | * devuelve null. */ |
13 | $valor = isset($_REQUEST[$parametro]) |
14 | ? $_REQUEST[$parametro] |
15 | : null; |
16 | return $valor; |
17 | } |
18 |
1 | <?php |
2 | |
3 | class ProblemDetails extends Exception |
4 | { |
5 | |
6 | public const BadRequest = 400; |
7 | public const NotFound = 404; |
8 | public const InternalServerError = 500; |
9 | |
10 | public int $status; |
11 | public string $title; |
12 | public ?string $type; |
13 | public ?string $detail; |
14 | |
15 | public function __construct( |
16 | int $status, |
17 | string $title, |
18 | ?string $type = null, |
19 | ?string $detail = null, |
20 | Throwable $previous = null |
21 | ) { |
22 | parent::__construct($title, $status, $previous); |
23 | $this->status = $status; |
24 | $this->type = $type; |
25 | $this->title = $title; |
26 | $this->detail = $detail; |
27 | } |
28 | } |
29 |
1 | <?php |
2 | |
3 | function recibeFetchAll(false|array $resultado): array |
4 | { |
5 | if ($resultado === false) { |
6 | return []; |
7 | } else { |
8 | return $resultado; |
9 | } |
10 | } |
11 |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Datos incorrectos</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Datos incorrectos</h1> |
16 | |
17 | <p>El cue y/o el match proporcionados son incorrectos.</p> |
18 | </body> |
19 | |
20 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Error interno del servidor</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Error interno del servidor</h1> |
16 | |
17 | <p>Se presentó de forma inesperada un error interno del servidor.</p> |
18 | |
19 | </body> |
20 | |
21 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Falta el cue</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Falta el cue</h1> |
16 | |
17 | </body> |
18 | |
19 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Falta la descripción</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Falta la descripción</h1> |
16 | |
17 | </body> |
18 | |
19 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Falta el id</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Falta el id</h1> |
16 | |
17 | <p>No se ha proporcionado el valor de id.</p> |
18 | |
19 | </body> |
20 | |
21 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Falta el match</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Falta el match</h1> |
16 | |
17 | </body> |
18 | |
19 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Falta el nombre</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Falta el nombre</h1> |
16 | |
17 | </body> |
18 | |
19 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>No autorizado</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>No autorizado</h1> |
16 | |
17 | <p>No está autorizado para usar este recurso.</p> |
18 | |
19 | </body> |
20 | |
21 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>El valor devuelto no puede representarse como JSON</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>El valor devuelto no puede representarse como JSON</h1> |
16 | |
17 | <p> |
18 | Debido a un error interno del servidor, la respuesta generada, no se puede |
19 | recuperar. |
20 | </p> |
21 | |
22 | </body> |
23 | |
24 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Pasatiempo no encontrado</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Pasatiempo no encontrado</h1> |
16 | |
17 | <p>No se encontró ningún pasatiempo con el id solicitado.</p> |
18 | |
19 | </body> |
20 | |
21 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Tipo incorrecto para un rol</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Tipo incorrecto para un rol</h1> |
16 | |
17 | </body> |
18 | |
19 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Sesión iniciada</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Sesión iniciada</h1> |
16 | |
17 | <p>La sesión ya está iniciada.</p> |
18 | |
19 | </body> |
20 | |
21 | </html> |
Este archivo ayuda a detectar errores en los archivos del proyecto.
Lo utiliza principalmente Visual Studio Code.
No se explica aquí su estructura, pero puede encontrarse la explicación de todo en la documentación del sitio de Visual Studio Code.
1 | { |
2 | "compilerOptions": { |
3 | "checkJs": true, |
4 | "strictNullChecks": true, |
5 | "target": "ES6", |
6 | "module": "ES6", |
7 | "moduleResolution": "classic", |
8 | "lib": [ |
9 | "ES2017", |
10 | "WebWorker", |
11 | "DOM" |
12 | ] |
13 | } |
14 | } |
En esta lección se amplió el ejemplo de la lección anterior para controlar el acceso a una app.