@dacyti-gsi/auth 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,6 +88,48 @@ El portal solo redirige a URIs registradas, así que sin ella la sesión se cier
88
88
  igual pero el navegador se queda en el portal. Si la registras, indícala también
89
89
  en `postLogoutRedirectUri`.
90
90
 
91
+ ## Declarar permisos y roles desde el código (`sso.sync()`)
92
+
93
+ El catálogo de la app vive en su código y se declara solo en el portal, sin copiarlo
94
+ a mano a la consola:
95
+
96
+ ```ts
97
+ // lib/sso.ts
98
+ export const sso = defineNextSSO({
99
+ permissions: ["pedidos:ver", "pedidos:anular"] as const,
100
+ roles: {
101
+ gerente: ["pedidos:ver", "pedidos:anular"],
102
+ lector: ["pedidos:ver"],
103
+ },
104
+ // Lo que la consola muestra al decidir a quién dar cada cosa.
105
+ descriptions: {
106
+ permissions: { "pedidos:anular": "Anular pedidos ya confirmados" },
107
+ roles: { gerente: "Gestiona los pedidos de su sucursal" },
108
+ },
109
+ });
110
+ ```
111
+
112
+ ```ts
113
+ // instrumentation.ts
114
+ export async function register() {
115
+ if (process.env.NEXT_RUNTIME !== "nodejs") return;
116
+ const { sso } = await import("@/lib/sso");
117
+ await sso.sync().catch((error) => console.warn("[sso] sin sincronizar:", error));
118
+ }
119
+ ```
120
+
121
+ - Los permisos de cada rol salen del tipo de `permissions`, y las claves de
122
+ `descriptions` también: una errata no compila.
123
+ - Es idempotente y solo toca el catálogo de este `clientId` (autentica con las mismas
124
+ credenciales del cliente, `client_secret_basic`). Se puede llamar en cada arranque.
125
+ - Declara el catálogo; **no asigna roles a cuentas**, eso se decide en la consola.
126
+ - No borra: un permiso o rol que quitas del código sigue en la consola hasta que
127
+ alguien lo elimina allí.
128
+ - Todo o nada: si un rol pide un permiso que no está en el catálogo, el portal rechaza
129
+ el manifiesto entero y `sync()` lanza con el motivo.
130
+ - Un rol que incluya `sso:asignar-roles` permite a su titular asignar roles de esta
131
+ app desde su cuenta, sin ser administrador del portal.
132
+
91
133
  ## Renovar el acceso (`proxy.ts`)
92
134
 
93
135
  El acceso dura 15 minutos. Los Server Components no pueden escribir cookies, así que
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dacyti-gsi/auth",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Cliente del SSO de la división: OIDC (PKCE), sesión y permisos para Next.js/React",
5
5
  "keywords": [
6
6
  "sso",
package/src/core.ts CHANGED
@@ -460,6 +460,10 @@ export async function syncManifest(input: {
460
460
  clientSecret: string;
461
461
  permissions: readonly string[];
462
462
  roles: Readonly<Record<string, readonly string[]>>;
463
+ descriptions?: {
464
+ permissions?: Readonly<Record<string, string | undefined>>;
465
+ roles?: Readonly<Record<string, string | undefined>>;
466
+ };
463
467
  }): Promise<{ permissions: number; roles: number }> {
464
468
  const endpoint = new URL("/api/apps/manifest", input.issuer);
465
469
  const credentials = Buffer.from(
@@ -473,10 +477,14 @@ export async function syncManifest(input: {
473
477
  "content-type": "application/json",
474
478
  },
475
479
  body: JSON.stringify({
476
- permissions: input.permissions.map((key) => ({ key })),
480
+ permissions: input.permissions.map((key) => ({
481
+ key,
482
+ description: input.descriptions?.permissions?.[key],
483
+ })),
477
484
  roles: Object.entries(input.roles).map(([name, permissions]) => ({
478
485
  name,
479
486
  permissions,
487
+ description: input.descriptions?.roles?.[name],
480
488
  })),
481
489
  }),
482
490
  });
package/src/index.ts CHANGED
@@ -658,6 +658,7 @@ export function defineSSO<const Permission extends string = string>(
658
658
  clientSecret: resolved.clientSecret,
659
659
  permissions: resolved.permissions ?? [],
660
660
  roles: resolved.roles ?? {},
661
+ descriptions: resolved.descriptions,
661
662
  }),
662
663
 
663
664
  async handleRequest(request) {
package/src/types.ts CHANGED
@@ -58,6 +58,19 @@ export type SSOClientOptions<Permission extends string = string> = {
58
58
  */
59
59
  roles?: Readonly<Record<string, readonly Permission[]>>;
60
60
 
61
+ /**
62
+ * Descripciones legibles de permisos y roles, para la consola del portal.
63
+ *
64
+ * `sso.sync()` las envía junto al manifiesto; sin ellas la consola solo muestra
65
+ * claves como `pedidos:anular`, que no dicen qué concede cada una a quien debe
66
+ * decidir a quién dárselo. Las claves de `permissions` salen del mismo tipo que
67
+ * `permissions`: una descripción para un permiso que no existe no compila.
68
+ */
69
+ descriptions?: {
70
+ permissions?: Partial<Record<Permission, string>>;
71
+ roles?: Readonly<Record<string, string>>;
72
+ };
73
+
61
74
  /** Ruta donde se montan los handlers. Por defecto `/api/sso`. */
62
75
  basePath?: string;
63
76