@stackframe/stack 2.4.18 → 2.4.20

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.
@@ -4,7 +4,7 @@ import { ReadonlyJson } from '@stackframe/stack-shared/dist/utils/json';
4
4
  import { ProjectUpdateOptions, ApiKeySetCreateOptions } from '@stackframe/stack-shared/dist/interface/adminInterface';
5
5
  import { ServerUserUpdateJson, ServerTeamCustomizableJson, ServerPermissionDefinitionCustomizableJson, ServerPermissionDefinitionJson, EmailTemplateType } from '@stackframe/stack-shared/dist/interface/serverInterface';
6
6
  import { ListEmailTemplatesCrud, EmailTemplateCrud } from '@stackframe/stack-shared/dist/interface/crud/email-templates';
7
- import { Session } from '@stackframe/stack-shared/dist/sessions';
7
+ import { InternalSession } from '@stackframe/stack-shared/dist/sessions';
8
8
 
9
9
  type RequestLike = {
10
10
  headers: {
@@ -45,7 +45,7 @@ type StackServerAppConstructorOptions<HasTokenStore extends boolean, ProjectId e
45
45
  type StackAdminAppConstructorOptions<HasTokenStore extends boolean, ProjectId extends string> = ((StackServerAppConstructorOptions<HasTokenStore, ProjectId> & {
46
46
  superSecretAdminKey?: string;
47
47
  }) | (Omit<StackServerAppConstructorOptions<HasTokenStore, ProjectId>, "publishableClientKey" | "secretServerKey"> & {
48
- projectOwnerSession: Session;
48
+ projectOwnerSession: InternalSession;
49
49
  }));
50
50
  type StackClientAppJson<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppConstructorOptions<HasTokenStore, ProjectId> & {
51
51
  uniqueIdentifier: string;
@@ -54,11 +54,21 @@ declare const stackAppInternalsSymbol: unique symbol;
54
54
  type RedirectToOptions = {
55
55
  replace?: boolean;
56
56
  };
57
+ type Session = {
58
+ getTokens(): Promise<{
59
+ accessToken: string | null;
60
+ refreshToken: string | null;
61
+ }>;
62
+ };
63
+ /**
64
+ * Contains everything related to the current user session.
65
+ */
57
66
  type Auth<T, C> = {
58
- readonly session: Session;
59
- updateSelectedTeam(this: T, team: Team | null): Promise<void>;
60
- update(this: T, user: C): Promise<void>;
67
+ readonly _internalSession: InternalSession;
68
+ readonly currentSession: Session;
61
69
  signOut(this: T): Promise<void>;
70
+ update(this: T, user: C): Promise<void>;
71
+ updateSelectedTeam(this: T, team: Team | null): Promise<void>;
62
72
  sendVerificationEmail(this: T): Promise<KnownErrors["EmailAlreadyVerified"] | void>;
63
73
  updatePassword(this: T, options: {
64
74
  oldPassword: string;
@@ -252,6 +262,69 @@ type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId extends s
252
262
  verifyPasswordResetCode(code: string): Promise<KnownErrors["PasswordResetCodeError"] | void>;
253
263
  verifyEmail(code: string): Promise<KnownErrors["EmailVerificationError"] | void>;
254
264
  signInWithMagicLink(code: string): Promise<KnownErrors["MagicLinkError"] | void>;
265
+ /**
266
+ * With most browsers now disabling third-party cookies by default, the best way to send authenticated requests
267
+ * across different origins is to pass the tokens in a header.
268
+ *
269
+ * This function returns a header object that can be used with `fetch` or other HTTP request libraries to send
270
+ * authenticated requests.
271
+ *
272
+ * On the server, you can then pass in the `Request` object to the `tokenStore` option
273
+ * on your Stack app to fetch user details. Please note that CORS by default does not allow custom headers, so you
274
+ * must set the [`Access-Control-Allow-Headers` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers)
275
+ * to include `x-stack-auth` in the CORS preflight response.
276
+ *
277
+ * Example:
278
+ *
279
+ * ```ts
280
+ * // client
281
+ * const res = await fetch("https://api.example.com", {
282
+ * headers: {
283
+ * ...await stackApp.getCrossOriginHeaders()
284
+ * // you can also add your own headers here
285
+ * },
286
+ * });
287
+ *
288
+ * // server
289
+ * function handleRequest(req: Request) {
290
+ * const user = await stackServerApp.getUser({ tokenStore: req });
291
+ * return new Response("Welcome, " + user.displayName);
292
+ * }
293
+ * ```
294
+ */
295
+ getCrossOriginHeaders(): Promise<{
296
+ "x-stack-auth": string;
297
+ }>;
298
+ /**
299
+ * With most browsers now disabling third-party cookies by default, there need to be new ways to send authenticated
300
+ * requests across different origins. While `getCrossOriginHeaders` is the recommended way to do this, there
301
+ * are some cases where you might want to send the tokens differently, for example when you are using WebSockets
302
+ * or non-HTTP protocols.
303
+ *
304
+ * This function returns a token object that can be JSON-serialized and sent to the server in any way you like.
305
+ * There, you can use the `tokenStore` option on your Stack app to fetch user details.
306
+ *
307
+ * Example:
308
+ *
309
+ * ```ts
310
+ * // client
311
+ * const res = await rpcCall(rpcEndpoint, {
312
+ * data: {
313
+ * auth: await stackApp.getCrossOriginTokenObject(),
314
+ * },
315
+ * });
316
+ *
317
+ * // server
318
+ * function handleRequest(data) {
319
+ * const user = await stackServerApp.getUser({ tokenStore: data.auth });
320
+ * return new Response("Welcome, " + user.displayName);
321
+ * }
322
+ * ```
323
+ */
324
+ getCrossOriginTokenObject(): Promise<{
325
+ accessToken: string | null;
326
+ refreshToken: string | null;
327
+ }>;
255
328
  [stackAppInternalsSymbol]: {
256
329
  toClientJson(): StackClientAppJson<HasTokenStore, ProjectId>;
257
330
  setCurrentUser(userJsonPromise: Promise<UserJson | null>): void;
@@ -4,7 +4,7 @@ import { ReadonlyJson } from '@stackframe/stack-shared/dist/utils/json';
4
4
  import { ProjectUpdateOptions, ApiKeySetCreateOptions } from '@stackframe/stack-shared/dist/interface/adminInterface';
5
5
  import { ServerUserUpdateJson, ServerTeamCustomizableJson, ServerPermissionDefinitionCustomizableJson, ServerPermissionDefinitionJson, EmailTemplateType } from '@stackframe/stack-shared/dist/interface/serverInterface';
6
6
  import { ListEmailTemplatesCrud, EmailTemplateCrud } from '@stackframe/stack-shared/dist/interface/crud/email-templates';
7
- import { Session } from '@stackframe/stack-shared/dist/sessions';
7
+ import { InternalSession } from '@stackframe/stack-shared/dist/sessions';
8
8
 
9
9
  type RequestLike = {
10
10
  headers: {
@@ -45,7 +45,7 @@ type StackServerAppConstructorOptions<HasTokenStore extends boolean, ProjectId e
45
45
  type StackAdminAppConstructorOptions<HasTokenStore extends boolean, ProjectId extends string> = ((StackServerAppConstructorOptions<HasTokenStore, ProjectId> & {
46
46
  superSecretAdminKey?: string;
47
47
  }) | (Omit<StackServerAppConstructorOptions<HasTokenStore, ProjectId>, "publishableClientKey" | "secretServerKey"> & {
48
- projectOwnerSession: Session;
48
+ projectOwnerSession: InternalSession;
49
49
  }));
50
50
  type StackClientAppJson<HasTokenStore extends boolean, ProjectId extends string> = StackClientAppConstructorOptions<HasTokenStore, ProjectId> & {
51
51
  uniqueIdentifier: string;
@@ -54,11 +54,21 @@ declare const stackAppInternalsSymbol: unique symbol;
54
54
  type RedirectToOptions = {
55
55
  replace?: boolean;
56
56
  };
57
+ type Session = {
58
+ getTokens(): Promise<{
59
+ accessToken: string | null;
60
+ refreshToken: string | null;
61
+ }>;
62
+ };
63
+ /**
64
+ * Contains everything related to the current user session.
65
+ */
57
66
  type Auth<T, C> = {
58
- readonly session: Session;
59
- updateSelectedTeam(this: T, team: Team | null): Promise<void>;
60
- update(this: T, user: C): Promise<void>;
67
+ readonly _internalSession: InternalSession;
68
+ readonly currentSession: Session;
61
69
  signOut(this: T): Promise<void>;
70
+ update(this: T, user: C): Promise<void>;
71
+ updateSelectedTeam(this: T, team: Team | null): Promise<void>;
62
72
  sendVerificationEmail(this: T): Promise<KnownErrors["EmailAlreadyVerified"] | void>;
63
73
  updatePassword(this: T, options: {
64
74
  oldPassword: string;
@@ -252,6 +262,69 @@ type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId extends s
252
262
  verifyPasswordResetCode(code: string): Promise<KnownErrors["PasswordResetCodeError"] | void>;
253
263
  verifyEmail(code: string): Promise<KnownErrors["EmailVerificationError"] | void>;
254
264
  signInWithMagicLink(code: string): Promise<KnownErrors["MagicLinkError"] | void>;
265
+ /**
266
+ * With most browsers now disabling third-party cookies by default, the best way to send authenticated requests
267
+ * across different origins is to pass the tokens in a header.
268
+ *
269
+ * This function returns a header object that can be used with `fetch` or other HTTP request libraries to send
270
+ * authenticated requests.
271
+ *
272
+ * On the server, you can then pass in the `Request` object to the `tokenStore` option
273
+ * on your Stack app to fetch user details. Please note that CORS by default does not allow custom headers, so you
274
+ * must set the [`Access-Control-Allow-Headers` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers)
275
+ * to include `x-stack-auth` in the CORS preflight response.
276
+ *
277
+ * Example:
278
+ *
279
+ * ```ts
280
+ * // client
281
+ * const res = await fetch("https://api.example.com", {
282
+ * headers: {
283
+ * ...await stackApp.getCrossOriginHeaders()
284
+ * // you can also add your own headers here
285
+ * },
286
+ * });
287
+ *
288
+ * // server
289
+ * function handleRequest(req: Request) {
290
+ * const user = await stackServerApp.getUser({ tokenStore: req });
291
+ * return new Response("Welcome, " + user.displayName);
292
+ * }
293
+ * ```
294
+ */
295
+ getCrossOriginHeaders(): Promise<{
296
+ "x-stack-auth": string;
297
+ }>;
298
+ /**
299
+ * With most browsers now disabling third-party cookies by default, there need to be new ways to send authenticated
300
+ * requests across different origins. While `getCrossOriginHeaders` is the recommended way to do this, there
301
+ * are some cases where you might want to send the tokens differently, for example when you are using WebSockets
302
+ * or non-HTTP protocols.
303
+ *
304
+ * This function returns a token object that can be JSON-serialized and sent to the server in any way you like.
305
+ * There, you can use the `tokenStore` option on your Stack app to fetch user details.
306
+ *
307
+ * Example:
308
+ *
309
+ * ```ts
310
+ * // client
311
+ * const res = await rpcCall(rpcEndpoint, {
312
+ * data: {
313
+ * auth: await stackApp.getCrossOriginTokenObject(),
314
+ * },
315
+ * });
316
+ *
317
+ * // server
318
+ * function handleRequest(data) {
319
+ * const user = await stackServerApp.getUser({ tokenStore: data.auth });
320
+ * return new Response("Welcome, " + user.displayName);
321
+ * }
322
+ * ```
323
+ */
324
+ getCrossOriginTokenObject(): Promise<{
325
+ accessToken: string | null;
326
+ refreshToken: string | null;
327
+ }>;
255
328
  [stackAppInternalsSymbol]: {
256
329
  toClientJson(): StackClientAppJson<HasTokenStore, ProjectId>;
257
330
  setCurrentUser(userJsonPromise: Promise<UserJson | null>): void;
@@ -59,7 +59,7 @@ var cookie = __toESM(require("cookie"));
59
59
  var import_sessions = require("@stackframe/stack-shared/dist/sessions");
60
60
  var import_use_trigger = require("@stackframe/stack-shared/dist/hooks/use-trigger");
61
61
  var NextNavigation = (0, import_compile_time.scrambleDuringCompileTime)(NextNavigationUnscrambled);
62
- var clientVersion = "js @stackframe/stack@2.4.18";
62
+ var clientVersion = "js @stackframe/stack@2.4.20";
63
63
  function permissionDefinitionScopeToType(scope) {
64
64
  return { "any-team": "team", "specific-team": "team", "global": "global" }[scope.type];
65
65
  }
@@ -105,43 +105,6 @@ function createEmptyTokenStore() {
105
105
  accessToken: null
106
106
  });
107
107
  }
108
- var storedCookieTokenStore = null;
109
- var getCookieTokenStore = () => {
110
- if (!(0, import_env.isBrowserLike)()) {
111
- throw new Error("Cannot use cookie token store on the server!");
112
- }
113
- if (storedCookieTokenStore === null) {
114
- const getCurrentValue = () => ({
115
- refreshToken: (0, import_cookie.getCookie)("stack-refresh"),
116
- accessToken: (0, import_cookie.getCookie)("stack-access")
117
- });
118
- storedCookieTokenStore = new import_stores.Store(getCurrentValue());
119
- let hasSucceededInWriting = true;
120
- setInterval(() => {
121
- if (hasSucceededInWriting) {
122
- const currentValue = getCurrentValue();
123
- const oldValue = storedCookieTokenStore.get();
124
- if (JSON.stringify(currentValue) !== JSON.stringify(oldValue)) {
125
- storedCookieTokenStore.set(currentValue);
126
- }
127
- }
128
- }, 100);
129
- storedCookieTokenStore.onChange((value) => {
130
- try {
131
- (0, import_cookie.setOrDeleteCookie)("stack-refresh", value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
132
- (0, import_cookie.setOrDeleteCookie)("stack-access", value.accessToken, { maxAge: 60 * 60 * 24 });
133
- hasSucceededInWriting = true;
134
- } catch (e) {
135
- if (!(0, import_env.isBrowserLike)()) {
136
- hasSucceededInWriting = false;
137
- } else {
138
- throw e;
139
- }
140
- }
141
- });
142
- }
143
- return storedCookieTokenStore;
144
- };
145
108
  var loadingSentinel = Symbol("stackAppCacheLoadingSentinel");
146
109
  function useAsyncCache(cache, dependencies, caller) {
147
110
  (0, import_react2.suspendIfSsr)(caller);
@@ -249,24 +212,72 @@ var _StackClientAppImpl = class __StackClientAppImpl {
249
212
  }
250
213
  _memoryTokenStore = createEmptyTokenStore();
251
214
  _requestTokenStores = /* @__PURE__ */ new WeakMap();
215
+ _storedCookieTokenStore = null;
216
+ get _refreshTokenCookieName() {
217
+ return `stack-refresh-${this.projectId}`;
218
+ }
219
+ get _accessTokenCookieName() {
220
+ return `stack-access`;
221
+ }
222
+ _getCookieTokenStore() {
223
+ if (!(0, import_env.isBrowserLike)()) {
224
+ throw new Error("Cannot use cookie token store on the server!");
225
+ }
226
+ if (this._storedCookieTokenStore === null) {
227
+ const getCurrentValue = (old) => ({
228
+ refreshToken: (0, import_cookie.getCookie)(this._refreshTokenCookieName) ?? (0, import_cookie.getCookie)("stack-refresh"),
229
+ // keep old cookie name for backwards-compatibility
230
+ // if there is an access token in memory already, don't update the access token based on cookies (access token
231
+ // cookies may be set by another project on the same domain)
232
+ // see the comment in _accessTokenCookieName for more information
233
+ accessToken: old === null ? (0, import_cookie.getCookie)(this._accessTokenCookieName) : old.accessToken
234
+ });
235
+ this._storedCookieTokenStore = new import_stores.Store(getCurrentValue(null));
236
+ let hasSucceededInWriting = true;
237
+ setInterval(() => {
238
+ if (hasSucceededInWriting) {
239
+ const oldValue = this._storedCookieTokenStore.get();
240
+ const currentValue = getCurrentValue(oldValue);
241
+ if (!(0, import_objects.deepPlainEquals)(currentValue, oldValue)) {
242
+ this._storedCookieTokenStore.set(currentValue);
243
+ }
244
+ }
245
+ }, 100);
246
+ this._storedCookieTokenStore.onChange((value) => {
247
+ try {
248
+ (0, import_cookie.setOrDeleteCookie)(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
249
+ (0, import_cookie.setOrDeleteCookie)(this._accessTokenCookieName, value.accessToken, { maxAge: 60 * 60 * 24 });
250
+ hasSucceededInWriting = true;
251
+ } catch (e) {
252
+ if (!(0, import_env.isBrowserLike)()) {
253
+ hasSucceededInWriting = false;
254
+ } else {
255
+ throw e;
256
+ }
257
+ }
258
+ });
259
+ }
260
+ return this._storedCookieTokenStore;
261
+ }
252
262
  _getOrCreateTokenStore(overrideTokenStoreInit) {
253
263
  const tokenStoreInit = overrideTokenStoreInit === void 0 ? this._tokenStoreInit : overrideTokenStoreInit;
254
264
  switch (tokenStoreInit) {
255
265
  case "cookie": {
256
- return getCookieTokenStore();
266
+ return this._getCookieTokenStore();
257
267
  }
258
268
  case "nextjs-cookie": {
259
269
  if ((0, import_env.isBrowserLike)()) {
260
- return getCookieTokenStore();
270
+ return this._getCookieTokenStore();
261
271
  } else {
262
272
  const store = new import_stores.Store({
263
- refreshToken: (0, import_cookie.getCookie)("stack-refresh"),
264
- accessToken: (0, import_cookie.getCookie)("stack-access")
273
+ refreshToken: (0, import_cookie.getCookie)(this._refreshTokenCookieName) ?? (0, import_cookie.getCookie)("stack-refresh"),
274
+ // keep old cookie name for backwards-compatibility
275
+ accessToken: (0, import_cookie.getCookie)(this._accessTokenCookieName)
265
276
  });
266
277
  store.onChange((value) => {
267
278
  try {
268
- (0, import_cookie.setOrDeleteCookie)("stack-refresh", value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
269
- (0, import_cookie.setOrDeleteCookie)("stack-access", value.accessToken, { maxAge: 60 * 60 * 24 });
279
+ (0, import_cookie.setOrDeleteCookie)(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
280
+ (0, import_cookie.setOrDeleteCookie)(this._accessTokenCookieName, value.accessToken, { maxAge: 60 * 60 * 24 });
270
281
  } catch (e) {
271
282
  }
272
283
  });
@@ -276,21 +287,43 @@ var _StackClientAppImpl = class __StackClientAppImpl {
276
287
  case "memory": {
277
288
  return this._memoryTokenStore;
278
289
  }
279
- case null: {
280
- return createEmptyTokenStore();
281
- }
282
290
  default: {
283
- if (tokenStoreInit !== null && typeof tokenStoreInit === "object" && "headers" in tokenStoreInit) {
291
+ if (tokenStoreInit === null) {
292
+ return createEmptyTokenStore();
293
+ } else if (typeof tokenStoreInit === "object" && "headers" in tokenStoreInit) {
284
294
  if (this._requestTokenStores.has(tokenStoreInit))
285
295
  return this._requestTokenStores.get(tokenStoreInit);
296
+ const stackAuthHeader = tokenStoreInit.headers.get("x-stack-auth");
297
+ if (stackAuthHeader) {
298
+ let parsed2;
299
+ try {
300
+ parsed2 = JSON.parse(stackAuthHeader);
301
+ if (typeof parsed2 !== "object")
302
+ throw new Error("x-stack-auth header must be a JSON object");
303
+ if (parsed2 === null)
304
+ throw new Error("x-stack-auth header must not be null");
305
+ } catch (e) {
306
+ throw new Error(`Invalid x-stack-auth header: ${stackAuthHeader}`, { cause: e });
307
+ }
308
+ return this._getOrCreateTokenStore({
309
+ accessToken: parsed2.accessToken ?? null,
310
+ refreshToken: parsed2.refreshToken ?? null
311
+ });
312
+ }
286
313
  const cookieHeader = tokenStoreInit.headers.get("cookie");
287
314
  const parsed = cookie.parse(cookieHeader || "");
288
315
  const res = new import_stores.Store({
289
- refreshToken: parsed["stack-refresh"] || null,
290
- accessToken: parsed["stack-access"] || null
316
+ refreshToken: parsed[this._refreshTokenCookieName] || parsed["stack-refresh"] || null,
317
+ // keep old cookie name for backwards-compatibility
318
+ accessToken: parsed[this._accessTokenCookieName] || null
291
319
  });
292
320
  this._requestTokenStores.set(tokenStoreInit, res);
293
321
  return res;
322
+ } else if ("accessToken" in tokenStoreInit || "refreshToken" in tokenStoreInit) {
323
+ return new import_stores.Store({
324
+ refreshToken: tokenStoreInit.refreshToken,
325
+ accessToken: tokenStoreInit.accessToken
326
+ });
294
327
  }
295
328
  throw new Error(`Invalid token store ${tokenStoreInit}`);
296
329
  }
@@ -307,7 +340,7 @@ var _StackClientAppImpl = class __StackClientAppImpl {
307
340
  _sessionsByTokenStoreAndSessionKey = /* @__PURE__ */ new WeakMap();
308
341
  _getSessionFromTokenStore(tokenStore) {
309
342
  const tokenObj = tokenStore.get();
310
- const sessionKey = import_sessions.Session.calculateSessionKey(tokenObj);
343
+ const sessionKey = import_sessions.InternalSession.calculateSessionKey(tokenObj);
311
344
  const existing = sessionKey ? this._sessionsByTokenStoreAndSessionKey.get(tokenStore)?.get(sessionKey) : null;
312
345
  if (existing)
313
346
  return existing;
@@ -481,7 +514,16 @@ var _StackClientAppImpl = class __StackClientAppImpl {
481
514
  const app = this;
482
515
  const currentUser = {
483
516
  ...this._userFromJson(json),
484
- session,
517
+ _internalSession: session,
518
+ currentSession: {
519
+ async getTokens() {
520
+ const tokens = await session.getPotentiallyExpiredTokens();
521
+ return {
522
+ accessToken: tokens?.accessToken.token ?? null,
523
+ refreshToken: tokens?.refreshToken?.token ?? null
524
+ };
525
+ }
526
+ },
485
527
  async updateSelectedTeam(team) {
486
528
  await app._updateUser({ selectedTeamId: team?.id ?? null }, session);
487
529
  },
@@ -568,6 +610,18 @@ var _StackClientAppImpl = class __StackClientAppImpl {
568
610
  get urls() {
569
611
  return getUrls(this._urlOptions);
570
612
  }
613
+ async getCrossOriginHeaders() {
614
+ return {
615
+ "x-stack-auth": JSON.stringify(await this.getCrossOriginTokenObject())
616
+ };
617
+ }
618
+ async getCrossOriginTokenObject() {
619
+ const user = await this.getUser();
620
+ if (!user)
621
+ return { accessToken: null, refreshToken: null };
622
+ const tokens = await user.currentSession.getTokens();
623
+ return tokens;
624
+ }
571
625
  async _redirectTo(handlerName, options) {
572
626
  const url = this.urls[handlerName];
573
627
  if (!url) {
@@ -1019,7 +1073,16 @@ var _StackServerAppImpl = class extends _StackClientAppImpl {
1019
1073
  const nonCurrentServerUser = this._serverUserFromJson(json);
1020
1074
  const currentUser = {
1021
1075
  ...nonCurrentServerUser,
1022
- session,
1076
+ _internalSession: session,
1077
+ currentSession: {
1078
+ async getTokens() {
1079
+ const tokens = await session.getPotentiallyExpiredTokens();
1080
+ return {
1081
+ accessToken: tokens?.accessToken.token ?? null,
1082
+ refreshToken: tokens?.refreshToken?.token ?? null
1083
+ };
1084
+ }
1085
+ },
1023
1086
  async delete() {
1024
1087
  const res = await nonCurrentServerUser.delete();
1025
1088
  await app._refreshUser(session);