@dacyti-gsi/auth 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/config.ts +212 -212
- package/src/core.ts +4 -7
- package/src/index.ts +671 -673
- package/src/next.ts +19 -6
- package/src/policy.ts +102 -102
- package/src/react.tsx +171 -171
- package/src/session-cookie.ts +1 -3
- package/src/types.ts +0 -2
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -1,212 +1,212 @@
|
|
|
1
|
-
import { SSOConfigError } from "./errors";
|
|
2
|
-
import type { SSOClientOptions } from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Lee una opción o su variable de entorno. Sin una de las dos no hay forma de
|
|
6
|
-
* arrancar, y el error dice exactamente cuál falta y dónde ponerla.
|
|
7
|
-
*/
|
|
8
|
-
function fromEnv(value: string | undefined, name: string): string {
|
|
9
|
-
if (value) return value;
|
|
10
|
-
|
|
11
|
-
const env = typeof process !== "undefined" ? process.env : undefined;
|
|
12
|
-
const found = env?.[name];
|
|
13
|
-
if (found) return found;
|
|
14
|
-
|
|
15
|
-
throw new SSOConfigError(
|
|
16
|
-
`Falta \`${name}\` en el entorno (o pasa \`${nameToOption(name)}\` al definir el cliente SSO).`,
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function nameToOption(name: string): string {
|
|
21
|
-
switch (name) {
|
|
22
|
-
case "SSO_ISSUER":
|
|
23
|
-
return "issuer";
|
|
24
|
-
case "SSO_CLIENT_ID":
|
|
25
|
-
return "clientId";
|
|
26
|
-
case "SSO_CLIENT_SECRET":
|
|
27
|
-
return "clientSecret";
|
|
28
|
-
case "SSO_SECRET":
|
|
29
|
-
return "secret";
|
|
30
|
-
default:
|
|
31
|
-
return name;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Configuración con los cuatro valores de entorno ya resueltos.
|
|
37
|
-
*/
|
|
38
|
-
export type ResolvedSSOOptions<Permission extends string = string> = Omit<
|
|
39
|
-
SSOClientOptions<Permission>,
|
|
40
|
-
"issuer" | "clientId" | "clientSecret" | "secret"
|
|
41
|
-
> & {
|
|
42
|
-
issuer: string;
|
|
43
|
-
clientId: string;
|
|
44
|
-
clientSecret: string;
|
|
45
|
-
secret: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Resuelve la configuración efectiva: lo explícito gana, lo demás sale del
|
|
50
|
-
* entorno. Es lo que permite `defineNextSSO({ permissions })` sin nada más.
|
|
51
|
-
*/
|
|
52
|
-
export function resolveOptions<Permission extends string>(
|
|
53
|
-
options: SSOClientOptions<Permission>,
|
|
54
|
-
): ResolvedSSOOptions<Permission> {
|
|
55
|
-
const env = typeof process !== "undefined" ? process.env : undefined;
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
...options,
|
|
59
|
-
issuer: fromEnv(options.issuer, "SSO_ISSUER"),
|
|
60
|
-
clientId: fromEnv(options.clientId, "SSO_CLIENT_ID"),
|
|
61
|
-
clientSecret: fromEnv(options.clientSecret, "SSO_CLIENT_SECRET"),
|
|
62
|
-
secret: fromEnv(options.secret, "SSO_SECRET"),
|
|
63
|
-
postLogoutRedirectUri:
|
|
64
|
-
options.postLogoutRedirectUri ?? env?.SSO_POST_LOGOUT_URI,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function isLocalhost(url: URL): boolean {
|
|
69
|
-
return (
|
|
70
|
-
url.hostname === "localhost" ||
|
|
71
|
-
url.hostname === "127.0.0.1" ||
|
|
72
|
-
url.hostname === "[::1]" ||
|
|
73
|
-
url.hostname.endsWith(".localhost")
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** https, o http solo en localhost (incluye `*.localhost`, que resuelve a loopback). */
|
|
78
|
-
function isSecureOrLocal(value: string): boolean {
|
|
79
|
-
try {
|
|
80
|
-
const url = new URL(value);
|
|
81
|
-
return url.protocol === "https:" || (url.protocol === "http:" && isLocalhost(url));
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function required(value: string | undefined, name: string): string {
|
|
88
|
-
if (!value) {
|
|
89
|
-
throw new SSOConfigError(
|
|
90
|
-
`Falta \`${name}\` en la configuración del cliente SSO.`,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return value;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Valida la configuración al crear el cliente y falla rápido.
|
|
99
|
-
*
|
|
100
|
-
* Todo lo que antes era un 500 críptico en el primer login —issuer http,
|
|
101
|
-
* secreto corto, catálogo con erratas— ahora es un error al arrancar, en el
|
|
102
|
-
* archivo donde se declara el cliente.
|
|
103
|
-
*/
|
|
104
|
-
export function validateOptions<Permission extends string>(
|
|
105
|
-
options: SSOClientOptions<Permission>,
|
|
106
|
-
): asserts options is ResolvedSSOOptions<Permission> {
|
|
107
|
-
const issuerValue = required(options.issuer, "issuer");
|
|
108
|
-
const secretValue = required(options.secret, "secret");
|
|
109
|
-
required(options.clientId, "clientId");
|
|
110
|
-
required(options.clientSecret, "clientSecret");
|
|
111
|
-
|
|
112
|
-
let issuer: URL;
|
|
113
|
-
try {
|
|
114
|
-
issuer = new URL(issuerValue);
|
|
115
|
-
} catch {
|
|
116
|
-
throw new SSOConfigError(`\`issuer\` no es una URL válida: ${issuerValue}`);
|
|
117
|
-
}
|
|
118
|
-
if (
|
|
119
|
-
issuer.protocol !== "https:" &&
|
|
120
|
-
!(issuer.protocol === "http:" && isLocalhost(issuer))
|
|
121
|
-
) {
|
|
122
|
-
throw new SSOConfigError(
|
|
123
|
-
`\`issuer\` debe ser https (o http solo en localhost). Recibido: ${issuerValue}`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (secretValue.length < 32) {
|
|
128
|
-
throw new SSOConfigError(
|
|
129
|
-
"`secret` necesita al menos 32 caracteres. Genera uno con:\n" +
|
|
130
|
-
" node -e \"console.log(require('crypto').randomBytes(32).toString('base64url'))\"",
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (options.appUrl !== undefined) {
|
|
135
|
-
let app: URL;
|
|
136
|
-
try {
|
|
137
|
-
app = new URL(options.appUrl);
|
|
138
|
-
} catch {
|
|
139
|
-
throw new SSOConfigError(
|
|
140
|
-
`\`appUrl\` no es una URL válida: ${options.appUrl}`,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
if (
|
|
144
|
-
app.protocol !== "https:" &&
|
|
145
|
-
!(app.protocol === "http:" && isLocalhost(app))
|
|
146
|
-
) {
|
|
147
|
-
throw new SSOConfigError(
|
|
148
|
-
"`appUrl` debe ser https (o http solo en localhost).",
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
options.postLogoutRedirectUri !== undefined &&
|
|
155
|
-
!isSecureOrLocal(options.postLogoutRedirectUri)
|
|
156
|
-
) {
|
|
157
|
-
throw new SSOConfigError(
|
|
158
|
-
"`postLogoutRedirectUri` debe ser https (o http solo en localhost) y estar registrada en el portal.",
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (options.basePath !== undefined && !options.basePath.startsWith("/")) {
|
|
163
|
-
throw new SSOConfigError(
|
|
164
|
-
"`basePath` debe empezar con `/` (p. ej. `/api/sso`).",
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (options.cookieName !== undefined) {
|
|
169
|
-
if (options.cookieName === "" || /[;,\s]/.test(options.cookieName)) {
|
|
170
|
-
throw new SSOConfigError(
|
|
171
|
-
"`cookieName` no puede contener `;`, `,` ni espacios.",
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
if (
|
|
175
|
-
options.cookieName.startsWith("__Host-") &&
|
|
176
|
-
(options.appUrl ?? "").startsWith("http://")
|
|
177
|
-
) {
|
|
178
|
-
throw new SSOConfigError("Una cookie `__Host-` exige origen https.");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (options.sessionMaxAge !== undefined && options.sessionMaxAge <= 0) {
|
|
183
|
-
throw new SSOConfigError(
|
|
184
|
-
"`sessionMaxAge` debe ser mayor que cero (segundos).",
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (options.permissions !== undefined) {
|
|
189
|
-
const seen = new Set<string>();
|
|
190
|
-
for (const permission of options.permissions) {
|
|
191
|
-
/**
|
|
192
|
-
* Solo cordura estructural: cadenas no vacías y sin repetir. La FORMA del
|
|
193
|
-
* permiso no se valida a propósito —el portal acepta cualquier cadena
|
|
194
|
-
* opaca y solo él define el catálogo—. Rechazar aquí `admin` o
|
|
195
|
-
* `acceso.total` porque no parecen `recurso:acción` convertiría una
|
|
196
|
-
* convención recomendada en una regla que el portal no tiene, y una app
|
|
197
|
-
* válida dejaría de arrancar.
|
|
198
|
-
*/
|
|
199
|
-
if (typeof permission !== "string" || permission.length === 0) {
|
|
200
|
-
throw new SSOConfigError(
|
|
201
|
-
"El catálogo `permissions` solo admite cadenas no vacías.",
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
if (seen.has(permission)) {
|
|
205
|
-
throw new SSOConfigError(
|
|
206
|
-
`Permiso duplicado en el catálogo: "${permission}".`,
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
seen.add(permission);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
1
|
+
import { SSOConfigError } from "./errors";
|
|
2
|
+
import type { SSOClientOptions } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lee una opción o su variable de entorno. Sin una de las dos no hay forma de
|
|
6
|
+
* arrancar, y el error dice exactamente cuál falta y dónde ponerla.
|
|
7
|
+
*/
|
|
8
|
+
function fromEnv(value: string | undefined, name: string): string {
|
|
9
|
+
if (value) return value;
|
|
10
|
+
|
|
11
|
+
const env = typeof process !== "undefined" ? process.env : undefined;
|
|
12
|
+
const found = env?.[name];
|
|
13
|
+
if (found) return found;
|
|
14
|
+
|
|
15
|
+
throw new SSOConfigError(
|
|
16
|
+
`Falta \`${name}\` en el entorno (o pasa \`${nameToOption(name)}\` al definir el cliente SSO).`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function nameToOption(name: string): string {
|
|
21
|
+
switch (name) {
|
|
22
|
+
case "SSO_ISSUER":
|
|
23
|
+
return "issuer";
|
|
24
|
+
case "SSO_CLIENT_ID":
|
|
25
|
+
return "clientId";
|
|
26
|
+
case "SSO_CLIENT_SECRET":
|
|
27
|
+
return "clientSecret";
|
|
28
|
+
case "SSO_SECRET":
|
|
29
|
+
return "secret";
|
|
30
|
+
default:
|
|
31
|
+
return name;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configuración con los cuatro valores de entorno ya resueltos.
|
|
37
|
+
*/
|
|
38
|
+
export type ResolvedSSOOptions<Permission extends string = string> = Omit<
|
|
39
|
+
SSOClientOptions<Permission>,
|
|
40
|
+
"issuer" | "clientId" | "clientSecret" | "secret"
|
|
41
|
+
> & {
|
|
42
|
+
issuer: string;
|
|
43
|
+
clientId: string;
|
|
44
|
+
clientSecret: string;
|
|
45
|
+
secret: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resuelve la configuración efectiva: lo explícito gana, lo demás sale del
|
|
50
|
+
* entorno. Es lo que permite `defineNextSSO({ permissions })` sin nada más.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveOptions<Permission extends string>(
|
|
53
|
+
options: SSOClientOptions<Permission>,
|
|
54
|
+
): ResolvedSSOOptions<Permission> {
|
|
55
|
+
const env = typeof process !== "undefined" ? process.env : undefined;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...options,
|
|
59
|
+
issuer: fromEnv(options.issuer, "SSO_ISSUER"),
|
|
60
|
+
clientId: fromEnv(options.clientId, "SSO_CLIENT_ID"),
|
|
61
|
+
clientSecret: fromEnv(options.clientSecret, "SSO_CLIENT_SECRET"),
|
|
62
|
+
secret: fromEnv(options.secret, "SSO_SECRET"),
|
|
63
|
+
postLogoutRedirectUri:
|
|
64
|
+
options.postLogoutRedirectUri ?? env?.SSO_POST_LOGOUT_URI,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isLocalhost(url: URL): boolean {
|
|
69
|
+
return (
|
|
70
|
+
url.hostname === "localhost" ||
|
|
71
|
+
url.hostname === "127.0.0.1" ||
|
|
72
|
+
url.hostname === "[::1]" ||
|
|
73
|
+
url.hostname.endsWith(".localhost")
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** https, o http solo en localhost (incluye `*.localhost`, que resuelve a loopback). */
|
|
78
|
+
function isSecureOrLocal(value: string): boolean {
|
|
79
|
+
try {
|
|
80
|
+
const url = new URL(value);
|
|
81
|
+
return url.protocol === "https:" || (url.protocol === "http:" && isLocalhost(url));
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function required(value: string | undefined, name: string): string {
|
|
88
|
+
if (!value) {
|
|
89
|
+
throw new SSOConfigError(
|
|
90
|
+
`Falta \`${name}\` en la configuración del cliente SSO.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Valida la configuración al crear el cliente y falla rápido.
|
|
99
|
+
*
|
|
100
|
+
* Todo lo que antes era un 500 críptico en el primer login —issuer http,
|
|
101
|
+
* secreto corto, catálogo con erratas— ahora es un error al arrancar, en el
|
|
102
|
+
* archivo donde se declara el cliente.
|
|
103
|
+
*/
|
|
104
|
+
export function validateOptions<Permission extends string>(
|
|
105
|
+
options: SSOClientOptions<Permission>,
|
|
106
|
+
): asserts options is ResolvedSSOOptions<Permission> {
|
|
107
|
+
const issuerValue = required(options.issuer, "issuer");
|
|
108
|
+
const secretValue = required(options.secret, "secret");
|
|
109
|
+
required(options.clientId, "clientId");
|
|
110
|
+
required(options.clientSecret, "clientSecret");
|
|
111
|
+
|
|
112
|
+
let issuer: URL;
|
|
113
|
+
try {
|
|
114
|
+
issuer = new URL(issuerValue);
|
|
115
|
+
} catch {
|
|
116
|
+
throw new SSOConfigError(`\`issuer\` no es una URL válida: ${issuerValue}`);
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
issuer.protocol !== "https:" &&
|
|
120
|
+
!(issuer.protocol === "http:" && isLocalhost(issuer))
|
|
121
|
+
) {
|
|
122
|
+
throw new SSOConfigError(
|
|
123
|
+
`\`issuer\` debe ser https (o http solo en localhost). Recibido: ${issuerValue}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (secretValue.length < 32) {
|
|
128
|
+
throw new SSOConfigError(
|
|
129
|
+
"`secret` necesita al menos 32 caracteres. Genera uno con:\n" +
|
|
130
|
+
" node -e \"console.log(require('crypto').randomBytes(32).toString('base64url'))\"",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (options.appUrl !== undefined) {
|
|
135
|
+
let app: URL;
|
|
136
|
+
try {
|
|
137
|
+
app = new URL(options.appUrl);
|
|
138
|
+
} catch {
|
|
139
|
+
throw new SSOConfigError(
|
|
140
|
+
`\`appUrl\` no es una URL válida: ${options.appUrl}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (
|
|
144
|
+
app.protocol !== "https:" &&
|
|
145
|
+
!(app.protocol === "http:" && isLocalhost(app))
|
|
146
|
+
) {
|
|
147
|
+
throw new SSOConfigError(
|
|
148
|
+
"`appUrl` debe ser https (o http solo en localhost).",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
options.postLogoutRedirectUri !== undefined &&
|
|
155
|
+
!isSecureOrLocal(options.postLogoutRedirectUri)
|
|
156
|
+
) {
|
|
157
|
+
throw new SSOConfigError(
|
|
158
|
+
"`postLogoutRedirectUri` debe ser https (o http solo en localhost) y estar registrada en el portal.",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (options.basePath !== undefined && !options.basePath.startsWith("/")) {
|
|
163
|
+
throw new SSOConfigError(
|
|
164
|
+
"`basePath` debe empezar con `/` (p. ej. `/api/sso`).",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (options.cookieName !== undefined) {
|
|
169
|
+
if (options.cookieName === "" || /[;,\s]/.test(options.cookieName)) {
|
|
170
|
+
throw new SSOConfigError(
|
|
171
|
+
"`cookieName` no puede contener `;`, `,` ni espacios.",
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (
|
|
175
|
+
options.cookieName.startsWith("__Host-") &&
|
|
176
|
+
(options.appUrl ?? "").startsWith("http://")
|
|
177
|
+
) {
|
|
178
|
+
throw new SSOConfigError("Una cookie `__Host-` exige origen https.");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (options.sessionMaxAge !== undefined && options.sessionMaxAge <= 0) {
|
|
183
|
+
throw new SSOConfigError(
|
|
184
|
+
"`sessionMaxAge` debe ser mayor que cero (segundos).",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (options.permissions !== undefined) {
|
|
189
|
+
const seen = new Set<string>();
|
|
190
|
+
for (const permission of options.permissions) {
|
|
191
|
+
/**
|
|
192
|
+
* Solo cordura estructural: cadenas no vacías y sin repetir. La FORMA del
|
|
193
|
+
* permiso no se valida a propósito —el portal acepta cualquier cadena
|
|
194
|
+
* opaca y solo él define el catálogo—. Rechazar aquí `admin` o
|
|
195
|
+
* `acceso.total` porque no parecen `recurso:acción` convertiría una
|
|
196
|
+
* convención recomendada en una regla que el portal no tiene, y una app
|
|
197
|
+
* válida dejaría de arrancar.
|
|
198
|
+
*/
|
|
199
|
+
if (typeof permission !== "string" || permission.length === 0) {
|
|
200
|
+
throw new SSOConfigError(
|
|
201
|
+
"El catálogo `permissions` solo admite cadenas no vacías.",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (seen.has(permission)) {
|
|
205
|
+
throw new SSOConfigError(
|
|
206
|
+
`Permiso duplicado en el catálogo: "${permission}".`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
seen.add(permission);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
package/src/core.ts
CHANGED
|
@@ -8,7 +8,10 @@ import type { DiscoveryDocument, SSOSession } from "./types";
|
|
|
8
8
|
const PORTAL_TIMEOUT_MS = 10_000;
|
|
9
9
|
|
|
10
10
|
function portalFetch(url: string, init: RequestInit = {}): Promise<Response> {
|
|
11
|
-
return fetch(url, {
|
|
11
|
+
return fetch(url, {
|
|
12
|
+
...init,
|
|
13
|
+
signal: AbortSignal.timeout(PORTAL_TIMEOUT_MS),
|
|
14
|
+
});
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
/** Cachea el documento de descubrimiento por emisor. */
|
|
@@ -104,9 +107,7 @@ export function buildSession<Permission extends string>(
|
|
|
104
107
|
user: {
|
|
105
108
|
id: String(profile.sub),
|
|
106
109
|
email: String(profile.email ?? ""),
|
|
107
|
-
emailVerified: profile.email_verified === true,
|
|
108
110
|
name: typeof profile.name === "string" ? profile.name : null,
|
|
109
|
-
image: typeof profile.picture === "string" ? profile.picture : null,
|
|
110
111
|
},
|
|
111
112
|
roles,
|
|
112
113
|
permissions,
|
|
@@ -129,9 +130,7 @@ export function buildSession<Permission extends string>(
|
|
|
129
130
|
export function sessionFromPayload<Permission extends string>(payload: {
|
|
130
131
|
sub: string;
|
|
131
132
|
email: string;
|
|
132
|
-
emailVerified: boolean;
|
|
133
133
|
name: string | null;
|
|
134
|
-
image: string | null;
|
|
135
134
|
roles: string[];
|
|
136
135
|
permissions: string[];
|
|
137
136
|
amr: string[];
|
|
@@ -145,9 +144,7 @@ export function sessionFromPayload<Permission extends string>(payload: {
|
|
|
145
144
|
user: {
|
|
146
145
|
id: payload.sub,
|
|
147
146
|
email: payload.email,
|
|
148
|
-
emailVerified: payload.emailVerified,
|
|
149
147
|
name: payload.name,
|
|
150
|
-
image: payload.image,
|
|
151
148
|
},
|
|
152
149
|
roles: payload.roles,
|
|
153
150
|
permissions,
|