@finueva/auth 0.5.0 → 0.7.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/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { _ as WorkspaceSelection, a as AuthPersistenceDriver, c as AuthUser, d as FinuevaAuthClient, f as InvitationAcceptance, g as SignInInput, h as SendVerificationEmailInput, i as AuthPersistence, l as DriveCredential, m as PersonalWorkspaceSelection, n as AuthClientOptions, o as AuthSession, p as OrganizationWorkspaceSelection, r as AuthOrganization, s as AuthState, t as AcceptInvitationInput, u as DriveCredentialInput } from "./types-HSdjQWMZ.js";
2
- import { n as AuthErrorCode, r as AuthErrorStatus, t as AuthError } from "./errors-Crd93Rah.js";
1
+ import { C as AuthErrorStatus, S as AuthErrorCode, _ as SocialIdTokenInput, a as AuthPersistenceDriver, b as WorkspaceSelection, c as AuthUser, d as FinuevaAuthClient, f as InvitationAcceptance, g as SignInInput, h as SendVerificationEmailInput, i as AuthPersistence, l as DriveCredential, m as PersonalWorkspaceSelection, n as AuthClientOptions, o as AuthSession, p as OrganizationWorkspaceSelection, r as AuthOrganization, s as AuthState, t as AcceptInvitationInput, u as DriveCredentialInput, v as SocialIdTokenProvider, x as AuthError, y as UpdateProfileInput } from "./types-B1SZbs6Z.js";
3
2
  //#region src/client.d.ts
4
3
  declare function createAuthClient(options: AuthClientOptions): FinuevaAuthClient;
5
4
  //#endregion
6
- export { type AcceptInvitationInput, type AuthClientOptions, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthPersistence, type AuthPersistenceDriver, type AuthSession, type AuthState, type AuthUser, type DriveCredential, type DriveCredentialInput, type FinuevaAuthClient, type InvitationAcceptance, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type SendVerificationEmailInput, type SignInInput, type WorkspaceSelection, createAuthClient };
5
+ export { type AcceptInvitationInput, type AuthClientOptions, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthPersistence, type AuthPersistenceDriver, type AuthSession, type AuthState, type AuthUser, type DriveCredential, type DriveCredentialInput, type FinuevaAuthClient, type InvitationAcceptance, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type SendVerificationEmailInput, type SignInInput, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateProfileInput, type WorkspaceSelection, createAuthClient };
package/dist/index.js CHANGED
@@ -1635,7 +1635,7 @@ function createAuthClient$1(options) {
1635
1635
  }, $fetch, pluginPathMethods, pluginsAtoms, atomListeners);
1636
1636
  }
1637
1637
  //#endregion
1638
- //#region ../../node_modules/.pnpm/@chiballc+utils@2.2.22_magic-string@0.30.21_magicast@0.5.3_rolldown@1.2.0_vite@7.3.6_@t_7ea103523467af2ecb75df55c94dbb80/node_modules/@chiballc/utils/dist/index.mjs
1638
+ //#region ../../node_modules/.pnpm/@chiballc+utils@2.5.0_@hono+zod-openapi@1.5.1_hono@4.13.0_zod@4.4.3__hono@4.13.0_magic-_deb761a9672ce7cf69932b564bd2142d/node_modules/@chiballc/utils/dist/index.mjs
1639
1639
  function isPromise(arg) {
1640
1640
  return arg && typeof arg.then === "function";
1641
1641
  }
@@ -1879,6 +1879,9 @@ function stringifyParsedURL(parsed) {
1879
1879
  //#region src/errors.ts
1880
1880
  const messages = {
1881
1881
  invalid_credentials: "The credentials are invalid.",
1882
+ invalid_auth_input: "The authentication input is invalid.",
1883
+ account_conflict: "The account is not available.",
1884
+ password_reset_invalid: "The password reset request is invalid.",
1882
1885
  authentication_required: "Authentication is required.",
1883
1886
  request_origin_denied: "The request origin is not allowed.",
1884
1887
  invalid_invitation: "The invitation is invalid.",
@@ -1888,6 +1891,7 @@ const messages = {
1888
1891
  invalid_workspace_selection: "The workspace selection is invalid.",
1889
1892
  organization_context_too_large: "The organization context is too large.",
1890
1893
  credential_rate_limited: "Credential requests are temporarily limited.",
1894
+ auth_request_rate_limited: "Authentication requests are temporarily limited.",
1891
1895
  state_conflict: "The authentication state conflicts with the request.",
1892
1896
  access_denied: "Access is denied.",
1893
1897
  identity_authority_unavailable: "Authentication is temporarily unavailable."
@@ -2169,8 +2173,72 @@ function createFetch(globalOptions = {}) {
2169
2173
  }
2170
2174
  //#endregion
2171
2175
  //#region src/shared.ts
2176
+ const DRIVE_CREDENTIAL_LIFETIME_SECONDS = 840;
2172
2177
  const id = /^[A-Za-z0-9_-]{1,128}$/;
2173
2178
  const email = /^[^\s@]+@[^\s@]+$/;
2179
+ const passwordResetToken = /^[A-Za-z0-9_-]{1,512}$/;
2180
+ const MAX_SOCIAL_ID_TOKEN_BYTES = 16 * 1024;
2181
+ function normalizeEmail(value) {
2182
+ if (typeof value !== "string") throw new TypeError("email is invalid");
2183
+ const normalized = value.trim().toLowerCase();
2184
+ if (normalized.length === 0 || normalized.length > 320 || !email.test(normalized)) throw new TypeError("email is invalid");
2185
+ return normalized;
2186
+ }
2187
+ function normalizePassword(value, name = "password", minLength = 8) {
2188
+ if (typeof value !== "string" || value.length < minLength || value.length > 128) throw new TypeError(`${name} is invalid`);
2189
+ return value;
2190
+ }
2191
+ function normalizeSocialIdTokenInput(value) {
2192
+ if (!isObject(value)) throw new TypeError("social ID token input is invalid");
2193
+ const keys = Object.keys(value);
2194
+ if (keys.length !== 3 && keys.length !== 4 || keys.some((key) => key !== "provider" && key !== "idToken" && key !== "nonce" && key !== "signUp") || value.provider !== "google" && value.provider !== "apple" || typeof value.signUp !== "boolean" || typeof value.idToken !== "string" || value.idToken.length < 1 || new TextEncoder().encode(value.idToken).byteLength > MAX_SOCIAL_ID_TOKEN_BYTES || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value.idToken) || value.nonce !== void 0 && (typeof value.nonce !== "string" || value.nonce.length < 1 || value.nonce.length > 256 || !/^[\x20-\x7e]+$/.test(value.nonce))) throw new TypeError("social ID token input is invalid");
2195
+ return {
2196
+ provider: value.provider,
2197
+ idToken: value.idToken,
2198
+ ...value.nonce === void 0 ? {} : { nonce: value.nonce },
2199
+ signUp: value.signUp
2200
+ };
2201
+ }
2202
+ function socialIdTokenRequest(value) {
2203
+ return {
2204
+ provider: value.provider,
2205
+ idToken: {
2206
+ token: value.idToken,
2207
+ ...value.nonce === void 0 ? {} : { nonce: value.nonce }
2208
+ },
2209
+ requestSignUp: value.signUp
2210
+ };
2211
+ }
2212
+ function normalizeName(value) {
2213
+ if (typeof value !== "string") throw new TypeError("name is invalid");
2214
+ const normalized = value.trim();
2215
+ if (normalized.length === 0 || normalized.length > 256) throw new TypeError("name is invalid");
2216
+ return normalized;
2217
+ }
2218
+ function normalizeProfileInput(value) {
2219
+ if (!isObject(value)) throw new TypeError("profile input is invalid");
2220
+ const keys = Object.keys(value);
2221
+ if (keys.length === 0 || keys.length > 2 || keys.some((key) => key !== "name" && key !== "image") || value.name === void 0 && value.image === void 0) throw new TypeError("profile input is invalid");
2222
+ const result = {};
2223
+ if (value.name !== void 0) result.name = normalizeName(value.name);
2224
+ if (value.image !== void 0) result.image = value.image === null ? null : normalizeProfileImage(value.image);
2225
+ return result;
2226
+ }
2227
+ function normalizePasswordResetToken(value) {
2228
+ if (typeof value !== "string" || !passwordResetToken.test(value)) throw new TypeError("password reset token is invalid");
2229
+ return value;
2230
+ }
2231
+ function normalizeSameAppURL(value, appOrigin, name) {
2232
+ if (typeof value !== "string" || value.length > 2048 || /[^\x20-\x7e]/.test(value)) throw new TypeError(`${name} must belong to appOrigin`);
2233
+ let url;
2234
+ try {
2235
+ url = new URL(value);
2236
+ } catch {
2237
+ throw new TypeError(`${name} must belong to appOrigin`);
2238
+ }
2239
+ if (url.origin !== appOrigin || url.username || url.password || url.toString() !== value) throw new TypeError(`${name} must belong to appOrigin`);
2240
+ return value;
2241
+ }
2174
2242
  function normalizeOrigin(value, name) {
2175
2243
  if (value.length === 0 || value.length > 2048 || /[^\x20-\x7e]/.test(value)) throw new TypeError(`${name} must be an ASCII HTTP origin`);
2176
2244
  let url;
@@ -2207,7 +2275,7 @@ function normalizeSession(value) {
2207
2275
  };
2208
2276
  }
2209
2277
  function normalizeDriveCredential(value, now) {
2210
- if (!isObject(value) || value.tokenType !== "Bearer" || value.expiresIn !== 900 || typeof value.token !== "string" || value.token.length === 0 || value.token.length + 7 > 16 * 1024) throw authorityUnavailable();
2278
+ if (!isObject(value) || value.tokenType !== "Bearer" || value.expiresIn !== DRIVE_CREDENTIAL_LIFETIME_SECONDS || typeof value.token !== "string" || value.token.length === 0 || new TextEncoder().encode(`Bearer ${value.token}`).byteLength > 16 * 1024) throw authorityUnavailable();
2211
2279
  return {
2212
2280
  tokenType: "Bearer",
2213
2281
  token: value.token,
@@ -2234,19 +2302,12 @@ function normalizeInvitationInput(value) {
2234
2302
  function normalizeVerificationEmailInput(value, appOrigin) {
2235
2303
  if (!isObject(value)) throw new TypeError("verification email input is invalid");
2236
2304
  const keys = Object.keys(value);
2237
- if (keys.length !== 1 && keys.length !== 2 || !keys.includes("email") || keys.length === 2 && !keys.includes("callbackURL") || typeof value.email !== "string" || value.email.length > 320 || !email.test(value.email)) throw new TypeError("verification email input is invalid");
2238
- if (value.callbackURL === void 0) return { email: value.email };
2239
- if (typeof value.callbackURL !== "string" || value.callbackURL.length > 2048 || /[^\x20-\x7e]/.test(value.callbackURL)) throw new TypeError("callbackURL must belong to appOrigin");
2240
- let callback;
2241
- try {
2242
- callback = new URL(value.callbackURL);
2243
- } catch {
2244
- throw new TypeError("callbackURL must belong to appOrigin");
2245
- }
2246
- if (callback.origin !== appOrigin || callback.username || callback.password || callback.toString() !== value.callbackURL) throw new TypeError("callbackURL must belong to appOrigin");
2305
+ if (keys.length !== 1 && keys.length !== 2 || !keys.includes("email") || keys.length === 2 && !keys.includes("callbackURL") || typeof value.email !== "string") throw new TypeError("verification email input is invalid");
2306
+ const normalizedEmail = normalizeEmail(value.email);
2307
+ if (value.callbackURL === void 0) return { email: normalizedEmail };
2247
2308
  return {
2248
- email: value.email,
2249
- callbackURL: value.callbackURL
2309
+ email: normalizedEmail,
2310
+ callbackURL: normalizeSameAppURL(value.callbackURL, appOrigin, "callbackURL")
2250
2311
  };
2251
2312
  }
2252
2313
  function normalizeVerificationEmailResponse(value) {
@@ -2270,6 +2331,26 @@ function fromAuthorityError(value, fallbackStatus, retryAfter) {
2270
2331
  const mapped = mapError(isObject(value) && typeof value.code === "string" ? value.code : void 0, fallbackStatus);
2271
2332
  return new AuthError(mapped.code, mapped.status, parseRetryAfter(retryAfter));
2272
2333
  }
2334
+ function fromAccountAuthorityError(value, status, retryAfter) {
2335
+ const code = isObject(value) && typeof value.code === "string" ? value.code : void 0;
2336
+ if (status === 403 && (code === "INVALID_ORIGIN" || code === "MISSING_OR_NULL_ORIGIN")) return new AuthError("request_origin_denied", 403);
2337
+ if (code === "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL") return new AuthError("account_conflict", 409);
2338
+ if (code === "INVALID_TOKEN") return new AuthError("password_reset_invalid", 422);
2339
+ if (code === "INVALID_PASSWORD" || code === "INVALID_EMAIL_OR_PASSWORD") return new AuthError("invalid_credentials", 401);
2340
+ if (code === "INVALID_EMAIL" || code === "PASSWORD_TOO_SHORT" || code === "PASSWORD_TOO_LONG" || code === "BODY_MUST_BE_AN_OBJECT" || code === "EMAIL_CAN_NOT_BE_UPDATED") return new AuthError("invalid_auth_input", 422);
2341
+ if (status === 401) return new AuthError("authentication_required", 401);
2342
+ if (status === 403) return new AuthError("access_denied", 403);
2343
+ if (status === 409) return new AuthError("state_conflict", 409);
2344
+ if (status === 429) return new AuthError("auth_request_rate_limited", 429, parseRetryAfter(retryAfter));
2345
+ if (status === 400 || status === 422) return new AuthError("invalid_auth_input", 422);
2346
+ return authorityUnavailable();
2347
+ }
2348
+ function fromSocialIdTokenAuthorityError(value, status, retryAfter) {
2349
+ const code = isObject(value) && typeof value.code === "string" ? value.code : void 0;
2350
+ if (code === "INVALID_TOKEN" || code === "FAILED_TO_GET_USER_INFO" || code === "USER_EMAIL_NOT_FOUND") return new AuthError("invalid_credentials", 401);
2351
+ if (code === "OAUTH_LINK_ERROR") return new AuthError("account_conflict", 409);
2352
+ return fromAccountAuthorityError(value, status, retryAfter);
2353
+ }
2273
2354
  function authorityUnavailable() {
2274
2355
  return new AuthError("identity_authority_unavailable", 503);
2275
2356
  }
@@ -2349,6 +2430,18 @@ function toDate(value) {
2349
2430
  const result = value instanceof Date ? value : typeof value === "string" ? new Date(value) : void 0;
2350
2431
  return result && Number.isFinite(result.getTime()) ? result : void 0;
2351
2432
  }
2433
+ function normalizeProfileImage(value) {
2434
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048 || /[^\x20-\x7e]/.test(value)) throw new TypeError("profile image is invalid");
2435
+ let url;
2436
+ try {
2437
+ url = new URL(value);
2438
+ } catch {
2439
+ throw new TypeError("profile image is invalid");
2440
+ }
2441
+ const local = url.hostname === "localhost" || url.hostname.endsWith(".localhost") || url.hostname === "127.0.0.1";
2442
+ if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.toString() !== value) throw new TypeError("profile image is invalid");
2443
+ return value;
2444
+ }
2352
2445
  function isId(value) {
2353
2446
  return typeof value === "string" && id.test(value);
2354
2447
  }
@@ -2368,7 +2461,7 @@ async function requestJsonResponse(url, options) {
2368
2461
  method: options.method,
2369
2462
  headers: options.headers,
2370
2463
  body: options.body,
2371
- credentials: "include",
2464
+ credentials: options.credentials ?? "include",
2372
2465
  retry: 0,
2373
2466
  timeout: options.timeoutMs,
2374
2467
  redirect: "manual",
@@ -3180,12 +3273,14 @@ function createAuthClient(options) {
3180
3273
  basePath: "/api/v1/auth",
3181
3274
  fetchOptions: {
3182
3275
  customFetchImpl: fetchImpl,
3276
+ credentials: "include",
3183
3277
  retry: 0,
3184
3278
  timeout: timeoutMs
3185
3279
  }
3186
3280
  });
3187
3281
  const persistence = createSessionPersistence(options.persistence, authOrigin, now, timeoutMs);
3188
3282
  const authStateListeners = new Set();
3283
+ const sessionListeners = new Set();
3189
3284
  let authState = { status: "loading" };
3190
3285
  let authStateStarted = false;
3191
3286
  let credential;
@@ -3193,15 +3288,29 @@ function createAuthClient(options) {
3193
3288
  let selectedWorkspaceKey;
3194
3289
  let credentialGeneration = 0;
3195
3290
  let authorityGeneration = 0;
3196
- let locallySignedOut = false;
3197
- let activeSignInGeneration;
3291
+ let sessionReadEpoch = 0;
3292
+ let activeAccountMutationGeneration;
3198
3293
  let invitationAbort;
3199
3294
  let invitationRequest;
3200
3295
  let signOutRequest;
3201
3296
  let sessionId;
3297
+ let sessionUserId;
3298
+ let sessionSignalSubscribed = false;
3299
+ let betterAuthRefetching = false;
3300
+ let suppressBetterAuthSettlement = false;
3301
+ let signalRefreshPending = false;
3302
+ let signalRefreshScheduled = false;
3303
+ let signalRefreshRequest;
3202
3304
  function setAuthState(state) {
3203
3305
  authState = state;
3204
- for (const listener of authStateListeners) listener(state);
3306
+ for (const listener of authStateListeners) try {
3307
+ listener(state);
3308
+ } catch {}
3309
+ }
3310
+ function emitSession(session) {
3311
+ for (const listener of sessionListeners) try {
3312
+ listener(session);
3313
+ } catch {}
3205
3314
  }
3206
3315
  function unavailable() {
3207
3316
  setAuthState({
@@ -3228,26 +3337,16 @@ function createAuthClient(options) {
3228
3337
  });
3229
3338
  persistence.remove();
3230
3339
  }
3340
+ emitSession(normalized);
3231
3341
  return normalized;
3232
3342
  }
3233
3343
  function startAuthState() {
3234
3344
  if (authStateStarted) return;
3235
3345
  authStateStarted = true;
3236
- betterAuth.useSession.subscribe((state) => {
3237
- if (state.isPending) return;
3238
- if (state.error) {
3239
- unavailable();
3240
- return;
3241
- }
3242
- try {
3243
- const session = normalizeSession(state.data);
3244
- if (session && (locallySignedOut || signOutRequest)) return;
3245
- acceptAuthoritySession(session);
3246
- } catch {
3247
- unavailable();
3248
- }
3249
- });
3346
+ subscribeToSessionSignals();
3347
+ const hydrationGeneration = authorityGeneration;
3250
3348
  persistence.hydrate((session) => {
3349
+ if (hydrationGeneration !== authorityGeneration || activeAccountMutationGeneration !== void 0) return;
3251
3350
  if (authState.status === "loading") setAuthState({
3252
3351
  status: "cached",
3253
3352
  session,
@@ -3259,7 +3358,45 @@ function createAuthClient(options) {
3259
3358
  authoritative: false
3260
3359
  });
3261
3360
  });
3262
- if (typeof window === "undefined" || typeof document === "undefined") getSession().catch(() => void 0);
3361
+ getSession().catch(() => void 0);
3362
+ }
3363
+ function subscribeToSessionSignals() {
3364
+ if (sessionSignalSubscribed) return;
3365
+ sessionSignalSubscribed = true;
3366
+ let initialized = false;
3367
+ betterAuth.useSession.subscribe((state) => {
3368
+ const refetching = state.isRefetching === true;
3369
+ if (!initialized) {
3370
+ initialized = true;
3371
+ betterAuthRefetching = refetching;
3372
+ return;
3373
+ }
3374
+ const settled = betterAuthRefetching && !refetching;
3375
+ betterAuthRefetching = refetching;
3376
+ if (refetching && signOutRequest) suppressBetterAuthSettlement = true;
3377
+ if (!settled) return;
3378
+ if (suppressBetterAuthSettlement) {
3379
+ suppressBetterAuthSettlement = false;
3380
+ return;
3381
+ }
3382
+ queueSignalRefresh();
3383
+ });
3384
+ }
3385
+ function queueSignalRefresh() {
3386
+ signalRefreshPending = true;
3387
+ if (signalRefreshScheduled || signalRefreshRequest || signOutRequest || activeAccountMutationGeneration !== void 0) return;
3388
+ signalRefreshScheduled = true;
3389
+ queueMicrotask(runSignalRefresh);
3390
+ }
3391
+ function runSignalRefresh() {
3392
+ signalRefreshScheduled = false;
3393
+ if (!signalRefreshPending || signalRefreshRequest || signOutRequest || activeAccountMutationGeneration !== void 0) return;
3394
+ signalRefreshPending = false;
3395
+ const request = refreshAuthoritySession(authorityGeneration).then(() => void 0, () => void 0).finally(() => {
3396
+ if (signalRefreshRequest === request) signalRefreshRequest = void 0;
3397
+ if (signalRefreshPending) queueSignalRefresh();
3398
+ });
3399
+ signalRefreshRequest = request;
3263
3400
  }
3264
3401
  function clearCredential() {
3265
3402
  credential = void 0;
@@ -3269,44 +3406,52 @@ function createAuthClient(options) {
3269
3406
  }
3270
3407
  function updateSession(session) {
3271
3408
  const unboundCredential = !sessionId && (credential || credentialRequests.size > 0);
3272
- if (!session || unboundCredential || sessionId && session.id !== sessionId) clearCredential();
3409
+ if (!session || unboundCredential || sessionId && (session.id !== sessionId || session.user.id !== sessionUserId)) clearCredential();
3273
3410
  sessionId = session?.id;
3411
+ sessionUserId = session?.user.id;
3274
3412
  return session;
3275
3413
  }
3276
- async function getSession() {
3277
- if (signOutRequest) throw new AuthError("state_conflict", 409);
3278
- locallySignedOut = false;
3279
- const generation = authorityGeneration;
3280
- let result;
3281
- try {
3282
- result = await betterAuth.getSession();
3283
- } catch {
3284
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3285
- unavailable();
3286
- throw authorityUnavailable();
3287
- }
3288
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3289
- if (result.error) {
3290
- unavailable();
3291
- throw fromAuthorityError(result.error, result.error.status);
3292
- }
3414
+ async function refreshAuthoritySession(generation) {
3415
+ const readEpoch = ++sessionReadEpoch;
3293
3416
  try {
3294
- return acceptAuthoritySession(normalizeSession(result.data), generation);
3417
+ const response = await requestJsonResponse(joinURL(authOrigin, "api/v1/auth/get-session"), {
3418
+ fetch: fetchImpl,
3419
+ timeoutMs,
3420
+ method: "GET"
3421
+ });
3422
+ if (generation !== authorityGeneration || readEpoch !== sessionReadEpoch) throw new AuthError("state_conflict", 409);
3423
+ return acceptAuthoritySession(normalizeSession(response.value), generation);
3295
3424
  } catch (error) {
3296
- if (error instanceof AuthError) throw error;
3425
+ if (generation !== authorityGeneration || readEpoch !== sessionReadEpoch) throw new AuthError("state_conflict", 409);
3426
+ if (error instanceof AuthError && error.code === "state_conflict") throw error;
3297
3427
  unavailable();
3428
+ if (error instanceof AuthError) throw error;
3298
3429
  throw authorityUnavailable();
3299
3430
  }
3300
3431
  }
3432
+ function getSession() {
3433
+ if (signOutRequest || activeAccountMutationGeneration !== void 0) return Promise.reject(new AuthError("state_conflict", 409));
3434
+ return refreshAuthoritySession(authorityGeneration);
3435
+ }
3301
3436
  function signOut() {
3302
3437
  if (signOutRequest) return signOutRequest;
3438
+ if (activeAccountMutationGeneration !== void 0) return Promise.reject(new AuthError("state_conflict", 409));
3303
3439
  const pendingInvitation = invitationRequest;
3304
3440
  invitationAbort?.abort();
3305
3441
  const generation = ++authorityGeneration;
3306
- locallySignedOut = true;
3442
+ sessionReadEpoch += 1;
3307
3443
  clearCredential();
3308
3444
  sessionId = void 0;
3445
+ sessionUserId = void 0;
3446
+ if (betterAuthRefetching) suppressBetterAuthSettlement = true;
3447
+ signalRefreshPending = false;
3309
3448
  persistence.remove();
3449
+ setAuthState({
3450
+ status: "unavailable",
3451
+ session: null,
3452
+ authoritative: false
3453
+ });
3454
+ emitSession(null);
3310
3455
  const request = (async () => {
3311
3456
  await pendingInvitation?.catch(() => void 0);
3312
3457
  let result;
@@ -3314,13 +3459,11 @@ function createAuthClient(options) {
3314
3459
  result = await betterAuth.signOut();
3315
3460
  } catch {
3316
3461
  if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3317
- locallySignedOut = false;
3318
3462
  unavailable();
3319
3463
  throw authorityUnavailable();
3320
3464
  }
3321
3465
  if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3322
3466
  if (result.error) {
3323
- locallySignedOut = false;
3324
3467
  unavailable();
3325
3468
  throw fromAuthorityError(result.error, result.error.status);
3326
3469
  }
@@ -3329,12 +3472,68 @@ function createAuthClient(options) {
3329
3472
  session: null,
3330
3473
  authoritative: true
3331
3474
  });
3475
+ emitSession(null);
3332
3476
  })().finally(() => {
3333
- if (signOutRequest === request) signOutRequest = void 0;
3477
+ if (signOutRequest === request) {
3478
+ signOutRequest = void 0;
3479
+ signalRefreshPending = false;
3480
+ }
3334
3481
  });
3335
3482
  signOutRequest = request;
3336
3483
  return request;
3337
3484
  }
3485
+ async function runAccountMutation(operation) {
3486
+ if (signOutRequest || activeAccountMutationGeneration !== void 0) throw new AuthError("state_conflict", 409);
3487
+ const pendingInvitation = invitationRequest;
3488
+ invitationAbort?.abort();
3489
+ const generation = ++authorityGeneration;
3490
+ sessionReadEpoch += 1;
3491
+ activeAccountMutationGeneration = generation;
3492
+ clearCredential();
3493
+ sessionId = void 0;
3494
+ sessionUserId = void 0;
3495
+ persistence.remove();
3496
+ setAuthState({
3497
+ status: "unavailable",
3498
+ session: null,
3499
+ authoritative: false
3500
+ });
3501
+ emitSession(null);
3502
+ try {
3503
+ await pendingInvitation?.catch(() => void 0);
3504
+ try {
3505
+ const data = await operation();
3506
+ if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3507
+ return {
3508
+ data,
3509
+ session: await refreshAuthoritySession(generation)
3510
+ };
3511
+ } catch (error) {
3512
+ if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3513
+ if (error instanceof AuthError) {
3514
+ unavailable();
3515
+ throw error;
3516
+ }
3517
+ unavailable();
3518
+ throw authorityUnavailable();
3519
+ }
3520
+ } finally {
3521
+ if (activeAccountMutationGeneration === generation) {
3522
+ activeAccountMutationGeneration = void 0;
3523
+ if (signalRefreshPending) queueSignalRefresh();
3524
+ }
3525
+ }
3526
+ }
3527
+ async function accountRequest(path, body, mapError = fromAccountAuthorityError) {
3528
+ return (await requestJsonResponse(joinURL(authOrigin, "api/v1/auth", path), {
3529
+ fetch: fetchImpl,
3530
+ timeoutMs,
3531
+ method: "POST",
3532
+ headers: { "content-type": "application/json; charset=utf-8" },
3533
+ body,
3534
+ mapError
3535
+ })).value;
3536
+ }
3338
3537
  return {
3339
3538
  acceptInvitation(input) {
3340
3539
  let invitation;
@@ -3343,7 +3542,7 @@ function createAuthClient(options) {
3343
3542
  } catch (error) {
3344
3543
  return Promise.reject(error);
3345
3544
  }
3346
- if (signOutRequest || activeSignInGeneration !== void 0 || invitationRequest) return Promise.reject(new AuthError("state_conflict", 409));
3545
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) return Promise.reject(new AuthError("state_conflict", 409));
3347
3546
  const generation = authorityGeneration;
3348
3547
  const controller = new AbortController();
3349
3548
  clearCredential();
@@ -3376,7 +3575,7 @@ function createAuthClient(options) {
3376
3575
  },
3377
3576
  getSession,
3378
3577
  async listOrganizations() {
3379
- if (signOutRequest || activeSignInGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
3578
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
3380
3579
  const generation = authorityGeneration;
3381
3580
  try {
3382
3581
  const value = await requestJson(joinURL(authOrigin, "api/v1/auth/organization/list"), {
@@ -3392,37 +3591,60 @@ function createAuthClient(options) {
3392
3591
  }
3393
3592
  },
3394
3593
  async signIn(input) {
3395
- if (signOutRequest) throw new AuthError("state_conflict", 409);
3396
- const pendingInvitation = invitationRequest;
3397
- invitationAbort?.abort();
3398
- const generation = ++authorityGeneration;
3399
- activeSignInGeneration = generation;
3400
- try {
3401
- await pendingInvitation?.catch(() => void 0);
3402
- locallySignedOut = false;
3403
- clearCredential();
3404
- let result;
3405
- try {
3406
- result = await betterAuth.signIn.email({
3407
- email: input.email,
3408
- password: input.password
3409
- });
3410
- } catch {
3411
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3412
- unavailable();
3413
- throw authorityUnavailable();
3414
- }
3415
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3416
- if (result.error) {
3417
- unavailable();
3418
- throw fromAuthorityError(result.error, result.error.status);
3419
- }
3420
- const session = await getSession();
3421
- if (!session) throw new AuthError("authentication_required", 401);
3422
- return session;
3423
- } finally {
3424
- if (activeSignInGeneration === generation) activeSignInGeneration = void 0;
3425
- }
3594
+ const body = {
3595
+ email: normalizeEmail(input?.email),
3596
+ password: normalizePassword(input?.password)
3597
+ };
3598
+ const result = await runAccountMutation(() => accountRequest("sign-in/email", body));
3599
+ if (!result.session) throw new AuthError("authentication_required", 401);
3600
+ return result.session;
3601
+ },
3602
+ async signInWithSocialIdToken(input) {
3603
+ const body = socialIdTokenRequest(normalizeSocialIdTokenInput(input));
3604
+ const result = await runAccountMutation(() => accountRequest("sign-in/social", body, fromSocialIdTokenAuthorityError));
3605
+ if (!result.session) throw new AuthError("authentication_required", 401);
3606
+ return result.session;
3607
+ },
3608
+ async signUp(email, password, name) {
3609
+ const body = {
3610
+ email: normalizeEmail(email),
3611
+ password: normalizePassword(password, "password", 10),
3612
+ name: normalizeName(name)
3613
+ };
3614
+ const result = await runAccountMutation(() => accountRequest("sign-up/email", body));
3615
+ if (!result.session) throw new AuthError("authentication_required", 401);
3616
+ return result.session;
3617
+ },
3618
+ async updateProfile(input) {
3619
+ const body = normalizeProfileInput(input);
3620
+ const result = await runAccountMutation(() => accountRequest("update-user", body));
3621
+ if (!result.session) throw new AuthError("authentication_required", 401);
3622
+ return result.session;
3623
+ },
3624
+ async changePassword(currentPassword, newPassword, revokeOtherSessions = true) {
3625
+ const current = normalizePassword(currentPassword, "currentPassword");
3626
+ const next = normalizePassword(newPassword, "newPassword", 10);
3627
+ if (typeof revokeOtherSessions !== "boolean") throw new TypeError("revokeOtherSessions is invalid");
3628
+ const result = await runAccountMutation(() => accountRequest("change-password", {
3629
+ currentPassword: current,
3630
+ newPassword: next,
3631
+ revokeOtherSessions
3632
+ }));
3633
+ if (!result.session) throw new AuthError("authentication_required", 401);
3634
+ return result.session;
3635
+ },
3636
+ async requestPasswordReset(email, redirectTo) {
3637
+ normalizeVerificationEmailResponse(await accountRequest("request-password-reset", {
3638
+ email: normalizeEmail(email),
3639
+ redirectTo: normalizeSameAppURL(redirectTo, appOrigin, "redirectTo")
3640
+ }));
3641
+ },
3642
+ async resetPassword(token, newPassword) {
3643
+ const body = {
3644
+ token: normalizePasswordResetToken(token),
3645
+ newPassword: normalizePassword(newPassword, "newPassword", 10)
3646
+ };
3647
+ normalizeVerificationEmailResponse((await runAccountMutation(() => accountRequest("reset-password", body))).data);
3426
3648
  },
3427
3649
  async sendVerificationEmail(input) {
3428
3650
  const body = normalizeVerificationEmailInput(input, appOrigin);
@@ -3442,7 +3664,7 @@ function createAuthClient(options) {
3442
3664
  } catch (error) {
3443
3665
  return Promise.reject(error);
3444
3666
  }
3445
- if (invitationRequest) return Promise.reject(new AuthError("state_conflict", 409));
3667
+ if (invitationRequest || signOutRequest || activeAccountMutationGeneration !== void 0) return Promise.reject(new AuthError("state_conflict", 409));
3446
3668
  const workspaceKey = workspace.type === "personal" ? "personal" : `organization:${workspace.organizationId}`;
3447
3669
  if (selectedWorkspaceKey !== workspaceKey) {
3448
3670
  credential = void 0;
@@ -3479,21 +3701,20 @@ function createAuthClient(options) {
3479
3701
  },
3480
3702
  onAuthStateChange(listener) {
3481
3703
  authStateListeners.add(listener);
3482
- listener(authState);
3704
+ try {
3705
+ listener(authState);
3706
+ } catch {}
3483
3707
  startAuthState();
3484
3708
  return () => authStateListeners.delete(listener);
3485
3709
  },
3486
3710
  onSessionChange(listener) {
3487
- return betterAuth.useSession.subscribe((state) => {
3488
- let session;
3489
- try {
3490
- session = updateSession(normalizeSession(state.data));
3491
- } catch {
3492
- clearCredential();
3493
- session = null;
3494
- }
3495
- listener(session);
3496
- });
3711
+ sessionListeners.add(listener);
3712
+ try {
3713
+ if (authState.status === "authenticated") listener(authState.session);
3714
+ else if (authState.status === "unauthenticated") listener(null);
3715
+ } catch {}
3716
+ startAuthState();
3717
+ return () => sessionListeners.delete(listener);
3497
3718
  }
3498
3719
  };
3499
3720
  }
package/dist/server.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { _ as WorkspaceSelection, c as AuthUser, f as InvitationAcceptance, l as DriveCredential, m as PersonalWorkspaceSelection, o as AuthSession, p as OrganizationWorkspaceSelection, r as AuthOrganization, t as AcceptInvitationInput, u as DriveCredentialInput } from "./types-HSdjQWMZ.js";
2
- import { n as AuthErrorCode, r as AuthErrorStatus, t as AuthError } from "./errors-Crd93Rah.js";
1
+ import { C as AuthErrorStatus, S as AuthErrorCode, _ as SocialIdTokenInput, b as WorkspaceSelection, c as AuthUser, f as InvitationAcceptance, l as DriveCredential, m as PersonalWorkspaceSelection, o as AuthSession, p as OrganizationWorkspaceSelection, r as AuthOrganization, t as AcceptInvitationInput, u as DriveCredentialInput, v as SocialIdTokenProvider, x as AuthError } from "./types-B1SZbs6Z.js";
3
2
  //#region src/server.d.ts
4
3
  interface ServerAuthOptions {
5
4
  authOrigin: string;
@@ -15,13 +14,17 @@ interface ServerSignOutResult {
15
14
  interface ServerInvitationAcceptance extends InvitationAcceptance {
16
15
  applyCookies(headers: Headers): void;
17
16
  }
17
+ interface ServerSocialIdTokenSession extends AuthSession {
18
+ applyCookies(headers: Headers): void;
19
+ }
18
20
  interface FinuevaServerAuth {
19
21
  acceptInvitation(input: AcceptInvitationInput): Promise<ServerInvitationAcceptance>;
20
22
  getSession(): Promise<AuthSession | null>;
21
23
  listOrganizations(): Promise<AuthOrganization[]>;
24
+ signInWithSocialIdToken(input: SocialIdTokenInput): Promise<ServerSocialIdTokenSession>;
22
25
  getDriveCredential(input: DriveCredentialInput): Promise<DriveCredential>;
23
26
  signOut(): Promise<ServerSignOutResult>;
24
27
  }
25
28
  declare function createServerAuth(options: ServerAuthOptions): FinuevaServerAuth;
26
29
  //#endregion
27
- export { type AcceptInvitationInput, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthSession, type AuthUser, type DriveCredential, type DriveCredentialInput, FinuevaServerAuth, type InvitationAcceptance, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, ServerAuthOptions, ServerInvitationAcceptance, ServerSignOutResult, type WorkspaceSelection, createServerAuth };
30
+ export { type AcceptInvitationInput, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthSession, type AuthUser, type DriveCredential, type DriveCredentialInput, FinuevaServerAuth, type InvitationAcceptance, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, ServerAuthOptions, ServerInvitationAcceptance, ServerSignOutResult, ServerSocialIdTokenSession, type SocialIdTokenInput, type SocialIdTokenProvider, type WorkspaceSelection, createServerAuth };