better-auth 1.6.10 → 1.6.12
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/api/index.d.mts +8 -2
- package/dist/api/routes/callback.d.mts +1 -1
- package/dist/api/routes/callback.mjs +36 -40
- package/dist/api/routes/email-verification.d.mts +1 -0
- package/dist/api/routes/email-verification.mjs +4 -3
- package/dist/api/routes/session.mjs +14 -9
- package/dist/api/routes/sign-in.d.mts +1 -0
- package/dist/api/routes/sign-in.mjs +2 -1
- package/dist/api/routes/sign-up.d.mts +1 -0
- package/dist/api/routes/sign-up.mjs +9 -7
- package/dist/api/routes/update-user.mjs +5 -5
- package/dist/client/index.d.mts +2 -2
- package/dist/client/parser.mjs +0 -1
- package/dist/client/plugins/index.d.mts +3 -3
- package/dist/client/proxy.mjs +2 -1
- package/dist/context/helpers.mjs +3 -2
- package/dist/cookies/cookie-utils.d.mts +24 -1
- package/dist/cookies/cookie-utils.mjs +85 -22
- package/dist/cookies/index.d.mts +2 -3
- package/dist/cookies/index.mjs +39 -11
- package/dist/cookies/session-store.mjs +4 -23
- package/dist/db/get-migration.mjs +4 -4
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +3 -2
- package/dist/db/internal-adapter.mjs +96 -1
- package/dist/db/schema.d.mts +15 -2
- package/dist/db/schema.mjs +26 -1
- package/dist/db/with-hooks.d.mts +1 -0
- package/dist/db/with-hooks.mjs +58 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/oauth2/errors.mjs +16 -1
- package/dist/oauth2/link-account.mjs +6 -4
- package/dist/oauth2/state.mjs +8 -2
- package/dist/package.mjs +1 -1
- package/dist/plugins/access/access.d.mts +3 -15
- package/dist/plugins/access/access.mjs +11 -6
- package/dist/plugins/access/index.d.mts +2 -2
- package/dist/plugins/access/types.d.mts +11 -4
- package/dist/plugins/admin/access/statement.d.mts +29 -93
- package/dist/plugins/admin/admin.mjs +0 -4
- package/dist/plugins/admin/client.d.mts +1 -1
- package/dist/plugins/admin/routes.mjs +1 -0
- package/dist/plugins/anonymous/client.d.mts +1 -0
- package/dist/plugins/anonymous/error-codes.d.mts +1 -0
- package/dist/plugins/anonymous/error-codes.mjs +1 -0
- package/dist/plugins/anonymous/index.d.mts +1 -0
- package/dist/plugins/anonymous/index.mjs +16 -2
- package/dist/plugins/bearer/index.mjs +4 -9
- package/dist/plugins/captcha/index.mjs +2 -2
- package/dist/plugins/device-authorization/error-codes.mjs +1 -0
- package/dist/plugins/device-authorization/index.d.mts +1 -0
- package/dist/plugins/device-authorization/routes.mjs +34 -3
- package/dist/plugins/generic-oauth/index.d.mts +1 -1
- package/dist/plugins/generic-oauth/index.mjs +6 -6
- package/dist/plugins/generic-oauth/routes.mjs +34 -32
- package/dist/plugins/generic-oauth/types.d.mts +7 -0
- package/dist/plugins/index.d.mts +2 -2
- package/dist/plugins/last-login-method/client.mjs +2 -2
- package/dist/plugins/magic-link/index.d.mts +8 -1
- package/dist/plugins/magic-link/index.mjs +4 -17
- package/dist/plugins/mcp/authorize.mjs +8 -2
- package/dist/plugins/mcp/index.mjs +73 -34
- package/dist/plugins/multi-session/index.mjs +2 -2
- package/dist/plugins/oauth-proxy/index.mjs +44 -31
- package/dist/plugins/oauth-proxy/utils.mjs +3 -10
- package/dist/plugins/oidc-provider/authorize.mjs +8 -2
- package/dist/plugins/oidc-provider/index.mjs +63 -37
- package/dist/plugins/one-tap/index.mjs +13 -8
- package/dist/plugins/open-api/generator.mjs +16 -5
- package/dist/plugins/organization/access/statement.d.mts +68 -201
- package/dist/plugins/organization/adapter.mjs +61 -56
- package/dist/plugins/organization/client.d.mts +3 -1
- package/dist/plugins/organization/error-codes.d.mts +2 -0
- package/dist/plugins/organization/error-codes.mjs +3 -1
- package/dist/plugins/organization/routes/crud-access-control.d.mts +2 -2
- package/dist/plugins/organization/routes/crud-invites.mjs +7 -2
- package/dist/plugins/organization/types.d.mts +12 -2
- package/dist/plugins/two-factor/index.mjs +3 -2
- package/dist/plugins/username/index.d.mts +24 -2
- package/dist/plugins/username/index.mjs +49 -3
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +18 -4
- package/dist/test-utils/headers.mjs +2 -7
- package/dist/test-utils/test-instance.d.mts +25 -6
- package/dist/test-utils/test-instance.mjs +11 -2
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/url.d.mts +2 -1
- package/dist/utils/url.mjs +9 -3
- package/package.json +15 -14
|
@@ -11,7 +11,7 @@ import { okta } from "./providers/okta.mjs";
|
|
|
11
11
|
import { patreon } from "./providers/patreon.mjs";
|
|
12
12
|
import { slack } from "./providers/slack.mjs";
|
|
13
13
|
import { APIError } from "@better-auth/core/error";
|
|
14
|
-
import { createAuthorizationURL, refreshAccessToken, validateAuthorizationCode } from "@better-auth/core/oauth2";
|
|
14
|
+
import { applyDefaultAccessTokenExpiry, createAuthorizationURL, refreshAccessToken, validateAuthorizationCode } from "@better-auth/core/oauth2";
|
|
15
15
|
import { betterFetch } from "@better-fetch/fetch";
|
|
16
16
|
//#region src/plugins/generic-oauth/index.ts
|
|
17
17
|
/**
|
|
@@ -63,7 +63,7 @@ const genericOAuth = (options) => {
|
|
|
63
63
|
});
|
|
64
64
|
},
|
|
65
65
|
async validateAuthorizationCode(data) {
|
|
66
|
-
if (c.getToken) return c.getToken(data);
|
|
66
|
+
if (c.getToken) return applyDefaultAccessTokenExpiry(await c.getToken(data), c.accessTokenExpiresIn);
|
|
67
67
|
let finalTokenUrl = c.tokenUrl;
|
|
68
68
|
if (c.discoveryUrl) {
|
|
69
69
|
const discovery = await betterFetch(c.discoveryUrl, {
|
|
@@ -76,7 +76,7 @@ const genericOAuth = (options) => {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
if (!finalTokenUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.TOKEN_URL_NOT_FOUND);
|
|
79
|
-
return validateAuthorizationCode({
|
|
79
|
+
return applyDefaultAccessTokenExpiry(await validateAuthorizationCode({
|
|
80
80
|
headers: c.authorizationHeaders,
|
|
81
81
|
code: data.code,
|
|
82
82
|
codeVerifier: data.codeVerifier,
|
|
@@ -88,7 +88,7 @@ const genericOAuth = (options) => {
|
|
|
88
88
|
},
|
|
89
89
|
tokenEndpoint: finalTokenUrl,
|
|
90
90
|
authentication: c.authentication
|
|
91
|
-
});
|
|
91
|
+
}), c.accessTokenExpiresIn);
|
|
92
92
|
},
|
|
93
93
|
async refreshAccessToken(refreshToken) {
|
|
94
94
|
let finalTokenUrl = c.tokenUrl;
|
|
@@ -100,7 +100,7 @@ const genericOAuth = (options) => {
|
|
|
100
100
|
if (discovery.data) finalTokenUrl = discovery.data.token_endpoint;
|
|
101
101
|
}
|
|
102
102
|
if (!finalTokenUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.TOKEN_URL_NOT_FOUND);
|
|
103
|
-
return refreshAccessToken({
|
|
103
|
+
return applyDefaultAccessTokenExpiry(await refreshAccessToken({
|
|
104
104
|
refreshToken,
|
|
105
105
|
options: {
|
|
106
106
|
clientId: c.clientId,
|
|
@@ -108,7 +108,7 @@ const genericOAuth = (options) => {
|
|
|
108
108
|
},
|
|
109
109
|
authentication: c.authentication,
|
|
110
110
|
tokenEndpoint: finalTokenUrl
|
|
111
|
-
});
|
|
111
|
+
}), c.accessTokenExpiresIn);
|
|
112
112
|
},
|
|
113
113
|
async getUserInfo(tokens) {
|
|
114
114
|
const userInfo = c.getUserInfo ? await c.getUserInfo(tokens) : await getUserInfo(tokens, finalUserInfoUrl);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { isAPIError } from "../../utils/is-api-error.mjs";
|
|
1
2
|
import { setSessionCookie } from "../../cookies/index.mjs";
|
|
2
|
-
import { missingEmailLogMessage } from "../../oauth2/errors.mjs";
|
|
3
|
+
import { missingEmailLogMessage, redirectOnError } from "../../oauth2/errors.mjs";
|
|
3
4
|
import { generateState, parseState } from "../../oauth2/state.mjs";
|
|
4
5
|
import { setTokenUtil } from "../../oauth2/utils.mjs";
|
|
5
6
|
import { sessionMiddleware } from "../../api/routes/session.mjs";
|
|
@@ -8,7 +9,7 @@ import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
|
|
|
8
9
|
import { APIError as APIError$1 } from "../../api/index.mjs";
|
|
9
10
|
import { GENERIC_OAUTH_ERROR_CODES } from "./error-codes.mjs";
|
|
10
11
|
import { BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
11
|
-
import { createAuthorizationURL, validateAuthorizationCode } from "@better-auth/core/oauth2";
|
|
12
|
+
import { applyDefaultAccessTokenExpiry, createAuthorizationURL, validateAuthorizationCode } from "@better-auth/core/oauth2";
|
|
12
13
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
13
14
|
import * as z from "zod";
|
|
14
15
|
import { decodeJwt } from "jose";
|
|
@@ -132,7 +133,7 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
132
133
|
}
|
|
133
134
|
}, async (ctx) => {
|
|
134
135
|
const defaultErrorURL = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;
|
|
135
|
-
if (ctx.query.error || !ctx.query.code)
|
|
136
|
+
if (ctx.query.error || !ctx.query.code) redirectOnError(ctx, defaultErrorURL, ctx.query.error || "oAuth_code_missing", ctx.query.error_description || void 0);
|
|
136
137
|
const providerId = ctx.params?.providerId;
|
|
137
138
|
if (!providerId) throw APIError$1.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.PROVIDER_ID_REQUIRED);
|
|
138
139
|
const providerConfig = options.config.find((p) => p.providerId === providerId);
|
|
@@ -140,13 +141,7 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
140
141
|
let tokens = void 0;
|
|
141
142
|
const { callbackURL, codeVerifier, errorURL, requestSignUp, newUserURL, link } = await parseState(ctx);
|
|
142
143
|
const code = ctx.query.code;
|
|
143
|
-
|
|
144
|
-
const defaultErrorURL = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;
|
|
145
|
-
let url = errorURL || defaultErrorURL;
|
|
146
|
-
if (url.includes("?")) url = `${url}&error=${encodeURIComponent(error)}`;
|
|
147
|
-
else url = `${url}?error=${encodeURIComponent(error)}`;
|
|
148
|
-
throw ctx.redirect(url);
|
|
149
|
-
}
|
|
144
|
+
const resolvedErrorURL = errorURL || defaultErrorURL;
|
|
150
145
|
let finalTokenUrl = providerConfig.tokenUrl;
|
|
151
146
|
let finalUserInfoUrl = providerConfig.userInfoUrl;
|
|
152
147
|
let expectedIssuer = providerConfig.issuer;
|
|
@@ -168,11 +163,11 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
168
163
|
expected: expectedIssuer,
|
|
169
164
|
received: ctx.query.iss
|
|
170
165
|
});
|
|
171
|
-
|
|
166
|
+
redirectOnError(ctx, resolvedErrorURL, "issuer_mismatch");
|
|
172
167
|
}
|
|
173
168
|
} else if (providerConfig.requireIssuerValidation) {
|
|
174
169
|
ctx.context.logger.error("OAuth issuer parameter missing", { expected: expectedIssuer });
|
|
175
|
-
|
|
170
|
+
redirectOnError(ctx, resolvedErrorURL, "issuer_missing");
|
|
176
171
|
}
|
|
177
172
|
}
|
|
178
173
|
try {
|
|
@@ -199,25 +194,26 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
199
194
|
additionalParams
|
|
200
195
|
});
|
|
201
196
|
}
|
|
197
|
+
tokens = applyDefaultAccessTokenExpiry(tokens, providerConfig.accessTokenExpiresIn);
|
|
202
198
|
} catch (e) {
|
|
203
199
|
ctx.context.logger.error(e && typeof e === "object" && "name" in e ? e.name : "", e);
|
|
204
|
-
|
|
200
|
+
redirectOnError(ctx, resolvedErrorURL, "oauth_code_verification_failed");
|
|
205
201
|
}
|
|
206
202
|
if (!tokens) throw APIError$1.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.INVALID_OAUTH_CONFIG);
|
|
207
203
|
const userInfo = await (async function handleUserInfo() {
|
|
208
204
|
const userInfo = providerConfig.getUserInfo ? await providerConfig.getUserInfo(tokens) : await getUserInfo(tokens, finalUserInfoUrl);
|
|
209
|
-
if (!userInfo)
|
|
205
|
+
if (!userInfo) redirectOnError(ctx, resolvedErrorURL, "user_info_is_missing");
|
|
210
206
|
const mapUser = providerConfig.mapProfileToUser ? await providerConfig.mapProfileToUser(userInfo) : userInfo;
|
|
211
207
|
const email = mapUser.email ? mapUser.email.toLowerCase() : userInfo.email?.toLowerCase();
|
|
212
208
|
if (!email) {
|
|
213
209
|
ctx.context.logger.error(missingEmailLogMessage(providerConfig.providerId, { source: "generic" }), userInfo);
|
|
214
|
-
|
|
210
|
+
redirectOnError(ctx, resolvedErrorURL, "email_is_missing");
|
|
215
211
|
}
|
|
216
212
|
const id = mapUser.id ? String(mapUser.id) : String(userInfo.id);
|
|
217
213
|
const name = mapUser.name ? mapUser.name : userInfo.name;
|
|
218
214
|
if (!name) {
|
|
219
215
|
ctx.context.logger.error("Unable to get user info", userInfo);
|
|
220
|
-
|
|
216
|
+
redirectOnError(ctx, resolvedErrorURL, "name_is_missing");
|
|
221
217
|
}
|
|
222
218
|
return {
|
|
223
219
|
...userInfo,
|
|
@@ -228,10 +224,10 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
228
224
|
};
|
|
229
225
|
})();
|
|
230
226
|
if (link) {
|
|
231
|
-
if (ctx.context.options.account?.accountLinking?.allowDifferentEmails !== true && link.email.toLowerCase() !== userInfo.email.toLowerCase())
|
|
227
|
+
if (ctx.context.options.account?.accountLinking?.allowDifferentEmails !== true && link.email.toLowerCase() !== userInfo.email.toLowerCase()) redirectOnError(ctx, resolvedErrorURL, "email_doesn't_match");
|
|
232
228
|
const existingAccount = await ctx.context.internalAdapter.findAccountByProviderId(String(userInfo.id), providerConfig.providerId);
|
|
233
229
|
if (existingAccount) {
|
|
234
|
-
if (existingAccount.userId !== link.userId)
|
|
230
|
+
if (existingAccount.userId !== link.userId) redirectOnError(ctx, resolvedErrorURL, "account_already_linked_to_different_user");
|
|
235
231
|
const updateData = Object.fromEntries(Object.entries({
|
|
236
232
|
accessToken: await setTokenUtil(tokens.accessToken, ctx.context),
|
|
237
233
|
idToken: tokens.idToken,
|
|
@@ -251,7 +247,7 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
251
247
|
scope: tokens.scopes?.join(","),
|
|
252
248
|
refreshToken: await setTokenUtil(tokens.refreshToken, ctx.context),
|
|
253
249
|
idToken: tokens.idToken
|
|
254
|
-
}))
|
|
250
|
+
})) redirectOnError(ctx, resolvedErrorURL, "unable_to_link_account");
|
|
255
251
|
let toRedirectTo;
|
|
256
252
|
try {
|
|
257
253
|
toRedirectTo = callbackURL.toString();
|
|
@@ -260,19 +256,25 @@ const oAuth2Callback = (options) => createAuthEndpoint("/oauth2/callback/:provid
|
|
|
260
256
|
}
|
|
261
257
|
throw ctx.redirect(toRedirectTo);
|
|
262
258
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
259
|
+
let result;
|
|
260
|
+
try {
|
|
261
|
+
result = await handleOAuthUserInfo(ctx, {
|
|
262
|
+
userInfo,
|
|
263
|
+
account: {
|
|
264
|
+
providerId: providerConfig.providerId,
|
|
265
|
+
accountId: userInfo.id,
|
|
266
|
+
...tokens,
|
|
267
|
+
scope: tokens.scopes?.join(",")
|
|
268
|
+
},
|
|
269
|
+
callbackURL,
|
|
270
|
+
disableSignUp: providerConfig.disableImplicitSignUp && !requestSignUp || providerConfig.disableSignUp,
|
|
271
|
+
overrideUserInfo: providerConfig.overrideUserInfo
|
|
272
|
+
});
|
|
273
|
+
} catch (e) {
|
|
274
|
+
if (isAPIError(e) && e.body?.code) redirectOnError(ctx, resolvedErrorURL, e.body.code, e.body.message);
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
if (result.error) redirectOnError(ctx, resolvedErrorURL, result.error.split(" ").join("_"));
|
|
276
278
|
const { session, user } = result.data;
|
|
277
279
|
await setSessionCookie(ctx, {
|
|
278
280
|
session,
|
|
@@ -87,6 +87,13 @@ interface GenericOAuthConfig {
|
|
|
87
87
|
* Use "offline" to request a refresh token.
|
|
88
88
|
*/
|
|
89
89
|
accessType?: string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Fallback access-token lifetime, in seconds, used only when the provider's
|
|
92
|
+
* token response omits `expires_in`. Set this so `getAccessToken` can track
|
|
93
|
+
* expiry and refresh the token; leave unset if the provider returns
|
|
94
|
+
* `expires_in`.
|
|
95
|
+
*/
|
|
96
|
+
accessTokenExpiresIn?: number | undefined;
|
|
90
97
|
/**
|
|
91
98
|
* Custom function to exchange authorization code for tokens.
|
|
92
99
|
* If provided, this function will be used instead of the default token exchange logic.
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginIDs } from "../types/plugins.mjs";
|
|
2
2
|
import { HIDE_METADATA } from "../utils/hide-metadata.mjs";
|
|
3
|
-
import { AccessControl, ArrayElement, Role, Statements, SubArray, Subset } from "./access/types.mjs";
|
|
3
|
+
import { AccessControl, ArrayElement, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset } from "./access/types.mjs";
|
|
4
4
|
import { AuthorizeResponse, createAccessControl, role } from "./access/access.mjs";
|
|
5
5
|
import { OrganizationOptions } from "./organization/types.mjs";
|
|
6
6
|
import { InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferTeam, Invitation, InvitationInput, InvitationStatus, Member, MemberInput, Organization, OrganizationInput, OrganizationRole, OrganizationSchema, Team, TeamInput, TeamMember, TeamMemberInput, defaultRolesSchema, invitationSchema, invitationStatus, memberSchema, organizationRoleSchema, organizationSchema, roleSchema, teamMemberSchema, teamSchema } from "./organization/schema.mjs";
|
|
@@ -62,4 +62,4 @@ import { USERNAME_ERROR_CODES } from "./username/error-codes.mjs";
|
|
|
62
62
|
import { UsernameOptions, username } from "./username/index.mjs";
|
|
63
63
|
import { hasPermission } from "./organization/has-permission.mjs";
|
|
64
64
|
import { DefaultOrganizationPlugin, DynamicAccessControlEndpoints, OrganizationCreator, OrganizationEndpoints, OrganizationPlugin, TeamEndpoints, organization, parseRoles } from "./organization/organization.mjs";
|
|
65
|
-
export { AccessControl, AdminOptions, AnonymousOptions, AnonymousSession, ArrayElement, Auth0Options, AuthorizationQuery, AuthorizeResponse, BackupCodeOptions, BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, CaptchaFoxOptions, CaptchaOptions, Client, CloudflareTurnstileOptions, CodeVerificationValue, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationOptions, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, EmailOTPOptions, FieldSchema, GenericOAuthConfig, GenericOAuthOptions, GoogleRecaptchaOptions, GumroadOptions, HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOptionSchema, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, InferTeam, Invitation, InvitationInput, InvitationStatus, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodOptions, LineOptions, LoginResult, MagicLinkOptions, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OAuthProxyOptions, OIDCMetadata, OIDCOptions, OTPOptions, OktaOptions, OneTapOptions, OneTimeTokenOptions, OpenAPIModelSchema, OpenAPIOptions, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, Path, PatreonOptions, PhoneNumberOptions, Provider, Role, SIWEPluginOptions, SessionWithImpersonatedBy, SlackOptions, Statements, SubArray, Subset, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, TestCookie, TestHelpers, TestUtilsOptions, TimeString, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, UsernameOptions, admin, anonymous, auth0, backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, encodeBackupCodes, generateBackupCodes, generateExportedKeyPair, generator, genericOAuth, getBackupCodes, getClient, getJwtToken, getMCPProtectedResourceMetadata, getMCPProviderMetadata, getMetadata, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, invitationSchema, invitationStatus, jwt, keycloak, lastLoginMethod, line, magicLink, mcp, memberSchema, microsoftEntraId, ms, multiSession, oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata, oAuthProxy, oidcProvider, okta, oneTap, oneTimeToken, openAPI, organization, organizationRoleSchema, organizationSchema, otp2fa, parseRoles, patreon, phoneNumber, role, roleSchema, sec, signJWT, siwe, slack, teamMemberSchema, teamSchema, testUtils, toExpJWT, totp2fa, twoFactor, twoFactorClient, username, verifyBackupCode, verifyJWT, withMcpAuth };
|
|
65
|
+
export { AccessControl, AdminOptions, AnonymousOptions, AnonymousSession, ArrayElement, Auth0Options, AuthorizationQuery, AuthorizeResponse, BackupCodeOptions, BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, CaptchaFoxOptions, CaptchaOptions, Client, CloudflareTurnstileOptions, CodeVerificationValue, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationOptions, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, EmailOTPOptions, ExactRoleStatements, FieldSchema, GenericOAuthConfig, GenericOAuthOptions, GoogleRecaptchaOptions, GumroadOptions, HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOptionSchema, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, InferTeam, Invitation, InvitationInput, InvitationStatus, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodOptions, LineOptions, LoginResult, MagicLinkOptions, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OAuthProxyOptions, OIDCMetadata, OIDCOptions, OTPOptions, OktaOptions, OneTapOptions, OneTimeTokenOptions, OpenAPIModelSchema, OpenAPIOptions, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, Path, PatreonOptions, PhoneNumberOptions, Provider, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SIWEPluginOptions, SessionWithImpersonatedBy, SlackOptions, Statements, SubArray, Subset, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, TestCookie, TestHelpers, TestUtilsOptions, TimeString, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, UsernameOptions, admin, anonymous, auth0, backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, encodeBackupCodes, generateBackupCodes, generateExportedKeyPair, generator, genericOAuth, getBackupCodes, getClient, getJwtToken, getMCPProtectedResourceMetadata, getMCPProviderMetadata, getMetadata, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, invitationSchema, invitationStatus, jwt, keycloak, lastLoginMethod, line, magicLink, mcp, memberSchema, microsoftEntraId, ms, multiSession, oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata, oAuthProxy, oidcProvider, okta, oneTap, oneTimeToken, openAPI, organization, organizationRoleSchema, organizationSchema, otp2fa, parseRoles, patreon, phoneNumber, role, roleSchema, sec, signJWT, siwe, slack, teamMemberSchema, teamSchema, testUtils, toExpJWT, totp2fa, twoFactor, twoFactorClient, username, verifyBackupCode, verifyJWT, withMcpAuth };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { parseCookies } from "../../cookies/cookie-utils.mjs";
|
|
1
2
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
2
3
|
//#region src/plugins/last-login-method/client.ts
|
|
3
4
|
function getCookieValue(name) {
|
|
4
5
|
if (typeof document === "undefined") return null;
|
|
5
|
-
|
|
6
|
-
return cookie ? cookie.split("=")[1] : null;
|
|
6
|
+
return parseCookies(document.cookie).get(name) ?? null;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Client-side plugin to retrieve the last used login method
|
|
@@ -18,7 +18,14 @@ interface MagicLinkOptions {
|
|
|
18
18
|
expiresIn?: number | undefined;
|
|
19
19
|
/**
|
|
20
20
|
* Allowed attempts for verifying the magic link token.
|
|
21
|
-
*
|
|
21
|
+
*
|
|
22
|
+
* @deprecated Multi-attempt verification is no longer supported. Each
|
|
23
|
+
* magic link token is consumed atomically on the first verification call,
|
|
24
|
+
* so a given token mints at most one session regardless of this value
|
|
25
|
+
* (see GHSA-hc7v-rggr-4hvx). The option is kept for source compatibility
|
|
26
|
+
* and may be removed in a future major; any value other than `1` is
|
|
27
|
+
* ignored and emits a `console.warn` at plugin construction.
|
|
28
|
+
*
|
|
22
29
|
* @default 1
|
|
23
30
|
*/
|
|
24
31
|
allowedAttempts?: number;
|
|
@@ -27,6 +27,7 @@ const magicLink = (options) => {
|
|
|
27
27
|
allowedAttempts: 1,
|
|
28
28
|
...options
|
|
29
29
|
};
|
|
30
|
+
if (options.allowedAttempts !== void 0 && options.allowedAttempts !== 1) console.warn("[better-auth/magic-link] `allowedAttempts` is ignored: tokens are consumed atomically on the first verification call (GHSA-hc7v-rggr-4hvx). Any value other than `1` has no effect; remove the option to silence this warning.");
|
|
30
31
|
async function storeToken(ctx, token) {
|
|
31
32
|
if (opts.storeToken === "hashed") return await defaultKeyHasher(token);
|
|
32
33
|
if (typeof opts.storeToken === "object" && "type" in opts.storeToken && opts.storeToken.type === "custom-hasher") return await opts.storeToken.hash(token);
|
|
@@ -59,8 +60,7 @@ const magicLink = (options) => {
|
|
|
59
60
|
identifier: storedToken,
|
|
60
61
|
value: JSON.stringify({
|
|
61
62
|
email,
|
|
62
|
-
name: ctx.body.name
|
|
63
|
-
attempt: 0
|
|
63
|
+
name: ctx.body.name
|
|
64
64
|
}),
|
|
65
65
|
expiresAt: new Date(Date.now() + (opts.expiresIn || 300) * 1e3)
|
|
66
66
|
});
|
|
@@ -119,22 +119,9 @@ const magicLink = (options) => {
|
|
|
119
119
|
}
|
|
120
120
|
const newUserCallbackURL = new URL(ctx.query.newUserCallbackURL ? decodeURIComponent(ctx.query.newUserCallbackURL) : callbackURL, ctx.context.baseURL).toString();
|
|
121
121
|
const storedToken = await storeToken(ctx, token);
|
|
122
|
-
const tokenValue = await ctx.context.internalAdapter.
|
|
122
|
+
const tokenValue = await ctx.context.internalAdapter.consumeVerificationValue(storedToken);
|
|
123
123
|
if (!tokenValue) redirectWithError("INVALID_TOKEN");
|
|
124
|
-
|
|
125
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(storedToken);
|
|
126
|
-
redirectWithError("EXPIRED_TOKEN");
|
|
127
|
-
}
|
|
128
|
-
const { email, name, attempt = 0 } = JSON.parse(tokenValue.value);
|
|
129
|
-
if (attempt >= opts.allowedAttempts) {
|
|
130
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(storedToken);
|
|
131
|
-
redirectWithError("ATTEMPTS_EXCEEDED");
|
|
132
|
-
}
|
|
133
|
-
await ctx.context.internalAdapter.updateVerificationByIdentifier(storedToken, { value: JSON.stringify({
|
|
134
|
-
email,
|
|
135
|
-
name,
|
|
136
|
-
attempt: attempt + 1
|
|
137
|
-
}) });
|
|
124
|
+
const { email, name } = JSON.parse(tokenValue.value);
|
|
138
125
|
let isNewUser = false;
|
|
139
126
|
let user = await ctx.context.internalAdapter.findUserByEmail(email).then((res) => res?.user);
|
|
140
127
|
if (!user) if (!opts.disableSignUp) {
|
|
@@ -72,8 +72,14 @@ async function authorizeMCPOAuth(ctx, options) {
|
|
|
72
72
|
});
|
|
73
73
|
if (invalidScopes.length) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`));
|
|
74
74
|
if ((!query.code_challenge || !query.code_challenge_method) && options.requirePKCE) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "pkce is required"));
|
|
75
|
-
if (!query.
|
|
76
|
-
if (
|
|
75
|
+
if (query.code_challenge_method && !query.code_challenge) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "code_challenge_method requires code_challenge"));
|
|
76
|
+
if (query.code_challenge) {
|
|
77
|
+
const allowedCodeChallengeMethods = options.allowPlainCodeChallengeMethod ? ["s256", "plain"] : ["s256"];
|
|
78
|
+
let codeChallengeMethod = query.code_challenge_method?.toLowerCase();
|
|
79
|
+
if (!codeChallengeMethod && options.allowPlainCodeChallengeMethod) codeChallengeMethod = "plain";
|
|
80
|
+
if (!codeChallengeMethod || !allowedCodeChallengeMethods.includes(codeChallengeMethod)) throw ctx.redirect(redirectErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method"));
|
|
81
|
+
query.code_challenge_method = codeChallengeMethod;
|
|
82
|
+
}
|
|
77
83
|
const code = generateRandomString(32, "a-z", "A-Z", "0-9");
|
|
78
84
|
const codeExpiresInMs = opts.codeExpiresIn * 1e3;
|
|
79
85
|
const expiresAt = new Date(Date.now() + codeExpiresInMs);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getBaseURL, isDynamicBaseURLConfig, resolveBaseURL } from "../../utils/url.mjs";
|
|
2
2
|
import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
|
|
3
|
+
import { constantTimeEqual } from "../../crypto/buffer.mjs";
|
|
3
4
|
import { generateRandomString } from "../../crypto/random.mjs";
|
|
4
5
|
import { expireCookie } from "../../cookies/index.mjs";
|
|
5
6
|
import { resolveDynamicTrustedProxyHeaders } from "../../context/helpers.mjs";
|
|
@@ -45,7 +46,7 @@ const getMCPProviderMetadata = (ctx, options) => {
|
|
|
45
46
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
46
47
|
acr_values_supported: ["urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze"],
|
|
47
48
|
subject_types_supported: ["public"],
|
|
48
|
-
id_token_signing_alg_values_supported: ["RS256"
|
|
49
|
+
id_token_signing_alg_values_supported: ["RS256"],
|
|
49
50
|
token_endpoint_auth_methods_supported: [
|
|
50
51
|
"client_secret_basic",
|
|
51
52
|
"client_secret_post",
|
|
@@ -81,7 +82,7 @@ const getMCPProtectedResourceMetadata = (ctx, options) => {
|
|
|
81
82
|
"offline_access"
|
|
82
83
|
],
|
|
83
84
|
bearer_methods_supported: ["header"],
|
|
84
|
-
resource_signing_alg_values_supported: ["RS256"
|
|
85
|
+
resource_signing_alg_values_supported: ["RS256"]
|
|
85
86
|
};
|
|
86
87
|
};
|
|
87
88
|
const registerMcpClientBodySchema = z.object({
|
|
@@ -122,7 +123,7 @@ const mcp = (options) => {
|
|
|
122
123
|
defaultScope: "openid",
|
|
123
124
|
accessTokenExpiresIn: 3600,
|
|
124
125
|
refreshTokenExpiresIn: 604800,
|
|
125
|
-
allowPlainCodeChallengeMethod:
|
|
126
|
+
allowPlainCodeChallengeMethod: false,
|
|
126
127
|
...options.oidcConfig,
|
|
127
128
|
loginPage: options.loginPage,
|
|
128
129
|
scopes: [
|
|
@@ -225,10 +226,6 @@ const mcp = (options) => {
|
|
|
225
226
|
allowedMediaTypes: ["application/x-www-form-urlencoded", "application/json"]
|
|
226
227
|
}
|
|
227
228
|
}, async (ctx) => {
|
|
228
|
-
ctx.setHeader("Access-Control-Allow-Origin", "*");
|
|
229
|
-
ctx.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
230
|
-
ctx.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
231
|
-
ctx.setHeader("Access-Control-Max-Age", "86400");
|
|
232
229
|
let { body } = ctx;
|
|
233
230
|
if (!body) throw ctx.error("BAD_REQUEST", {
|
|
234
231
|
error_description: "request body not found",
|
|
@@ -241,25 +238,43 @@ const mcp = (options) => {
|
|
|
241
238
|
});
|
|
242
239
|
let { client_id, client_secret } = body;
|
|
243
240
|
const authorization = ctx.request?.headers.get("authorization") || null;
|
|
244
|
-
if (authorization && !
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
if (authorization && !client_secret && authorization.startsWith("Basic ")) {
|
|
242
|
+
let decoded;
|
|
243
|
+
try {
|
|
244
|
+
const encoded = authorization.replace("Basic ", "");
|
|
245
|
+
decoded = new TextDecoder().decode(base64.decode(encoded));
|
|
246
|
+
} catch {
|
|
247
|
+
throw new APIError("UNAUTHORIZED", {
|
|
248
|
+
error_description: "invalid authorization header format",
|
|
249
|
+
error: "invalid_client"
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const colonIndex = decoded.indexOf(":");
|
|
253
|
+
if (colonIndex === -1) throw new APIError("UNAUTHORIZED", {
|
|
248
254
|
error_description: "invalid authorization header format",
|
|
249
255
|
error: "invalid_client"
|
|
250
256
|
});
|
|
251
|
-
|
|
257
|
+
let id;
|
|
258
|
+
let secret;
|
|
259
|
+
try {
|
|
260
|
+
id = decodeURIComponent(decoded.slice(0, colonIndex));
|
|
261
|
+
secret = decodeURIComponent(decoded.slice(colonIndex + 1));
|
|
262
|
+
} catch {
|
|
263
|
+
throw new APIError("UNAUTHORIZED", {
|
|
264
|
+
error_description: "invalid authorization header format",
|
|
265
|
+
error: "invalid_client"
|
|
266
|
+
});
|
|
267
|
+
}
|
|
252
268
|
if (!id || !secret) throw new APIError("UNAUTHORIZED", {
|
|
253
269
|
error_description: "invalid authorization header format",
|
|
254
270
|
error: "invalid_client"
|
|
255
271
|
});
|
|
256
|
-
client_id
|
|
257
|
-
|
|
258
|
-
} catch {
|
|
259
|
-
throw new APIError("UNAUTHORIZED", {
|
|
260
|
-
error_description: "invalid authorization header format",
|
|
272
|
+
if (client_id && client_id.toString() !== id) throw new APIError("UNAUTHORIZED", {
|
|
273
|
+
error_description: "client_id in body does not match Authorization header",
|
|
261
274
|
error: "invalid_client"
|
|
262
275
|
});
|
|
276
|
+
client_id = id;
|
|
277
|
+
client_secret = secret;
|
|
263
278
|
}
|
|
264
279
|
const { grant_type, code, redirect_uri, refresh_token, code_verifier } = body;
|
|
265
280
|
if (grant_type === "refresh_token") {
|
|
@@ -286,6 +301,38 @@ const mcp = (options) => {
|
|
|
286
301
|
error_description: "refresh token expired",
|
|
287
302
|
error: "invalid_grant"
|
|
288
303
|
});
|
|
304
|
+
const refreshClient = await ctx.context.adapter.findOne({
|
|
305
|
+
model: modelName.oauthClient,
|
|
306
|
+
where: [{
|
|
307
|
+
field: "clientId",
|
|
308
|
+
value: client_id.toString()
|
|
309
|
+
}]
|
|
310
|
+
}).then((res) => {
|
|
311
|
+
if (!res) return null;
|
|
312
|
+
return {
|
|
313
|
+
...res,
|
|
314
|
+
redirectUrls: res.redirectUrls.split(","),
|
|
315
|
+
metadata: res.metadata ? JSON.parse(res.metadata) : {}
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
if (!refreshClient) throw new APIError("UNAUTHORIZED", {
|
|
319
|
+
error_description: "invalid client_id",
|
|
320
|
+
error: "invalid_client"
|
|
321
|
+
});
|
|
322
|
+
if (refreshClient.disabled) throw new APIError("UNAUTHORIZED", {
|
|
323
|
+
error_description: "client is disabled",
|
|
324
|
+
error: "invalid_client"
|
|
325
|
+
});
|
|
326
|
+
if (refreshClient.type !== "public") {
|
|
327
|
+
if (!refreshClient.clientSecret || !client_secret) throw new APIError("UNAUTHORIZED", {
|
|
328
|
+
error_description: "client_secret is required for confidential clients",
|
|
329
|
+
error: "invalid_client"
|
|
330
|
+
});
|
|
331
|
+
if (!constantTimeEqual(refreshClient.clientSecret, client_secret.toString())) throw new APIError("UNAUTHORIZED", {
|
|
332
|
+
error_description: "invalid client_secret",
|
|
333
|
+
error: "invalid_client"
|
|
334
|
+
});
|
|
335
|
+
}
|
|
289
336
|
const accessToken = generateRandomString(32, "a-z", "A-Z");
|
|
290
337
|
const newRefreshToken = generateRandomString(32, "a-z", "A-Z");
|
|
291
338
|
const accessTokenExpiresAt = new Date(Date.now() + opts.accessTokenExpiresIn * 1e3);
|
|
@@ -320,20 +367,11 @@ const mcp = (options) => {
|
|
|
320
367
|
error_description: "code verifier is missing",
|
|
321
368
|
error: "invalid_request"
|
|
322
369
|
});
|
|
323
|
-
|
|
324
|
-
* We need to check if the code is valid before we can proceed
|
|
325
|
-
* with the rest of the request.
|
|
326
|
-
*/
|
|
327
|
-
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(code.toString());
|
|
370
|
+
const verificationValue = await ctx.context.internalAdapter.consumeVerificationValue(code.toString());
|
|
328
371
|
if (!verificationValue) throw new APIError("UNAUTHORIZED", {
|
|
329
372
|
error_description: "invalid code",
|
|
330
373
|
error: "invalid_grant"
|
|
331
374
|
});
|
|
332
|
-
if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("UNAUTHORIZED", {
|
|
333
|
-
error_description: "code expired",
|
|
334
|
-
error: "invalid_grant"
|
|
335
|
-
});
|
|
336
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(code.toString());
|
|
337
375
|
if (!client_id) throw new APIError("UNAUTHORIZED", {
|
|
338
376
|
error_description: "client_id is required",
|
|
339
377
|
error: "invalid_client"
|
|
@@ -378,11 +416,11 @@ const mcp = (options) => {
|
|
|
378
416
|
error: "invalid_request"
|
|
379
417
|
});
|
|
380
418
|
} else {
|
|
381
|
-
if (!client_secret) throw new APIError("UNAUTHORIZED", {
|
|
419
|
+
if (!client.clientSecret || !client_secret) throw new APIError("UNAUTHORIZED", {
|
|
382
420
|
error_description: "client_secret is required for confidential clients",
|
|
383
421
|
error: "invalid_client"
|
|
384
422
|
});
|
|
385
|
-
if (!(client.clientSecret
|
|
423
|
+
if (!constantTimeEqual(client.clientSecret, client_secret.toString())) throw new APIError("UNAUTHORIZED", {
|
|
386
424
|
error_description: "invalid client_secret",
|
|
387
425
|
error: "invalid_client"
|
|
388
426
|
});
|
|
@@ -400,12 +438,13 @@ const mcp = (options) => {
|
|
|
400
438
|
error_description: "code verifier is missing",
|
|
401
439
|
error: "invalid_request"
|
|
402
440
|
});
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
441
|
+
if (value.codeChallenge) {
|
|
442
|
+
if ((value.codeChallengeMethod === "plain" ? code_verifier : await createHash("SHA-256", "base64urlnopad").digest(code_verifier)) !== value.codeChallenge) throw new APIError("UNAUTHORIZED", {
|
|
443
|
+
error_description: "code verification failed",
|
|
444
|
+
error: "invalid_request"
|
|
445
|
+
});
|
|
446
|
+
}
|
|
407
447
|
const requestedScopes = value.scope;
|
|
408
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(code.toString());
|
|
409
448
|
const accessToken = generateRandomString(32, "a-z", "A-Z");
|
|
410
449
|
const refreshToken = generateRandomString(32, "A-Z", "a-z");
|
|
411
450
|
const accessTokenExpiresAt = new Date(Date.now() + opts.accessTokenExpiresIn * 1e3);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs";
|
|
2
|
-
import { SECURE_COOKIE_PREFIX, parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
|
|
3
|
-
import { deleteSessionCookie, expireCookie,
|
|
2
|
+
import { SECURE_COOKIE_PREFIX, parseCookies, parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
|
|
3
|
+
import { deleteSessionCookie, expireCookie, setSessionCookie } from "../../cookies/index.mjs";
|
|
4
4
|
import { sessionMiddleware } from "../../api/routes/session.mjs";
|
|
5
5
|
import { APIError } from "../../api/index.mjs";
|
|
6
6
|
import { PACKAGE_VERSION } from "../../version.mjs";
|