@bigso/auth-sdk 0.4.6 → 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 +180 -135
- package/dist/browser/index.cjs +11 -23
- package/dist/browser/index.d.cts +3 -20
- package/dist/browser/index.d.ts +3 -20
- package/dist/browser/index.js +12 -24
- package/dist/chunk-5ECHA2VH.js +32 -0
- package/dist/express/index.cjs +61 -122
- package/dist/express/index.d.cts +16 -13
- package/dist/express/index.d.ts +16 -13
- package/dist/express/index.js +61 -122
- package/dist/node/index.cjs +69 -98
- package/dist/node/index.d.cts +6 -30
- package/dist/node/index.d.ts +6 -30
- package/dist/node/index.js +53 -99
- package/dist/types-BQzACpj3.d.cts +73 -0
- package/dist/types-BQzACpj3.d.ts +73 -0
- package/package.json +3 -3
- package/dist/chunk-LDDK6SJD.js +0 -13
- package/dist/types-CoXgtTry.d.cts +0 -51
- package/dist/types-CoXgtTry.d.ts +0 -51
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
async function verifyAccessToken(accessToken, jwksUrl) {
|
|
11
|
+
const JWKS = createRemoteJWKSet(new URL(jwksUrl));
|
|
12
|
+
const { payload } = await jwtVerify(accessToken, JWKS);
|
|
13
|
+
if (!payload.sub || !payload.jti) {
|
|
14
|
+
throw new Error("Invalid token structure: missing sub or jti");
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
sub: payload.sub,
|
|
18
|
+
jti: payload.jti,
|
|
19
|
+
iss: payload.iss,
|
|
20
|
+
aud: payload.aud || "",
|
|
21
|
+
exp: payload.exp,
|
|
22
|
+
iat: payload.iat,
|
|
23
|
+
tenants: payload.tenants || [],
|
|
24
|
+
systemRole: payload.systemRole || "user",
|
|
25
|
+
deviceFingerprint: payload.deviceFingerprint
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
verifySignedPayload,
|
|
31
|
+
verifyAccessToken
|
|
32
|
+
};
|
package/dist/express/index.cjs
CHANGED
|
@@ -29,56 +29,32 @@ module.exports = __toCommonJS(express_exports);
|
|
|
29
29
|
|
|
30
30
|
// src/express/middlewares/ssoAuth.ts
|
|
31
31
|
function ssoAuthMiddleware(options) {
|
|
32
|
-
const cookieName = options.cookieName || "sso_session";
|
|
33
|
-
const isProduction = options.isProduction ?? process.env.NODE_ENV === "production";
|
|
34
32
|
return async (req, res, next) => {
|
|
35
33
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
}
|
|
34
|
+
const authHeader = req.headers.authorization;
|
|
35
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
36
|
+
res.status(401).json({ error: "Missing access token" });
|
|
37
|
+
return;
|
|
71
38
|
}
|
|
72
|
-
|
|
73
|
-
|
|
39
|
+
const accessToken = authHeader.substring(7);
|
|
40
|
+
const payload = await options.ssoClient.validateAccessToken(accessToken);
|
|
41
|
+
if (!payload) {
|
|
42
|
+
res.status(401).json({ error: "Invalid or expired access token" });
|
|
43
|
+
return;
|
|
74
44
|
}
|
|
75
|
-
|
|
76
|
-
req.
|
|
77
|
-
|
|
45
|
+
const primaryTenant = payload.tenants?.[0];
|
|
46
|
+
req.user = {
|
|
47
|
+
userId: payload.sub,
|
|
48
|
+
email: "",
|
|
49
|
+
firstName: "",
|
|
50
|
+
lastName: ""
|
|
51
|
+
};
|
|
52
|
+
req.tenant = primaryTenant || void 0;
|
|
53
|
+
req.tokenPayload = payload;
|
|
78
54
|
next();
|
|
79
55
|
} catch (error) {
|
|
80
|
-
console.error("
|
|
81
|
-
res.status(
|
|
56
|
+
console.error("[BigsoAuthSDK] Authentication Middleware Error:", error instanceof Error ? error.message : error);
|
|
57
|
+
res.status(401).json({ error: "Authentication failed" });
|
|
82
58
|
}
|
|
83
59
|
};
|
|
84
60
|
}
|
|
@@ -122,59 +98,26 @@ function ssoSyncGuardMiddleware(options) {
|
|
|
122
98
|
var import_express = require("express");
|
|
123
99
|
function createSsoAuthRouter(options) {
|
|
124
100
|
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
101
|
router.post("/exchange", async (req, res) => {
|
|
141
102
|
try {
|
|
142
|
-
const { code } = req.body;
|
|
143
|
-
if (!code) {
|
|
144
|
-
res.status(400).json({ error: "
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const ssoResponse = await options.ssoClient.exchangeCodeForToken(code);
|
|
148
|
-
if (!ssoResponse.success) {
|
|
149
|
-
res.status(401).json({ error: "Invalid authorization code" });
|
|
103
|
+
const { code, codeVerifier } = req.body;
|
|
104
|
+
if (!code || !codeVerifier) {
|
|
105
|
+
res.status(400).json({ error: "code and codeVerifier are required" });
|
|
150
106
|
return;
|
|
151
107
|
}
|
|
152
|
-
const
|
|
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
|
-
}
|
|
108
|
+
const ssoResponse = await options.ssoClient.exchangeCode(code, codeVerifier);
|
|
164
109
|
if (options.onLoginSuccess) {
|
|
165
110
|
await options.onLoginSuccess(ssoResponse);
|
|
166
111
|
}
|
|
167
112
|
res.json({
|
|
168
113
|
success: true,
|
|
114
|
+
tokens: ssoResponse.tokens,
|
|
169
115
|
user: ssoResponse.user,
|
|
170
|
-
tenant: ssoResponse.tenant
|
|
171
|
-
expiresAt: ssoResponse.expiresAt
|
|
116
|
+
tenant: ssoResponse.tenant
|
|
172
117
|
});
|
|
173
118
|
} catch (error) {
|
|
174
|
-
console.error("
|
|
175
|
-
res.status(
|
|
176
|
-
error: error.message || "Failed to exchange authorization code"
|
|
177
|
-
});
|
|
119
|
+
console.error("[BigsoAuthSDK] Error exchanging code:", error.message);
|
|
120
|
+
res.status(401).json({ error: error.message || "Failed to exchange authorization code" });
|
|
178
121
|
}
|
|
179
122
|
});
|
|
180
123
|
router.post("/exchange-v2", async (req, res) => {
|
|
@@ -189,40 +132,27 @@ function createSsoAuthRouter(options) {
|
|
|
189
132
|
res.status(400).json({ error: "No authorization code found in payload" });
|
|
190
133
|
return;
|
|
191
134
|
}
|
|
192
|
-
const
|
|
193
|
-
if (!
|
|
194
|
-
res.status(
|
|
135
|
+
const codeVerifier = verified.code_verifier;
|
|
136
|
+
if (!codeVerifier) {
|
|
137
|
+
res.status(400).json({ error: "code_verifier is required for PKCE exchange" });
|
|
195
138
|
return;
|
|
196
139
|
}
|
|
197
|
-
const
|
|
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
|
-
}
|
|
140
|
+
const ssoResponse = await options.ssoClient.exchangeCode(verified.code, codeVerifier);
|
|
209
141
|
if (options.onLoginSuccess) {
|
|
210
142
|
await options.onLoginSuccess(ssoResponse);
|
|
211
143
|
}
|
|
212
144
|
res.json({
|
|
213
145
|
success: true,
|
|
146
|
+
tokens: ssoResponse.tokens,
|
|
214
147
|
user: ssoResponse.user,
|
|
215
|
-
tenant: ssoResponse.tenant
|
|
216
|
-
expiresAt: ssoResponse.expiresAt
|
|
148
|
+
tenant: ssoResponse.tenant
|
|
217
149
|
});
|
|
218
150
|
} catch (error) {
|
|
219
|
-
console.error("
|
|
220
|
-
res.status(401).json({
|
|
221
|
-
error: error.message || "Failed to verify signed payload"
|
|
222
|
-
});
|
|
151
|
+
console.error("[BigsoAuthSDK] Error exchanging v2 payload:", error.message);
|
|
152
|
+
res.status(401).json({ error: error.message || "Failed to verify signed payload" });
|
|
223
153
|
}
|
|
224
154
|
});
|
|
225
|
-
router.get("/session", ssoAuthMiddleware(options), (req, res) => {
|
|
155
|
+
router.get("/session", ssoAuthMiddleware({ ssoClient: options.ssoClient }), (req, res) => {
|
|
226
156
|
res.set("Cache-Control", "no-store, no-cache, must-revalidate, private");
|
|
227
157
|
res.set("Pragma", "no-cache");
|
|
228
158
|
res.set("Expires", "0");
|
|
@@ -230,25 +160,34 @@ function createSsoAuthRouter(options) {
|
|
|
230
160
|
success: true,
|
|
231
161
|
user: req.user,
|
|
232
162
|
tenant: req.tenant,
|
|
233
|
-
|
|
163
|
+
tokenPayload: req.tokenPayload
|
|
234
164
|
});
|
|
235
165
|
});
|
|
236
|
-
router.post("/
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
166
|
+
router.post("/refresh", async (req, res) => {
|
|
167
|
+
try {
|
|
168
|
+
const ssoResponse = await options.ssoClient.refreshTokens();
|
|
169
|
+
res.json({
|
|
170
|
+
success: true,
|
|
171
|
+
tokens: ssoResponse.tokens
|
|
172
|
+
});
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error("[BigsoAuthSDK] Error refreshing tokens:", error.message);
|
|
175
|
+
res.status(401).json({ error: error.message || "Failed to refresh tokens" });
|
|
244
176
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
177
|
+
});
|
|
178
|
+
router.post("/logout", ssoAuthMiddleware({ ssoClient: options.ssoClient }), async (req, res) => {
|
|
179
|
+
try {
|
|
180
|
+
const accessToken = req.headers.authorization?.substring(7) || "";
|
|
181
|
+
const { revokeAll = false } = req.body || {};
|
|
182
|
+
await options.ssoClient.logout(accessToken, revokeAll);
|
|
183
|
+
if (options.onLogout) {
|
|
184
|
+
await options.onLogout(accessToken);
|
|
185
|
+
}
|
|
186
|
+
res.json({ success: true, message: "Logged out" });
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.warn("[BigsoAuthSDK] Failed to logout in SSO Backend.", error.message);
|
|
189
|
+
res.json({ success: true, message: "Logged out (backend revocation failed)" });
|
|
250
190
|
}
|
|
251
|
-
res.json({ success: true, message: "Logged out" });
|
|
252
191
|
});
|
|
253
192
|
return router;
|
|
254
193
|
}
|
package/dist/express/index.d.cts
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { Request, Response, NextFunction, Router } from 'express';
|
|
2
2
|
import { BigsoSsoClient } from '../node/index.cjs';
|
|
3
|
-
import { S as
|
|
3
|
+
import { S as SsoTokenPayload, V as V2ExchangeResponse } from '../types-BQzACpj3.cjs';
|
|
4
4
|
|
|
5
5
|
interface SsoAuthMiddlewareOptions {
|
|
6
6
|
ssoClient: BigsoSsoClient;
|
|
7
|
-
cookieName?: string;
|
|
8
|
-
cookieDomain?: string;
|
|
9
|
-
isProduction?: boolean;
|
|
10
|
-
onSessionValidated?: (session: SsoSessionData, req: Request) => Promise<void> | void;
|
|
11
7
|
}
|
|
12
8
|
declare global {
|
|
13
9
|
namespace Express {
|
|
14
10
|
interface Request {
|
|
15
|
-
user?:
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
user?: {
|
|
12
|
+
userId: string;
|
|
13
|
+
email: string;
|
|
14
|
+
firstName: string;
|
|
15
|
+
lastName: string;
|
|
16
|
+
};
|
|
17
|
+
tenant?: {
|
|
18
|
+
tenantId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
slug: string;
|
|
21
|
+
role: string;
|
|
22
|
+
};
|
|
23
|
+
tokenPayload?: SsoTokenPayload;
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
26
|
}
|
|
@@ -29,11 +35,8 @@ declare function ssoSyncGuardMiddleware(options: SsoSyncGuardOptions): (req: Req
|
|
|
29
35
|
interface CreateSsoAuthRouterOptions {
|
|
30
36
|
ssoClient: BigsoSsoClient;
|
|
31
37
|
frontendUrl: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
isProduction?: boolean;
|
|
35
|
-
onLoginSuccess?: (session: SsoSessionData) => void | Promise<void>;
|
|
36
|
-
onLogout?: (sessionToken: string) => void | Promise<void>;
|
|
38
|
+
onLoginSuccess?: (session: V2ExchangeResponse) => void | Promise<void>;
|
|
39
|
+
onLogout?: (accessToken: string) => void | Promise<void>;
|
|
37
40
|
}
|
|
38
41
|
declare function createSsoAuthRouter(options: CreateSsoAuthRouterOptions): Router;
|
|
39
42
|
|
package/dist/express/index.d.ts
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { Request, Response, NextFunction, Router } from 'express';
|
|
2
2
|
import { BigsoSsoClient } from '../node/index.js';
|
|
3
|
-
import { S as
|
|
3
|
+
import { S as SsoTokenPayload, V as V2ExchangeResponse } from '../types-BQzACpj3.js';
|
|
4
4
|
|
|
5
5
|
interface SsoAuthMiddlewareOptions {
|
|
6
6
|
ssoClient: BigsoSsoClient;
|
|
7
|
-
cookieName?: string;
|
|
8
|
-
cookieDomain?: string;
|
|
9
|
-
isProduction?: boolean;
|
|
10
|
-
onSessionValidated?: (session: SsoSessionData, req: Request) => Promise<void> | void;
|
|
11
7
|
}
|
|
12
8
|
declare global {
|
|
13
9
|
namespace Express {
|
|
14
10
|
interface Request {
|
|
15
|
-
user?:
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
user?: {
|
|
12
|
+
userId: string;
|
|
13
|
+
email: string;
|
|
14
|
+
firstName: string;
|
|
15
|
+
lastName: string;
|
|
16
|
+
};
|
|
17
|
+
tenant?: {
|
|
18
|
+
tenantId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
slug: string;
|
|
21
|
+
role: string;
|
|
22
|
+
};
|
|
23
|
+
tokenPayload?: SsoTokenPayload;
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
26
|
}
|
|
@@ -29,11 +35,8 @@ declare function ssoSyncGuardMiddleware(options: SsoSyncGuardOptions): (req: Req
|
|
|
29
35
|
interface CreateSsoAuthRouterOptions {
|
|
30
36
|
ssoClient: BigsoSsoClient;
|
|
31
37
|
frontendUrl: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
isProduction?: boolean;
|
|
35
|
-
onLoginSuccess?: (session: SsoSessionData) => void | Promise<void>;
|
|
36
|
-
onLogout?: (sessionToken: string) => void | Promise<void>;
|
|
38
|
+
onLoginSuccess?: (session: V2ExchangeResponse) => void | Promise<void>;
|
|
39
|
+
onLogout?: (accessToken: string) => void | Promise<void>;
|
|
37
40
|
}
|
|
38
41
|
declare function createSsoAuthRouter(options: CreateSsoAuthRouterOptions): Router;
|
|
39
42
|
|
package/dist/express/index.js
CHANGED
|
@@ -1,55 +1,31 @@
|
|
|
1
1
|
// src/express/middlewares/ssoAuth.ts
|
|
2
2
|
function ssoAuthMiddleware(options) {
|
|
3
|
-
const cookieName = options.cookieName || "sso_session";
|
|
4
|
-
const isProduction = options.isProduction ?? process.env.NODE_ENV === "production";
|
|
5
3
|
return async (req, res, next) => {
|
|
6
4
|
try {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
if (!session) {
|
|
13
|
-
const refreshToken = req.cookies?.[`${cookieName}_refresh`];
|
|
14
|
-
if (refreshToken) {
|
|
15
|
-
const newSessionData = await options.ssoClient.refreshAppSession(refreshToken);
|
|
16
|
-
if (newSessionData) {
|
|
17
|
-
const sessionMaxAge = new Date(newSessionData.expiresAt).getTime() - Date.now();
|
|
18
|
-
const refreshMaxAge = newSessionData.refreshExpiresAt ? new Date(newSessionData.refreshExpiresAt).getTime() - Date.now() : 7 * 24 * 60 * 60 * 1e3;
|
|
19
|
-
const sessionCookieOptions = {
|
|
20
|
-
httpOnly: true,
|
|
21
|
-
secure: isProduction,
|
|
22
|
-
sameSite: "lax",
|
|
23
|
-
path: "/",
|
|
24
|
-
maxAge: sessionMaxAge > 0 ? sessionMaxAge : 0,
|
|
25
|
-
...isProduction && options.cookieDomain ? { domain: options.cookieDomain } : {}
|
|
26
|
-
};
|
|
27
|
-
const refreshCookieOptions = {
|
|
28
|
-
...sessionCookieOptions,
|
|
29
|
-
maxAge: refreshMaxAge > 0 ? refreshMaxAge : 0
|
|
30
|
-
};
|
|
31
|
-
res.cookie(cookieName, newSessionData.sessionToken, sessionCookieOptions);
|
|
32
|
-
res.cookie(`${cookieName}_refresh`, newSessionData.refreshToken, refreshCookieOptions);
|
|
33
|
-
session = await options.ssoClient.validateSessionToken(newSessionData.sessionToken);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (!session) {
|
|
37
|
-
res.clearCookie(cookieName);
|
|
38
|
-
res.clearCookie(`${cookieName}_refresh`);
|
|
39
|
-
res.status(401).json({ error: "Session expired or invalid" });
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
5
|
+
const authHeader = req.headers.authorization;
|
|
6
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
7
|
+
res.status(401).json({ error: "Missing access token" });
|
|
8
|
+
return;
|
|
42
9
|
}
|
|
43
|
-
|
|
44
|
-
|
|
10
|
+
const accessToken = authHeader.substring(7);
|
|
11
|
+
const payload = await options.ssoClient.validateAccessToken(accessToken);
|
|
12
|
+
if (!payload) {
|
|
13
|
+
res.status(401).json({ error: "Invalid or expired access token" });
|
|
14
|
+
return;
|
|
45
15
|
}
|
|
46
|
-
|
|
47
|
-
req.
|
|
48
|
-
|
|
16
|
+
const primaryTenant = payload.tenants?.[0];
|
|
17
|
+
req.user = {
|
|
18
|
+
userId: payload.sub,
|
|
19
|
+
email: "",
|
|
20
|
+
firstName: "",
|
|
21
|
+
lastName: ""
|
|
22
|
+
};
|
|
23
|
+
req.tenant = primaryTenant || void 0;
|
|
24
|
+
req.tokenPayload = payload;
|
|
49
25
|
next();
|
|
50
26
|
} catch (error) {
|
|
51
|
-
console.error("
|
|
52
|
-
res.status(
|
|
27
|
+
console.error("[BigsoAuthSDK] Authentication Middleware Error:", error instanceof Error ? error.message : error);
|
|
28
|
+
res.status(401).json({ error: "Authentication failed" });
|
|
53
29
|
}
|
|
54
30
|
};
|
|
55
31
|
}
|
|
@@ -93,59 +69,26 @@ function ssoSyncGuardMiddleware(options) {
|
|
|
93
69
|
import { Router } from "express";
|
|
94
70
|
function createSsoAuthRouter(options) {
|
|
95
71
|
const router = Router();
|
|
96
|
-
const cookieName = options.cookieName || "sso_session";
|
|
97
|
-
const isProduction = options.isProduction ?? process.env.NODE_ENV === "production";
|
|
98
|
-
const getCookieOptions = (customOptions = {}) => {
|
|
99
|
-
const base = {
|
|
100
|
-
httpOnly: true,
|
|
101
|
-
secure: isProduction,
|
|
102
|
-
sameSite: "lax",
|
|
103
|
-
path: "/",
|
|
104
|
-
...customOptions
|
|
105
|
-
};
|
|
106
|
-
if (isProduction && options.cookieDomain) {
|
|
107
|
-
base.domain = options.cookieDomain;
|
|
108
|
-
}
|
|
109
|
-
return base;
|
|
110
|
-
};
|
|
111
72
|
router.post("/exchange", async (req, res) => {
|
|
112
73
|
try {
|
|
113
|
-
const { code } = req.body;
|
|
114
|
-
if (!code) {
|
|
115
|
-
res.status(400).json({ error: "
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const ssoResponse = await options.ssoClient.exchangeCodeForToken(code);
|
|
119
|
-
if (!ssoResponse.success) {
|
|
120
|
-
res.status(401).json({ error: "Invalid authorization code" });
|
|
74
|
+
const { code, codeVerifier } = req.body;
|
|
75
|
+
if (!code || !codeVerifier) {
|
|
76
|
+
res.status(400).json({ error: "code and codeVerifier are required" });
|
|
121
77
|
return;
|
|
122
78
|
}
|
|
123
|
-
const
|
|
124
|
-
const refreshMaxAge = ssoResponse.refreshExpiresAt ? new Date(ssoResponse.refreshExpiresAt).getTime() - Date.now() : 7 * 24 * 60 * 60 * 1e3;
|
|
125
|
-
const sessionCookieOptions = getCookieOptions({
|
|
126
|
-
maxAge: sessionMaxAge > 0 ? sessionMaxAge : 0
|
|
127
|
-
});
|
|
128
|
-
const refreshCookieOptions = getCookieOptions({
|
|
129
|
-
maxAge: refreshMaxAge > 0 ? refreshMaxAge : 0
|
|
130
|
-
});
|
|
131
|
-
res.cookie(cookieName, ssoResponse.sessionToken, sessionCookieOptions);
|
|
132
|
-
if (ssoResponse.refreshToken) {
|
|
133
|
-
res.cookie(`${cookieName}_refresh`, ssoResponse.refreshToken, refreshCookieOptions);
|
|
134
|
-
}
|
|
79
|
+
const ssoResponse = await options.ssoClient.exchangeCode(code, codeVerifier);
|
|
135
80
|
if (options.onLoginSuccess) {
|
|
136
81
|
await options.onLoginSuccess(ssoResponse);
|
|
137
82
|
}
|
|
138
83
|
res.json({
|
|
139
84
|
success: true,
|
|
85
|
+
tokens: ssoResponse.tokens,
|
|
140
86
|
user: ssoResponse.user,
|
|
141
|
-
tenant: ssoResponse.tenant
|
|
142
|
-
expiresAt: ssoResponse.expiresAt
|
|
87
|
+
tenant: ssoResponse.tenant
|
|
143
88
|
});
|
|
144
89
|
} catch (error) {
|
|
145
|
-
console.error("
|
|
146
|
-
res.status(
|
|
147
|
-
error: error.message || "Failed to exchange authorization code"
|
|
148
|
-
});
|
|
90
|
+
console.error("[BigsoAuthSDK] Error exchanging code:", error.message);
|
|
91
|
+
res.status(401).json({ error: error.message || "Failed to exchange authorization code" });
|
|
149
92
|
}
|
|
150
93
|
});
|
|
151
94
|
router.post("/exchange-v2", async (req, res) => {
|
|
@@ -160,40 +103,27 @@ function createSsoAuthRouter(options) {
|
|
|
160
103
|
res.status(400).json({ error: "No authorization code found in payload" });
|
|
161
104
|
return;
|
|
162
105
|
}
|
|
163
|
-
const
|
|
164
|
-
if (!
|
|
165
|
-
res.status(
|
|
106
|
+
const codeVerifier = verified.code_verifier;
|
|
107
|
+
if (!codeVerifier) {
|
|
108
|
+
res.status(400).json({ error: "code_verifier is required for PKCE exchange" });
|
|
166
109
|
return;
|
|
167
110
|
}
|
|
168
|
-
const
|
|
169
|
-
const refreshMaxAge = ssoResponse.refreshExpiresAt ? new Date(ssoResponse.refreshExpiresAt).getTime() - Date.now() : 7 * 24 * 60 * 60 * 1e3;
|
|
170
|
-
const sessionCookieOptions = getCookieOptions({
|
|
171
|
-
maxAge: sessionMaxAge > 0 ? sessionMaxAge : 0
|
|
172
|
-
});
|
|
173
|
-
const refreshCookieOptions = getCookieOptions({
|
|
174
|
-
maxAge: refreshMaxAge > 0 ? refreshMaxAge : 0
|
|
175
|
-
});
|
|
176
|
-
res.cookie(cookieName, ssoResponse.sessionToken, sessionCookieOptions);
|
|
177
|
-
if (ssoResponse.refreshToken) {
|
|
178
|
-
res.cookie(`${cookieName}_refresh`, ssoResponse.refreshToken, refreshCookieOptions);
|
|
179
|
-
}
|
|
111
|
+
const ssoResponse = await options.ssoClient.exchangeCode(verified.code, codeVerifier);
|
|
180
112
|
if (options.onLoginSuccess) {
|
|
181
113
|
await options.onLoginSuccess(ssoResponse);
|
|
182
114
|
}
|
|
183
115
|
res.json({
|
|
184
116
|
success: true,
|
|
117
|
+
tokens: ssoResponse.tokens,
|
|
185
118
|
user: ssoResponse.user,
|
|
186
|
-
tenant: ssoResponse.tenant
|
|
187
|
-
expiresAt: ssoResponse.expiresAt
|
|
119
|
+
tenant: ssoResponse.tenant
|
|
188
120
|
});
|
|
189
121
|
} catch (error) {
|
|
190
|
-
console.error("
|
|
191
|
-
res.status(401).json({
|
|
192
|
-
error: error.message || "Failed to verify signed payload"
|
|
193
|
-
});
|
|
122
|
+
console.error("[BigsoAuthSDK] Error exchanging v2 payload:", error.message);
|
|
123
|
+
res.status(401).json({ error: error.message || "Failed to verify signed payload" });
|
|
194
124
|
}
|
|
195
125
|
});
|
|
196
|
-
router.get("/session", ssoAuthMiddleware(options), (req, res) => {
|
|
126
|
+
router.get("/session", ssoAuthMiddleware({ ssoClient: options.ssoClient }), (req, res) => {
|
|
197
127
|
res.set("Cache-Control", "no-store, no-cache, must-revalidate, private");
|
|
198
128
|
res.set("Pragma", "no-cache");
|
|
199
129
|
res.set("Expires", "0");
|
|
@@ -201,25 +131,34 @@ function createSsoAuthRouter(options) {
|
|
|
201
131
|
success: true,
|
|
202
132
|
user: req.user,
|
|
203
133
|
tenant: req.tenant,
|
|
204
|
-
|
|
134
|
+
tokenPayload: req.tokenPayload
|
|
205
135
|
});
|
|
206
136
|
});
|
|
207
|
-
router.post("/
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
137
|
+
router.post("/refresh", async (req, res) => {
|
|
138
|
+
try {
|
|
139
|
+
const ssoResponse = await options.ssoClient.refreshTokens();
|
|
140
|
+
res.json({
|
|
141
|
+
success: true,
|
|
142
|
+
tokens: ssoResponse.tokens
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error("[BigsoAuthSDK] Error refreshing tokens:", error.message);
|
|
146
|
+
res.status(401).json({ error: error.message || "Failed to refresh tokens" });
|
|
215
147
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
148
|
+
});
|
|
149
|
+
router.post("/logout", ssoAuthMiddleware({ ssoClient: options.ssoClient }), async (req, res) => {
|
|
150
|
+
try {
|
|
151
|
+
const accessToken = req.headers.authorization?.substring(7) || "";
|
|
152
|
+
const { revokeAll = false } = req.body || {};
|
|
153
|
+
await options.ssoClient.logout(accessToken, revokeAll);
|
|
154
|
+
if (options.onLogout) {
|
|
155
|
+
await options.onLogout(accessToken);
|
|
156
|
+
}
|
|
157
|
+
res.json({ success: true, message: "Logged out" });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.warn("[BigsoAuthSDK] Failed to logout in SSO Backend.", error.message);
|
|
160
|
+
res.json({ success: true, message: "Logged out (backend revocation failed)" });
|
|
221
161
|
}
|
|
222
|
-
res.json({ success: true, message: "Logged out" });
|
|
223
162
|
});
|
|
224
163
|
return router;
|
|
225
164
|
}
|