@cayotv/sso 0.0.0 → 0.1.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/README.md CHANGED
@@ -1 +1,92 @@
1
- # @cayotv/sso — reserva del nombre. Usa 0.1.0 o posterior.
1
+ # @cayotv/sso
2
+
3
+ Librería de las apps detrás del gateway del SSO de CayoTV (D22). Valida el access token que el gateway pone en `Authorization`, con las doce comprobaciones del contrato ([`docs/12-contrato-apps.md`](../../docs/12-contrato-apps.md), §3). Cada rechazo lleva su código.
4
+
5
+ Cómo integrar una app: [`docs/13-integracion-sso.md`](../../docs/13-integracion-sso.md).
6
+
7
+ ## Instalación
8
+
9
+ ```sh
10
+ npm install --save-exact @cayotv/sso@0.1.0
11
+ ```
12
+
13
+ Siempre con la versión exacta: una versión nueva se adopta a propósito, no en un `npm install` cualquiera. El paquete es público, pero no lleva nada secreto: el issuer y el JWKS ya lo son.
14
+
15
+ ## Uso
16
+
17
+ ```ts
18
+ import { verifyRequest, SsoError } from "@cayotv/sso";
19
+
20
+ const config = {
21
+ issuer: "https://cognito-idp.<region>.amazonaws.com/<pool>",
22
+ clientId: "<client de la app>",
23
+ appId: "opta-media",
24
+ };
25
+
26
+ try {
27
+ const identity = await verifyRequest(request.headers, config); // { sub, role, apps, managedApps }
28
+ } catch (err) {
29
+ if (err instanceof SsoError) {
30
+ // err.code (p. ej. "expired") y err.status (401 o 403)
31
+ }
32
+ throw err; // otro error (el JWKS no responde): la app falla en cerrado
33
+ }
34
+ ```
35
+
36
+ - **Configuración obligatoria:** `issuer`, `clientId` y `appId`. Sin ellos, `verifyRequest` lanza un `Error` (no un `SsoError`): es un fallo de programación, no un 401.
37
+ - **El JWKS** se pide a `<issuer>/.well-known/jwks.json`, se guarda en caché y se vuelve a pedir ante un `kid` desconocido.
38
+ - **`allowAnyApp: true`** es solo para el portal, que admite a cualquiera con sesión. Una app nunca lo pone.
39
+
40
+ ## Operaciones sensibles: `checkNow`
41
+
42
+ ```ts
43
+ import { checkNow } from "@cayotv/sso";
44
+
45
+ const identity = await verifyRequest(request.headers, config); // primero, como siempre
46
+ const check = await checkNow(request.headers); // { allowed: true } o { allowed: false, reason }
47
+ if (!check.allowed) return new Response(null, { status: 403 });
48
+ // ...la operación sensible
49
+ ```
50
+
51
+ - Pregunta al gateway (`http://sso-gateway:8081/_sso/check`, en la red del host) si el usuario puede todavía, con los datos del directorio y no los del token (contrato, §6).
52
+ - Solo antes de la operación concreta, nunca en cada petición.
53
+ - **Nunca lanza.** Si el gateway no responde, o responde algo inesperado, da `{ allowed: false, reason: "unavailable" }`.
54
+ - `url`, `timeoutMs` (2 s) y `fetch` se pueden cambiar en el segundo argumento.
55
+
56
+ ## Next.js
57
+
58
+ ```ts
59
+ import { headers } from "next/headers";
60
+ import { pageIdentity, withIdentity } from "@cayotv/sso/next";
61
+
62
+ // Página de servidor: la identidad o null.
63
+ const identity = await pageIdentity(await headers(), config);
64
+
65
+ // Route Handler: 401 o 403 con {"error": <código>} sin llegar al handler.
66
+ export const GET = withIdentity(config, async (req, identity) => Response.json({ sub: identity.sub }));
67
+ ```
68
+
69
+ La identidad se comprueba en cada página y en cada Route Handler, nunca en el middleware de Next (D20).
70
+
71
+ ## Selector de apps (React)
72
+
73
+ ```tsx
74
+ import { AppsSelector } from "@cayotv/sso/react";
75
+
76
+ <AppsSelector withLogout />
77
+ ```
78
+
79
+ Solo envuelve el Web Component que sirve el gateway en `/_sso/selector.js` (contrato, §7). `react` es una dependencia opcional del paquete.
80
+
81
+ ## Tests
82
+
83
+ `npm test -w sdk/js` pasa, entre otros, los vectores del contrato (`sdk/test-vectors/vectors.json`) con el código exacto de cada caso.
84
+
85
+ ## Publicación
86
+
87
+ El paquete se publica en npm con el workflow `release-sdk`, por publicación de confianza (OIDC), sin tokens y sin procedencia (el repositorio es privado). El workflow solo propone la versión: no se publica hasta que un mantenedor la aprueba con 2FA (staged publishing). Lo que se publica no es este directorio: `npm run stage -w sdk/js` compila `src/` a `dist/` y prepara `.publish/`, con un `package.json` generado a partir de este que exporta `dist/`. `npm run check:package -w sdk/js` comprueba qué ficheros entran y que lo compilado carga. Las dos cosas corren en cada `ci`.
88
+
89
+ Para publicar una versión:
90
+ 1. Una PR que sube `version` en `package.json` y en los ejemplos de instalación, el de arriba y el de `docs/13-integracion-sso.md` (§3).
91
+ 2. Fusionada y con `ci` en verde, un operador lanza `release-sdk` (Actions → Run workflow). Propone la versión de la punta de `main` con `npm stage publish` y crea el tag `sdk-v<version>`.
92
+ 3. Un mantenedor del paquete en npm la revisa y la aprueba con 2FA: npmjs.com → `@cayotv/sso` → *Staged Packages*, o `npm stage list @cayotv/sso` y `npm stage approve <stage-id>`. Hasta entonces no está publicada. Si se rechaza (`npm stage reject`), el tag se queda y la siguiente sube la versión.
@@ -0,0 +1,80 @@
1
+ import { type JWTVerifyGetKey } from "jose";
2
+ /**
3
+ * Validacion del access token que el gateway pone en `Authorization`, segun el
4
+ * contrato de las apps (docs/12-contrato-apps.md, §3; D22). Las doce
5
+ * comprobaciones, en su orden, y cada fallo con su codigo. Los vectores de
6
+ * sdk/test-vectors son la prueba de que esto y el contrato dicen lo mismo.
7
+ *
8
+ * Sin framework: recibe cualquier objeto con `get(nombre)`, como los Headers de
9
+ * la plataforma web o los de Next.js. El adaptador de Next.js esta en ./next.
10
+ */
11
+ /** Los codigos del contrato (§3). Del 1 al 10 y el 12 son un 401; el 11, un 403. */
12
+ export type SsoErrorCode = "missing_token" | "malformed_token" | "invalid_token" | "missing_claim" | "wrong_issuer" | "expired" | "not_yet_valid" | "issued_in_future" | "wrong_token_use" | "wrong_client" | "forbidden_app" | "user_mismatch";
13
+ /** El token no vale para esta app. Cualquier otro error (p. ej. el JWKS no responde) no es un SsoError: la app falla en cerrado. */
14
+ export declare class SsoError extends Error {
15
+ readonly code: SsoErrorCode;
16
+ readonly status: 401 | 403;
17
+ constructor(code: SsoErrorCode, detail?: string);
18
+ }
19
+ /** Los claims del token (§4). Sirven para leer; no bastan para una operacion sensible (§6). */
20
+ export interface Identity {
21
+ sub: string;
22
+ role: "admin" | "app_manager" | "user";
23
+ apps: string[];
24
+ managedApps: string[];
25
+ }
26
+ export interface SsoConfig {
27
+ /** `iss` exacto del pool: `https://cognito-idp.<region>.amazonaws.com/<pool>`. */
28
+ issuer: string;
29
+ /** El client de la app. Cognito lo pone en `client_id`, no en `aud`. */
30
+ clientId: string;
31
+ /** El `app_id` de la app en el catalogo. Obligatorio salvo con `allowAnyApp`. */
32
+ appId?: string;
33
+ /**
34
+ * Solo el portal: admite a cualquiera con sesion, como hace el gateway con
35
+ * el host del portal. Una app nunca lo pone: se saltaria la comprobacion 11.
36
+ */
37
+ allowAnyApp?: true;
38
+ /** Por defecto, el JWKS del pool (`<issuer>/.well-known/jwks.json`), en cache. Para los tests, uno local. */
39
+ keys?: JWTVerifyGetKey;
40
+ /** Por defecto, el reloj del sistema. */
41
+ now?: () => Date;
42
+ }
43
+ /** Lo minimo de unas cabeceras: los Headers web, los de Next.js o un adaptador. */
44
+ export interface HeadersLike {
45
+ get(name: string): string | null;
46
+ }
47
+ /** La configuracion incompleta es un error de programacion: se lanza siempre, no se convierte en un 401. */
48
+ export declare function checkConfig(config: SsoConfig): void;
49
+ /**
50
+ * Valida la peticion y devuelve la identidad, o lanza un SsoError con el
51
+ * codigo del contrato. Hay que llamarla en cada peticion que necesite
52
+ * identidad, en el backend (§3).
53
+ */
54
+ export declare function verifyRequest(headers: HeadersLike, config: SsoConfig): Promise<Identity>;
55
+ /** Respuesta de `checkNow`. `invalid_token` y `unavailable` los pone la libreria; el resto, el gateway. */
56
+ export type CheckResult = {
57
+ allowed: true;
58
+ } | {
59
+ allowed: false;
60
+ reason: "not_found" | "revoked" | "expired" | "no_app" | "invalid_token" | "unavailable";
61
+ };
62
+ export interface CheckOptions {
63
+ /** Por defecto, el puerto interno del gateway en la red del host. */
64
+ url?: string;
65
+ /** Por defecto, 2 s. */
66
+ timeoutMs?: number;
67
+ /** Para los tests. */
68
+ fetch?: typeof fetch;
69
+ }
70
+ export declare const DEFAULT_CHECK_URL = "http://sso-gateway:8081/_sso/check";
71
+ /**
72
+ * Comprobacion en tiempo real antes de una operacion sensible (contrato, §6;
73
+ * D22): el gateway relee del directorio si este usuario puede todavia usar la
74
+ * app. Se llama despues de `verifyRequest` y solo antes de la operacion
75
+ * concreta, nunca en cada peticion. **Nunca lanza y nunca da `allowed: true`
76
+ * por un fallo:** si el gateway no responde o responde algo inesperado, la
77
+ * respuesta es `{ allowed: false, reason: "unavailable" }` y la operacion no se
78
+ * hace.
79
+ */
80
+ export declare function checkNow(headers: HeadersLike, options?: CheckOptions): Promise<CheckResult>;
package/dist/index.js ADDED
@@ -0,0 +1,167 @@
1
+ import { createRemoteJWKSet, errors, jwtVerify } from "jose";
2
+ /** El token no vale para esta app. Cualquier otro error (p. ej. el JWKS no responde) no es un SsoError: la app falla en cerrado. */
3
+ export class SsoError extends Error {
4
+ code;
5
+ status;
6
+ constructor(code, detail) {
7
+ super(detail ?? code);
8
+ this.code = code;
9
+ this.name = "SsoError";
10
+ this.status = code === "forbidden_app" ? 403 : 401;
11
+ }
12
+ }
13
+ /** Margen para `iat` (§3, comprobacion 8). `exp` y `nbf` van sin margen. */
14
+ const IAT_SKEW_S = 5;
15
+ const BEARER = /^Bearer ([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/;
16
+ const REQUIRED = ["sub", "exp", "iat", "token_use", "client_id"];
17
+ const remoteKeys = new Map();
18
+ function keysFor(config) {
19
+ if (config.keys)
20
+ return config.keys;
21
+ let keys = remoteKeys.get(config.issuer);
22
+ if (!keys) {
23
+ // jose vuelve a pedirlo ante un kid desconocido, con un tiempo minimo entre peticiones.
24
+ keys = createRemoteJWKSet(new URL(`${config.issuer}/.well-known/jwks.json`), { timeoutDuration: 3000 });
25
+ remoteKeys.set(config.issuer, keys);
26
+ }
27
+ return keys;
28
+ }
29
+ /** La configuracion incompleta es un error de programacion: se lanza siempre, no se convierte en un 401. */
30
+ export function checkConfig(config) {
31
+ if (!/^https:\/\/\S+[^/]$/.test(config.issuer ?? ""))
32
+ throw new Error("@cayotv/sso: falta issuer, o no es una URL https sin barra final");
33
+ if (!config.clientId)
34
+ throw new Error("@cayotv/sso: falta clientId");
35
+ // Desde JavaScript podria llegar cualquier cosa ("false", 1): solo vale true, y lo demas se rechaza.
36
+ if (config.allowAnyApp !== undefined && config.allowAnyApp !== true)
37
+ throw new Error("@cayotv/sso: allowAnyApp solo admite true");
38
+ if (config.allowAnyApp === true) {
39
+ if (config.appId !== undefined)
40
+ throw new Error("@cayotv/sso: appId y allowAnyApp no van juntos");
41
+ }
42
+ else if (!config.appId) {
43
+ throw new Error("@cayotv/sso: falta appId");
44
+ }
45
+ }
46
+ const strings = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === "string") : []);
47
+ /** Traduce un error de jose a su codigo. Lo que no es culpa del token (red, JWKS roto) se relanza. */
48
+ function codeOf(err) {
49
+ if (err instanceof errors.JWTExpired)
50
+ return "expired";
51
+ if (err instanceof errors.JWTClaimValidationFailed) {
52
+ if (err.reason === "missing")
53
+ return "missing_claim";
54
+ if (err.claim === "iss")
55
+ return "wrong_issuer";
56
+ if (err.claim === "nbf")
57
+ return "not_yet_valid";
58
+ return "invalid_token";
59
+ }
60
+ if (err instanceof errors.JWSSignatureVerificationFailed ||
61
+ err instanceof errors.JOSEAlgNotAllowed ||
62
+ err instanceof errors.JWKSNoMatchingKey ||
63
+ err instanceof errors.JWKSMultipleMatchingKeys ||
64
+ err instanceof errors.JWSInvalid ||
65
+ err instanceof errors.JWTInvalid ||
66
+ err instanceof errors.JOSENotSupported) {
67
+ return "invalid_token";
68
+ }
69
+ return null;
70
+ }
71
+ /**
72
+ * Valida la peticion y devuelve la identidad, o lanza un SsoError con el
73
+ * codigo del contrato. Hay que llamarla en cada peticion que necesite
74
+ * identidad, en el backend (§3).
75
+ */
76
+ export async function verifyRequest(headers, config) {
77
+ checkConfig(config);
78
+ const now = config.now ?? (() => new Date());
79
+ // 1 y 2.
80
+ const auth = headers.get("authorization");
81
+ if (auth === null)
82
+ throw new SsoError("missing_token");
83
+ const match = BEARER.exec(auth);
84
+ if (!match)
85
+ throw new SsoError("malformed_token");
86
+ // 3 a 7: firma solo RS256, claims obligatorios, iss, exp y nbf.
87
+ let payload;
88
+ try {
89
+ ({ payload } = await jwtVerify(match[1], keysFor(config), {
90
+ issuer: config.issuer,
91
+ algorithms: ["RS256"],
92
+ requiredClaims: REQUIRED,
93
+ currentDate: now(),
94
+ }));
95
+ }
96
+ catch (err) {
97
+ const code = codeOf(err);
98
+ if (!code)
99
+ throw err;
100
+ throw new SsoError(code, err.message);
101
+ }
102
+ if (typeof payload.sub !== "string" || payload.sub.length === 0)
103
+ throw new SsoError("missing_claim", "sub vacio");
104
+ // 8 a 10.
105
+ if (typeof payload.iat !== "number" || payload.iat > now().getTime() / 1000 + IAT_SKEW_S)
106
+ throw new SsoError("issued_in_future");
107
+ if (payload.token_use !== "access")
108
+ throw new SsoError("wrong_token_use");
109
+ if (payload.client_id !== config.clientId)
110
+ throw new SsoError("wrong_client");
111
+ const role = payload["cayotv:role"];
112
+ const identity = {
113
+ sub: payload.sub,
114
+ role: role === "admin" || role === "app_manager" ? role : "user",
115
+ apps: strings(payload["cayotv:apps"]),
116
+ managedApps: strings(payload["cayotv:managed_apps"]),
117
+ };
118
+ // 11: la misma regla que el gateway en forward_auth.
119
+ if (config.allowAnyApp !== true && identity.role !== "admin" && !identity.apps.includes(config.appId))
120
+ throw new SsoError("forbidden_app");
121
+ // 12.
122
+ const user = headers.get("x-auth-user");
123
+ if (user !== null && user !== identity.sub)
124
+ throw new SsoError("user_mismatch");
125
+ return identity;
126
+ }
127
+ export const DEFAULT_CHECK_URL = "http://sso-gateway:8081/_sso/check";
128
+ const GATEWAY_REASONS = new Set(["not_found", "revoked", "expired", "no_app"]);
129
+ /**
130
+ * Comprobacion en tiempo real antes de una operacion sensible (contrato, §6;
131
+ * D22): el gateway relee del directorio si este usuario puede todavia usar la
132
+ * app. Se llama despues de `verifyRequest` y solo antes de la operacion
133
+ * concreta, nunca en cada peticion. **Nunca lanza y nunca da `allowed: true`
134
+ * por un fallo:** si el gateway no responde o responde algo inesperado, la
135
+ * respuesta es `{ allowed: false, reason: "unavailable" }` y la operacion no se
136
+ * hace.
137
+ */
138
+ export async function checkNow(headers, options = {}) {
139
+ const authorization = headers.get("authorization");
140
+ if (!authorization)
141
+ return { allowed: false, reason: "invalid_token" };
142
+ let res;
143
+ try {
144
+ res = await (options.fetch ?? fetch)(options.url ?? DEFAULT_CHECK_URL, {
145
+ method: "GET",
146
+ headers: { authorization, accept: "application/json" },
147
+ redirect: "error",
148
+ signal: AbortSignal.timeout(options.timeoutMs ?? 2000),
149
+ });
150
+ }
151
+ catch {
152
+ return { allowed: false, reason: "unavailable" };
153
+ }
154
+ if (res.status === 401)
155
+ return { allowed: false, reason: "invalid_token" };
156
+ if (res.status !== 200)
157
+ return { allowed: false, reason: "unavailable" };
158
+ const body = await res.json().catch(() => null);
159
+ if (typeof body !== "object" || body === null)
160
+ return { allowed: false, reason: "unavailable" };
161
+ const { allowed, reason } = body;
162
+ if (allowed === true)
163
+ return { allowed: true };
164
+ if (allowed === false && typeof reason === "string" && GATEWAY_REASONS.has(reason))
165
+ return { allowed: false, reason: reason };
166
+ return { allowed: false, reason: "unavailable" };
167
+ }
package/dist/next.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { type HeadersLike, type Identity, type SsoConfig } from "@cayotv/sso";
2
+ /**
3
+ * Adaptador para Next.js (App Router; D20, D22). Sin dependencia de `next`: los
4
+ * Route Handlers reciben un Request web, y en una pagina de servidor las
5
+ * cabeceras salen de `await headers()`.
6
+ *
7
+ * La identidad se comprueba en cada pagina y en cada Route Handler, nunca en el
8
+ * middleware (`proxy`) de Next: si el middleware se salta, no se abre nada
9
+ * (D20, «Reglas de uso de Next.js»).
10
+ */
11
+ /**
12
+ * Para una pagina de servidor: la identidad, o null si el token no vale. Con
13
+ * null, la pagina no pinta datos del usuario. Un error que no es del token
14
+ * (el JWKS no responde) se lanza.
15
+ *
16
+ * const identity = await pageIdentity(await headers(), config);
17
+ */
18
+ export declare function pageIdentity(headers: HeadersLike, config: SsoConfig): Promise<Identity | null>;
19
+ /**
20
+ * Envoltorio de un Route Handler: valida y llama al handler con la identidad.
21
+ * Si el token no vale, responde 401 o 403 con `{"error": <codigo>}` sin llegar
22
+ * al handler.
23
+ *
24
+ * export const GET = withIdentity(config, async (req, identity) => Response.json({ sub: identity.sub }));
25
+ */
26
+ export declare function withIdentity<Args extends unknown[]>(config: SsoConfig, handler: (req: Request, identity: Identity, ...args: Args) => Promise<Response>): (req: Request, ...args: Args) => Promise<Response>;
package/dist/next.js ADDED
@@ -0,0 +1,48 @@
1
+ import { SsoError, verifyRequest } from "@cayotv/sso";
2
+ /**
3
+ * Adaptador para Next.js (App Router; D20, D22). Sin dependencia de `next`: los
4
+ * Route Handlers reciben un Request web, y en una pagina de servidor las
5
+ * cabeceras salen de `await headers()`.
6
+ *
7
+ * La identidad se comprueba en cada pagina y en cada Route Handler, nunca en el
8
+ * middleware (`proxy`) de Next: si el middleware se salta, no se abre nada
9
+ * (D20, «Reglas de uso de Next.js»).
10
+ */
11
+ /**
12
+ * Para una pagina de servidor: la identidad, o null si el token no vale. Con
13
+ * null, la pagina no pinta datos del usuario. Un error que no es del token
14
+ * (el JWKS no responde) se lanza.
15
+ *
16
+ * const identity = await pageIdentity(await headers(), config);
17
+ */
18
+ export async function pageIdentity(headers, config) {
19
+ try {
20
+ return await verifyRequest(headers, config);
21
+ }
22
+ catch (err) {
23
+ if (err instanceof SsoError)
24
+ return null;
25
+ throw err;
26
+ }
27
+ }
28
+ /**
29
+ * Envoltorio de un Route Handler: valida y llama al handler con la identidad.
30
+ * Si el token no vale, responde 401 o 403 con `{"error": <codigo>}` sin llegar
31
+ * al handler.
32
+ *
33
+ * export const GET = withIdentity(config, async (req, identity) => Response.json({ sub: identity.sub }));
34
+ */
35
+ export function withIdentity(config, handler) {
36
+ return async (req, ...args) => {
37
+ let identity;
38
+ try {
39
+ identity = await verifyRequest(req.headers, config);
40
+ }
41
+ catch (err) {
42
+ if (err instanceof SsoError)
43
+ return Response.json({ error: err.code }, { status: err.status });
44
+ throw err;
45
+ }
46
+ return handler(req, identity, ...args);
47
+ };
48
+ }
@@ -0,0 +1,5 @@
1
+ export declare function AppsSelector({ withLogout }: {
2
+ withLogout?: boolean;
3
+ }): import("react").ReactElement<{
4
+ "with-logout": string;
5
+ }, string | import("react").JSXElementConstructor<any>>;
package/dist/react.js ADDED
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { createElement, useEffect } from "react";
3
+ /**
4
+ * Selector de apps para React (D22): solo envuelve el Web Component que sirve
5
+ * el gateway en `/_sso/selector.js` (contrato de las apps, §7). Carga el
6
+ * script una vez, en el navegador, y pinta `<cayotv-apps>`; el resto (leer
7
+ * `/_sso/apps`, recargar ante un 401, enlaces solo https) lo hace el
8
+ * componente. Sin JSX, para no obligar a la app a compilar el paquete.
9
+ * `"use client"`: en el App Router de Next se puede usar desde un layout o una
10
+ * pagina de servidor sin envolverlo.
11
+ *
12
+ * <AppsSelector withLogout />
13
+ */
14
+ const SCRIPT_SRC = "/_sso/selector.js";
15
+ export function AppsSelector({ withLogout = false }) {
16
+ useEffect(() => {
17
+ if (customElements.get("cayotv-apps") || document.querySelector(`script[src="${SCRIPT_SRC}"]`))
18
+ return;
19
+ const script = document.createElement("script");
20
+ script.type = "module";
21
+ script.src = SCRIPT_SRC;
22
+ document.head.append(script);
23
+ }, []);
24
+ return createElement("cayotv-apps", withLogout ? { "with-logout": "" } : null);
25
+ }
package/package.json CHANGED
@@ -1,7 +1,43 @@
1
1
  {
2
2
  "name": "@cayotv/sso",
3
- "version": "0.0.0",
4
- "description": "Reserva del nombre. Usa 0.1.0 o posterior.",
3
+ "version": "0.1.0",
4
+ "description": "Libreria de las apps detras del gateway del SSO de CayoTV (D22): validacion del access token segun el contrato (docs/12), adaptador para Next.js y selector de apps para React",
5
5
  "license": "UNLICENSED",
6
- "repository": { "type": "git", "url": "git+https://github.com/CayoTV/cayotv-sso.git", "directory": "sdk/js" }
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/CayoTV/cayotv-sso.git",
9
+ "directory": "sdk/js"
10
+ },
11
+ "type": "module",
12
+ "engines": {
13
+ "node": ">=22"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./next": {
21
+ "types": "./dist/next.d.ts",
22
+ "default": "./dist/next.js"
23
+ },
24
+ "./react": {
25
+ "types": "./dist/react.d.ts",
26
+ "default": "./dist/react.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "dependencies": {
33
+ "jose": "6.2.12"
34
+ },
35
+ "peerDependencies": {
36
+ "react": ">=19"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "react": {
40
+ "optional": true
41
+ }
42
+ }
7
43
  }