@better-auth/electron 1.7.0-beta.5 → 1.7.0-beta.6

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/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-DzWb5tB_.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-BTlyLl-N.mjs";
2
2
  import { n as isProcessType, r as parseProtocolScheme, t as getChannelPrefixWithDelimiter } from "./utils-DxDKRT6e.mjs";
3
3
  import { BetterAuthError } from "@better-auth/core/error";
4
4
  import { base64, base64Url } from "@better-auth/utils/base64";
@@ -578,6 +578,16 @@ const electronClient = (options) => {
578
578
  const cookieName = `${opts.storagePrefix}.cookie`;
579
579
  const localCacheName = `${opts.storagePrefix}.local_cache`;
580
580
  const { getDecrypted, setEncrypted } = storageAdapter(opts.storage, new Set([cookieName, localCacheName]));
581
+ const clearSessionCache = () => {
582
+ setEncrypted(cookieName, "{}");
583
+ store?.atoms.session?.set({
584
+ ...store.atoms.session.get(),
585
+ data: null,
586
+ error: null,
587
+ isPending: false
588
+ });
589
+ setEncrypted(localCacheName, "{}");
590
+ };
581
591
  if ((isDevelopment() || isTest()) && /^(?!\.)(?!.*\.\.)(?!.*\.$)[^.]+\.[^.]+$/.test(scheme)) console.warn("The provided scheme does not follow the reverse domain name notation. For example: `app.example.com` -> `com.example.app`.");
582
592
  return {
583
593
  id: "electron",
@@ -597,16 +607,7 @@ const electronClient = (options) => {
597
607
  "user-agent": app.userAgentFallback,
598
608
  "x-skip-oauth-proxy": "true"
599
609
  };
600
- if (url.endsWith("/sign-out")) {
601
- setEncrypted(cookieName, "{}");
602
- store?.atoms.session?.set({
603
- ...store.atoms.session.get(),
604
- data: null,
605
- error: null,
606
- isPending: false
607
- });
608
- setEncrypted(localCacheName, "{}");
609
- }
610
+ if (url.endsWith("/sign-out")) clearSessionCache();
610
611
  return {
611
612
  url,
612
613
  options
@@ -629,6 +630,7 @@ const electronClient = (options) => {
629
630
  const data = context.data;
630
631
  setEncrypted(localCacheName, JSON.stringify(data));
631
632
  }
633
+ if (context.request.url.toString().includes("/sign-out")) clearSessionCache();
632
634
  },
633
635
  onError: async (context) => {
634
636
  webContents.getFocusedWebContents()?.send(`${getChannelPrefixWithDelimiter(opts.channelPrefix)}error`, {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-DzWb5tB_.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-BTlyLl-N.mjs";
2
2
  import { createAuthMiddleware } from "@better-auth/core/api";
3
3
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
4
4
  import { base64Url } from "@better-auth/utils/base64";
@@ -53,14 +53,13 @@ const electronToken = (_opts) => createAuthEndpoint("/electron/token", {
53
53
  }
54
54
  }
55
55
  }, async (ctx) => {
56
- const token = await ctx.context.internalAdapter.findVerificationValue(`electron:${ctx.body.token}`);
57
- if (!token || token.expiresAt < /* @__PURE__ */ new Date()) throw APIError.from("NOT_FOUND", ELECTRON_ERROR_CODES.INVALID_TOKEN);
56
+ const token = await ctx.context.internalAdapter.consumeVerificationValue(`electron:${ctx.body.token}`);
57
+ if (!token) throw APIError.from("NOT_FOUND", ELECTRON_ERROR_CODES.INVALID_TOKEN);
58
58
  const tokenRecord = safeJSONParse$1(token.value);
59
59
  if (!tokenRecord) throw APIError.from("INTERNAL_SERVER_ERROR", ELECTRON_ERROR_CODES.INVALID_TOKEN);
60
60
  if (tokenRecord.state !== ctx.body.state) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.STATE_MISMATCH);
61
61
  if (!tokenRecord.codeChallenge) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.MISSING_CODE_CHALLENGE);
62
62
  if (await generateCodeChallenge(ctx.body.code_verifier) !== tokenRecord.codeChallenge) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.INVALID_CODE_VERIFIER);
63
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`electron:${ctx.body.token}`);
64
63
  const user = await ctx.context.internalAdapter.findUserById(tokenRecord.userId);
65
64
  if (!user) throw APIError.from("INTERNAL_SERVER_ERROR", BASE_ERROR_CODES.USER_NOT_FOUND);
66
65
  const session = await ctx.context.internalAdapter.createSession(user.id);
@@ -33,6 +33,7 @@ type DBAdapterDebugLogOption = boolean | {
33
33
  delete?: boolean | undefined;
34
34
  deleteMany?: boolean | undefined;
35
35
  consumeOne?: boolean | undefined;
36
+ incrementOne?: boolean | undefined;
36
37
  count?: boolean | undefined;
37
38
  } | {
38
39
  /**
@@ -208,7 +209,7 @@ interface DBAdapterFactoryConfig<Options extends BetterAuthOptions = BetterAuthO
208
209
  /**
209
210
  * The action which was called from the adapter.
210
211
  */
211
- action: "create" | "update" | "findOne" | "findMany" | "updateMany" | "delete" | "deleteMany" | "consumeOne" | "count";
212
+ action: "create" | "update" | "findOne" | "findMany" | "updateMany" | "delete" | "deleteMany" | "consumeOne" | "incrementOne" | "count";
212
213
  /**
213
214
  * The model name.
214
215
  */
@@ -437,15 +438,43 @@ type DBAdapter<Options extends BetterAuthOptions = BetterAuthOptions> = {
437
438
  * race-safe primitive for consuming single-use credentials
438
439
  * (verification tokens, authorization codes, one-time tokens).
439
440
  *
440
- * Always defined on the factory-wrapped adapter. When the underlying
441
- * `CustomAdapter` does not implement `consumeOne`, the factory provides
442
- * a fallback that wraps `findMany + deleteMany` in `transaction(...)`
443
- * and returns the row only when the delete reports an affected row.
441
+ * Always defined on the factory-wrapped adapter. The underlying
442
+ * `CustomAdapter` must implement this natively; there is no portable
443
+ * fallback that can guarantee cross-process single-use semantics.
444
444
  */
445
445
  consumeOne: <T>(data: {
446
446
  model: string;
447
447
  where: Where[];
448
448
  }) => Promise<T | null>;
449
+ /**
450
+ * Atomically apply signed numeric deltas to a single row matching the where
451
+ * clause. For each entry in `increment`, the operation applies
452
+ * `field = field + delta` in one atomic step; a negative delta decrements.
453
+ *
454
+ * The `where` clause is both the selector AND the guard: comparison
455
+ * operators are honored, so passing `{ field: "remaining", operator: "gt",
456
+ * value: 0 }` only mutates the row while `remaining` is still above zero.
457
+ * When the guard matches no row, the operation makes no change and returns
458
+ * `null`.
459
+ *
460
+ * The optional `set` map assigns absolute values to fields in the same
461
+ * atomic operation, alongside the increments.
462
+ *
463
+ * Returns the updated row, or `null` when the guard matched no row. Under
464
+ * concurrent invocation against the same row, this is the race-safe
465
+ * primitive for guarded counter updates (e.g. decrementing a remaining-uses
466
+ * counter only while it is still positive).
467
+ *
468
+ * Always defined on the factory-wrapped adapter. The underlying
469
+ * `CustomAdapter` must implement this natively; there is no portable
470
+ * fallback that can guarantee guarded counter semantics across runtimes.
471
+ */
472
+ incrementOne: <T>(data: {
473
+ model: string;
474
+ where: Where[];
475
+ increment: Record<string, number>;
476
+ set?: Record<string, unknown> | undefined;
477
+ }) => Promise<T | null>;
449
478
  /**
450
479
  * Execute multiple operations in a transaction.
451
480
  * If the adapter doesn't support transactions, operations will be executed sequentially.
@@ -528,18 +557,33 @@ interface CustomAdapter {
528
557
  where: CleanedWhere[];
529
558
  }) => Promise<number>;
530
559
  /**
531
- * Optional native atomic single-row consume. When omitted, the adapter
532
- * factory falls back to `transaction(findMany + deleteMany)`.
560
+ * Native atomic single-row consume.
533
561
  * Implementing this method natively (e.g. `DELETE ... RETURNING *`,
534
562
  * `findOneAndDelete`, `OUTPUT deleted.*`) gives one round trip and the
535
563
  * strongest race-safety guarantee. Implementations must delete at most
536
- * one matching row. TODO(consume-one-required): tighten to required in the
537
- * next minor on `next`.
564
+ * one matching row.
538
565
  */
539
- consumeOne?: <T>(data: {
566
+ consumeOne: <T>(data: {
540
567
  model: string;
541
568
  where: CleanedWhere[];
542
569
  }) => Promise<T | null>;
570
+ /**
571
+ * Native atomic guarded counter mutation. Applies
572
+ * `field = field + delta` for each entry in `increment` (negative deltas
573
+ * decrement), with `where` acting as both selector and guard and `set`
574
+ * assigning absolute values in the same operation. Returns the updated row,
575
+ * or `null` when the guard matched no row.
576
+ *
577
+ * Implementing this natively (e.g. `UPDATE ... SET n = n + $delta WHERE ...
578
+ * RETURNING *`) gives one round trip and the strongest race-safety
579
+ * guarantee.
580
+ */
581
+ incrementOne: <T>(data: {
582
+ model: string;
583
+ where: CleanedWhere[];
584
+ increment: Record<string, number>;
585
+ set?: Record<string, unknown> | undefined;
586
+ }) => Promise<T | null>;
543
587
  count: ({
544
588
  model,
545
589
  where
@@ -577,7 +621,6 @@ type BaseRateLimit = z.infer<typeof rateLimitSchema>;
577
621
  /**
578
622
  * Rate limit schema type used by better-auth for rate limiting
579
623
  */
580
- type RateLimit<DBOptions extends BetterAuthOptions["rateLimit"] = BetterAuthOptions["rateLimit"], Plugins extends BetterAuthOptions["plugins"] = BetterAuthOptions["plugins"]> = Prettify$1<BaseRateLimit & InferDBFieldsFromOptions<DBOptions> & InferDBFieldsFromPlugins<"rateLimit", Plugins>>; //#endregion
581
624
  //#endregion
582
625
  //#region ../core/dist/db/schema/session.d.mts
583
626
  //#region src/db/schema/session.d.ts
@@ -2783,6 +2826,7 @@ declare const socialProviders: {
2783
2826
  audience: string | string[];
2784
2827
  maxTokenAge: string;
2785
2828
  issuer: string | undefined;
2829
+ verifyClaims: (claims: Record<string, unknown>) => boolean;
2786
2830
  };
2787
2831
  getUserInfo(token: OAuth2Tokens & {
2788
2832
  user?: {
@@ -5368,6 +5412,23 @@ interface InternalAdapter<_Options extends BetterAuthOptions = BetterAuthOptions
5368
5412
  * pair at single-use credential consumption sites.
5369
5413
  */
5370
5414
  consumeVerificationValue(identifier: string): Promise<Verification | null>;
5415
+ /**
5416
+ * First-writer-wins create keyed by a deterministic primary key derived from
5417
+ * `identifier`. Returns `true` when this caller created the row and `false`
5418
+ * when a row for the same identifier already existed.
5419
+ *
5420
+ * The dual of `consumeVerificationValue`: reserve races to create a marker
5421
+ * exactly once, where consume races to delete one exactly once. Use it for
5422
+ * replay tombstones (a SAML assertion id, a JWT `jti`) where the first caller
5423
+ * wins. The database path is atomic via the primary key. Secondary-storage-only
5424
+ * verification is not supported for reservation and runtime implementations
5425
+ * should fail closed unless verification is backed by the database.
5426
+ */
5427
+ reserveVerificationValue(data: {
5428
+ identifier: string;
5429
+ value: string;
5430
+ expiresAt: Date;
5431
+ }): Promise<boolean>;
5371
5432
  updateVerificationByIdentifier(identifier: string, data: Partial<Verification>): Promise<Verification>;
5372
5433
  refreshUserSessions(user: User): Promise<void>;
5373
5434
  }
@@ -5919,8 +5980,30 @@ type DynamicBaseURLConfig = {
5919
5980
  */
5920
5981
  type BaseURLConfig = string | DynamicBaseURLConfig;
5921
5982
  interface BetterAuthRateLimitStorage {
5922
- get: (key: string) => Promise<RateLimit | null | undefined>;
5923
- set: (key: string, value: RateLimit, update?: boolean | undefined) => Promise<void>;
5983
+ /**
5984
+ * Atomically records one request against `key` within the rolling `window`
5985
+ * (in seconds) and reports whether it is allowed.
5986
+ *
5987
+ * When `allowed` is true the count was incremented within the active window,
5988
+ * or the window had elapsed and was reset to start at 1. When `allowed` is
5989
+ * false the limit was already reached and `retryAfter` is the number of
5990
+ * seconds until the window frees up.
5991
+ *
5992
+ * Performing the check and the increment in a single step closes the
5993
+ * concurrent-bypass gap of the separate `get`/`set` path: N simultaneous
5994
+ * requests can no longer all pass a stale read before any increment lands.
5995
+ *
5996
+ * Custom storages must implement this operation directly. Better Auth no
5997
+ * longer accepts separate `get`/`set` rate-limit storage because that shape
5998
+ * cannot enforce a distributed limit under concurrent requests.
5999
+ */
6000
+ consume: (key: string, rule: {
6001
+ window: number;
6002
+ max: number;
6003
+ }) => Promise<{
6004
+ allowed: boolean;
6005
+ retryAfter: number | null;
6006
+ }>;
5924
6007
  }
5925
6008
  type BetterAuthRateLimitRule = {
5926
6009
  /**
@@ -6702,6 +6785,20 @@ type BetterAuthOptions = {
6702
6785
  * @default "compact"
6703
6786
  */
6704
6787
  strategy?: "compact" | "jwt" | "jwe";
6788
+ /**
6789
+ * JWT-specific configuration for `strategy: "jwt"`.
6790
+ */
6791
+ jwt?: {
6792
+ /**
6793
+ * Which signing key is used for cookie-cache JWTs.
6794
+ *
6795
+ * - `"secret"`: uses the Better Auth secret with HS256.
6796
+ * - `"jwt-plugin"`: uses the installed `jwt()` plugin's asymmetric signing keys.
6797
+ *
6798
+ * @default "secret"
6799
+ */
6800
+ signingKey?: "secret" | "jwt-plugin";
6801
+ };
6705
6802
  /**
6706
6803
  * Controls stateless cookie cache refresh behavior.
6707
6804
  *
@@ -6881,9 +6978,13 @@ type BetterAuthOptions = {
6881
6978
  */
6882
6979
  storeStateStrategy?: "database" | "cookie";
6883
6980
  /**
6884
- * Store account data after oauth flow on a cookie
6981
+ * Store provider account data after an OAuth flow in an encrypted
6982
+ * cookie. This includes OAuth token material such as access tokens,
6983
+ * refresh tokens, ID tokens, scopes, and token expiry.
6885
6984
  *
6886
- * This is useful for database-less flow
6985
+ * This is useful for database-less flows, but large provider tokens can
6986
+ * still hit browser or proxy cookie/header limits even though Better Auth
6987
+ * chunks oversized account cookies.
6887
6988
  *
6888
6989
  * @default false
6889
6990
  *
@@ -7388,16 +7489,21 @@ interface SecondaryStorage {
7388
7489
  get: (key: string) => Awaitable<unknown>;
7389
7490
  /**
7390
7491
  * Atomically get a value and delete it from storage.
7492
+ */
7493
+ getAndDelete: (key: string) => Awaitable<unknown>;
7494
+ /**
7495
+ * Atomically increment the counter at `key` by one, returning the
7496
+ * post-increment value.
7391
7497
  *
7392
- * This is optional for backwards compatibility with existing secondary
7393
- * storage implementations. Single-use credential consumers use it when
7394
- * present to avoid a read-then-delete race.
7498
+ * When the key is absent, it is created with a value of `1` and the given
7499
+ * `ttl` (in SECONDS). The TTL is applied only on creation; later increments
7500
+ * never extend it, so the counter expires a fixed window after it was first
7501
+ * created.
7395
7502
  *
7396
- * TODO(secondary-storage-atomic-consume): make this required in the next
7397
- * breaking release, or require database-backed verification storage for
7398
- * security-sensitive consume paths.
7503
+ * Required so secondary-storage-backed rate limiting can enforce the limit
7504
+ * in one distributed-safe operation.
7399
7505
  */
7400
- getAndDelete?: (key: string) => Awaitable<unknown>;
7506
+ increment: (key: string, ttl: number) => Awaitable<number>;
7401
7507
  set: (
7402
7508
  /**
7403
7509
  * Key to store
package/dist/proxy.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-DzWb5tB_.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-BTlyLl-N.mjs";
2
2
  import { r as parseProtocolScheme } from "./utils-DxDKRT6e.mjs";
3
3
  import { parseCookies } from "better-auth/cookies";
4
4
  //#region src/proxy.ts
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.7.0-beta.5";
3
+ const PACKAGE_VERSION = "1.7.0-beta.6";
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/electron",
3
- "version": "1.7.0-beta.5",
3
+ "version": "1.7.0-beta.6",
4
4
  "description": "Better Auth integration for Electron applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -80,17 +80,17 @@
80
80
  "better-sqlite3": "^12.6.2",
81
81
  "electron": "^41.2.2",
82
82
  "tsdown": "0.21.1",
83
- "@better-auth/core": "1.7.0-beta.5",
84
- "better-auth": "1.7.0-beta.5"
83
+ "@better-auth/core": "1.7.0-beta.6",
84
+ "better-auth": "1.7.0-beta.6"
85
85
  },
86
86
  "peerDependencies": {
87
- "@better-auth/utils": "0.4.1",
88
- "@better-fetch/fetch": "1.2.2",
87
+ "@better-auth/utils": "0.4.2",
88
+ "@better-fetch/fetch": "1.3.1",
89
89
  "better-call": "1.3.6",
90
90
  "conf": "^15.0.2",
91
91
  "electron": ">=36.0.0",
92
- "@better-auth/core": "^1.7.0-beta.5",
93
- "better-auth": "^1.7.0-beta.5"
92
+ "@better-auth/core": "^1.7.0-beta.6",
93
+ "better-auth": "^1.7.0-beta.6"
94
94
  },
95
95
  "peerDependenciesMeta": {
96
96
  "electron": {