@codelitdev/oauth-server-kit 0.9.0 → 0.11.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/README.md +31 -0
- package/dist/better-auth.d.mts +107 -2
- package/dist/better-auth.d.ts +107 -2
- package/dist/better-auth.js +83 -1
- package/dist/better-auth.mjs +79 -2
- package/dist/express.d.mts +33 -2
- package/dist/express.d.ts +33 -2
- package/dist/express.js +22 -0
- package/dist/express.mjs +20 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/{types-DJxCmhYu.d.mts → types-sqdNO7Nc.d.mts} +2 -0
- package/dist/{types-DJxCmhYu.d.ts → types-sqdNO7Nc.d.ts} +2 -0
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -31,12 +31,19 @@ import {
|
|
|
31
31
|
|
|
32
32
|
import {
|
|
33
33
|
createOAuthProviderOptions,
|
|
34
|
+
isRecentlyAuthenticated,
|
|
34
35
|
resolveBetterAuthSession,
|
|
36
|
+
secureCookieOptions,
|
|
37
|
+
secureEmailAndPasswordOptions,
|
|
38
|
+
secureSessionOptions,
|
|
39
|
+
sessionCookieName,
|
|
35
40
|
} from "@codelitdev/oauth-server-kit/better-auth";
|
|
36
41
|
|
|
37
42
|
import {
|
|
43
|
+
createClientIpMiddleware,
|
|
38
44
|
createOAuthBearerMiddleware,
|
|
39
45
|
createOAuthPagesRouter,
|
|
46
|
+
parseTrustProxy,
|
|
40
47
|
} from "@codelitdev/oauth-server-kit/express";
|
|
41
48
|
|
|
42
49
|
import { createMcpOAuthDiscoveryRoutes } from "@codelitdev/oauth-server-kit/mcp";
|
|
@@ -57,9 +64,33 @@ application.
|
|
|
57
64
|
- Ordinary-login redirects are restricted to configured origins.
|
|
58
65
|
- Hosted pages reject framing and clear configured stale host-only cookies at
|
|
59
66
|
both `/login` and `/oauth/login`.
|
|
67
|
+
- Session cookies are `__Host-<prefix>.session_token` (Secure, `Path=/`, no
|
|
68
|
+
`Domain`) over https through `secureCookieOptions`; with cross-subdomain
|
|
69
|
+
cookies they stay `__Secure-`, and over http (local dev) they are plain.
|
|
70
|
+
Products keep their own `cookiePrefix` and pass it to the helper.
|
|
71
|
+
- `secureSessionOptions()` turns the session cookie cache off, so a revoked
|
|
72
|
+
session stops working on the next request.
|
|
73
|
+
- `secureEmailAndPasswordOptions()` keeps password sign-up off; a product
|
|
74
|
+
that opts in with `{ enabled: true }` also requires a verified email before
|
|
75
|
+
sign-in, so nobody can claim an address they don't own.
|
|
76
|
+
- `createClientIpMiddleware` resolves the client address from a trusted proxy
|
|
77
|
+
chain (`req.ip`, which ignores the attacker-controlled left of
|
|
78
|
+
`X-Forwarded-For`) into an internal header, and `secureCookieOptions`'
|
|
79
|
+
`clientIpHeader` makes Better Auth read only that header.
|
|
80
|
+
- Session identities carry `authenticatedAt`; `isRecentlyAuthenticated`
|
|
81
|
+
supports step-up checks. OAuth identities never count as recent.
|
|
60
82
|
- Product claims such as `team_id`, roles, or permissions are not returned as
|
|
61
83
|
trusted package identity fields.
|
|
62
84
|
|
|
85
|
+
Known limitation: `@better-auth/oauth-provider`'s RP-initiated logout
|
|
86
|
+
confirmation cookie (`<session cookie>.oauth_logout_confirmation`) is set on
|
|
87
|
+
`/api/auth/oauth2/end-session/confirm`. Browsers reject a `__Host-` cookie with a
|
|
88
|
+
path other than `/`, so under `__Host-` that confirmation step (shown when an
|
|
89
|
+
OAuth client calls end-session without a valid `id_token_hint`) cannot complete.
|
|
90
|
+
Neither the plugin options nor `advanced.cookies` can change that cookie's name
|
|
91
|
+
or path. Use cross-subdomain mode (`__Secure-`) if a product needs the flow.
|
|
92
|
+
See [ADR 0018](../../docs/decisions/0018-secure-session-cookies-and-client-ip.md).
|
|
93
|
+
|
|
63
94
|
See [docs/integration.md](docs/integration.md) for full web, mobile, REST, and
|
|
64
95
|
MCP setup, and [docs/architecture.md](docs/architecture.md) for the normative
|
|
65
96
|
product requirements and public contract.
|
package/dist/better-auth.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OAuthOptions } from '@better-auth/oauth-provider';
|
|
2
|
-
import { A as AuthenticationResult } from './types-
|
|
2
|
+
import { A as AuthenticationResult, a as AuthenticatedIdentity } from './types-sqdNO7Nc.mjs';
|
|
3
3
|
import '@better-auth/oauth-provider/resource-client';
|
|
4
4
|
|
|
5
5
|
type BetterAuthSession = {
|
|
@@ -11,6 +11,8 @@ type BetterAuthSession = {
|
|
|
11
11
|
session: {
|
|
12
12
|
id: string;
|
|
13
13
|
expiresAt: Date;
|
|
14
|
+
/** When the session was created, i.e. when the user signed in. */
|
|
15
|
+
createdAt: Date | string;
|
|
14
16
|
};
|
|
15
17
|
};
|
|
16
18
|
interface BetterAuthSessionApi {
|
|
@@ -37,4 +39,107 @@ interface CreateOAuthProviderOptionsInput {
|
|
|
37
39
|
}
|
|
38
40
|
declare function createOAuthProviderOptions(input: CreateOAuthProviderOptionsInput): OAuthOptions<string[]>;
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
interface IsRecentlyAuthenticatedOptions {
|
|
43
|
+
maxAgeMs: number;
|
|
44
|
+
now?: Date;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether the identity is a browser session that signed in within `maxAgeMs`.
|
|
48
|
+
* OAuth identities never count: a bearer token carries no interactive
|
|
49
|
+
* sign-in. Products choose which actions require this (step-up checks).
|
|
50
|
+
*/
|
|
51
|
+
declare function isRecentlyAuthenticated(identity: AuthenticatedIdentity, options: IsRecentlyAuthenticatedOptions): boolean;
|
|
52
|
+
|
|
53
|
+
interface SecureCookieOptionsInput {
|
|
54
|
+
/** Public URL Better Auth is served from (its `baseURL`). */
|
|
55
|
+
baseURL: string;
|
|
56
|
+
/** Product-specific prefix, e.g. `courselit`. Must not already carry `__Host-`/`__Secure-`. */
|
|
57
|
+
cookiePrefix: string;
|
|
58
|
+
/** Pass through only when cookies must be shared across sibling subdomains. */
|
|
59
|
+
crossSubDomainCookies?: {
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
domain?: string;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Name of an internal header the app's edge sets to the trusted client
|
|
65
|
+
* address (see `createClientIpMiddleware`). Better Auth then reads the
|
|
66
|
+
* address only from it, never from `X-Forwarded-For`.
|
|
67
|
+
*/
|
|
68
|
+
clientIpHeader?: string;
|
|
69
|
+
}
|
|
70
|
+
/** The subset of Better Auth's `advanced` options these helpers produce. */
|
|
71
|
+
interface SecureCookieAdvancedOptions {
|
|
72
|
+
cookiePrefix: string;
|
|
73
|
+
useSecureCookies?: boolean;
|
|
74
|
+
defaultCookieAttributes?: {
|
|
75
|
+
secure: boolean;
|
|
76
|
+
path: string;
|
|
77
|
+
};
|
|
78
|
+
crossSubDomainCookies?: {
|
|
79
|
+
enabled: boolean;
|
|
80
|
+
domain?: string;
|
|
81
|
+
};
|
|
82
|
+
ipAddress?: {
|
|
83
|
+
ipAddressHeaders: string[];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Better Auth `advanced` options for hardened session cookies. Spread them
|
|
88
|
+
* into your own config: `advanced: { ...secureCookieOptions({...}) }`.
|
|
89
|
+
*
|
|
90
|
+
* - https, no cross-subdomain cookies: the session cookie is
|
|
91
|
+
* `__Host-<prefix>.session_token` (Secure, `Path=/`, no `Domain`), so a
|
|
92
|
+
* sibling subdomain cannot plant or shadow it. Better Auth only knows
|
|
93
|
+
* `__Secure-` and would produce `__Secure-__Host-...`, so `__Host-` is
|
|
94
|
+
* folded into `cookiePrefix` and Secure is set via `defaultCookieAttributes`.
|
|
95
|
+
* - https with cross-subdomain cookies: `__Host-` forbids `Domain`, so the
|
|
96
|
+
* cookie keeps Better Auth's `__Secure-` prefix and is shared as configured.
|
|
97
|
+
* - http (local development): plain `<prefix>.session_token`.
|
|
98
|
+
*
|
|
99
|
+
* Known limitation: `@better-auth/oauth-provider` stores a short-lived
|
|
100
|
+
* RP-initiated-logout confirmation cookie named
|
|
101
|
+
* `<session cookie name>.oauth_logout_confirmation` on the path
|
|
102
|
+
* `/api/auth/oauth2/end-session/confirm`. Browsers reject a `__Host-` cookie
|
|
103
|
+
* with any path but `/`, so that cookie is dropped under `__Host-` and the
|
|
104
|
+
* logout confirmation step (shown when an OAuth client calls end-session
|
|
105
|
+
* without a valid `id_token_hint`) cannot complete. Neither the plugin's
|
|
106
|
+
* options nor `advanced.cookies` can change its name or path. Products that
|
|
107
|
+
* need that flow must use cross-subdomain mode (`__Secure-`) or accept it.
|
|
108
|
+
*/
|
|
109
|
+
declare function secureCookieOptions(input: SecureCookieOptionsInput): SecureCookieAdvancedOptions;
|
|
110
|
+
/**
|
|
111
|
+
* The exact session-token cookie name Better Auth produces for options built
|
|
112
|
+
* by `secureCookieOptions`, for products that choose credentials by cookie
|
|
113
|
+
* name. Pass the object `secureCookieOptions` returned.
|
|
114
|
+
*/
|
|
115
|
+
declare function sessionCookieName(advanced: Pick<SecureCookieAdvancedOptions, "cookiePrefix" | "useSecureCookies">): string;
|
|
116
|
+
/**
|
|
117
|
+
* Better Auth `session` options with the cookie cache off: every request
|
|
118
|
+
* checks the session row, so a revoked session stops working immediately
|
|
119
|
+
* instead of until the cached cookie expires (5 minutes by default).
|
|
120
|
+
*/
|
|
121
|
+
declare function secureSessionOptions(): {
|
|
122
|
+
cookieCache: {
|
|
123
|
+
enabled: false;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
interface SecureEmailAndPasswordInput {
|
|
127
|
+
/** Turn password sign-in on. Off by default: products sign people in
|
|
128
|
+
* with email codes, social providers or OAuth. */
|
|
129
|
+
enabled?: boolean;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Better Auth `emailAndPassword` settings. Password sign-up is off unless a
|
|
133
|
+
* product opts in: with it on and no verification, anyone can register an
|
|
134
|
+
* address they don't own and sit on it until the real owner shows up. When
|
|
135
|
+
* a product does enable passwords, the address must be verified before the
|
|
136
|
+
* account can sign in.
|
|
137
|
+
*/
|
|
138
|
+
declare function secureEmailAndPasswordOptions(input?: SecureEmailAndPasswordInput): {
|
|
139
|
+
enabled: false;
|
|
140
|
+
} | {
|
|
141
|
+
enabled: true;
|
|
142
|
+
requireEmailVerification: true;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export { type BetterAuthSession, type BetterAuthSessionApi, type CreateOAuthProviderOptionsInput, type IsRecentlyAuthenticatedOptions, type ResolveBetterAuthSessionOptions, type SecureCookieAdvancedOptions, type SecureCookieOptionsInput, type SecureEmailAndPasswordInput, createOAuthProviderOptions, isRecentlyAuthenticated, resolveBetterAuthSession, secureCookieOptions, secureEmailAndPasswordOptions, secureSessionOptions, sessionCookieName };
|
package/dist/better-auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OAuthOptions } from '@better-auth/oauth-provider';
|
|
2
|
-
import { A as AuthenticationResult } from './types-
|
|
2
|
+
import { A as AuthenticationResult, a as AuthenticatedIdentity } from './types-sqdNO7Nc.js';
|
|
3
3
|
import '@better-auth/oauth-provider/resource-client';
|
|
4
4
|
|
|
5
5
|
type BetterAuthSession = {
|
|
@@ -11,6 +11,8 @@ type BetterAuthSession = {
|
|
|
11
11
|
session: {
|
|
12
12
|
id: string;
|
|
13
13
|
expiresAt: Date;
|
|
14
|
+
/** When the session was created, i.e. when the user signed in. */
|
|
15
|
+
createdAt: Date | string;
|
|
14
16
|
};
|
|
15
17
|
};
|
|
16
18
|
interface BetterAuthSessionApi {
|
|
@@ -37,4 +39,107 @@ interface CreateOAuthProviderOptionsInput {
|
|
|
37
39
|
}
|
|
38
40
|
declare function createOAuthProviderOptions(input: CreateOAuthProviderOptionsInput): OAuthOptions<string[]>;
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
interface IsRecentlyAuthenticatedOptions {
|
|
43
|
+
maxAgeMs: number;
|
|
44
|
+
now?: Date;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether the identity is a browser session that signed in within `maxAgeMs`.
|
|
48
|
+
* OAuth identities never count: a bearer token carries no interactive
|
|
49
|
+
* sign-in. Products choose which actions require this (step-up checks).
|
|
50
|
+
*/
|
|
51
|
+
declare function isRecentlyAuthenticated(identity: AuthenticatedIdentity, options: IsRecentlyAuthenticatedOptions): boolean;
|
|
52
|
+
|
|
53
|
+
interface SecureCookieOptionsInput {
|
|
54
|
+
/** Public URL Better Auth is served from (its `baseURL`). */
|
|
55
|
+
baseURL: string;
|
|
56
|
+
/** Product-specific prefix, e.g. `courselit`. Must not already carry `__Host-`/`__Secure-`. */
|
|
57
|
+
cookiePrefix: string;
|
|
58
|
+
/** Pass through only when cookies must be shared across sibling subdomains. */
|
|
59
|
+
crossSubDomainCookies?: {
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
domain?: string;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Name of an internal header the app's edge sets to the trusted client
|
|
65
|
+
* address (see `createClientIpMiddleware`). Better Auth then reads the
|
|
66
|
+
* address only from it, never from `X-Forwarded-For`.
|
|
67
|
+
*/
|
|
68
|
+
clientIpHeader?: string;
|
|
69
|
+
}
|
|
70
|
+
/** The subset of Better Auth's `advanced` options these helpers produce. */
|
|
71
|
+
interface SecureCookieAdvancedOptions {
|
|
72
|
+
cookiePrefix: string;
|
|
73
|
+
useSecureCookies?: boolean;
|
|
74
|
+
defaultCookieAttributes?: {
|
|
75
|
+
secure: boolean;
|
|
76
|
+
path: string;
|
|
77
|
+
};
|
|
78
|
+
crossSubDomainCookies?: {
|
|
79
|
+
enabled: boolean;
|
|
80
|
+
domain?: string;
|
|
81
|
+
};
|
|
82
|
+
ipAddress?: {
|
|
83
|
+
ipAddressHeaders: string[];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Better Auth `advanced` options for hardened session cookies. Spread them
|
|
88
|
+
* into your own config: `advanced: { ...secureCookieOptions({...}) }`.
|
|
89
|
+
*
|
|
90
|
+
* - https, no cross-subdomain cookies: the session cookie is
|
|
91
|
+
* `__Host-<prefix>.session_token` (Secure, `Path=/`, no `Domain`), so a
|
|
92
|
+
* sibling subdomain cannot plant or shadow it. Better Auth only knows
|
|
93
|
+
* `__Secure-` and would produce `__Secure-__Host-...`, so `__Host-` is
|
|
94
|
+
* folded into `cookiePrefix` and Secure is set via `defaultCookieAttributes`.
|
|
95
|
+
* - https with cross-subdomain cookies: `__Host-` forbids `Domain`, so the
|
|
96
|
+
* cookie keeps Better Auth's `__Secure-` prefix and is shared as configured.
|
|
97
|
+
* - http (local development): plain `<prefix>.session_token`.
|
|
98
|
+
*
|
|
99
|
+
* Known limitation: `@better-auth/oauth-provider` stores a short-lived
|
|
100
|
+
* RP-initiated-logout confirmation cookie named
|
|
101
|
+
* `<session cookie name>.oauth_logout_confirmation` on the path
|
|
102
|
+
* `/api/auth/oauth2/end-session/confirm`. Browsers reject a `__Host-` cookie
|
|
103
|
+
* with any path but `/`, so that cookie is dropped under `__Host-` and the
|
|
104
|
+
* logout confirmation step (shown when an OAuth client calls end-session
|
|
105
|
+
* without a valid `id_token_hint`) cannot complete. Neither the plugin's
|
|
106
|
+
* options nor `advanced.cookies` can change its name or path. Products that
|
|
107
|
+
* need that flow must use cross-subdomain mode (`__Secure-`) or accept it.
|
|
108
|
+
*/
|
|
109
|
+
declare function secureCookieOptions(input: SecureCookieOptionsInput): SecureCookieAdvancedOptions;
|
|
110
|
+
/**
|
|
111
|
+
* The exact session-token cookie name Better Auth produces for options built
|
|
112
|
+
* by `secureCookieOptions`, for products that choose credentials by cookie
|
|
113
|
+
* name. Pass the object `secureCookieOptions` returned.
|
|
114
|
+
*/
|
|
115
|
+
declare function sessionCookieName(advanced: Pick<SecureCookieAdvancedOptions, "cookiePrefix" | "useSecureCookies">): string;
|
|
116
|
+
/**
|
|
117
|
+
* Better Auth `session` options with the cookie cache off: every request
|
|
118
|
+
* checks the session row, so a revoked session stops working immediately
|
|
119
|
+
* instead of until the cached cookie expires (5 minutes by default).
|
|
120
|
+
*/
|
|
121
|
+
declare function secureSessionOptions(): {
|
|
122
|
+
cookieCache: {
|
|
123
|
+
enabled: false;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
interface SecureEmailAndPasswordInput {
|
|
127
|
+
/** Turn password sign-in on. Off by default: products sign people in
|
|
128
|
+
* with email codes, social providers or OAuth. */
|
|
129
|
+
enabled?: boolean;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Better Auth `emailAndPassword` settings. Password sign-up is off unless a
|
|
133
|
+
* product opts in: with it on and no verification, anyone can register an
|
|
134
|
+
* address they don't own and sit on it until the real owner shows up. When
|
|
135
|
+
* a product does enable passwords, the address must be verified before the
|
|
136
|
+
* account can sign in.
|
|
137
|
+
*/
|
|
138
|
+
declare function secureEmailAndPasswordOptions(input?: SecureEmailAndPasswordInput): {
|
|
139
|
+
enabled: false;
|
|
140
|
+
} | {
|
|
141
|
+
enabled: true;
|
|
142
|
+
requireEmailVerification: true;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export { type BetterAuthSession, type BetterAuthSessionApi, type CreateOAuthProviderOptionsInput, type IsRecentlyAuthenticatedOptions, type ResolveBetterAuthSessionOptions, type SecureCookieAdvancedOptions, type SecureCookieOptionsInput, type SecureEmailAndPasswordInput, createOAuthProviderOptions, isRecentlyAuthenticated, resolveBetterAuthSession, secureCookieOptions, secureEmailAndPasswordOptions, secureSessionOptions, sessionCookieName };
|
package/dist/better-auth.js
CHANGED
|
@@ -49,7 +49,8 @@ async function resolveBetterAuthSession(options, headers) {
|
|
|
49
49
|
subject: session.user.id,
|
|
50
50
|
email: session.user.email,
|
|
51
51
|
name: session.user.name,
|
|
52
|
-
scopes: []
|
|
52
|
+
scopes: [],
|
|
53
|
+
authenticatedAt: new Date(session.session.createdAt)
|
|
53
54
|
}
|
|
54
55
|
};
|
|
55
56
|
} catch (cause) {
|
|
@@ -124,6 +125,87 @@ function createOAuthProviderOptions(input) {
|
|
|
124
125
|
allowUnauthenticatedClientRegistration: (_b = input.allowUnauthenticatedDynamicClientRegistration) != null ? _b : false
|
|
125
126
|
}, clientRegistrationDefaultScopes ? { clientRegistrationDefaultScopes } : {}), clientRegistrationAllowedScopes ? { clientRegistrationAllowedScopes } : {});
|
|
126
127
|
}
|
|
128
|
+
function isRecentlyAuthenticated(identity, options) {
|
|
129
|
+
var _a, _b;
|
|
130
|
+
if (identity.method !== "session") return false;
|
|
131
|
+
const signedInAt = (_a = identity.authenticatedAt) == null ? void 0 : _a.getTime();
|
|
132
|
+
if (signedInAt === void 0 || !Number.isFinite(signedInAt)) return false;
|
|
133
|
+
const age = ((_b = options.now) != null ? _b : /* @__PURE__ */ new Date()).getTime() - signedInAt;
|
|
134
|
+
return age >= -6e4 && age <= options.maxAgeMs;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/secure-cookies.ts
|
|
138
|
+
var COOKIE_PREFIX_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
139
|
+
var HOST_PREFIX = "__Host-";
|
|
140
|
+
var SECURE_PREFIX = "__Secure-";
|
|
141
|
+
var SESSION_TOKEN_SUFFIX = ".session_token";
|
|
142
|
+
function assertBaseUrl(value) {
|
|
143
|
+
let url;
|
|
144
|
+
try {
|
|
145
|
+
url = new URL(value);
|
|
146
|
+
} catch (e) {
|
|
147
|
+
throw new Error("baseURL must be an absolute URL");
|
|
148
|
+
}
|
|
149
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
150
|
+
throw new Error("baseURL must use http or https");
|
|
151
|
+
}
|
|
152
|
+
return url;
|
|
153
|
+
}
|
|
154
|
+
function assertCookiePrefix(value) {
|
|
155
|
+
if (!COOKIE_PREFIX_PATTERN.test(value)) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"cookiePrefix must be non-empty and contain only letters, digits, '_' or '-'"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (value.startsWith(HOST_PREFIX) || value.startsWith(SECURE_PREFIX)) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
"cookiePrefix must not include __Host- or __Secure-; the kit adds it"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function secureCookieOptions(input) {
|
|
167
|
+
var _a, _b;
|
|
168
|
+
const url = assertBaseUrl(input.baseURL);
|
|
169
|
+
assertCookiePrefix(input.cookiePrefix);
|
|
170
|
+
const clientIpHeader = (_a = input.clientIpHeader) == null ? void 0 : _a.trim().toLowerCase();
|
|
171
|
+
if (input.clientIpHeader !== void 0 && !clientIpHeader) {
|
|
172
|
+
throw new Error("clientIpHeader must not be empty");
|
|
173
|
+
}
|
|
174
|
+
const ipAddress = clientIpHeader ? { ipAddress: { ipAddressHeaders: [clientIpHeader] } } : {};
|
|
175
|
+
const crossSubdomain = ((_b = input.crossSubDomainCookies) == null ? void 0 : _b.enabled) === true;
|
|
176
|
+
if (url.protocol !== "https:") {
|
|
177
|
+
return __spreadValues(__spreadValues({
|
|
178
|
+
cookiePrefix: input.cookiePrefix
|
|
179
|
+
}, input.crossSubDomainCookies ? { crossSubDomainCookies: input.crossSubDomainCookies } : {}), ipAddress);
|
|
180
|
+
}
|
|
181
|
+
if (crossSubdomain) {
|
|
182
|
+
return __spreadValues({
|
|
183
|
+
cookiePrefix: input.cookiePrefix,
|
|
184
|
+
useSecureCookies: true,
|
|
185
|
+
crossSubDomainCookies: input.crossSubDomainCookies
|
|
186
|
+
}, ipAddress);
|
|
187
|
+
}
|
|
188
|
+
return __spreadValues({
|
|
189
|
+
cookiePrefix: `${HOST_PREFIX}${input.cookiePrefix}`,
|
|
190
|
+
useSecureCookies: false,
|
|
191
|
+
defaultCookieAttributes: { secure: true, path: "/" }
|
|
192
|
+
}, ipAddress);
|
|
193
|
+
}
|
|
194
|
+
function sessionCookieName(advanced) {
|
|
195
|
+
const secure = advanced.useSecureCookies === true ? SECURE_PREFIX : "";
|
|
196
|
+
return `${secure}${advanced.cookiePrefix}${SESSION_TOKEN_SUFFIX}`;
|
|
197
|
+
}
|
|
198
|
+
function secureSessionOptions() {
|
|
199
|
+
return { cookieCache: { enabled: false } };
|
|
200
|
+
}
|
|
201
|
+
function secureEmailAndPasswordOptions(input = {}) {
|
|
202
|
+
return input.enabled === true ? { enabled: true, requireEmailVerification: true } : { enabled: false };
|
|
203
|
+
}
|
|
127
204
|
|
|
128
205
|
exports.createOAuthProviderOptions = createOAuthProviderOptions;
|
|
206
|
+
exports.isRecentlyAuthenticated = isRecentlyAuthenticated;
|
|
129
207
|
exports.resolveBetterAuthSession = resolveBetterAuthSession;
|
|
208
|
+
exports.secureCookieOptions = secureCookieOptions;
|
|
209
|
+
exports.secureEmailAndPasswordOptions = secureEmailAndPasswordOptions;
|
|
210
|
+
exports.secureSessionOptions = secureSessionOptions;
|
|
211
|
+
exports.sessionCookieName = sessionCookieName;
|
package/dist/better-auth.mjs
CHANGED
|
@@ -32,7 +32,8 @@ async function resolveBetterAuthSession(options, headers) {
|
|
|
32
32
|
subject: session.user.id,
|
|
33
33
|
email: session.user.email,
|
|
34
34
|
name: session.user.name,
|
|
35
|
-
scopes: []
|
|
35
|
+
scopes: [],
|
|
36
|
+
authenticatedAt: new Date(session.session.createdAt)
|
|
36
37
|
}
|
|
37
38
|
};
|
|
38
39
|
} catch (cause) {
|
|
@@ -107,5 +108,81 @@ function createOAuthProviderOptions(input) {
|
|
|
107
108
|
allowUnauthenticatedClientRegistration: (_b = input.allowUnauthenticatedDynamicClientRegistration) != null ? _b : false
|
|
108
109
|
}, clientRegistrationDefaultScopes ? { clientRegistrationDefaultScopes } : {}), clientRegistrationAllowedScopes ? { clientRegistrationAllowedScopes } : {});
|
|
109
110
|
}
|
|
111
|
+
function isRecentlyAuthenticated(identity, options) {
|
|
112
|
+
var _a, _b;
|
|
113
|
+
if (identity.method !== "session") return false;
|
|
114
|
+
const signedInAt = (_a = identity.authenticatedAt) == null ? void 0 : _a.getTime();
|
|
115
|
+
if (signedInAt === void 0 || !Number.isFinite(signedInAt)) return false;
|
|
116
|
+
const age = ((_b = options.now) != null ? _b : /* @__PURE__ */ new Date()).getTime() - signedInAt;
|
|
117
|
+
return age >= -6e4 && age <= options.maxAgeMs;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/secure-cookies.ts
|
|
121
|
+
var COOKIE_PREFIX_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
122
|
+
var HOST_PREFIX = "__Host-";
|
|
123
|
+
var SECURE_PREFIX = "__Secure-";
|
|
124
|
+
var SESSION_TOKEN_SUFFIX = ".session_token";
|
|
125
|
+
function assertBaseUrl(value) {
|
|
126
|
+
let url;
|
|
127
|
+
try {
|
|
128
|
+
url = new URL(value);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
throw new Error("baseURL must be an absolute URL");
|
|
131
|
+
}
|
|
132
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
133
|
+
throw new Error("baseURL must use http or https");
|
|
134
|
+
}
|
|
135
|
+
return url;
|
|
136
|
+
}
|
|
137
|
+
function assertCookiePrefix(value) {
|
|
138
|
+
if (!COOKIE_PREFIX_PATTERN.test(value)) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"cookiePrefix must be non-empty and contain only letters, digits, '_' or '-'"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (value.startsWith(HOST_PREFIX) || value.startsWith(SECURE_PREFIX)) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"cookiePrefix must not include __Host- or __Secure-; the kit adds it"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function secureCookieOptions(input) {
|
|
150
|
+
var _a, _b;
|
|
151
|
+
const url = assertBaseUrl(input.baseURL);
|
|
152
|
+
assertCookiePrefix(input.cookiePrefix);
|
|
153
|
+
const clientIpHeader = (_a = input.clientIpHeader) == null ? void 0 : _a.trim().toLowerCase();
|
|
154
|
+
if (input.clientIpHeader !== void 0 && !clientIpHeader) {
|
|
155
|
+
throw new Error("clientIpHeader must not be empty");
|
|
156
|
+
}
|
|
157
|
+
const ipAddress = clientIpHeader ? { ipAddress: { ipAddressHeaders: [clientIpHeader] } } : {};
|
|
158
|
+
const crossSubdomain = ((_b = input.crossSubDomainCookies) == null ? void 0 : _b.enabled) === true;
|
|
159
|
+
if (url.protocol !== "https:") {
|
|
160
|
+
return __spreadValues(__spreadValues({
|
|
161
|
+
cookiePrefix: input.cookiePrefix
|
|
162
|
+
}, input.crossSubDomainCookies ? { crossSubDomainCookies: input.crossSubDomainCookies } : {}), ipAddress);
|
|
163
|
+
}
|
|
164
|
+
if (crossSubdomain) {
|
|
165
|
+
return __spreadValues({
|
|
166
|
+
cookiePrefix: input.cookiePrefix,
|
|
167
|
+
useSecureCookies: true,
|
|
168
|
+
crossSubDomainCookies: input.crossSubDomainCookies
|
|
169
|
+
}, ipAddress);
|
|
170
|
+
}
|
|
171
|
+
return __spreadValues({
|
|
172
|
+
cookiePrefix: `${HOST_PREFIX}${input.cookiePrefix}`,
|
|
173
|
+
useSecureCookies: false,
|
|
174
|
+
defaultCookieAttributes: { secure: true, path: "/" }
|
|
175
|
+
}, ipAddress);
|
|
176
|
+
}
|
|
177
|
+
function sessionCookieName(advanced) {
|
|
178
|
+
const secure = advanced.useSecureCookies === true ? SECURE_PREFIX : "";
|
|
179
|
+
return `${secure}${advanced.cookiePrefix}${SESSION_TOKEN_SUFFIX}`;
|
|
180
|
+
}
|
|
181
|
+
function secureSessionOptions() {
|
|
182
|
+
return { cookieCache: { enabled: false } };
|
|
183
|
+
}
|
|
184
|
+
function secureEmailAndPasswordOptions(input = {}) {
|
|
185
|
+
return input.enabled === true ? { enabled: true, requireEmailVerification: true } : { enabled: false };
|
|
186
|
+
}
|
|
110
187
|
|
|
111
|
-
export { createOAuthProviderOptions, resolveBetterAuthSession };
|
|
188
|
+
export { createOAuthProviderOptions, isRecentlyAuthenticated, resolveBetterAuthSession, secureCookieOptions, secureEmailAndPasswordOptions, secureSessionOptions, sessionCookieName };
|
package/dist/express.d.mts
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { RequestHandler, Router } from 'express';
|
|
2
|
-
import { a as AuthenticatedIdentity, A as AuthenticationResult } from './types-
|
|
2
|
+
import { a as AuthenticatedIdentity, A as AuthenticationResult } from './types-sqdNO7Nc.mjs';
|
|
3
3
|
import { VerifyOAuthAccessTokenOptions } from './index.mjs';
|
|
4
4
|
import '@better-auth/oauth-provider/resource-client';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Private networks: the cluster and local development. Fine when clients come
|
|
8
|
+
* from the public internet. If clients can reach the app from a private
|
|
9
|
+
* network too, set `TRUST_PROXY` to your proxies' actual addresses or subnet,
|
|
10
|
+
* or those clients could forge their address (Better Auth gives the same
|
|
11
|
+
* advice for its own `trustedProxies`).
|
|
12
|
+
*/
|
|
13
|
+
declare const DEFAULT_TRUST_PROXY = "loopback, linklocal, uniquelocal";
|
|
14
|
+
/**
|
|
15
|
+
* Parses a `TRUST_PROXY` value into what Express's `trust proxy` setting
|
|
16
|
+
* accepts: a hop count (`1`), `true`/`false`, or a comma-separated list of
|
|
17
|
+
* addresses, CIDRs and the names `loopback`, `linklocal`, `uniquelocal`.
|
|
18
|
+
* Empty means {@link DEFAULT_TRUST_PROXY}.
|
|
19
|
+
*/
|
|
20
|
+
declare function parseTrustProxy(raw?: string): boolean | number | string;
|
|
21
|
+
interface CreateClientIpMiddlewareOptions {
|
|
22
|
+
/** Internal header to carry the resolved client address. */
|
|
23
|
+
header: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sets `req.headers[header]` to the client address, overwriting anything the
|
|
27
|
+
* client sent. Pair it with `secureCookieOptions({ clientIpHeader })` so
|
|
28
|
+
* Better Auth's rate limiting and session IP never read `X-Forwarded-For`.
|
|
29
|
+
*
|
|
30
|
+
* The app must call `app.set("trust proxy", parseTrustProxy(process.env.TRUST_PROXY))`
|
|
31
|
+
* first and mount this before Better Auth's handler. `req.ip` then walks
|
|
32
|
+
* `X-Forwarded-For` from the right, skipping trusted proxy hops, so the
|
|
33
|
+
* attacker-controlled left side never counts.
|
|
34
|
+
*/
|
|
35
|
+
declare function createClientIpMiddleware(options: CreateClientIpMiddlewareOptions): RequestHandler;
|
|
36
|
+
|
|
6
37
|
declare global {
|
|
7
38
|
namespace Express {
|
|
8
39
|
interface Request {
|
|
@@ -45,4 +76,4 @@ interface CreateOAuthPagesRouterOptions {
|
|
|
45
76
|
}
|
|
46
77
|
declare function createOAuthPagesRouter(rawOptions: CreateOAuthPagesRouterOptions): Router;
|
|
47
78
|
|
|
48
|
-
export { type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, type HostedLoginCaptcha, type HostedLoginMethod, createOAuthBearerMiddleware, createOAuthPagesRouter };
|
|
79
|
+
export { type CreateClientIpMiddlewareOptions, type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, DEFAULT_TRUST_PROXY, type HostedLoginCaptcha, type HostedLoginMethod, createClientIpMiddleware, createOAuthBearerMiddleware, createOAuthPagesRouter, parseTrustProxy };
|
package/dist/express.d.ts
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { RequestHandler, Router } from 'express';
|
|
2
|
-
import { a as AuthenticatedIdentity, A as AuthenticationResult } from './types-
|
|
2
|
+
import { a as AuthenticatedIdentity, A as AuthenticationResult } from './types-sqdNO7Nc.js';
|
|
3
3
|
import { VerifyOAuthAccessTokenOptions } from './index.js';
|
|
4
4
|
import '@better-auth/oauth-provider/resource-client';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Private networks: the cluster and local development. Fine when clients come
|
|
8
|
+
* from the public internet. If clients can reach the app from a private
|
|
9
|
+
* network too, set `TRUST_PROXY` to your proxies' actual addresses or subnet,
|
|
10
|
+
* or those clients could forge their address (Better Auth gives the same
|
|
11
|
+
* advice for its own `trustedProxies`).
|
|
12
|
+
*/
|
|
13
|
+
declare const DEFAULT_TRUST_PROXY = "loopback, linklocal, uniquelocal";
|
|
14
|
+
/**
|
|
15
|
+
* Parses a `TRUST_PROXY` value into what Express's `trust proxy` setting
|
|
16
|
+
* accepts: a hop count (`1`), `true`/`false`, or a comma-separated list of
|
|
17
|
+
* addresses, CIDRs and the names `loopback`, `linklocal`, `uniquelocal`.
|
|
18
|
+
* Empty means {@link DEFAULT_TRUST_PROXY}.
|
|
19
|
+
*/
|
|
20
|
+
declare function parseTrustProxy(raw?: string): boolean | number | string;
|
|
21
|
+
interface CreateClientIpMiddlewareOptions {
|
|
22
|
+
/** Internal header to carry the resolved client address. */
|
|
23
|
+
header: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sets `req.headers[header]` to the client address, overwriting anything the
|
|
27
|
+
* client sent. Pair it with `secureCookieOptions({ clientIpHeader })` so
|
|
28
|
+
* Better Auth's rate limiting and session IP never read `X-Forwarded-For`.
|
|
29
|
+
*
|
|
30
|
+
* The app must call `app.set("trust proxy", parseTrustProxy(process.env.TRUST_PROXY))`
|
|
31
|
+
* first and mount this before Better Auth's handler. `req.ip` then walks
|
|
32
|
+
* `X-Forwarded-For` from the right, skipping trusted proxy hops, so the
|
|
33
|
+
* attacker-controlled left side never counts.
|
|
34
|
+
*/
|
|
35
|
+
declare function createClientIpMiddleware(options: CreateClientIpMiddlewareOptions): RequestHandler;
|
|
36
|
+
|
|
6
37
|
declare global {
|
|
7
38
|
namespace Express {
|
|
8
39
|
interface Request {
|
|
@@ -45,4 +76,4 @@ interface CreateOAuthPagesRouterOptions {
|
|
|
45
76
|
}
|
|
46
77
|
declare function createOAuthPagesRouter(rawOptions: CreateOAuthPagesRouterOptions): Router;
|
|
47
78
|
|
|
48
|
-
export { type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, type HostedLoginCaptcha, type HostedLoginMethod, createOAuthBearerMiddleware, createOAuthPagesRouter };
|
|
79
|
+
export { type CreateClientIpMiddlewareOptions, type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, DEFAULT_TRUST_PROXY, type HostedLoginCaptcha, type HostedLoginMethod, createClientIpMiddleware, createOAuthBearerMiddleware, createOAuthPagesRouter, parseTrustProxy };
|
package/dist/express.js
CHANGED
|
@@ -34,6 +34,25 @@ var __objRest = (source, exclude) => {
|
|
|
34
34
|
return target;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
// src/client-ip.ts
|
|
38
|
+
var DEFAULT_TRUST_PROXY = "loopback, linklocal, uniquelocal";
|
|
39
|
+
function parseTrustProxy(raw) {
|
|
40
|
+
const value = raw == null ? void 0 : raw.trim();
|
|
41
|
+
if (!value) return DEFAULT_TRUST_PROXY;
|
|
42
|
+
if (value === "true") return true;
|
|
43
|
+
if (value === "false") return false;
|
|
44
|
+
if (/^\d+$/.test(value)) return Number.parseInt(value, 10);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
function createClientIpMiddleware(options) {
|
|
48
|
+
const header = options.header.trim().toLowerCase();
|
|
49
|
+
if (!header) throw new Error("header must not be empty");
|
|
50
|
+
return (req, _res, next) => {
|
|
51
|
+
req.headers[header] = req.ip || req.socket.remoteAddress || "unknown";
|
|
52
|
+
next();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
37
56
|
// src/verify-access-token.ts
|
|
38
57
|
function requireAbsoluteUrl(value, field) {
|
|
39
58
|
let url;
|
|
@@ -433,5 +452,8 @@ function createOAuthPagesRouter(rawOptions) {
|
|
|
433
452
|
return router;
|
|
434
453
|
}
|
|
435
454
|
|
|
455
|
+
exports.DEFAULT_TRUST_PROXY = DEFAULT_TRUST_PROXY;
|
|
456
|
+
exports.createClientIpMiddleware = createClientIpMiddleware;
|
|
436
457
|
exports.createOAuthBearerMiddleware = createOAuthBearerMiddleware;
|
|
437
458
|
exports.createOAuthPagesRouter = createOAuthPagesRouter;
|
|
459
|
+
exports.parseTrustProxy = parseTrustProxy;
|
package/dist/express.mjs
CHANGED
|
@@ -2,6 +2,25 @@ import { verifyOAuthAccessToken, validateOAuthVerificationOptions } from './chun
|
|
|
2
2
|
import { __objRest, __spreadProps, __spreadValues } from './chunk-FWCSY2DS.mjs';
|
|
3
3
|
import { Router } from 'express';
|
|
4
4
|
|
|
5
|
+
// src/client-ip.ts
|
|
6
|
+
var DEFAULT_TRUST_PROXY = "loopback, linklocal, uniquelocal";
|
|
7
|
+
function parseTrustProxy(raw) {
|
|
8
|
+
const value = raw == null ? void 0 : raw.trim();
|
|
9
|
+
if (!value) return DEFAULT_TRUST_PROXY;
|
|
10
|
+
if (value === "true") return true;
|
|
11
|
+
if (value === "false") return false;
|
|
12
|
+
if (/^\d+$/.test(value)) return Number.parseInt(value, 10);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function createClientIpMiddleware(options) {
|
|
16
|
+
const header = options.header.trim().toLowerCase();
|
|
17
|
+
if (!header) throw new Error("header must not be empty");
|
|
18
|
+
return (req, _res, next) => {
|
|
19
|
+
req.headers[header] = req.ip || req.socket.remoteAddress || "unknown";
|
|
20
|
+
next();
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
5
24
|
// src/express.ts
|
|
6
25
|
function setBearerChallenge(response, resourceMetadataUrl) {
|
|
7
26
|
if (resourceMetadataUrl) {
|
|
@@ -320,4 +339,4 @@ function createOAuthPagesRouter(rawOptions) {
|
|
|
320
339
|
return router;
|
|
321
340
|
}
|
|
322
341
|
|
|
323
|
-
export { createOAuthBearerMiddleware, createOAuthPagesRouter };
|
|
342
|
+
export { DEFAULT_TRUST_PROXY, createClientIpMiddleware, createOAuthBearerMiddleware, createOAuthPagesRouter, parseTrustProxy };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { O as OAuthResourceClient, A as AuthenticationResult } from './types-
|
|
2
|
-
export { a as AuthenticatedIdentity } from './types-
|
|
1
|
+
import { O as OAuthResourceClient, A as AuthenticationResult } from './types-sqdNO7Nc.mjs';
|
|
2
|
+
export { a as AuthenticatedIdentity } from './types-sqdNO7Nc.mjs';
|
|
3
3
|
import '@better-auth/oauth-provider/resource-client';
|
|
4
4
|
|
|
5
5
|
interface VerifyOAuthAccessTokenOptions {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { O as OAuthResourceClient, A as AuthenticationResult } from './types-
|
|
2
|
-
export { a as AuthenticatedIdentity } from './types-
|
|
1
|
+
import { O as OAuthResourceClient, A as AuthenticationResult } from './types-sqdNO7Nc.js';
|
|
2
|
+
export { a as AuthenticatedIdentity } from './types-sqdNO7Nc.js';
|
|
3
3
|
import '@better-auth/oauth-provider/resource-client';
|
|
4
4
|
|
|
5
5
|
interface VerifyOAuthAccessTokenOptions {
|
package/dist/mcp.d.mts
CHANGED
package/dist/mcp.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codelitdev/oauth-server-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Lean Better Auth OAuth authentication framework for web, mobile, REST, and MCP surfaces.",
|
|
5
5
|
"author": "CodeLit",
|
|
6
6
|
"license": "MIT",
|
|
@@ -79,20 +79,20 @@
|
|
|
79
79
|
"tag": "latest"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@better-auth/oauth-provider": "^1.7.
|
|
83
|
-
"better-auth": "^1.7.
|
|
82
|
+
"@better-auth/oauth-provider": "^1.7.16",
|
|
83
|
+
"better-auth": "^1.7.16",
|
|
84
84
|
"express": "^4.21.2"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
|
-
"@better-auth/core": "1.7.
|
|
88
|
-
"@better-auth/oauth-provider": "1.7.
|
|
89
|
-
"@better-auth/utils": "0.
|
|
90
|
-
"@better-fetch/fetch": "1.3.
|
|
87
|
+
"@better-auth/core": "1.7.22",
|
|
88
|
+
"@better-auth/oauth-provider": "1.7.22",
|
|
89
|
+
"@better-auth/utils": "0.5.1",
|
|
90
|
+
"@better-fetch/fetch": "1.3.3",
|
|
91
91
|
"@types/express": "^4.17.20",
|
|
92
92
|
"@types/node": "^22.14.1",
|
|
93
93
|
"@types/bun": "^1.4.1",
|
|
94
|
-
"better-auth": "1.7.
|
|
95
|
-
"better-call": "1.4.
|
|
94
|
+
"better-auth": "1.7.22",
|
|
95
|
+
"better-call": "1.4.5",
|
|
96
96
|
"express": "^4.21.2",
|
|
97
97
|
"rimraf": "^5.0.0",
|
|
98
98
|
"tsup": "^8.3.0",
|