authscape 1.0.778 → 1.0.782

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/index.js CHANGED
@@ -9645,7 +9645,7 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
9645
9645
  Object.defineProperty(exports, "__esModule", {
9646
9646
  value: true
9647
9647
  });
9648
- exports.apiService = void 0;
9648
+ exports.invalidateCurrentUser = exports.apiService = void 0;
9649
9649
  var _axios = _interopRequireDefault(require("axios"));
9650
9650
  var _queryString = _interopRequireDefault(require("query-string"));
9651
9651
  var _jsFileDownload = _interopRequireDefault(require("js-file-download"));
@@ -9659,6 +9659,50 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
9659
9659
  function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
9660
9660
  function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
9661
9661
  function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
9662
+ // ---------------------------------------------------------------------------
9663
+ // Signed-in user cache (client-side only)
9664
+ // ---------------------------------------------------------------------------
9665
+ // Caches the result of GetCurrentUser in sessionStorage to avoid re-calling
9666
+ // /UserManagement on every hard page reload. The entry is KEYED BY THE ACCESS
9667
+ // TOKEN: company/location/impersonation context lives in the token claims, so
9668
+ // switching context issues a new token -> new key -> the stale entry is never
9669
+ // read. Cleared on logout, and bounded by a short TTL as a backstop. This is
9670
+ // purely per-browser; it has no effect on the server handling many users.
9671
+ var CURRENT_USER_CACHE_KEY = 'authscape_current_user';
9672
+ var CURRENT_USER_TTL_MS = 60 * 1000; // 60s backstop
9673
+
9674
+ var readCurrentUserCache = function readCurrentUserCache(token) {
9675
+ if (typeof window === 'undefined' || !token) return null;
9676
+ try {
9677
+ var raw = window.sessionStorage.getItem(CURRENT_USER_CACHE_KEY);
9678
+ if (!raw) return null;
9679
+ var entry = JSON.parse(raw);
9680
+ if (entry.t !== token) return null; // different token => different context
9681
+ if (!entry.exp || entry.exp < Date.now()) return null; // TTL backstop
9682
+ return entry.u;
9683
+ } catch (e) {
9684
+ return null;
9685
+ }
9686
+ };
9687
+ var writeCurrentUserCache = function writeCurrentUserCache(token, user) {
9688
+ if (typeof window === 'undefined' || !token) return;
9689
+ try {
9690
+ window.sessionStorage.setItem(CURRENT_USER_CACHE_KEY, JSON.stringify({
9691
+ t: token,
9692
+ u: user,
9693
+ exp: Date.now() + CURRENT_USER_TTL_MS
9694
+ }));
9695
+ } catch (e) {/* sessionStorage unavailable (private mode / quota) — skip caching */}
9696
+ };
9697
+
9698
+ // Clear the cached signed-in user. Call after impersonation / company / location
9699
+ // switches, and it is also called automatically on logout and when no token exists.
9700
+ var invalidateCurrentUser = exports.invalidateCurrentUser = function invalidateCurrentUser() {
9701
+ if (typeof window === 'undefined') return;
9702
+ try {
9703
+ window.sessionStorage.removeItem(CURRENT_USER_CACHE_KEY);
9704
+ } catch (e) {/* ignore */}
9705
+ };
9662
9706
  var setupDefaultOptions = /*#__PURE__*/function () {
9663
9707
  var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
9664
9708
  var ctx,
@@ -9980,42 +10024,63 @@ var apiService = exports.apiService = function apiService() {
9980
10024
  }(),
9981
10025
  GetCurrentUser: function () {
9982
10026
  var _GetCurrentUser = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee8() {
9983
- var accessToken, defaultOptions, response;
10027
+ var forceRefresh,
10028
+ accessToken,
10029
+ cached,
10030
+ defaultOptions,
10031
+ response,
10032
+ _args8 = arguments;
9984
10033
  return _regeneratorRuntime().wrap(function _callee8$(_context8) {
9985
10034
  while (1) switch (_context8.prev = _context8.next) {
9986
10035
  case 0:
9987
- _context8.prev = 0;
10036
+ forceRefresh = _args8.length > 0 && _args8[0] !== undefined ? _args8[0] : false;
10037
+ _context8.prev = 1;
9988
10038
  accessToken = _jsCookie["default"].get('access_token') || null;
9989
- if (!accessToken) {
9990
- _context8.next = 11;
10039
+ if (accessToken) {
10040
+ _context8.next = 6;
9991
10041
  break;
9992
10042
  }
9993
- _context8.next = 5;
10043
+ invalidateCurrentUser();
10044
+ return _context8.abrupt("return", null);
10045
+ case 6:
10046
+ if (forceRefresh) {
10047
+ _context8.next = 10;
10048
+ break;
10049
+ }
10050
+ cached = readCurrentUserCache(accessToken);
10051
+ if (!cached) {
10052
+ _context8.next = 10;
10053
+ break;
10054
+ }
10055
+ return _context8.abrupt("return", cached);
10056
+ case 10:
10057
+ _context8.next = 12;
9994
10058
  return setupDefaultOptions(null);
9995
- case 5:
10059
+ case 12:
9996
10060
  defaultOptions = _context8.sent;
9997
- _context8.next = 8;
10061
+ _context8.next = 15;
9998
10062
  return instance.get('/UserManagement', defaultOptions);
9999
- case 8:
10063
+ case 15:
10000
10064
  response = _context8.sent;
10001
10065
  if (!(response != null && response.status == 200)) {
10002
- _context8.next = 11;
10066
+ _context8.next = 19;
10003
10067
  break;
10004
10068
  }
10069
+ writeCurrentUserCache(accessToken, response.data);
10005
10070
  return _context8.abrupt("return", response.data);
10006
- case 11:
10007
- _context8.next = 15;
10071
+ case 19:
10072
+ _context8.next = 23;
10008
10073
  break;
10009
- case 13:
10010
- _context8.prev = 13;
10011
- _context8.t0 = _context8["catch"](0);
10012
- case 15:
10074
+ case 21:
10075
+ _context8.prev = 21;
10076
+ _context8.t0 = _context8["catch"](1);
10077
+ case 23:
10013
10078
  return _context8.abrupt("return", null);
10014
- case 16:
10079
+ case 24:
10015
10080
  case "end":
10016
10081
  return _context8.stop();
10017
10082
  }
10018
- }, _callee8, null, [[0, 13]]);
10083
+ }, _callee8, null, [[1, 21]]);
10019
10084
  }));
10020
10085
  function GetCurrentUser() {
10021
10086
  return _GetCurrentUser.apply(this, arguments);
@@ -10385,12 +10450,17 @@ var _authService = exports.authService = function authService() {
10385
10450
  domain: domainHost,
10386
10451
  secure: typeof window !== "undefined" && window.location.protocol === "https:"
10387
10452
  });
10453
+
10454
+ // Drop the cached signed-in user so the next sign-in never reads a stale identity.
10455
+ try {
10456
+ if (typeof window !== "undefined") window.sessionStorage.removeItem("authscape_current_user");
10457
+ } catch (e) {/* ignore */}
10388
10458
  if (redirectUri == null) {
10389
10459
  window.location.href = AuthUri + "/connect/logout?redirect=" + window.location.href;
10390
10460
  } else {
10391
10461
  window.location.href = AuthUri + "/connect/logout?redirect=" + redirectUri;
10392
10462
  }
10393
- case 7:
10463
+ case 8:
10394
10464
  case "end":
10395
10465
  return _context6.stop();
10396
10466
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "authscape",
3
- "version": "1.0.778",
3
+ "version": "1.0.782",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -41,6 +41,64 @@ import { createSitemapRoute } from 'authscape/src/lib/sitemap-route';
41
41
  export const GET = createSitemapRoute(process.env.apiUri);
42
42
  `;
43
43
 
44
+ // ---------------------------------------------------------------------------
45
+ // /signin-oidc page templates
46
+ // ---------------------------------------------------------------------------
47
+ // AuthScapeApp reads the PKCE `code` query param from `useSearchParams()`
48
+ // (next/navigation). If the consumer doesn't have a `/signin-oidc` page, the
49
+ // IDP's redirect back lands on Next.js's 404 page — and Webkit/Safari
50
+ // doesn't reliably hydrate `useSearchParams()` on the 404 page. The code is
51
+ // never read, the user is stuck.
52
+ //
53
+ // Making /signin-oidc a real Next.js page (200 status) fixes this in every
54
+ // browser. The page itself is a placeholder; AuthScapeApp's signInValidator
55
+ // does the actual token exchange.
56
+
57
+ const SIGNIN_OIDC_PAGES_ROUTER_TEMPLATE = `// Auto-generated by AuthScape - Do not edit manually.
58
+ // Exists so /signin-oidc returns HTTP 200 (not the Next.js 404 page).
59
+ // Required for Webkit/Safari: useSearchParams() doesn't hydrate on the 404
60
+ // page in Webkit, so the PKCE 'code' query param is never read and sign-in
61
+ // silently stalls. With this real page in place, AuthScapeApp's
62
+ // signInValidator picks up the code and completes the exchange normally.
63
+ export default function SignInOidc() {
64
+ return (
65
+ <div
66
+ style={{
67
+ display: "flex",
68
+ alignItems: "center",
69
+ justifyContent: "center",
70
+ minHeight: "60vh",
71
+ fontFamily: "system-ui, -apple-system, Segoe UI, sans-serif",
72
+ color: "#666",
73
+ }}
74
+ >
75
+ Signing you in…
76
+ </div>
77
+ );
78
+ }
79
+ `;
80
+
81
+ const SIGNIN_OIDC_APP_ROUTER_TEMPLATE = `// Auto-generated by AuthScape - Do not edit manually.
82
+ // Exists so /signin-oidc returns HTTP 200 (not the Next.js 404 page).
83
+ // Required for Webkit/Safari to pick up the PKCE 'code' query param.
84
+ export default function SignInOidc() {
85
+ return (
86
+ <div
87
+ style={{
88
+ display: "flex",
89
+ alignItems: "center",
90
+ justifyContent: "center",
91
+ minHeight: "60vh",
92
+ fontFamily: "system-ui, -apple-system, Segoe UI, sans-serif",
93
+ color: "#666",
94
+ }}
95
+ >
96
+ Signing you in…
97
+ </div>
98
+ );
99
+ }
100
+ `;
101
+
44
102
  function detectProjectStructure() {
45
103
  // Get the parent directory where the user ran npm install
46
104
  // This goes up from node_modules/authscape to the project root
@@ -118,9 +176,42 @@ function setupSitemap() {
118
176
  }
119
177
  }
120
178
 
179
+ function setupSigninOidc() {
180
+ const structure = detectProjectStructure();
181
+ if (!structure) return;
182
+
183
+ const targetFile =
184
+ structure.type === 'app'
185
+ ? path.join(structure.baseDir, 'signin-oidc', 'page.js')
186
+ : path.join(structure.baseDir, 'signin-oidc.js');
187
+
188
+ if (fs.existsSync(targetFile)) {
189
+ // File exists, don't overwrite — consumer may have their own handler
190
+ return;
191
+ }
192
+
193
+ try {
194
+ fs.mkdirSync(path.dirname(targetFile), { recursive: true });
195
+ fs.writeFileSync(
196
+ targetFile,
197
+ structure.type === 'app' ? SIGNIN_OIDC_APP_ROUTER_TEMPLATE : SIGNIN_OIDC_PAGES_ROUTER_TEMPLATE,
198
+ 'utf8'
199
+ );
200
+ console.log(
201
+ '✅ AuthScape signin-oidc page configured at /signin-oidc (' +
202
+ (structure.type === 'app' ? 'App Router' : 'Pages Router') +
203
+ ')'
204
+ );
205
+ } catch (error) {
206
+ console.log('⚠️ Could not auto-configure /signin-oidc:', error.message);
207
+ console.log(' Without this page, Safari users will fail to sign in.');
208
+ }
209
+ }
210
+
121
211
  // Run the setup
122
212
  try {
123
213
  setupSitemap();
214
+ setupSigninOidc();
124
215
  } catch (error) {
125
216
  // Completely silent failure to avoid breaking npm install
126
217
  // Only log if there's an unexpected error
@@ -3,6 +3,49 @@ import querystring from 'query-string';
3
3
  import fileDownload from 'js-file-download';
4
4
  import Cookies from 'js-cookie';
5
5
 
6
+ // ---------------------------------------------------------------------------
7
+ // Signed-in user cache (client-side only)
8
+ // ---------------------------------------------------------------------------
9
+ // Caches the result of GetCurrentUser in sessionStorage to avoid re-calling
10
+ // /UserManagement on every hard page reload. The entry is KEYED BY THE ACCESS
11
+ // TOKEN: company/location/impersonation context lives in the token claims, so
12
+ // switching context issues a new token -> new key -> the stale entry is never
13
+ // read. Cleared on logout, and bounded by a short TTL as a backstop. This is
14
+ // purely per-browser; it has no effect on the server handling many users.
15
+ const CURRENT_USER_CACHE_KEY = 'authscape_current_user';
16
+ const CURRENT_USER_TTL_MS = 60 * 1000; // 60s backstop
17
+
18
+ const readCurrentUserCache = (token) => {
19
+ if (typeof window === 'undefined' || !token) return null;
20
+ try {
21
+ const raw = window.sessionStorage.getItem(CURRENT_USER_CACHE_KEY);
22
+ if (!raw) return null;
23
+ const entry = JSON.parse(raw);
24
+ if (entry.t !== token) return null; // different token => different context
25
+ if (!entry.exp || entry.exp < Date.now()) return null; // TTL backstop
26
+ return entry.u;
27
+ } catch (e) {
28
+ return null;
29
+ }
30
+ };
31
+
32
+ const writeCurrentUserCache = (token, user) => {
33
+ if (typeof window === 'undefined' || !token) return;
34
+ try {
35
+ window.sessionStorage.setItem(
36
+ CURRENT_USER_CACHE_KEY,
37
+ JSON.stringify({ t: token, u: user, exp: Date.now() + CURRENT_USER_TTL_MS })
38
+ );
39
+ } catch (e) { /* sessionStorage unavailable (private mode / quota) — skip caching */ }
40
+ };
41
+
42
+ // Clear the cached signed-in user. Call after impersonation / company / location
43
+ // switches, and it is also called automatically on logout and when no token exists.
44
+ export const invalidateCurrentUser = () => {
45
+ if (typeof window === 'undefined') return;
46
+ try { window.sessionStorage.removeItem(CURRENT_USER_CACHE_KEY); } catch (e) { /* ignore */ }
47
+ };
48
+
6
49
  const setupDefaultOptions = async (ctx = null) => {
7
50
  let defaultOptions = {};
8
51
  if (ctx == null) {
@@ -160,16 +203,25 @@ export const apiService = (ctx = null) => {
160
203
  return error.response;
161
204
  }
162
205
  },
163
- GetCurrentUser: async () => {
206
+ GetCurrentUser: async (forceRefresh = false) => {
164
207
  try {
165
208
  let accessToken = Cookies.get('access_token') || null;
166
209
 
167
- if (accessToken) {
168
- let defaultOptions = await setupDefaultOptions(null);
169
- const response = await instance.get('/UserManagement', defaultOptions);
170
- if (response != null && response.status == 200) {
171
- return response.data;
172
- }
210
+ if (!accessToken) {
211
+ invalidateCurrentUser();
212
+ return null;
213
+ }
214
+
215
+ if (!forceRefresh) {
216
+ const cached = readCurrentUserCache(accessToken);
217
+ if (cached) return cached;
218
+ }
219
+
220
+ let defaultOptions = await setupDefaultOptions(null);
221
+ const response = await instance.get('/UserManagement', defaultOptions);
222
+ if (response != null && response.status == 200) {
223
+ writeCurrentUserCache(accessToken, response.data);
224
+ return response.data;
173
225
  }
174
226
  } catch (exp) {
175
227
  // Token invalid or expired
@@ -120,6 +120,9 @@ export const authService = () => {
120
120
  Cookies.remove('refresh_token', { path: '/', domain: domainHost, secure: (typeof window !== "undefined" && window.location.protocol === "https:") });
121
121
  Cookies.remove('expires_in', { path: '/', domain: domainHost, secure: (typeof window !== "undefined" && window.location.protocol === "https:") });
122
122
 
123
+ // Drop the cached signed-in user so the next sign-in never reads a stale identity.
124
+ try { if (typeof window !== "undefined") window.sessionStorage.removeItem("authscape_current_user"); } catch (e) { /* ignore */ }
125
+
123
126
  if (redirectUri == null)
124
127
  {
125
128
  window.location.href = AuthUri + "/connect/logout?redirect=" + window.location.href;