@better-auth/oauth-provider 1.7.9 → 1.8.0-beta.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.
@@ -6,6 +6,146 @@ import { BetterAuthError } from "@better-auth/core/error";
6
6
  import { CLIENT_ASSERTION_TYPE, decodeBasicCredentials } from "@better-auth/core/oauth2";
7
7
  import { base64Url } from "@better-auth/utils/base64";
8
8
  import { createHash } from "@better-auth/utils/hash";
9
+ //#region src/client-metadata.ts
10
+ const OAUTH_CLIENT_RECORD_FIELD_NAMES = Object.freeze(Object.keys({
11
+ clientId: true,
12
+ clientSecret: true,
13
+ clientDiscoveryId: true,
14
+ disabled: true,
15
+ scopes: true,
16
+ clientCredentialsScopes: true,
17
+ userId: true,
18
+ createdAt: true,
19
+ updatedAt: true,
20
+ expiresAt: true,
21
+ name: true,
22
+ uri: true,
23
+ icon: true,
24
+ contacts: true,
25
+ tos: true,
26
+ policy: true,
27
+ softwareId: true,
28
+ softwareVersion: true,
29
+ softwareStatement: true,
30
+ redirectUris: true,
31
+ postLogoutRedirectUris: true,
32
+ backchannelLogoutUri: true,
33
+ backchannelLogoutSessionRequired: true,
34
+ frontchannelLogoutUri: true,
35
+ frontchannelLogoutSessionRequired: true,
36
+ tokenEndpointAuthMethod: true,
37
+ grantTypes: true,
38
+ responseTypes: true,
39
+ applicationType: true,
40
+ jwks: true,
41
+ jwksUri: true,
42
+ requirePKCE: true,
43
+ dpopBoundAccessTokens: true,
44
+ skipConsent: true,
45
+ enableEndSession: true,
46
+ subjectType: true,
47
+ sectorIdentifier: true,
48
+ referenceId: true,
49
+ metadata: true
50
+ }));
51
+ /**
52
+ * Canonical wire fields emitted by schemaToOAuth. They are mapped explicitly
53
+ * and therefore must never also survive inside the opaque metadata envelope.
54
+ */
55
+ const OAUTH_CLIENT_WIRE_FIELD_NAMES = [
56
+ "client_id",
57
+ "client_secret",
58
+ "client_secret_expires_at",
59
+ "scope",
60
+ "user_id",
61
+ "client_id_issued_at",
62
+ "client_name",
63
+ "client_uri",
64
+ "logo_uri",
65
+ "contacts",
66
+ "tos_uri",
67
+ "policy_uri",
68
+ "jwks",
69
+ "jwks_uri",
70
+ "software_id",
71
+ "software_version",
72
+ "software_statement",
73
+ "redirect_uris",
74
+ "post_logout_redirect_uris",
75
+ "backchannel_logout_uri",
76
+ "backchannel_logout_session_required",
77
+ "frontchannel_logout_uri",
78
+ "frontchannel_logout_session_required",
79
+ "token_endpoint_auth_method",
80
+ "grant_types",
81
+ "response_types",
82
+ "application_type",
83
+ "disabled",
84
+ "skip_consent",
85
+ "enable_end_session",
86
+ "require_pkce",
87
+ "dpop_bound_access_tokens",
88
+ "subject_type",
89
+ "reference_id"
90
+ ];
91
+ const OPAQUE_METADATA_RESERVED_FIELDS = new Set([
92
+ ...OAUTH_CLIENT_RECORD_FIELD_NAMES,
93
+ ...OAUTH_CLIENT_WIRE_FIELD_NAMES,
94
+ "public",
95
+ "type",
96
+ "resources",
97
+ "client_credentials_scopes"
98
+ ]);
99
+ const CIMD_FORBIDDEN_SERVER_FIELD_NAMES = new Set([
100
+ "disabled",
101
+ "client_secret",
102
+ "client_secret_expires_at",
103
+ "client_id_issued_at",
104
+ "skip_consent",
105
+ "enable_end_session",
106
+ "require_pkce",
107
+ "reference_id",
108
+ "user_id",
109
+ "resources",
110
+ "clientSecret",
111
+ "clientDiscoveryId",
112
+ "sectorIdentifier",
113
+ "skipConsent",
114
+ "enableEndSession",
115
+ "requirePKCE",
116
+ "referenceId",
117
+ "userId",
118
+ "clientId",
119
+ "applicationType",
120
+ "tokenEndpointAuthMethod",
121
+ "redirectUris",
122
+ "postLogoutRedirectUris",
123
+ "grantTypes",
124
+ "responseTypes",
125
+ "scopes",
126
+ "expiresAt",
127
+ "createdAt",
128
+ "updatedAt",
129
+ "softwareId",
130
+ "softwareVersion",
131
+ "softwareStatement",
132
+ "backchannelLogoutUri",
133
+ "backchannelLogoutSessionRequired",
134
+ "jwksUri",
135
+ "dpopBoundAccessTokens",
136
+ "subjectType"
137
+ ]);
138
+ function isForbiddenCimdClientMetadataField(field) {
139
+ return CIMD_FORBIDDEN_SERVER_FIELD_NAMES.has(field);
140
+ }
141
+ function isReservedOAuthClientMetadataField(field) {
142
+ return OPAQUE_METADATA_RESERVED_FIELDS.has(field);
143
+ }
144
+ function stripReservedOAuthClientMetadataExtensions(metadata) {
145
+ if (!metadata) return void 0;
146
+ return Object.fromEntries(Object.entries(metadata).filter(([field]) => !OPAQUE_METADATA_RESERVED_FIELDS.has(field)));
147
+ }
148
+ //#endregion
9
149
  //#region src/extensions.ts
10
150
  const DEFAULT_GRANT_TYPES = [
11
151
  "authorization_code",
@@ -70,11 +210,16 @@ function collectExtensionKeys(extension) {
70
210
  }
71
211
  const clientDiscoveryIds = (extension.clientDiscovery ? Array.isArray(extension.clientDiscovery) ? extension.clientDiscovery : [extension.clientDiscovery] : []).map((discovery) => discovery.id);
72
212
  for (const clientDiscoveryId of clientDiscoveryIds) assertNonEmptyExtensionValue("client discovery id", clientDiscoveryId);
213
+ for (const field of extension.clientMetadata?.fields ?? []) {
214
+ assertNonEmptyExtensionValue("client metadata field", field);
215
+ if (isReservedOAuthClientMetadataField(field)) throw new BetterAuthError(`OAuth Provider extension client metadata field is reserved: ${field}`);
216
+ }
73
217
  return {
74
218
  grantTypes,
75
219
  authMethods,
76
220
  assertionTypes,
77
- clientDiscoveryIds
221
+ clientDiscoveryIds,
222
+ interactionRequestParameters: Object.keys(extension.interactionRequests ?? {})
78
223
  };
79
224
  }
80
225
  function assertNoDuplicateAcrossExtensions(label, values) {
@@ -97,6 +242,7 @@ function validateOAuthProviderExtensions(extensions) {
97
242
  assertNoDuplicateAcrossExtensions("token_endpoint_auth_method", keys.flatMap((k) => k.authMethods));
98
243
  assertNoDuplicateAcrossExtensions("client_assertion_type", keys.flatMap((k) => k.assertionTypes));
99
244
  assertNoDuplicateAcrossExtensions("client discovery id", keys.flatMap((k) => k.clientDiscoveryIds));
245
+ assertNoDuplicateAcrossExtensions("interaction request parameter", keys.flatMap((k) => k.interactionRequestParameters));
100
246
  }
101
247
  function getOAuthProviderExtensions(opts) {
102
248
  return opts.extensions ?? [];
@@ -153,6 +299,23 @@ function getExtensionGrantTypes(opts) {
153
299
  function getSupportedGrantTypes(opts) {
154
300
  return Array.from(new Set([...opts.grantTypes ?? DEFAULT_GRANT_TYPES, ...getExtensionGrantTypes(opts)]));
155
301
  }
302
+ /**
303
+ * Finds the interaction request a query identifies, such as a device approval
304
+ * keyed by `user_code`. Authorization requests, which carry `response_type` or
305
+ * a pushed `request_uri`, never match.
306
+ */
307
+ function getExtensionInteractionRequest(opts, query) {
308
+ const parameters = query;
309
+ if (parameters.response_type !== void 0 || parameters.request_uri !== void 0) return;
310
+ for (const extension of getOAuthProviderExtensions(opts)) for (const [parameter, handler] of Object.entries(extension.interactionRequests ?? {})) {
311
+ const value = parameters[parameter];
312
+ if (typeof value === "string" && value) return {
313
+ parameter,
314
+ handler,
315
+ value
316
+ };
317
+ }
318
+ }
156
319
  function getExtensionGrantHandler(opts, grantType) {
157
320
  for (const extension of getOAuthProviderExtensions(opts)) {
158
321
  const handler = extension.grants?.[grantType];
@@ -230,6 +393,29 @@ function collectExtensionIdTokenClaims(opts, input) {
230
393
  function collectExtensionUserInfoClaims(opts, input) {
231
394
  return collectClaims(opts, (extension) => extension.claims?.userInfo?.(input));
232
395
  }
396
+ /** Whether any extension declares client metadata fields or a validator. */
397
+ function hasExtensionClientMetadata(opts) {
398
+ return getOAuthProviderExtensions(opts).some((extension) => extension.clientMetadata);
399
+ }
400
+ /** Registration fields that extensions define, stored in the client's `metadata`. */
401
+ function getExtensionClientMetadataFields(opts) {
402
+ return new Set(getOAuthProviderExtensions(opts).flatMap((extension) => extension.clientMetadata?.fields ?? []));
403
+ }
404
+ /**
405
+ * Runs every extension's client metadata validator, in registration order.
406
+ * Extensions read their fields from `metadata` only, so `metadata` and
407
+ * declared fields are removed from the client they receive; a metadata
408
+ * document carries declared fields at its top level.
409
+ */
410
+ async function validateExtensionClientMetadata(opts, client, metadata) {
411
+ const { metadata: _metadata, ...coreClient } = client;
412
+ const extensionFields = getExtensionClientMetadataFields(opts);
413
+ const input = {
414
+ client: Object.fromEntries(Object.entries(coreClient).filter(([field]) => !extensionFields.has(field))),
415
+ metadata: stripReservedOAuthClientMetadataExtensions(metadata) ?? {}
416
+ };
417
+ for (const extension of getOAuthProviderExtensions(opts)) await extension.clientMetadata?.validate?.(input);
418
+ }
233
419
  /**
234
420
  * Whether any registered extension contributes UserInfo claims. Lets the
235
421
  * UserInfo endpoint skip loading the client when nothing needs it.
@@ -371,6 +557,16 @@ async function verifyOAuthQueryParams(oauth_query, secret) {
371
557
  return sigs.length === 1 && !!sig && constantTimeEqual(sig, verifySig) && /* @__PURE__ */ new Date(exp * 1e3) >= /* @__PURE__ */ new Date();
372
558
  }
373
559
  /**
560
+ * Rejects changes to a client listed in `cachedTrustedClients`. Cached trusted
561
+ * clients are immutable through the client management endpoints.
562
+ */
563
+ function assertNotTrustedClient(opts, clientId) {
564
+ if (opts.cachedTrustedClients?.has(clientId)) throw new APIError("BAD_REQUEST", {
565
+ error: "invalid_client",
566
+ error_description: "trusted clients must be updated manually"
567
+ });
568
+ }
569
+ /**
374
570
  * Get a client by ID, checking trusted clients first, then database
375
571
  */
376
572
  async function getClient(ctx, options, clientId) {
@@ -531,6 +727,53 @@ function throwInvalidAuthenticationRequest(errorDescription) {
531
727
  });
532
728
  }
533
729
  /**
730
+ * Re-reads a request's raw `application/x-www-form-urlencoded` body, keeping
731
+ * every value of a repeated key. better-call's form-body parser keeps only the
732
+ * last one, so `resource=https://a&resource=https://b` reaches the handler as
733
+ * `{ resource: "https://b" }`. Query strings are unaffected: the router keeps
734
+ * repeated query keys as arrays.
735
+ *
736
+ * Returns `undefined` for other media types or an unreadable or empty body.
737
+ * The endpoint MUST set `cloneRequest: true`, because the body stream is read
738
+ * a second time.
739
+ *
740
+ * @internal
741
+ */
742
+ async function readFormParams(request) {
743
+ if (!(request.headers.get("content-type")?.toLowerCase() ?? "").includes("application/x-www-form-urlencoded")) return;
744
+ let text;
745
+ try {
746
+ text = await request.clone().text();
747
+ } catch {
748
+ return;
749
+ }
750
+ return text ? new URLSearchParams(text) : void 0;
751
+ }
752
+ /**
753
+ * Applies RFC 6749 §3.2 to a form body the parser collapsed to one value per
754
+ * key: a parameter sent without a value counts as omitted, and a parameter
755
+ * sent more than once is rejected with `invalid_request`. A name in
756
+ * `repeatable`, such as RFC 8707 `resource`, keeps every value as an array
757
+ * when it repeats. A value a `before` hook replaced is left as the hook set it.
758
+ *
759
+ * @internal
760
+ */
761
+ function applyFormParams(params, body, repeatable = []) {
762
+ const merged = { ...body };
763
+ for (const name of new Set(params.keys())) {
764
+ const raw = params.getAll(name);
765
+ const values = raw.filter(Boolean);
766
+ if (values.length > 1 && !repeatable.includes(name)) throw new APIError("BAD_REQUEST", {
767
+ error: "invalid_request",
768
+ error_description: `${name} must not be repeated`
769
+ });
770
+ if (merged[name] !== raw.at(-1)) continue;
771
+ if (values.length === 0) delete merged[name];
772
+ else merged[name] = values.length === 1 ? values[0] : values;
773
+ }
774
+ return merged;
775
+ }
776
+ /**
534
777
  * Normalizes client identification and authentication parameters while
535
778
  * enforcing RFC 6749 §2.3 and RFC 8628 §3.1 cardinality. Empty values are
536
779
  * omitted. Client identification remains separate from authentication-method
@@ -609,6 +852,37 @@ function clientAllowsGrant(client, grantType) {
609
852
  return allowedGrants.includes(grantType);
610
853
  }
611
854
  /**
855
+ * Throws an OAuth error response (RFC 6749 §5.2).
856
+ *
857
+ * @internal
858
+ */
859
+ function tokenError(status, error, errorDescription) {
860
+ throw new APIError(status, {
861
+ error,
862
+ error_description: errorDescription
863
+ });
864
+ }
865
+ /**
866
+ * Splits a space-delimited OAuth `scope` value (RFC 6749 §3.3).
867
+ *
868
+ * @internal
869
+ */
870
+ function parseScopes(scope) {
871
+ const normalized = scope?.trim();
872
+ return normalized ? normalized.split(/\s+/) : [];
873
+ }
874
+ /**
875
+ * Whether a stored consent covers every requested scope and resource. Shared by
876
+ * authorization and grants that need prior consent, so the coverage rule cannot
877
+ * drift between them. Each caller still chooses which consent rows to check.
878
+ *
879
+ * @internal
880
+ */
881
+ function consentCovers(consent, scopes, resources) {
882
+ const consentedResources = consent.resources ?? [];
883
+ return scopes.every((scope) => consent.scopes.includes(scope)) && resources.every((resource) => consentedResources.includes(resource));
884
+ }
885
+ /**
612
886
  * Validates requested scopes against a registered client's allowed scopes.
613
887
  *
614
888
  * @internal
@@ -640,7 +914,7 @@ function throwInvalidClient(errorDescription, authentication) {
640
914
  */
641
915
  async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes, preVerified, grantType, authMethod) {
642
916
  const client = await getClient(ctx, options, clientId);
643
- if (!client) throwInvalidClient("missing client", { method: authMethod });
917
+ if (!client) throwInvalidClient("client not found", { method: authMethod });
644
918
  if (client.disabled) throwInvalidClient("client is disabled", { method: authMethod });
645
919
  const registeredAuthMethod = client.tokenEndpointAuthMethod ?? "client_secret_basic";
646
920
  if (authMethod && registeredAuthMethod !== authMethod) throwInvalidClient(`client registered for ${registeredAuthMethod} cannot use ${authMethod}`, { method: authMethod });
@@ -712,7 +986,7 @@ async function extractClientCredentials(ctx, opts, expectedAudience) {
712
986
  confirmation: result.confirmation
713
987
  };
714
988
  }
715
- const { verifyClientAssertion: verify } = await import("./authorize-D8r6mvZx.mjs").then((n) => n.o);
989
+ const { verifyClientAssertion: verify } = await import("./authorize-92EtLsBi.mjs").then((n) => n.s);
716
990
  return {
717
991
  kind: "pre_verified",
718
992
  method: "private_key_jwt",
@@ -761,12 +1035,31 @@ function parsePrompt(prompt) {
761
1035
  return new Set(set);
762
1036
  }
763
1037
  /**
764
- * Extracts the sector identifier (hostname) from a client's first redirect URI.
1038
+ * Derives the pairwise sector to store for a client.
1039
+ *
1040
+ * Only an `https` host identifies who controls a redirect URI, so clients
1041
+ * whose redirect URIs are all `https` share the sector of that host. Any app
1042
+ * can register a custom scheme, and any local process can listen on a
1043
+ * loopback port, so every other client gets a sector of its own. `/` cannot
1044
+ * appear in a URL host, so a client sector never equals a host sector.
1045
+ *
1046
+ * @see https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
1047
+ * @internal
1048
+ */
1049
+ function deriveSectorIdentifier(clientId, redirectUris) {
1050
+ const urls = (redirectUris ?? []).map((uri) => new URL(uri));
1051
+ const first = urls[0];
1052
+ return first && urls.every((url) => url.protocol === "https:") ? first.host : `client/${clientId}`;
1053
+ }
1054
+ /**
1055
+ * Returns the client's stored pairwise sector, or for a client created before
1056
+ * sectors were stored, the host of its first redirect URI.
765
1057
  *
766
1058
  * @see https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
767
1059
  * @internal
768
1060
  */
769
1061
  function getSectorIdentifier(client) {
1062
+ if (client.sectorIdentifier) return client.sectorIdentifier;
770
1063
  const uri = client.redirectUris?.[0];
771
1064
  if (!uri) throw new BetterAuthError("Client has no redirect URIs for sector identifier");
772
1065
  return new URL(uri).host;
@@ -791,6 +1084,51 @@ async function resolveSubjectIdentifier(userId, client, opts) {
791
1084
  return userId;
792
1085
  }
793
1086
  /**
1087
+ * Resolves `postLogin.consentReferenceId`, treating an empty value as no reference.
1088
+ *
1089
+ * @internal
1090
+ */
1091
+ async function getConsentReferenceId(opts, context) {
1092
+ return await opts.postLogin?.consentReferenceId?.(context) || void 0;
1093
+ }
1094
+ /**
1095
+ * Finds the consent stored for exactly this reference. Without a reference,
1096
+ * only consent stored without one (missing, null, or empty) matches.
1097
+ *
1098
+ * @internal
1099
+ */
1100
+ async function findConsent(ctx, clientId, userId, referenceId) {
1101
+ const where = [{
1102
+ field: "clientId",
1103
+ value: clientId
1104
+ }, {
1105
+ field: "userId",
1106
+ value: userId
1107
+ }];
1108
+ if (referenceId) return ctx.context.adapter.findOne({
1109
+ model: "oauthConsent",
1110
+ where: [...where, {
1111
+ field: "referenceId",
1112
+ value: referenceId
1113
+ }]
1114
+ });
1115
+ const pageSize = 100;
1116
+ for (let offset = 0;; offset += pageSize) {
1117
+ const consents = await ctx.context.adapter.findMany({
1118
+ model: "oauthConsent",
1119
+ where,
1120
+ limit: pageSize,
1121
+ offset,
1122
+ sortBy: {
1123
+ field: "id",
1124
+ direction: "asc"
1125
+ }
1126
+ });
1127
+ const consent = consents.find((row) => !row.referenceId);
1128
+ if (consent || consents.length < pageSize) return consent ?? null;
1129
+ }
1130
+ }
1131
+ /**
794
1132
  * Converts URLSearchParams to a plain object, preserving
795
1133
  * multi-valued keys as arrays instead of discarding duplicates.
796
1134
  */
@@ -849,4 +1187,4 @@ function isPKCERequired(client, request) {
849
1187
  return false;
850
1188
  }
851
1189
  //#endregion
852
- export { collectExtensionAccessTokenClaims as A, validateOAuthProviderExtensions as B, storeToken as C, validateClientScopes as D, validateClientCredentials as E, getExtensionGrantHandler as F, getSupportedAuthMethods as I, getSupportedGrantTypes as L, collectExtensionUserInfoClaims as M, extendOAuthProvider as N, verifyOAuthQueryParams as O, getClientDiscoveries as P, hasUserInfoClaimExtension as R, storeClientSecret as S, toResourceList as T, removeMaxAgeFromQuery as _, getClient as a, resolveSubjectIdentifier as b, getStoredToken as c, mergeDiscoveryMetadata as d, normalizeClientAuthenticationParameters as f, parsePrompt as g, parseClientMetadata as h, extractClientCredentials as i, collectExtensionIdTokenClaims as j, applyOAuthProviderMetadataExtensions as k, isPKCERequired as l, parseBearerToken as m, decryptStoredClientSecret as n, getJwtPlugin as o, normalizeTimestampValue as p, destructureCredentials as r, getOAuthProviderPlugin as s, clientAllowsGrant as t, isSessionFreshForSignedQuery as u, removePromptFromQuery as v, toAudienceClaim as w, searchParamsToQuery as x, resolveSessionAuthTime as y, isExtensionTokenEndpointAuthMethod as z };
1190
+ export { validateOAuthProviderExtensions as $, storeClientSecret as A, collectExtensionIdTokenClaims as B, parseScopes as C, resolveSessionAuthTime as D, removePromptFromQuery as E, validateClientCredentials as F, getExtensionGrantHandler as G, extendOAuthProvider as H, validateClientScopes as I, getSupportedGrantTypes as J, getExtensionInteractionRequest as K, verifyOAuthQueryParams as L, toAudienceClaim as M, toResourceList as N, resolveSubjectIdentifier as O, tokenError as P, validateExtensionClientMetadata as Q, applyOAuthProviderMetadataExtensions as R, parsePrompt as S, removeMaxAgeFromQuery as T, getClientDiscoveries as U, collectExtensionUserInfoClaims as V, getExtensionClientMetadataFields as W, hasUserInfoClaimExtension as X, hasExtensionClientMetadata as Y, isExtensionTokenEndpointAuthMethod as Z, mergeDiscoveryMetadata as _, decryptStoredClientSecret as a, parseBearerToken as b, extractClientCredentials as c, getConsentReferenceId as d, isForbiddenCimdClientMetadataField as et, getJwtPlugin as f, isSessionFreshForSignedQuery as g, isPKCERequired as h, consentCovers as i, storeToken as j, searchParamsToQuery as k, findConsent as l, getStoredToken as m, assertNotTrustedClient as n, deriveSectorIdentifier as o, getOAuthProviderPlugin as p, getSupportedAuthMethods as q, clientAllowsGrant as r, destructureCredentials as s, applyFormParams as t, stripReservedOAuthClientMetadataExtensions as tt, getClient as u, normalizeClientAuthenticationParameters as v, readFormParams as w, parseClientMetadata as x, normalizeTimestampValue as y, collectExtensionAccessTokenClaims as z };
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.7.9";
3
+ const PACKAGE_VERSION = "1.8.0-beta.0";
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/oauth-provider",
3
- "version": "1.7.9",
3
+ "version": "1.8.0-beta.0",
4
4
  "description": "An oauth provider plugin for Better Auth",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -71,16 +71,16 @@
71
71
  "devDependencies": {
72
72
  "listhen": "^1.9.0",
73
73
  "tsdown": "0.21.10",
74
- "@better-auth/core": "1.7.9",
75
- "@better-auth/memory-adapter": "1.7.9",
76
- "better-auth": "1.7.9"
74
+ "@better-auth/core": "1.8.0-beta.0",
75
+ "@better-auth/memory-adapter": "1.8.0-beta.0",
76
+ "better-auth": "1.8.0-beta.0"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "@better-auth/utils": "^0.4.2 || ^0.5.0",
80
80
  "@better-fetch/fetch": "1.3.2",
81
81
  "better-call": "1.4.2",
82
- "@better-auth/core": "^1.7.9",
83
- "better-auth": "^1.7.9"
82
+ "@better-auth/core": "^1.8.0-beta.0",
83
+ "better-auth": "^1.8.0-beta.0"
84
84
  },
85
85
  "scripts": {
86
86
  "build": "tsdown",