@finueva/auth 0.6.0 → 0.8.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-DDopQNZR.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
@@ -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."
@@ -2172,6 +2176,69 @@ function createFetch(globalOptions = {}) {
2172
2176
  const DRIVE_CREDENTIAL_LIFETIME_SECONDS = 840;
2173
2177
  const id = /^[A-Za-z0-9_-]{1,128}$/;
2174
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
+ }
2175
2242
  function normalizeOrigin(value, name) {
2176
2243
  if (value.length === 0 || value.length > 2048 || /[^\x20-\x7e]/.test(value)) throw new TypeError(`${name} must be an ASCII HTTP origin`);
2177
2244
  let url;
@@ -2208,7 +2275,7 @@ function normalizeSession(value) {
2208
2275
  };
2209
2276
  }
2210
2277
  function normalizeDriveCredential(value, now) {
2211
- if (!isObject(value) || value.tokenType !== "Bearer" || value.expiresIn !== DRIVE_CREDENTIAL_LIFETIME_SECONDS || 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();
2212
2279
  return {
2213
2280
  tokenType: "Bearer",
2214
2281
  token: value.token,
@@ -2235,19 +2302,12 @@ function normalizeInvitationInput(value) {
2235
2302
  function normalizeVerificationEmailInput(value, appOrigin) {
2236
2303
  if (!isObject(value)) throw new TypeError("verification email input is invalid");
2237
2304
  const keys = Object.keys(value);
2238
- 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");
2239
- if (value.callbackURL === void 0) return { email: value.email };
2240
- if (typeof value.callbackURL !== "string" || value.callbackURL.length > 2048 || /[^\x20-\x7e]/.test(value.callbackURL)) throw new TypeError("callbackURL must belong to appOrigin");
2241
- let callback;
2242
- try {
2243
- callback = new URL(value.callbackURL);
2244
- } catch {
2245
- throw new TypeError("callbackURL must belong to appOrigin");
2246
- }
2247
- 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 };
2248
2308
  return {
2249
- email: value.email,
2250
- callbackURL: value.callbackURL
2309
+ email: normalizedEmail,
2310
+ callbackURL: normalizeSameAppURL(value.callbackURL, appOrigin, "callbackURL")
2251
2311
  };
2252
2312
  }
2253
2313
  function normalizeVerificationEmailResponse(value) {
@@ -2271,6 +2331,26 @@ function fromAuthorityError(value, fallbackStatus, retryAfter) {
2271
2331
  const mapped = mapError(isObject(value) && typeof value.code === "string" ? value.code : void 0, fallbackStatus);
2272
2332
  return new AuthError(mapped.code, mapped.status, parseRetryAfter(retryAfter));
2273
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
+ }
2274
2354
  function authorityUnavailable() {
2275
2355
  return new AuthError("identity_authority_unavailable", 503);
2276
2356
  }
@@ -2350,6 +2430,18 @@ function toDate(value) {
2350
2430
  const result = value instanceof Date ? value : typeof value === "string" ? new Date(value) : void 0;
2351
2431
  return result && Number.isFinite(result.getTime()) ? result : void 0;
2352
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
+ }
2353
2445
  function isId(value) {
2354
2446
  return typeof value === "string" && id.test(value);
2355
2447
  }
@@ -2369,7 +2461,7 @@ async function requestJsonResponse(url, options) {
2369
2461
  method: options.method,
2370
2462
  headers: options.headers,
2371
2463
  body: options.body,
2372
- credentials: "include",
2464
+ credentials: options.credentials ?? "include",
2373
2465
  retry: 0,
2374
2466
  timeout: options.timeoutMs,
2375
2467
  redirect: "manual",
@@ -3181,12 +3273,14 @@ function createAuthClient(options) {
3181
3273
  basePath: "/api/v1/auth",
3182
3274
  fetchOptions: {
3183
3275
  customFetchImpl: fetchImpl,
3276
+ credentials: "include",
3184
3277
  retry: 0,
3185
3278
  timeout: timeoutMs
3186
3279
  }
3187
3280
  });
3188
3281
  const persistence = createSessionPersistence(options.persistence, authOrigin, now, timeoutMs);
3189
3282
  const authStateListeners = new Set();
3283
+ const sessionListeners = new Set();
3190
3284
  let authState = { status: "loading" };
3191
3285
  let authStateStarted = false;
3192
3286
  let credential;
@@ -3194,15 +3288,29 @@ function createAuthClient(options) {
3194
3288
  let selectedWorkspaceKey;
3195
3289
  let credentialGeneration = 0;
3196
3290
  let authorityGeneration = 0;
3197
- let locallySignedOut = false;
3198
- let activeSignInGeneration;
3291
+ let sessionReadEpoch = 0;
3292
+ let activeAccountMutationGeneration;
3199
3293
  let invitationAbort;
3200
3294
  let invitationRequest;
3201
3295
  let signOutRequest;
3202
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;
3203
3304
  function setAuthState(state) {
3204
3305
  authState = state;
3205
- 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 {}
3206
3314
  }
3207
3315
  function unavailable() {
3208
3316
  setAuthState({
@@ -3229,26 +3337,16 @@ function createAuthClient(options) {
3229
3337
  });
3230
3338
  persistence.remove();
3231
3339
  }
3340
+ emitSession(normalized);
3232
3341
  return normalized;
3233
3342
  }
3234
3343
  function startAuthState() {
3235
3344
  if (authStateStarted) return;
3236
3345
  authStateStarted = true;
3237
- betterAuth.useSession.subscribe((state) => {
3238
- if (state.isPending) return;
3239
- if (state.error) {
3240
- unavailable();
3241
- return;
3242
- }
3243
- try {
3244
- const session = normalizeSession(state.data);
3245
- if (session && (locallySignedOut || signOutRequest)) return;
3246
- acceptAuthoritySession(session);
3247
- } catch {
3248
- unavailable();
3249
- }
3250
- });
3346
+ subscribeToSessionSignals();
3347
+ const hydrationGeneration = authorityGeneration;
3251
3348
  persistence.hydrate((session) => {
3349
+ if (hydrationGeneration !== authorityGeneration || activeAccountMutationGeneration !== void 0) return;
3252
3350
  if (authState.status === "loading") setAuthState({
3253
3351
  status: "cached",
3254
3352
  session,
@@ -3260,7 +3358,45 @@ function createAuthClient(options) {
3260
3358
  authoritative: false
3261
3359
  });
3262
3360
  });
3263
- 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;
3264
3400
  }
3265
3401
  function clearCredential() {
3266
3402
  credential = void 0;
@@ -3270,44 +3406,52 @@ function createAuthClient(options) {
3270
3406
  }
3271
3407
  function updateSession(session) {
3272
3408
  const unboundCredential = !sessionId && (credential || credentialRequests.size > 0);
3273
- if (!session || unboundCredential || sessionId && session.id !== sessionId) clearCredential();
3409
+ if (!session || unboundCredential || sessionId && (session.id !== sessionId || session.user.id !== sessionUserId)) clearCredential();
3274
3410
  sessionId = session?.id;
3411
+ sessionUserId = session?.user.id;
3275
3412
  return session;
3276
3413
  }
3277
- async function getSession() {
3278
- if (signOutRequest) throw new AuthError("state_conflict", 409);
3279
- locallySignedOut = false;
3280
- const generation = authorityGeneration;
3281
- let result;
3282
- try {
3283
- result = await betterAuth.getSession();
3284
- } catch {
3285
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3286
- unavailable();
3287
- throw authorityUnavailable();
3288
- }
3289
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3290
- if (result.error) {
3291
- unavailable();
3292
- throw fromAuthorityError(result.error, result.error.status);
3293
- }
3414
+ async function refreshAuthoritySession(generation) {
3415
+ const readEpoch = ++sessionReadEpoch;
3294
3416
  try {
3295
- 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);
3296
3424
  } catch (error) {
3297
- 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;
3298
3427
  unavailable();
3428
+ if (error instanceof AuthError) throw error;
3299
3429
  throw authorityUnavailable();
3300
3430
  }
3301
3431
  }
3432
+ function getSession() {
3433
+ if (signOutRequest || activeAccountMutationGeneration !== void 0) return Promise.reject(new AuthError("state_conflict", 409));
3434
+ return refreshAuthoritySession(authorityGeneration);
3435
+ }
3302
3436
  function signOut() {
3303
3437
  if (signOutRequest) return signOutRequest;
3438
+ if (activeAccountMutationGeneration !== void 0) return Promise.reject(new AuthError("state_conflict", 409));
3304
3439
  const pendingInvitation = invitationRequest;
3305
3440
  invitationAbort?.abort();
3306
3441
  const generation = ++authorityGeneration;
3307
- locallySignedOut = true;
3442
+ sessionReadEpoch += 1;
3308
3443
  clearCredential();
3309
3444
  sessionId = void 0;
3445
+ sessionUserId = void 0;
3446
+ if (betterAuthRefetching) suppressBetterAuthSettlement = true;
3447
+ signalRefreshPending = false;
3310
3448
  persistence.remove();
3449
+ setAuthState({
3450
+ status: "unavailable",
3451
+ session: null,
3452
+ authoritative: false
3453
+ });
3454
+ emitSession(null);
3311
3455
  const request = (async () => {
3312
3456
  await pendingInvitation?.catch(() => void 0);
3313
3457
  let result;
@@ -3315,13 +3459,11 @@ function createAuthClient(options) {
3315
3459
  result = await betterAuth.signOut();
3316
3460
  } catch {
3317
3461
  if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3318
- locallySignedOut = false;
3319
3462
  unavailable();
3320
3463
  throw authorityUnavailable();
3321
3464
  }
3322
3465
  if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3323
3466
  if (result.error) {
3324
- locallySignedOut = false;
3325
3467
  unavailable();
3326
3468
  throw fromAuthorityError(result.error, result.error.status);
3327
3469
  }
@@ -3330,13 +3472,70 @@ function createAuthClient(options) {
3330
3472
  session: null,
3331
3473
  authoritative: true
3332
3474
  });
3475
+ emitSession(null);
3333
3476
  })().finally(() => {
3334
- if (signOutRequest === request) signOutRequest = void 0;
3477
+ if (signOutRequest === request) {
3478
+ signOutRequest = void 0;
3479
+ signalRefreshPending = false;
3480
+ }
3335
3481
  });
3336
3482
  signOutRequest = request;
3337
3483
  return request;
3338
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
+ }
3339
3537
  return {
3538
+ _type: "browser",
3340
3539
  acceptInvitation(input) {
3341
3540
  let invitation;
3342
3541
  try {
@@ -3344,7 +3543,7 @@ function createAuthClient(options) {
3344
3543
  } catch (error) {
3345
3544
  return Promise.reject(error);
3346
3545
  }
3347
- if (signOutRequest || activeSignInGeneration !== void 0 || invitationRequest) return Promise.reject(new AuthError("state_conflict", 409));
3546
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) return Promise.reject(new AuthError("state_conflict", 409));
3348
3547
  const generation = authorityGeneration;
3349
3548
  const controller = new AbortController();
3350
3549
  clearCredential();
@@ -3377,7 +3576,7 @@ function createAuthClient(options) {
3377
3576
  },
3378
3577
  getSession,
3379
3578
  async listOrganizations() {
3380
- if (signOutRequest || activeSignInGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
3579
+ if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
3381
3580
  const generation = authorityGeneration;
3382
3581
  try {
3383
3582
  const value = await requestJson(joinURL(authOrigin, "api/v1/auth/organization/list"), {
@@ -3393,37 +3592,60 @@ function createAuthClient(options) {
3393
3592
  }
3394
3593
  },
3395
3594
  async signIn(input) {
3396
- if (signOutRequest) throw new AuthError("state_conflict", 409);
3397
- const pendingInvitation = invitationRequest;
3398
- invitationAbort?.abort();
3399
- const generation = ++authorityGeneration;
3400
- activeSignInGeneration = generation;
3401
- try {
3402
- await pendingInvitation?.catch(() => void 0);
3403
- locallySignedOut = false;
3404
- clearCredential();
3405
- let result;
3406
- try {
3407
- result = await betterAuth.signIn.email({
3408
- email: input.email,
3409
- password: input.password
3410
- });
3411
- } catch {
3412
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3413
- unavailable();
3414
- throw authorityUnavailable();
3415
- }
3416
- if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
3417
- if (result.error) {
3418
- unavailable();
3419
- throw fromAuthorityError(result.error, result.error.status);
3420
- }
3421
- const session = await getSession();
3422
- if (!session) throw new AuthError("authentication_required", 401);
3423
- return session;
3424
- } finally {
3425
- if (activeSignInGeneration === generation) activeSignInGeneration = void 0;
3426
- }
3595
+ const body = {
3596
+ email: normalizeEmail(input?.email),
3597
+ password: normalizePassword(input?.password)
3598
+ };
3599
+ const result = await runAccountMutation(() => accountRequest("sign-in/email", body));
3600
+ if (!result.session) throw new AuthError("authentication_required", 401);
3601
+ return result.session;
3602
+ },
3603
+ async signInWithSocialIdToken(input) {
3604
+ const body = socialIdTokenRequest(normalizeSocialIdTokenInput(input));
3605
+ const result = await runAccountMutation(() => accountRequest("sign-in/social", body, fromSocialIdTokenAuthorityError));
3606
+ if (!result.session) throw new AuthError("authentication_required", 401);
3607
+ return result.session;
3608
+ },
3609
+ async signUp(email, password, name) {
3610
+ const body = {
3611
+ email: normalizeEmail(email),
3612
+ password: normalizePassword(password, "password", 10),
3613
+ name: normalizeName(name)
3614
+ };
3615
+ const result = await runAccountMutation(() => accountRequest("sign-up/email", body));
3616
+ if (!result.session) throw new AuthError("authentication_required", 401);
3617
+ return result.session;
3618
+ },
3619
+ async updateProfile(input) {
3620
+ const body = normalizeProfileInput(input);
3621
+ const result = await runAccountMutation(() => accountRequest("update-user", body));
3622
+ if (!result.session) throw new AuthError("authentication_required", 401);
3623
+ return result.session;
3624
+ },
3625
+ async changePassword(currentPassword, newPassword, revokeOtherSessions = true) {
3626
+ const current = normalizePassword(currentPassword, "currentPassword");
3627
+ const next = normalizePassword(newPassword, "newPassword", 10);
3628
+ if (typeof revokeOtherSessions !== "boolean") throw new TypeError("revokeOtherSessions is invalid");
3629
+ const result = await runAccountMutation(() => accountRequest("change-password", {
3630
+ currentPassword: current,
3631
+ newPassword: next,
3632
+ revokeOtherSessions
3633
+ }));
3634
+ if (!result.session) throw new AuthError("authentication_required", 401);
3635
+ return result.session;
3636
+ },
3637
+ async requestPasswordReset(email, redirectTo) {
3638
+ normalizeVerificationEmailResponse(await accountRequest("request-password-reset", {
3639
+ email: normalizeEmail(email),
3640
+ redirectTo: normalizeSameAppURL(redirectTo, appOrigin, "redirectTo")
3641
+ }));
3642
+ },
3643
+ async resetPassword(token, newPassword) {
3644
+ const body = {
3645
+ token: normalizePasswordResetToken(token),
3646
+ newPassword: normalizePassword(newPassword, "newPassword", 10)
3647
+ };
3648
+ normalizeVerificationEmailResponse((await runAccountMutation(() => accountRequest("reset-password", body))).data);
3427
3649
  },
3428
3650
  async sendVerificationEmail(input) {
3429
3651
  const body = normalizeVerificationEmailInput(input, appOrigin);
@@ -3443,7 +3665,7 @@ function createAuthClient(options) {
3443
3665
  } catch (error) {
3444
3666
  return Promise.reject(error);
3445
3667
  }
3446
- if (invitationRequest) return Promise.reject(new AuthError("state_conflict", 409));
3668
+ if (invitationRequest || signOutRequest || activeAccountMutationGeneration !== void 0) return Promise.reject(new AuthError("state_conflict", 409));
3447
3669
  const workspaceKey = workspace.type === "personal" ? "personal" : `organization:${workspace.organizationId}`;
3448
3670
  if (selectedWorkspaceKey !== workspaceKey) {
3449
3671
  credential = void 0;
@@ -3480,21 +3702,20 @@ function createAuthClient(options) {
3480
3702
  },
3481
3703
  onAuthStateChange(listener) {
3482
3704
  authStateListeners.add(listener);
3483
- listener(authState);
3705
+ try {
3706
+ listener(authState);
3707
+ } catch {}
3484
3708
  startAuthState();
3485
3709
  return () => authStateListeners.delete(listener);
3486
3710
  },
3487
3711
  onSessionChange(listener) {
3488
- return betterAuth.useSession.subscribe((state) => {
3489
- let session;
3490
- try {
3491
- session = updateSession(normalizeSession(state.data));
3492
- } catch {
3493
- clearCredential();
3494
- session = null;
3495
- }
3496
- listener(session);
3497
- });
3712
+ sessionListeners.add(listener);
3713
+ try {
3714
+ if (authState.status === "authenticated") listener(authState.session);
3715
+ else if (authState.status === "unauthenticated") listener(null);
3716
+ } catch {}
3717
+ startAuthState();
3718
+ return () => sessionListeners.delete(listener);
3498
3719
  }
3499
3720
  };
3500
3721
  }
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-DDopQNZR.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 };