@bigso/auth-sdk 0.4.7 → 0.5.1
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 +180 -135
- package/package.json +2 -2
- package/dist/browser/index.cjs +0 -376
- package/dist/browser/index.d.cts +0 -49
- package/dist/browser/index.d.ts +0 -49
- package/dist/browser/index.js +0 -343
- package/dist/chunk-LDDK6SJD.js +0 -13
- package/dist/express/index.cjs +0 -287
- package/dist/express/index.d.cts +0 -48
- package/dist/express/index.d.ts +0 -48
- package/dist/express/index.js +0 -257
- package/dist/node/index.cjs +0 -170
- package/dist/node/index.d.cts +0 -45
- package/dist/node/index.d.ts +0 -45
- package/dist/node/index.js +0 -137
- package/dist/types-CoXgtTry.d.cts +0 -51
- package/dist/types-CoXgtTry.d.ts +0 -51
package/dist/browser/index.js
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
verifySignedPayload
|
|
3
|
-
} from "../chunk-LDDK6SJD.js";
|
|
4
|
-
|
|
5
|
-
// src/utils/crypto.ts
|
|
6
|
-
async function sha256Base64Url(input) {
|
|
7
|
-
const encoder = new TextEncoder();
|
|
8
|
-
const data = encoder.encode(input);
|
|
9
|
-
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
10
|
-
return base64Url(new Uint8Array(digest));
|
|
11
|
-
}
|
|
12
|
-
function generateVerifier(length = 32) {
|
|
13
|
-
const array = new Uint8Array(length);
|
|
14
|
-
crypto.getRandomValues(array);
|
|
15
|
-
return base64Url(array);
|
|
16
|
-
}
|
|
17
|
-
function base64Url(bytes) {
|
|
18
|
-
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
19
|
-
}
|
|
20
|
-
function generateRandomId() {
|
|
21
|
-
return crypto.randomUUID();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// src/utils/events.ts
|
|
25
|
-
var EventEmitter = class {
|
|
26
|
-
constructor() {
|
|
27
|
-
this.events = {};
|
|
28
|
-
}
|
|
29
|
-
on(event, handler) {
|
|
30
|
-
if (!this.events[event]) this.events[event] = [];
|
|
31
|
-
this.events[event].push(handler);
|
|
32
|
-
}
|
|
33
|
-
off(event, handler) {
|
|
34
|
-
if (!this.events[event]) return;
|
|
35
|
-
this.events[event] = this.events[event].filter((h) => h !== handler);
|
|
36
|
-
}
|
|
37
|
-
emit(event, data) {
|
|
38
|
-
this.events[event]?.forEach((fn) => fn(data));
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// src/browser/auth.ts
|
|
43
|
-
var BigsoAuth = class extends EventEmitter {
|
|
44
|
-
constructor(options) {
|
|
45
|
-
super();
|
|
46
|
-
this.authCompleted = false;
|
|
47
|
-
this.requestId = generateRandomId();
|
|
48
|
-
this.loginInProgress = false;
|
|
49
|
-
this.options = {
|
|
50
|
-
timeout: 5e3,
|
|
51
|
-
// por defecto 5s (estándar v2.3)
|
|
52
|
-
debug: false,
|
|
53
|
-
redirectUri: "",
|
|
54
|
-
tenantHint: "",
|
|
55
|
-
theme: "light",
|
|
56
|
-
...options
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Inicia el flujo de autenticación.
|
|
61
|
-
* @returns Promise que resuelve con el payload decodificado del JWS (solo para información; el backend debe validar)
|
|
62
|
-
*/
|
|
63
|
-
async login() {
|
|
64
|
-
if (this.loginInProgress) {
|
|
65
|
-
this.debug("login() ya en curso, ignorando llamada duplicada");
|
|
66
|
-
return Promise.reject(new Error("Login already in progress"));
|
|
67
|
-
}
|
|
68
|
-
this.loginInProgress = true;
|
|
69
|
-
this.authCompleted = false;
|
|
70
|
-
const state = generateRandomId();
|
|
71
|
-
const nonce = generateRandomId();
|
|
72
|
-
const verifier = generateVerifier();
|
|
73
|
-
const requestId = this.requestId;
|
|
74
|
-
sessionStorage.setItem("sso_ctx", JSON.stringify({ state, nonce, verifier, requestId }));
|
|
75
|
-
this.createUI();
|
|
76
|
-
return new Promise((resolve, reject) => {
|
|
77
|
-
this.abortController = new AbortController();
|
|
78
|
-
const { signal } = this.abortController;
|
|
79
|
-
const cleanup = () => {
|
|
80
|
-
if (this.timeoutId) clearTimeout(this.timeoutId);
|
|
81
|
-
if (this.messageListener) window.removeEventListener("message", this.messageListener);
|
|
82
|
-
this.iframe?.remove();
|
|
83
|
-
this.iframe = void 0;
|
|
84
|
-
this.authCompleted = true;
|
|
85
|
-
this.loginInProgress = false;
|
|
86
|
-
};
|
|
87
|
-
this.messageListener = async (event) => {
|
|
88
|
-
if (event.origin !== this.options.ssoOrigin) {
|
|
89
|
-
this.debug("Ignorado mensaje de origen no autorizado:", event.origin);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const msg = event.data;
|
|
93
|
-
this.debug("Mensaje recibido:", msg);
|
|
94
|
-
if (msg.requestId && msg.requestId !== requestId) {
|
|
95
|
-
this.debug("requestId no coincide, ignorado");
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (msg.type === "sso-ready") {
|
|
99
|
-
this.debug("sso-ready recibido, iniciando timeout y enviando sso-init");
|
|
100
|
-
this.timeoutId = window.setTimeout(() => {
|
|
101
|
-
if (!this.authCompleted) {
|
|
102
|
-
this.debug("Timeout alcanzado, activando fallback");
|
|
103
|
-
this.closeUI();
|
|
104
|
-
cleanup();
|
|
105
|
-
this.emit("fallback");
|
|
106
|
-
window.location.href = this.buildFallbackUrl();
|
|
107
|
-
reject(new Error("Timeout"));
|
|
108
|
-
}
|
|
109
|
-
}, this.options.timeout);
|
|
110
|
-
const codeChallenge = await sha256Base64Url(verifier);
|
|
111
|
-
const initPayload = {
|
|
112
|
-
state,
|
|
113
|
-
nonce,
|
|
114
|
-
code_challenge: codeChallenge,
|
|
115
|
-
code_challenge_method: "S256",
|
|
116
|
-
origin: window.location.origin,
|
|
117
|
-
...this.options.redirectUri && { redirect_uri: this.options.redirectUri },
|
|
118
|
-
...this.options.tenantHint && { tenant_hint: this.options.tenantHint },
|
|
119
|
-
timeout_ms: this.options.timeout
|
|
120
|
-
// pasar el timeout configurado (opcional)
|
|
121
|
-
};
|
|
122
|
-
this.iframe?.contentWindow?.postMessage({
|
|
123
|
-
v: "2.3",
|
|
124
|
-
// versión del protocolo (estándar v2.3)
|
|
125
|
-
source: "@app/widget",
|
|
126
|
-
type: "sso-init",
|
|
127
|
-
requestId: this.requestId,
|
|
128
|
-
payload: initPayload
|
|
129
|
-
}, this.options.ssoOrigin);
|
|
130
|
-
this.emit("ready");
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (msg.type === "sso-success") {
|
|
134
|
-
this.debug("sso-success recibido");
|
|
135
|
-
clearTimeout(this.timeoutId);
|
|
136
|
-
try {
|
|
137
|
-
const payload = msg.payload;
|
|
138
|
-
const ctx = JSON.parse(sessionStorage.getItem("sso_ctx") || "{}");
|
|
139
|
-
if (payload.state !== ctx.state) {
|
|
140
|
-
throw new Error("Invalid state");
|
|
141
|
-
}
|
|
142
|
-
const decoded = await verifySignedPayload(
|
|
143
|
-
payload.signed_payload,
|
|
144
|
-
this.options.jwksUrl,
|
|
145
|
-
window.location.origin
|
|
146
|
-
// aud esperado
|
|
147
|
-
);
|
|
148
|
-
if (decoded.nonce !== ctx.nonce) {
|
|
149
|
-
throw new Error("Invalid nonce");
|
|
150
|
-
}
|
|
151
|
-
this.debug("JWS v\xE1lido, payload:", decoded);
|
|
152
|
-
this.closeUI();
|
|
153
|
-
cleanup();
|
|
154
|
-
const result = {
|
|
155
|
-
...decoded,
|
|
156
|
-
signed_payload: payload.signed_payload
|
|
157
|
-
};
|
|
158
|
-
this.emit("success", result);
|
|
159
|
-
resolve(result);
|
|
160
|
-
} catch (err) {
|
|
161
|
-
this.debug("Error en sso-success:", err);
|
|
162
|
-
this.closeUI();
|
|
163
|
-
cleanup();
|
|
164
|
-
this.emit("error", err);
|
|
165
|
-
reject(err);
|
|
166
|
-
}
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (msg.type === "sso-error") {
|
|
170
|
-
const errorPayload = msg.payload;
|
|
171
|
-
this.debug("sso-error recibido:", errorPayload);
|
|
172
|
-
clearTimeout(this.timeoutId);
|
|
173
|
-
this.closeUI();
|
|
174
|
-
cleanup();
|
|
175
|
-
if (errorPayload.code === "version_mismatch") {
|
|
176
|
-
this.emit("error", errorPayload);
|
|
177
|
-
window.location.href = this.buildFallbackUrl();
|
|
178
|
-
reject(new Error(`Version mismatch: expected ${errorPayload.expected_version}`));
|
|
179
|
-
} else {
|
|
180
|
-
this.emit("error", errorPayload);
|
|
181
|
-
reject(errorPayload);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
if (msg.type === "sso-close") {
|
|
185
|
-
this.debug("sso-close recibido");
|
|
186
|
-
this.closeUI();
|
|
187
|
-
cleanup();
|
|
188
|
-
reject(new Error("Login cancelled by user"));
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
window.addEventListener("message", this.messageListener);
|
|
192
|
-
signal.addEventListener("abort", () => {
|
|
193
|
-
this.debug("Operaci\xF3n abortada");
|
|
194
|
-
this.closeUI();
|
|
195
|
-
cleanup();
|
|
196
|
-
reject(new Error("Login aborted"));
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
/** Cancela el flujo de autenticación en curso */
|
|
201
|
-
abort() {
|
|
202
|
-
this.abortController?.abort();
|
|
203
|
-
}
|
|
204
|
-
// ─── UI Management ───────────────────────────────────────────────
|
|
205
|
-
/**
|
|
206
|
-
* Crea (o reutiliza) el overlay con Shadow DOM y el iframe visible.
|
|
207
|
-
* Patrón tomado del CDN widget v1: Shadow DOM para aislar estilos.
|
|
208
|
-
*/
|
|
209
|
-
createUI() {
|
|
210
|
-
if (!this.hostEl) {
|
|
211
|
-
this.hostEl = document.createElement("div");
|
|
212
|
-
this.hostEl.id = "bigso-auth-host";
|
|
213
|
-
this.shadowRoot = this.hostEl.attachShadow({ mode: "open" });
|
|
214
|
-
const style = document.createElement("style");
|
|
215
|
-
style.textContent = this.getOverlayStyles();
|
|
216
|
-
this.shadowRoot.appendChild(style);
|
|
217
|
-
this.overlayEl = document.createElement("div");
|
|
218
|
-
this.overlayEl.className = "sso-overlay";
|
|
219
|
-
const closeBtn = document.createElement("button");
|
|
220
|
-
closeBtn.className = "sso-close-btn";
|
|
221
|
-
closeBtn.innerHTML = "×";
|
|
222
|
-
closeBtn.setAttribute("aria-label", "Cerrar modal");
|
|
223
|
-
closeBtn.addEventListener("click", () => this.abort());
|
|
224
|
-
this.overlayEl.appendChild(closeBtn);
|
|
225
|
-
this.overlayEl.addEventListener("click", (event) => {
|
|
226
|
-
if (event.target === this.overlayEl) {
|
|
227
|
-
this.abort();
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
this.shadowRoot.appendChild(this.overlayEl);
|
|
231
|
-
document.body.appendChild(this.hostEl);
|
|
232
|
-
}
|
|
233
|
-
this.iframe = document.createElement("iframe");
|
|
234
|
-
this.iframe.className = "sso-frame";
|
|
235
|
-
this.iframe.src = `${this.options.ssoOrigin}/auth/sign-in?v=2.3&client_id=${this.options.clientId}`;
|
|
236
|
-
this.iframe.setAttribute("title", "SSO Login");
|
|
237
|
-
this.overlayEl.appendChild(this.iframe);
|
|
238
|
-
this.debug("Iframe creado", this.iframe.src);
|
|
239
|
-
this.overlayEl.classList.remove("sso-closing");
|
|
240
|
-
this.overlayEl.style.display = "flex";
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Cierra el overlay con animación suave (fadeOut + slideDown).
|
|
244
|
-
* El overlay persiste en el DOM (solo se oculta).
|
|
245
|
-
*/
|
|
246
|
-
closeUI() {
|
|
247
|
-
if (!this.overlayEl || this.overlayEl.style.display === "none") return;
|
|
248
|
-
this.overlayEl.classList.add("sso-closing");
|
|
249
|
-
setTimeout(() => {
|
|
250
|
-
if (this.overlayEl) {
|
|
251
|
-
this.overlayEl.style.display = "none";
|
|
252
|
-
this.overlayEl.classList.remove("sso-closing");
|
|
253
|
-
}
|
|
254
|
-
}, 200);
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Estilos CSS encapsulados dentro del Shadow DOM.
|
|
258
|
-
* Migrados del widget CDN v1 con las mismas animaciones y responsive.
|
|
259
|
-
*/
|
|
260
|
-
getOverlayStyles() {
|
|
261
|
-
return `
|
|
262
|
-
.sso-overlay {
|
|
263
|
-
position: fixed;
|
|
264
|
-
inset: 0;
|
|
265
|
-
display: none;
|
|
266
|
-
justify-content: center;
|
|
267
|
-
align-items: center;
|
|
268
|
-
background: rgba(0, 0, 0, 0.6);
|
|
269
|
-
z-index: 999999;
|
|
270
|
-
backdrop-filter: blur(4px);
|
|
271
|
-
-webkit-backdrop-filter: blur(4px);
|
|
272
|
-
animation: fadeIn 0.2s ease;
|
|
273
|
-
}
|
|
274
|
-
.sso-frame {
|
|
275
|
-
width: 370px;
|
|
276
|
-
height: 350px;
|
|
277
|
-
border: none;
|
|
278
|
-
border-radius: 16px;
|
|
279
|
-
background: var(--card-bg, #fff);
|
|
280
|
-
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
|
|
281
|
-
animation: slideUp 0.3s ease;
|
|
282
|
-
}
|
|
283
|
-
@media (max-width: 480px), (max-height: 480px) {
|
|
284
|
-
.sso-frame {
|
|
285
|
-
width: 100%;
|
|
286
|
-
height: 100%;
|
|
287
|
-
border-radius: 0;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
.sso-close-btn {
|
|
291
|
-
position: absolute;
|
|
292
|
-
top: 12px;
|
|
293
|
-
right: 12px;
|
|
294
|
-
width: 32px;
|
|
295
|
-
height: 32px;
|
|
296
|
-
background: rgba(0, 0, 0, 0.4);
|
|
297
|
-
color: white;
|
|
298
|
-
border: none;
|
|
299
|
-
border-radius: 50%;
|
|
300
|
-
font-size: 24px;
|
|
301
|
-
line-height: 1;
|
|
302
|
-
cursor: pointer;
|
|
303
|
-
display: flex;
|
|
304
|
-
align-items: center;
|
|
305
|
-
justify-content: center;
|
|
306
|
-
z-index: 1000000;
|
|
307
|
-
transition: background 0.2s;
|
|
308
|
-
}
|
|
309
|
-
.sso-close-btn:hover {
|
|
310
|
-
background: rgba(0, 0, 0, 0.8);
|
|
311
|
-
}
|
|
312
|
-
.sso-overlay.sso-closing {
|
|
313
|
-
animation: fadeOut 0.2s ease forwards;
|
|
314
|
-
}
|
|
315
|
-
.sso-overlay.sso-closing .sso-frame {
|
|
316
|
-
animation: slideDown 0.2s ease forwards;
|
|
317
|
-
}
|
|
318
|
-
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
319
|
-
@keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
320
|
-
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
|
|
321
|
-
@keyframes slideDown { from { transform: translateY(0); opacity: 1; } to { transform: translateY(20px); opacity: 0; } }
|
|
322
|
-
`;
|
|
323
|
-
}
|
|
324
|
-
// ─── Helpers ──────────────────────────────────────────────────────
|
|
325
|
-
buildFallbackUrl() {
|
|
326
|
-
const url = new URL(this.options.ssoOrigin);
|
|
327
|
-
url.searchParams.set("app_id", this.options.clientId);
|
|
328
|
-
url.searchParams.set("redirect_uri", this.options.redirectUri || window.location.origin);
|
|
329
|
-
url.searchParams.set("response_type", "code");
|
|
330
|
-
url.searchParams.set("state", generateRandomId());
|
|
331
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
332
|
-
url.searchParams.set("client_id", this.options.clientId);
|
|
333
|
-
return url.toString();
|
|
334
|
-
}
|
|
335
|
-
debug(...args) {
|
|
336
|
-
if (this.options.debug) {
|
|
337
|
-
console.log("[BigsoAuth]", ...args);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
export {
|
|
342
|
-
BigsoAuth
|
|
343
|
-
};
|
package/dist/chunk-LDDK6SJD.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// src/utils/jws.ts
|
|
2
|
-
import { jwtVerify, createRemoteJWKSet } from "jose";
|
|
3
|
-
async function verifySignedPayload(token, jwksUrl, expectedAudience) {
|
|
4
|
-
const JWKS = createRemoteJWKSet(new URL(jwksUrl));
|
|
5
|
-
const { payload } = await jwtVerify(token, JWKS, {
|
|
6
|
-
audience: expectedAudience
|
|
7
|
-
});
|
|
8
|
-
return payload;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export {
|
|
12
|
-
verifySignedPayload
|
|
13
|
-
};
|
package/dist/express/index.cjs
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/express/index.ts
|
|
21
|
-
var express_exports = {};
|
|
22
|
-
__export(express_exports, {
|
|
23
|
-
createSsoAuthRouter: () => createSsoAuthRouter,
|
|
24
|
-
createSsoSyncRouter: () => createSsoSyncRouter,
|
|
25
|
-
ssoAuthMiddleware: () => ssoAuthMiddleware,
|
|
26
|
-
ssoSyncGuardMiddleware: () => ssoSyncGuardMiddleware
|
|
27
|
-
});
|
|
28
|
-
module.exports = __toCommonJS(express_exports);
|
|
29
|
-
|
|
30
|
-
// src/express/middlewares/ssoAuth.ts
|
|
31
|
-
function ssoAuthMiddleware(options) {
|
|
32
|
-
const cookieName = options.cookieName || "sso_session";
|
|
33
|
-
const isProduction = options.isProduction ?? process.env.NODE_ENV === "production";
|
|
34
|
-
return async (req, res, next) => {
|
|
35
|
-
try {
|
|
36
|
-
let sessionToken = req.cookies?.[cookieName];
|
|
37
|
-
let session = null;
|
|
38
|
-
if (sessionToken) {
|
|
39
|
-
session = await options.ssoClient.validateSessionToken(sessionToken);
|
|
40
|
-
}
|
|
41
|
-
if (!session) {
|
|
42
|
-
const refreshToken = req.cookies?.[`${cookieName}_refresh`];
|
|
43
|
-
if (refreshToken) {
|
|
44
|
-
const newSessionData = await options.ssoClient.refreshAppSession(refreshToken);
|
|
45
|
-
if (newSessionData) {
|
|
46
|
-
const sessionMaxAge = new Date(newSessionData.expiresAt).getTime() - Date.now();
|
|
47
|
-
const refreshMaxAge = newSessionData.refreshExpiresAt ? new Date(newSessionData.refreshExpiresAt).getTime() - Date.now() : 7 * 24 * 60 * 60 * 1e3;
|
|
48
|
-
const sessionCookieOptions = {
|
|
49
|
-
httpOnly: true,
|
|
50
|
-
secure: isProduction,
|
|
51
|
-
sameSite: "lax",
|
|
52
|
-
path: "/",
|
|
53
|
-
maxAge: sessionMaxAge > 0 ? sessionMaxAge : 0,
|
|
54
|
-
...isProduction && options.cookieDomain ? { domain: options.cookieDomain } : {}
|
|
55
|
-
};
|
|
56
|
-
const refreshCookieOptions = {
|
|
57
|
-
...sessionCookieOptions,
|
|
58
|
-
maxAge: refreshMaxAge > 0 ? refreshMaxAge : 0
|
|
59
|
-
};
|
|
60
|
-
res.cookie(cookieName, newSessionData.sessionToken, sessionCookieOptions);
|
|
61
|
-
res.cookie(`${cookieName}_refresh`, newSessionData.refreshToken, refreshCookieOptions);
|
|
62
|
-
session = await options.ssoClient.validateSessionToken(newSessionData.sessionToken);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (!session) {
|
|
66
|
-
res.clearCookie(cookieName);
|
|
67
|
-
res.clearCookie(`${cookieName}_refresh`);
|
|
68
|
-
res.status(401).json({ error: "Session expired or invalid" });
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (options.onSessionValidated) {
|
|
73
|
-
await options.onSessionValidated(session, req);
|
|
74
|
-
}
|
|
75
|
-
req.user = session.user;
|
|
76
|
-
req.tenant = session.tenant;
|
|
77
|
-
req.ssoSession = session;
|
|
78
|
-
next();
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error("\u274C [BigsoAuthSDK] Authentication Middleware Error:", error instanceof Error ? error.message : error);
|
|
81
|
-
res.status(500).json({ error: "Internal authentication error" });
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// src/express/middlewares/ssoSyncGuard.ts
|
|
87
|
-
var import_dns = require("dns");
|
|
88
|
-
function ssoSyncGuardMiddleware(options) {
|
|
89
|
-
const isProduction = options.isProduction ?? process.env.NODE_ENV === "production";
|
|
90
|
-
return async (req, res, next) => {
|
|
91
|
-
try {
|
|
92
|
-
const isSecure = req.secure || req.headers["x-forwarded-proto"] === "https";
|
|
93
|
-
if (!isSecure && isProduction) {
|
|
94
|
-
console.warn("\u26A0\uFE0F [BigsoAuthSDK] Blocked non-HTTPS sync request");
|
|
95
|
-
res.status(403).json({ error: "HTTPS required" });
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const clientIp = req.ip || req.socket.remoteAddress || "";
|
|
99
|
-
const isLoopback = clientIp === "::1" || clientIp === "127.0.0.1" || clientIp === "::ffff:127.0.0.1";
|
|
100
|
-
if (!isProduction && isLoopback) {
|
|
101
|
-
return next();
|
|
102
|
-
}
|
|
103
|
-
const ssoUrl = new URL(options.ssoBackendUrl);
|
|
104
|
-
const ssoHostname = ssoUrl.hostname;
|
|
105
|
-
const ssoIps = await import_dns.promises.resolve4(ssoHostname).catch(() => []);
|
|
106
|
-
const cleanClientIp = clientIp.replace(/^.*:/, "");
|
|
107
|
-
const isPrivateIp = cleanClientIp.startsWith("10.") || cleanClientIp.startsWith("192.168.") || cleanClientIp.startsWith("172.") && parseInt(cleanClientIp.split(".")[1], 10) >= 16 && parseInt(cleanClientIp.split(".")[1], 10) <= 31;
|
|
108
|
-
if (!ssoIps.includes(cleanClientIp) && !isPrivateIp) {
|
|
109
|
-
console.warn(`\u26D4\uFE0F [BigsoAuthSDK] Blocked sync request from unauthorized IP: ${clientIp}`);
|
|
110
|
-
res.status(403).json({ error: "Unauthorized origin" });
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
next();
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.error("\u274C [BigsoAuthSDK] Sync Guard Validation Error:", error instanceof Error ? error.message : error);
|
|
116
|
-
res.status(500).json({ error: "Security validation failed" });
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/express/routes/createSsoAuthRouter.ts
|
|
122
|
-
var import_express = require("express");
|
|
123
|
-
function createSsoAuthRouter(options) {
|
|
124
|
-
const router = (0, import_express.Router)();
|
|
125
|
-
const cookieName = options.cookieName || "sso_session";
|
|
126
|
-
const isProduction = options.isProduction ?? process.env.NODE_ENV === "production";
|
|
127
|
-
const getCookieOptions = (customOptions = {}) => {
|
|
128
|
-
const base = {
|
|
129
|
-
httpOnly: true,
|
|
130
|
-
secure: isProduction,
|
|
131
|
-
sameSite: "lax",
|
|
132
|
-
path: "/",
|
|
133
|
-
...customOptions
|
|
134
|
-
};
|
|
135
|
-
if (isProduction && options.cookieDomain) {
|
|
136
|
-
base.domain = options.cookieDomain;
|
|
137
|
-
}
|
|
138
|
-
return base;
|
|
139
|
-
};
|
|
140
|
-
router.post("/exchange", async (req, res) => {
|
|
141
|
-
try {
|
|
142
|
-
const { code } = req.body;
|
|
143
|
-
if (!code) {
|
|
144
|
-
res.status(400).json({ error: "Authorization code is required" });
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const ssoResponse = await options.ssoClient.exchangeCodeForToken(code);
|
|
148
|
-
if (!ssoResponse.success) {
|
|
149
|
-
res.status(401).json({ error: "Invalid authorization code" });
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const sessionMaxAge = new Date(ssoResponse.expiresAt).getTime() - Date.now();
|
|
153
|
-
const refreshMaxAge = ssoResponse.refreshExpiresAt ? new Date(ssoResponse.refreshExpiresAt).getTime() - Date.now() : 7 * 24 * 60 * 60 * 1e3;
|
|
154
|
-
const sessionCookieOptions = getCookieOptions({
|
|
155
|
-
maxAge: sessionMaxAge > 0 ? sessionMaxAge : 0
|
|
156
|
-
});
|
|
157
|
-
const refreshCookieOptions = getCookieOptions({
|
|
158
|
-
maxAge: refreshMaxAge > 0 ? refreshMaxAge : 0
|
|
159
|
-
});
|
|
160
|
-
res.cookie(cookieName, ssoResponse.sessionToken, sessionCookieOptions);
|
|
161
|
-
if (ssoResponse.refreshToken) {
|
|
162
|
-
res.cookie(`${cookieName}_refresh`, ssoResponse.refreshToken, refreshCookieOptions);
|
|
163
|
-
}
|
|
164
|
-
if (options.onLoginSuccess) {
|
|
165
|
-
await options.onLoginSuccess(ssoResponse);
|
|
166
|
-
}
|
|
167
|
-
res.json({
|
|
168
|
-
success: true,
|
|
169
|
-
user: ssoResponse.user,
|
|
170
|
-
tenant: ssoResponse.tenant,
|
|
171
|
-
expiresAt: ssoResponse.expiresAt
|
|
172
|
-
});
|
|
173
|
-
} catch (error) {
|
|
174
|
-
console.error("\u274C [BigsoAuthSDK] Error exchanging code:", error.message);
|
|
175
|
-
res.status(500).json({
|
|
176
|
-
error: error.message || "Failed to exchange authorization code"
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
router.post("/exchange-v2", async (req, res) => {
|
|
181
|
-
try {
|
|
182
|
-
const { payload } = req.body;
|
|
183
|
-
if (!payload) {
|
|
184
|
-
res.status(400).json({ error: "Signed payload is required" });
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
const verified = await options.ssoClient.verifySignedPayload(payload, options.frontendUrl);
|
|
188
|
-
if (!verified.code) {
|
|
189
|
-
res.status(400).json({ error: "No authorization code found in payload" });
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const ssoResponse = await options.ssoClient.exchangeCodeForToken(verified.code);
|
|
193
|
-
if (!ssoResponse.success) {
|
|
194
|
-
res.status(401).json({ error: "Invalid authorization code" });
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
const sessionMaxAge = new Date(ssoResponse.expiresAt).getTime() - Date.now();
|
|
198
|
-
const refreshMaxAge = ssoResponse.refreshExpiresAt ? new Date(ssoResponse.refreshExpiresAt).getTime() - Date.now() : 7 * 24 * 60 * 60 * 1e3;
|
|
199
|
-
const sessionCookieOptions = getCookieOptions({
|
|
200
|
-
maxAge: sessionMaxAge > 0 ? sessionMaxAge : 0
|
|
201
|
-
});
|
|
202
|
-
const refreshCookieOptions = getCookieOptions({
|
|
203
|
-
maxAge: refreshMaxAge > 0 ? refreshMaxAge : 0
|
|
204
|
-
});
|
|
205
|
-
res.cookie(cookieName, ssoResponse.sessionToken, sessionCookieOptions);
|
|
206
|
-
if (ssoResponse.refreshToken) {
|
|
207
|
-
res.cookie(`${cookieName}_refresh`, ssoResponse.refreshToken, refreshCookieOptions);
|
|
208
|
-
}
|
|
209
|
-
if (options.onLoginSuccess) {
|
|
210
|
-
await options.onLoginSuccess(ssoResponse);
|
|
211
|
-
}
|
|
212
|
-
res.json({
|
|
213
|
-
success: true,
|
|
214
|
-
user: ssoResponse.user,
|
|
215
|
-
tenant: ssoResponse.tenant,
|
|
216
|
-
expiresAt: ssoResponse.expiresAt
|
|
217
|
-
});
|
|
218
|
-
} catch (error) {
|
|
219
|
-
console.error("\u274C [BigsoAuthSDK] Error exchanging v2 payload:", error.message);
|
|
220
|
-
res.status(401).json({
|
|
221
|
-
error: error.message || "Failed to verify signed payload"
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
router.get("/session", ssoAuthMiddleware(options), (req, res) => {
|
|
226
|
-
res.set("Cache-Control", "no-store, no-cache, must-revalidate, private");
|
|
227
|
-
res.set("Pragma", "no-cache");
|
|
228
|
-
res.set("Expires", "0");
|
|
229
|
-
res.json({
|
|
230
|
-
success: true,
|
|
231
|
-
user: req.user,
|
|
232
|
-
tenant: req.tenant,
|
|
233
|
-
expiresAt: req.ssoSession?.expiresAt
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
router.post("/logout", async (req, res) => {
|
|
237
|
-
const sessionToken = req.cookies?.[cookieName];
|
|
238
|
-
if (sessionToken) {
|
|
239
|
-
try {
|
|
240
|
-
await options.ssoClient.revokeSession(sessionToken);
|
|
241
|
-
} catch (error) {
|
|
242
|
-
console.warn("\u26A0\uFE0F [BigsoAuthSDK] Failed to revoke session in SSO Backend. Clearing local anyway.", error.message);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
const cookieOptions = getCookieOptions({ maxAge: 0 });
|
|
246
|
-
res.clearCookie(cookieName, cookieOptions);
|
|
247
|
-
res.clearCookie(`${cookieName}_refresh`, cookieOptions);
|
|
248
|
-
if (options.onLogout && sessionToken) {
|
|
249
|
-
await options.onLogout(sessionToken);
|
|
250
|
-
}
|
|
251
|
-
res.json({ success: true, message: "Logged out" });
|
|
252
|
-
});
|
|
253
|
-
return router;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// src/express/routes/createSsoSyncRouter.ts
|
|
257
|
-
var import_express2 = require("express");
|
|
258
|
-
function createSsoSyncRouter(options) {
|
|
259
|
-
const router = (0, import_express2.Router)();
|
|
260
|
-
router.get("/resources", ssoSyncGuardMiddleware({
|
|
261
|
-
ssoBackendUrl: options.ssoBackendUrl,
|
|
262
|
-
isProduction: options.isProduction
|
|
263
|
-
}), (req, res) => {
|
|
264
|
-
try {
|
|
265
|
-
res.json({
|
|
266
|
-
success: true,
|
|
267
|
-
resources: options.resources,
|
|
268
|
-
meta: {
|
|
269
|
-
appId: options.appId,
|
|
270
|
-
count: options.resources.length,
|
|
271
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
} catch (error) {
|
|
275
|
-
console.error("\u274C [BigsoAuthSDK] Error in sync endpoint:", error.message);
|
|
276
|
-
res.status(500).json({ error: error.message });
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
return router;
|
|
280
|
-
}
|
|
281
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
282
|
-
0 && (module.exports = {
|
|
283
|
-
createSsoAuthRouter,
|
|
284
|
-
createSsoSyncRouter,
|
|
285
|
-
ssoAuthMiddleware,
|
|
286
|
-
ssoSyncGuardMiddleware
|
|
287
|
-
});
|
package/dist/express/index.d.cts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction, Router } from 'express';
|
|
2
|
-
import { BigsoSsoClient } from '../node/index.cjs';
|
|
3
|
-
import { S as SsoSessionData } from '../types-CoXgtTry.cjs';
|
|
4
|
-
|
|
5
|
-
interface SsoAuthMiddlewareOptions {
|
|
6
|
-
ssoClient: BigsoSsoClient;
|
|
7
|
-
cookieName?: string;
|
|
8
|
-
cookieDomain?: string;
|
|
9
|
-
isProduction?: boolean;
|
|
10
|
-
onSessionValidated?: (session: SsoSessionData, req: Request) => Promise<void> | void;
|
|
11
|
-
}
|
|
12
|
-
declare global {
|
|
13
|
-
namespace Express {
|
|
14
|
-
interface Request {
|
|
15
|
-
user?: SsoSessionData['user'];
|
|
16
|
-
tenant?: SsoSessionData['tenant'];
|
|
17
|
-
ssoSession?: SsoSessionData;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
declare function ssoAuthMiddleware(options: SsoAuthMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
22
|
-
|
|
23
|
-
interface SsoSyncGuardOptions {
|
|
24
|
-
ssoBackendUrl: string;
|
|
25
|
-
isProduction?: boolean;
|
|
26
|
-
}
|
|
27
|
-
declare function ssoSyncGuardMiddleware(options: SsoSyncGuardOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
28
|
-
|
|
29
|
-
interface CreateSsoAuthRouterOptions {
|
|
30
|
-
ssoClient: BigsoSsoClient;
|
|
31
|
-
frontendUrl: string;
|
|
32
|
-
cookieName?: string;
|
|
33
|
-
cookieDomain?: string;
|
|
34
|
-
isProduction?: boolean;
|
|
35
|
-
onLoginSuccess?: (session: SsoSessionData) => void | Promise<void>;
|
|
36
|
-
onLogout?: (sessionToken: string) => void | Promise<void>;
|
|
37
|
-
}
|
|
38
|
-
declare function createSsoAuthRouter(options: CreateSsoAuthRouterOptions): Router;
|
|
39
|
-
|
|
40
|
-
interface SsoSyncRouterOptions {
|
|
41
|
-
resources: any[];
|
|
42
|
-
appId: string;
|
|
43
|
-
ssoBackendUrl: string;
|
|
44
|
-
isProduction?: boolean;
|
|
45
|
-
}
|
|
46
|
-
declare function createSsoSyncRouter(options: SsoSyncRouterOptions): Router;
|
|
47
|
-
|
|
48
|
-
export { type CreateSsoAuthRouterOptions, type SsoAuthMiddlewareOptions, type SsoSyncGuardOptions, type SsoSyncRouterOptions, createSsoAuthRouter, createSsoSyncRouter, ssoAuthMiddleware, ssoSyncGuardMiddleware };
|