diff --git a/packages/core/src/adapters.ts b/packages/core/src/adapters.ts index 43c4800870..7f8f861a71 100644 --- a/packages/core/src/adapters.ts +++ b/packages/core/src/adapters.ts @@ -183,6 +183,20 @@ export interface AdapterUser extends User { * It is `null` if the user has not signed in with the Email provider yet, or the date of the first successful signin. */ emailVerified: Date | null + /** The user's phoneNumber. */ + phoneNumber?: string + /** + * Whether the user has verified their phoneNumber via an [SMS provider](https://authjs.dev/getting-started/authentication/sms). + * It is `null` if the user has not signed in with the SMS provider yet, or the date of the first successful signin. + */ + phoneNumberVerified?: Date | null + /** A unique identifier for the anonymous user. */ + anonymousId?: string + /** + * Whether the user has a verified anonymousId. + * It is `null` if the user has not signed in with the Anonymous provider yet, or the date of the first successful signin. + */ + anonymousIdVerified?: Date | null } /** @@ -190,7 +204,7 @@ export interface AdapterUser extends User { */ export type AdapterAccountType = Extract< ProviderType, - "oauth" | "oidc" | "email" | "webauthn" + "oauth" | "oidc" | "email" | "webauthn" | "sms" | "anonymous" > /** @@ -298,6 +312,18 @@ export interface Adapter { * See also [Verification tokens](https://authjs.dev/guides/creating-a-database-adapter#verification-tokens) */ getUserByEmail?(email: string): Awaitable + /** + * Returns a user from the database via the user's phone number address. + * + * See also [Verification tokens](https://authjs.dev/guides/creating-a-database-adapter#verification-tokens) + */ + getUserByPhoneNumber?(phone_number: string): Awaitable + /** + * Returns a user from the database via the user's anonymous id. + * + * See also [Verification tokens](https://authjs.dev/guides/creating-a-database-adapter#verification-tokens) + */ + getUserByAnonymousId?(anonymous_id: string): Awaitable /** * Using the provider id and the id of the user for a specific account, get the user. * diff --git a/packages/core/src/lib/actions/callback/handle-login.ts b/packages/core/src/lib/actions/callback/handle-login.ts index e053c3ef63..4a504a76ad 100644 --- a/packages/core/src/lib/actions/callback/handle-login.ts +++ b/packages/core/src/lib/actions/callback/handle-login.ts @@ -32,7 +32,11 @@ export async function handleLoginOrRegister( // Input validation if (!_account?.providerAccountId || !_account.type) throw new Error("Missing or invalid provider account") - if (!["email", "oauth", "oidc", "webauthn"].includes(_account.type)) + if ( + !["email", "sms", "anonymous", "oauth", "oidc", "webauthn"].includes( + _account.type + ) + ) throw new Error("Provider not supported") const { @@ -57,6 +61,8 @@ export async function handleLoginOrRegister( getUser, getUserByAccount, getUserByEmail, + getUserByPhoneNumber, + getUserByAnonymousId, linkAccount, createSession, getSessionAndUser, @@ -115,6 +121,83 @@ export async function handleLoginOrRegister( isNewUser = true } + // Create new session + session = useJwtSession + ? {} + : await createSession({ + sessionToken: generateSessionToken(), + userId: user.id, + expires: fromDate(options.session.maxAge), + }) + + return { session, user, isNewUser } + } else if (account.type === "sms") { + // If signing in with an email, check if an account with the same email address exists already + const userByPhoneNumber = await getUserByPhoneNumber(profile.phoneNumber!) + if (userByPhoneNumber) { + // If they are not already signed in as the same user, this flow will + // sign them out of the current session and sign them in as the new user + if (user?.id !== userByPhoneNumber.id && !useJwtSession && sessionToken) { + // Delete existing session if they are currently signed in as another user. + // This will switch user accounts for the session in cases where the user was + // already logged in with a different account. + await deleteSession(sessionToken) + } + + // Update emailVerified property on the user object + user = await updateUser({ + id: userByPhoneNumber.id, + phoneNumberVerified: new Date(), + }) + await events.updateUser?.({ user }) + } else { + // Create user account if there isn't one for the email address already + user = await createUser({ ...profile, phoneNumberVerified: new Date() }) + await events.createUser?.({ user }) + isNewUser = true + } + + // Create new session + session = useJwtSession + ? {} + : await createSession({ + sessionToken: generateSessionToken(), + userId: user.id, + expires: fromDate(options.session.maxAge), + }) + + return { session, user, isNewUser } + } else if (account.type === "anonymous") { + // If signing in as anonymous, check if an account already exists + const userByAnonymousId = await getUserByAnonymousId(profile.anonymousId!) + if (userByAnonymousId) { + // If they are not already signed in as the same user, this flow will + // sign them out of the current session and sign them in as the new user + if (user?.id !== userByAnonymousId.id && !useJwtSession && sessionToken) { + // Delete existing session if they are currently signed in as another user. + // This will switch user accounts for the session in cases where the user was + // already logged in with a different account. + await deleteSession(sessionToken) + } + + // Update emailVerified property on the user object + user = await updateUser({ + id: userByAnonymousId.id, + anonymousId: profile.anonymousId, + anonymousIdVerified: new Date(), + }) + await events.updateUser?.({ user }) + } else { + // Create user account if there isn't one for the anonymousId already + user = await createUser({ + ...profile, + anonymousId: profile.anonymousId, + anonymousIdVerified: new Date(), + }) + await events.createUser?.({ user }) + isNewUser = true + } + // Create new session session = useJwtSession ? {} diff --git a/packages/core/src/lib/actions/callback/index.ts b/packages/core/src/lib/actions/callback/index.ts index d11048f7cb..a74ff30395 100644 --- a/packages/core/src/lib/actions/callback/index.ts +++ b/packages/core/src/lib/actions/callback/index.ts @@ -1,8 +1,8 @@ // TODO: Make this file smaller import { - AuthError, AccessDenied, + AuthError, CallbackRouteError, CredentialsSignin, InvalidProvider, @@ -11,9 +11,9 @@ import { import { handleLoginOrRegister } from "./handle-login.js" import { handleOAuth } from "./oauth/callback.js" import { state } from "./oauth/checks.js" -import { createHash } from "../../utils/web.js" +import { createHash, randomString, toRequest } from "../../utils/web.js" -import type { AdapterSession } from "../../../adapters.js" +import type { AdapterSession, VerificationToken } from "../../../adapters.js" import type { Account, Authenticator, @@ -203,8 +203,9 @@ export async function callback( return { redirect: callbackUrl, cookies } } else if (provider.type === "email") { - const paramToken = query?.token as string | undefined - const paramIdentifier = query?.email as string | undefined + const paramToken = body?.token || (query?.token as string | undefined) + const paramIdentifier = + body?.email || (query?.email as string | undefined) if (!paramToken) { const e = new TypeError( @@ -218,7 +219,6 @@ export async function callback( const secret = provider.secret ?? options.secret // @ts-expect-error -- Verified in `assertConfig`. const invite = await adapter.useVerificationToken({ - // @ts-expect-error User-land adapters might decide to omit the identifier during lookup identifier: paramIdentifier, // TODO: Drop this requirement for lookup in official adapters too token: await createHash(`${paramToken}${secret}`), }) @@ -320,6 +320,242 @@ export async function callback( } } + // Callback URL is already verified at this point, so safe to use if specified + return { redirect: callbackUrl, cookies } + } else if (provider.type === "sms") { + const paramToken = body?.token || (query?.token as string | undefined) + const paramIdentifier = + body?.phone_number || (query?.phone_number as string | undefined) + + if (!paramIdentifier) { + const e = new TypeError( + "Missing phone_number. The sign-in URL was manually opened without phone_number or the link was not sent correctly in the sms.", + { cause: { hasToken: !!paramToken } } + ) + e.name = "Configuration" + throw e + } + + if (!paramToken) { + const e = new TypeError( + "Missing token. The sign-in URL was manually opened without token or the link was not sent correctly in the sms.", + { cause: { hasToken: !!paramToken } } + ) + e.name = "Configuration" + throw e + } + + const secret = provider.secret ?? options.secret + let invite: VerificationToken | null + if (provider?.checkVerificationRequest) { + invite = await provider.checkVerificationRequest({ + identifier: paramIdentifier, + url: `${url}/callback/${provider.id}`, + expires: new Date(Date.now() + (provider.maxAge ?? 300) * 1000), + provider, + secret, + token: paramToken, + theme: options.theme, + request: toRequest(request), + adapter, + }) + } else { + // @ts-expect-error -- Verified in `assertConfig`. + invite = await adapter.useVerificationToken({ + identifier: paramIdentifier, // TODO: Drop this requirement for lookup in official adapters too + token: await createHash(`${paramToken}${secret}`), + }) + } + if (!invite) { + throw new Verification({ hasInvite: false, expired: false }) + } + + const hasInvite = !!invite + const expired = hasInvite && invite.expires.valueOf() < Date.now() + const invalidInvite = + !hasInvite || + expired || + // The user might have configured the link to not contain the identifier + // so we only compare if it exists + (paramIdentifier && invite.identifier !== paramIdentifier) + if (invalidInvite) throw new Verification({ hasInvite, expired }) + + const { identifier } = invite + const user = (await adapter!.getUserByPhoneNumber(identifier)) ?? { + id: crypto.randomUUID(), + phoneNumber: identifier, + phoneNumberVerified: null, + } + + const account: Account = { + providerAccountId: user.phoneNumber!, + userId: user.id, + type: "sms" as const, + provider: provider.id, + } + + const redirect = await handleAuthorized({ user, account }, options) + if (redirect) return { redirect, cookies } + + // Sign user in + const { + user: loggedInUser, + session, + isNewUser, + } = await handleLoginOrRegister( + sessionStore.value, + user, + account, + options + ) + + if (useJwtSession) { + const defaultToken = { + name: loggedInUser.name, + phoneNumber: loggedInUser.phoneNumber, + picture: loggedInUser.image, + sub: loggedInUser.id?.toString(), + } + const token = await callbacks.jwt({ + token: defaultToken, + user: loggedInUser, + account, + isNewUser, + trigger: isNewUser ? "signUp" : "signIn", + }) + + // Clear cookies if token is null + if (token === null) { + cookies.push(...sessionStore.clean()) + } else { + const salt = options.cookies.sessionToken.name + // Encode token + const newToken = await jwt.encode({ ...jwt, token, salt }) + + // Set cookie expiry date + const cookieExpires = new Date() + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) + + const sessionCookies = sessionStore.chunk(newToken, { + expires: cookieExpires, + }) + cookies.push(...sessionCookies) + } + } else { + // Save Session Token in cookie + cookies.push({ + name: options.cookies.sessionToken.name, + value: (session as AdapterSession).sessionToken, + options: { + ...options.cookies.sessionToken.options, + expires: (session as AdapterSession).expires, + }, + }) + } + + await events.signIn?.({ user: loggedInUser, account, isNewUser }) + + // Handle first logins on new accounts + // e.g. option to send users to a new account landing page on initial login + // Note that the callback URL is preserved, so the journey can still be resumed + if (isNewUser && pages.newUser) { + return { + redirect: `${pages.newUser}${ + pages.newUser.includes("?") ? "&" : "?" + }${new URLSearchParams({ callbackUrl })}`, + cookies, + } + } + + // Callback URL is already verified at this point, so safe to use if specified + return { redirect: callbackUrl, cookies } + } else if (provider.type === "anonymous") { + const anonymousId = randomString(64) + const user = (await adapter!.getUserByPhoneNumber(anonymousId)) ?? { + id: crypto.randomUUID(), + anonymousId, + anonymousIdVerified: null, + } + + const account: Account = { + providerAccountId: user.id!, + userId: user.id, + type: "anonymous" as const, + provider: provider.id, + } + const redirect = await handleAuthorized({ user, account }, options) + if (redirect) return { redirect, cookies } + + // Sign user in + const { + user: loggedInUser, + session, + isNewUser, + } = await handleLoginOrRegister( + sessionStore.value, + user, + account, + options + ) + + if (useJwtSession) { + const defaultToken = { + name: loggedInUser.name, + phoneNumber: loggedInUser.phoneNumber, + picture: loggedInUser.image, + sub: loggedInUser.id?.toString(), + } + const token = await callbacks.jwt({ + token: defaultToken, + user: loggedInUser, + account, + isNewUser, + trigger: isNewUser ? "signUp" : "signIn", + }) + + // Clear cookies if token is null + if (token === null) { + cookies.push(...sessionStore.clean()) + } else { + const salt = options.cookies.sessionToken.name + // Encode token + const newToken = await jwt.encode({ ...jwt, token, salt }) + + // Set cookie expiry date + const cookieExpires = new Date() + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) + + const sessionCookies = sessionStore.chunk(newToken, { + expires: cookieExpires, + }) + cookies.push(...sessionCookies) + } + } else { + // Save Session Token in cookie + cookies.push({ + name: options.cookies.sessionToken.name, + value: (session as AdapterSession).sessionToken, + options: { + ...options.cookies.sessionToken.options, + expires: (session as AdapterSession).expires, + }, + }) + } + + await events.signIn?.({ user: loggedInUser, account, isNewUser }) + + // Handle first logins on new accounts + // e.g. option to send users to a new account landing page on initial login + // Note that the callback URL is preserved, so the journey can still be resumed + if (isNewUser && pages.newUser) { + return { + redirect: `${pages.newUser}${ + pages.newUser.includes("?") ? "&" : "?" + }${new URLSearchParams({ callbackUrl })}`, + cookies, + } + } + // Callback URL is already verified at this point, so safe to use if specified return { redirect: callbackUrl, cookies } } else if (provider.type === "credentials" && method === "POST") { @@ -332,7 +568,7 @@ export async function callback( const userFromAuthorize = await provider.authorize( credentials, // prettier-ignore - new Request(url, { headers, method, body: JSON.stringify(body) }) + new Request(url, {headers, method, body: JSON.stringify(body)}) ) const user = userFromAuthorize diff --git a/packages/core/src/lib/actions/signin/index.ts b/packages/core/src/lib/actions/signin/index.ts index e47c934ea0..52422cbd59 100644 --- a/packages/core/src/lib/actions/signin/index.ts +++ b/packages/core/src/lib/actions/signin/index.ts @@ -7,6 +7,7 @@ import type { RequestInternal, ResponseInternal, } from "../../../types.js" +import { sendSmsToken } from "./send-sms-token.js" export async function signIn( request: RequestInternal, @@ -31,6 +32,11 @@ export async function signIn( const response = await sendToken(request, options) return { ...response, cookies } } + case "sms": { + const response = await sendSmsToken(request, options) + return { ...response, cookies } + } + case "anonymous": default: return { redirect: signInUrl, cookies } } diff --git a/packages/core/src/lib/actions/signin/send-sms-token.ts b/packages/core/src/lib/actions/signin/send-sms-token.ts new file mode 100644 index 0000000000..5ae1dc6cea --- /dev/null +++ b/packages/core/src/lib/actions/signin/send-sms-token.ts @@ -0,0 +1,120 @@ +import { createHash, randomNumber, toRequest } from "../../utils/web.js" +import { AccessDenied } from "../../../errors.js" + +import type { + Account, + InternalOptions, + RequestInternal, +} from "../../../types.js" +import { AdapterUser } from "../../../adapters.js" + +/** + * Starts an e-mail login flow, by generating a token, + * and sending it to the user's e-mail (with the help of a DB adapter). + * At the end, it returns a redirect to the `verify-request` page. + */ +export async function sendSmsToken( + request: RequestInternal, + options: InternalOptions<"sms"> +) { + const { body } = request + const { provider, callbacks, adapter } = options + const normalizer = provider.normalizeIdentifier ?? defaultNormalizer + const phone_number = normalizer(body?.phone_number) + + const captchaVerified = + (await provider?.verifyCaptchaToken?.(body?.captcha_token)) ?? true + if (!captchaVerified) { + throw new AccessDenied("Captcha verification failed.") + } + + const defaultUser = { id: crypto.randomUUID(), phone_number } + const user = + (await adapter!.getUserByPhoneNumber(phone_number)) ?? defaultUser + + const account = { + providerAccountId: phone_number, + userId: user.id, + type: "sms", + provider: provider.id, + } satisfies Account + + let authorized + try { + authorized = await callbacks.signIn({ + user, + account, + email: { verificationRequest: true }, + }) + } catch (e) { + throw new AccessDenied(e as Error) + } + if (!authorized) throw new AccessDenied("AccessDenied") + if (typeof authorized === "string") { + return { + redirect: await callbacks.redirect({ + url: authorized, + baseUrl: options.url.origin, + }), + } + } + + const { callbackUrl, theme } = options + const token = + (await provider.generateVerificationToken?.({ + identifier: phone_number, + user: user as AdapterUser, + provider, + theme, + request: toRequest(request), + })) ?? randomNumber(6) + + const QUARTER_HOUR_IN_SECONDS = 3600 + const expires = new Date( + Date.now() + (provider.maxAge ?? QUARTER_HOUR_IN_SECONDS) * 1000 + ) + + const secret = provider.secret ?? options.secret + + const baseUrl = new URL(options.basePath, options.url.origin) + + const sendRequest = provider.sendVerificationRequest({ + identifier: phone_number, + token, + expires, + url: `${baseUrl}/callback/${provider.id}?${new URLSearchParams({ + callbackUrl, + token, + phone_number, + })}`, + provider, + theme, + request: toRequest(request), + }) + + const createToken = adapter!.createVerificationToken?.({ + identifier: phone_number, + token: await createHash(`${token}${secret}`), + expires, + }) + + await Promise.all([sendRequest, createToken]) + + return { + redirect: `${baseUrl}/verify-request?${new URLSearchParams({ + provider: provider.id, + type: provider.type, + phone_number: phone_number, + })}`, + } +} + +function defaultNormalizer(phone_number?: string) { + if (!phone_number) throw new Error("Missing phone_number from request body.") + if (!/^\+\d{11}$/.test(phone_number)) { + throw new Error( + "Invalid phone number format, it should be like +XXXXXXXXXXXX" + ) + } + return phone_number +} diff --git a/packages/core/src/lib/actions/signin/send-token.ts b/packages/core/src/lib/actions/signin/send-token.ts index 27024affaf..8454dfe1af 100644 --- a/packages/core/src/lib/actions/signin/send-token.ts +++ b/packages/core/src/lib/actions/signin/send-token.ts @@ -1,8 +1,11 @@ import { createHash, randomString, toRequest } from "../../utils/web.js" import { AccessDenied } from "../../../errors.js" -import type { InternalOptions, RequestInternal } from "../../../types.js" -import type { Account } from "../../../types.js" +import type { + Account, + InternalOptions, + RequestInternal, +} from "../../../types.js" /** * Starts an e-mail login flow, by generating a token, @@ -18,6 +21,12 @@ export async function sendToken( const normalizer = provider.normalizeIdentifier ?? defaultNormalizer const email = normalizer(body?.email) + const captchaVerified = + (await provider?.verifyCaptchaToken?.(body?.captcha_token)) ?? true + if (!captchaVerified) { + throw new AccessDenied("Captcha verification failed.") + } + const defaultUser = { id: crypto.randomUUID(), email, emailVerified: null } const user = (await adapter!.getUserByEmail(email)) ?? defaultUser @@ -87,6 +96,7 @@ export async function sendToken( redirect: `${baseUrl}/verify-request?${new URLSearchParams({ provider: provider.id, type: provider.type, + email: email, })}`, } } diff --git a/packages/core/src/lib/index.ts b/packages/core/src/lib/index.ts index 00ee0027c1..e1e9e70c9a 100644 --- a/packages/core/src/lib/index.ts +++ b/packages/core/src/lib/index.ts @@ -70,7 +70,10 @@ export async function AuthInternal( const { csrfTokenVerified } = options switch (action) { case "callback": - if (options.provider.type === "credentials") + if ( + options.provider.type === "credentials" || + options.provider.type === "anonymous" + ) // Verified CSRF Token required for credentials providers only validateCSRF(action, csrfTokenVerified) return await actions.callback(request, options, sessionStore, cookies) diff --git a/packages/core/src/lib/pages/index.ts b/packages/core/src/lib/pages/index.ts index 2c91513a3f..f90ae9937a 100644 --- a/packages/core/src/lib/pages/index.ts +++ b/packages/core/src/lib/pages/index.ts @@ -116,7 +116,9 @@ export default function renderPage(params: RenderPageParams) { providers: params.providers?.filter( (provider) => // Always render oauth and email type providers - ["email", "oauth", "oidc"].includes(provider.type) || + ["email", "sms", "anonymous", "oauth", "oidc"].includes( + provider.type + ) || // Only render credentials type provider if credentials are defined (provider.type === "credentials" && provider.credentials) || // Only render webauthn type provider if formFields are defined diff --git a/packages/core/src/lib/pages/signin.tsx b/packages/core/src/lib/pages/signin.tsx index 6bf15d0c81..293330528b 100644 --- a/packages/core/src/lib/pages/signin.tsx +++ b/packages/core/src/lib/pages/signin.tsx @@ -177,6 +177,43 @@ export default function SigninPage(props: { )} + {provider.type === "sms" && ( +
+ + + + +
+ )} + {provider.type === "anonymous" && ( +
+ + + +
+ )} {provider.type === "credentials" && (
diff --git a/packages/core/src/lib/utils/assert.ts b/packages/core/src/lib/utils/assert.ts index 749cbf633a..75dce2504d 100644 --- a/packages/core/src/lib/utils/assert.ts +++ b/packages/core/src/lib/utils/assert.ts @@ -46,6 +46,8 @@ function isSemverString(version: string): version is SemverString { let hasCredentials = false let hasEmail = false +let hasSMS = false +let hasAnonymous = false let hasWebAuthn = false const emailMethods: (keyof Adapter)[] = [ @@ -54,6 +56,14 @@ const emailMethods: (keyof Adapter)[] = [ "getUserByEmail", ] +const smsMethods: (keyof Adapter)[] = [ + "createVerificationToken", + "useVerificationToken", + "getUserByPhoneNumber", +] + +const anonymousMethods: (keyof Adapter)[] = ["getUserByAnonymousId"] + const sessionMethods: (keyof Adapter)[] = [ "createUser", "getUser", @@ -148,6 +158,8 @@ export function assertConfig( if (provider.type === "credentials") hasCredentials = true else if (provider.type === "email") hasEmail = true + else if (provider.type === "sms") hasSMS = true + else if (provider.type === "anonymous") hasAnonymous = true else if (provider.type === "webauthn") { hasWebAuthn = true @@ -211,6 +223,8 @@ export function assertConfig( const requiredMethods: (keyof Adapter)[] = [] if ( + hasSMS || + hasAnonymous || hasEmail || session?.strategy === "database" || (!session?.strategy && adapter) @@ -218,6 +232,13 @@ export function assertConfig( if (hasEmail) { if (!adapter) return new MissingAdapter("Email login requires an adapter") requiredMethods.push(...emailMethods) + } else if (hasSMS) { + if (!adapter) return new MissingAdapter("SMS login requires an adapter") + requiredMethods.push(...smsMethods) + } else if (hasSMS) { + if (!adapter) + return new MissingAdapter("Anonymous login requires an adapter") + requiredMethods.push(...anonymousMethods) } else { if (!adapter) return new MissingAdapter("Database session requires an adapter") diff --git a/packages/core/src/lib/utils/web.ts b/packages/core/src/lib/utils/web.ts index 9e782978bb..3670487adf 100644 --- a/packages/core/src/lib/utils/web.ts +++ b/packages/core/src/lib/utils/web.ts @@ -114,6 +114,24 @@ export function randomString(size: number) { return Array.from(bytes).reduce(r, "") } +/** Web compatible method to create a random number of a given length */ +export function randomNumber(size: number) { + if (size <= 0) { + throw new Error("Size must be a positive integer.") + } + + // Generate an array of random bytes + const bytes = crypto.getRandomValues(new Uint8Array(size)) + + // Convert the bytes to a single number + let result = 0 + for (let i = 0; i < size; i++) { + result = (result << 8) + bytes[i] + } + + return result.toString() +} + /** @internal Parse the action and provider id from a URL pathname. */ export function parseActionAndProviderId( pathname: string, diff --git a/packages/core/src/providers/anonymous.ts b/packages/core/src/providers/anonymous.ts new file mode 100644 index 0000000000..b81f7aeaa9 --- /dev/null +++ b/packages/core/src/providers/anonymous.ts @@ -0,0 +1,27 @@ +import { CommonProviderOptions } from "./index.js" + +export interface AnonymousConfig extends CommonProviderOptions { + id: string + type: "anonymous" + maxAge?: number + /** Used to hash the verification token. */ + secret?: string + options?: AnonymousUserConfig +} + +export type AnonymousUserConfig = Omit< + Partial, + "options" | "type" +> + +export default function AnonymousProvider( + config: AnonymousUserConfig +): AnonymousConfig { + return { + id: "anonymous", + type: "anonymous", + name: "Anonymous", + maxAge: 60 * 24 * 60 * 60, + options: config, + } +} diff --git a/packages/core/src/providers/email.ts b/packages/core/src/providers/email.ts index 35bd306d77..7223b496d6 100644 --- a/packages/core/src/providers/email.ts +++ b/packages/core/src/providers/email.ts @@ -1,12 +1,12 @@ import type { CommonProviderOptions } from "./index.js" import type { Awaitable, Theme } from "../types.js" -export type { EmailProviderId } from "./provider-types.js" - +import type { NodemailerConfig, NodemailerUserConfig } from "./nodemailer.js" // TODO: Kepts for backwards compatibility // Remove this import and encourage users // to import it from @auth/core/providers/nodemailer directly import Nodemailer from "./nodemailer.js" -import type { NodemailerConfig, NodemailerUserConfig } from "./nodemailer.js" + +export type { EmailProviderId } from "./provider-types.js" /** * @deprecated @@ -53,6 +53,7 @@ export interface EmailConfig extends CommonProviderOptions { /** Used with SMTP-based email providers. */ server?: NodemailerConfig["server"] generateVerificationToken?: () => Awaitable + verifyCaptchaToken?: (captcha_token: string) => Awaitable normalizeIdentifier?: (identifier: string) => string options?: EmailUserConfig } diff --git a/packages/core/src/providers/index.ts b/packages/core/src/providers/index.ts index 9edfe85872..7a77588137 100644 --- a/packages/core/src/providers/index.ts +++ b/packages/core/src/providers/index.ts @@ -10,6 +10,8 @@ import type { OIDCConfig, } from "./oauth.js" import type { WebAuthnConfig, WebAuthnProviderType } from "./webauthn.js" +import { SMSConfig } from "./sms.js" +import { AnonymousConfig } from "./anonymous.js" export * from "./credentials.js" export * from "./email.js" @@ -28,6 +30,8 @@ export type ProviderType = | "oauth" | "email" | "credentials" + | "sms" + | "anonymous" | WebAuthnProviderType /** Shared across all {@link ProviderType} */ @@ -70,6 +74,8 @@ export type Provider

= ( | OIDCConfig

| OAuth2Config

| EmailConfig + | SMSConfig + | AnonymousConfig | CredentialsConfig | WebAuthnConfig ) & @@ -80,6 +86,8 @@ export type Provider

= ( | OAuth2Config

| OIDCConfig

| EmailConfig + | SMSConfig + | AnonymousConfig | CredentialsConfig | WebAuthnConfig ) & diff --git a/packages/core/src/providers/sms.ts b/packages/core/src/providers/sms.ts new file mode 100644 index 0000000000..4434adbc25 --- /dev/null +++ b/packages/core/src/providers/sms.ts @@ -0,0 +1,72 @@ +import { CommonProviderOptions } from "./index.js" +import { Awaitable, Theme } from "../types.js" +import { Adapter, AdapterUser, VerificationToken } from "../adapters.js" + +export type SmsProviderSendVerificationRequestParams = { + identifier: string + url: string + expires: Date + provider: SMSConfig + token: string + theme: Theme + request: Request +} + +export type SmsProviderGenerateVerificationTokenParams = { + identifier: string + user: AdapterUser | null + provider: SMSConfig + theme: Theme + request: Request +} + +export type SmsProviderCheckVerificationTokenParams = { + identifier: string + url: string + expires: Date + provider: SMSConfig + secret: string | string[] + token: string + theme: Theme + request: Request + adapter?: Required +} + +export interface SMSConfig extends CommonProviderOptions { + id: string + type: "sms" + maxAge?: number + sendVerificationRequest: ( + params: SmsProviderSendVerificationRequestParams + ) => Awaitable + /** Used to hash the verification token. */ + secret?: string + generateVerificationToken?: ( + params: SmsProviderGenerateVerificationTokenParams + ) => Awaitable + checkVerificationRequest?: ( + params: SmsProviderCheckVerificationTokenParams + ) => Awaitable + normalizeIdentifier?: (identifier: string) => string + verifyCaptchaToken?: (captcha_token: string) => Awaitable + options?: SMSUserConfig +} + +export type SMSUserConfig = Omit, "options" | "type"> + +export default function SMSProvider(config: SMSUserConfig): SMSConfig { + return { + id: "sms", + type: "sms", + name: "SMS", + maxAge: 5 * 60, + async generateVerificationToken() { + const random = crypto.getRandomValues(new Uint8Array(6)) + return Array.from(random, (byte) => byte % 10).join("") + }, + async sendVerificationRequest(params) { + throw new Error(`sendVerificationRequest not implemented: ${params}`) + }, + options: config, + } +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 53bd508ba2..1d31fbb4df 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -69,6 +69,8 @@ import type { WebAuthnConfig, WebAuthnProviderType, } from "./providers/webauthn.js" +import { SMSConfig } from "./providers/sms.js" +import { AnonymousConfig } from "./providers/anonymous.js" export type { WebAuthnOptionsResponseBody } from "./lib/utils/webauthn-utils.js" export type { AuthConfig } from "./index.js" @@ -252,6 +254,7 @@ export interface DefaultUser { id?: string name?: string | null email?: string | null + phoneNumber?: string | null image?: string | null } @@ -271,11 +274,15 @@ export type InternalProvider = (T extends "oauth" ? OIDCConfigInternal : T extends "email" ? EmailConfig - : T extends "credentials" - ? CredentialsConfig - : T extends WebAuthnProviderType - ? WebAuthnConfig - : never) & { + : T extends "sms" + ? SMSConfig + : T extends "anonymous" + ? AnonymousConfig + : T extends "credentials" + ? CredentialsConfig + : T extends WebAuthnProviderType + ? WebAuthnConfig + : never) & { signinUrl: string /** @example `"https://example.com/api/auth/callback/id"` */ callbackUrl: string diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index 9929b6cce0..6dc5a776a7 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -1,9 +1,12 @@ { "name": "next-auth", - "version": "5.0.0-beta.25", + "version": "5.0.0-beta.26", "description": "Authentication for Next.js", "homepage": "https://nextjs.authjs.dev", - "repository": "https://github.com/nextauthjs/next-auth.git", + "repository": { + "type": "git", + "url": "git+https://github.com/nextauthjs/next-auth.git" + }, "author": "Balázs Orbán ", "contributors": [ "Iain Collins ", diff --git a/packages/next-auth/src/index.ts b/packages/next-auth/src/index.ts index e6e5f06db3..f196fc03d6 100644 --- a/packages/next-auth/src/index.ts +++ b/packages/next-auth/src/index.ts @@ -386,6 +386,7 @@ export default function NextAuth( signIn: async (provider, options, authorizationParams) => { const _config = await config(undefined) setEnvDefaults(_config) + console.log("BLABLA3:", { provider, options, authorizationParams }) return signIn(provider, options, authorizationParams, _config) }, signOut: async (options) => { diff --git a/packages/next-auth/src/lib/actions.ts b/packages/next-auth/src/lib/actions.ts index 80081fa2e0..bae008f26b 100644 --- a/packages/next-auth/src/lib/actions.ts +++ b/packages/next-auth/src/lib/actions.ts @@ -62,7 +62,11 @@ export async function signIn( return url } - if (foundProvider.type === "credentials") { + if ( + foundProvider.type === "credentials" || + (foundProvider.type === "sms" && rest.token) || + foundProvider.type === "anonymous" + ) { url = url.replace("signin", "callback") } diff --git a/packages/next-auth/src/react.tsx b/packages/next-auth/src/react.tsx index a900155c68..ecbc40de09 100644 --- a/packages/next-auth/src/react.tsx +++ b/packages/next-auth/src/react.tsx @@ -13,17 +13,6 @@ "use client" import * as React from "react" -import { - apiBaseUrl, - ClientSessionError, - fetchData, - now, - parseUrl, - useOnline, -} from "./lib/client.js" - -import type { ProviderId } from "@auth/core/providers" -import type { LoggerInstance, Session } from "@auth/core/types" import type { AuthClientConfig, ClientSafeProvider, @@ -35,6 +24,17 @@ import type { SignOutResponse, UseSessionOptions, } from "./lib/client.js" +import { + apiBaseUrl, + ClientSessionError, + fetchData, + now, + parseUrl, + useOnline, +} from "./lib/client.js" + +import type { ProviderId } from "@auth/core/providers" +import type { LoggerInstance, Session } from "@auth/core/types" // TODO: Remove/move to core? export type { @@ -271,7 +271,11 @@ export async function signIn( } const signInUrl = `${baseUrl}/${ - providerType === "credentials" ? "callback" : "signin" + providerType === "credentials" || + (providerType === "sms" && signInParams.token) || + (providerType === "email" && signInParams.token) + ? "callback" + : "signin" }/${provider}` const csrfToken = await getCsrfToken() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 128478d869..783866a157 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -246,10 +246,10 @@ importers: version: 0.4.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@inkeep/widgets': specifier: ^0.2.289 - version: 0.2.289(@internationalized/date@3.5.6)(@types/react@18.2.78)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + version: 0.2.289(@internationalized/date@3.5.2)(@types/react@18.2.78)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@next/third-parties': specifier: ^14.2.15 - version: 14.2.15(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react@18.3.1) + version: 14.2.15(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react@18.3.1) '@radix-ui/react-accordion': specifier: ^1.2.1 version: 1.2.1(@types/react-dom@18.2.18)(@types/react@18.2.78)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -258,7 +258,7 @@ importers: version: 1.1.1(@types/react-dom@18.2.18)(@types/react@18.2.78)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@vercel/analytics': specifier: ^1.3.1 - version: 1.3.1(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react@18.3.1) + version: 1.3.1(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react@18.3.1) '@vercel/kv': specifier: ^1.0.1 version: 1.0.1 @@ -272,17 +272,17 @@ importers: specifier: ^11.11.8 version: 11.11.8(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: - specifier: 14.2.15 - version: 14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) + specifier: 14.2.21 + version: 14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0)) + version: 4.2.3(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0)) nextra: specifier: 3.0.15 - version: 3.0.15(@types/react@18.2.78)(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + version: 3.0.15(@types/react@18.2.78)(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) nextra-theme-docs: specifier: 3.0.15 - version: 3.0.15(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(nextra@3.0.15(@types/react@18.2.78)(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.0.15(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(nextra@3.0.15(@types/react@18.2.78)(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -294,7 +294,7 @@ importers: version: 7.13.3(algoliasearch@4.24.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-instantsearch-nextjs: specifier: ^0.2.5 - version: 0.2.5(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-instantsearch@7.13.3(algoliasearch@4.24.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 0.2.5(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-instantsearch@7.13.3(algoliasearch@4.24.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) devDependencies: '@types/node': specifier: 20.12.7 @@ -377,7 +377,7 @@ importers: version: 0.23.0 drizzle-orm: specifier: ^0.32.0 - version: 0.32.1(@cloudflare/workers-types@4.20240117.0)(@libsql/client@0.6.0)(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.7.0)(@prisma/client@5.20.0)(@types/better-sqlite3@7.6.9)(@types/pg@8.11.6)(@types/react@18.2.78)(@xata.io/client@0.28.0(typescript@5.3.3))(better-sqlite3@9.6.0)(knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.9.7)(pg@8.11.3)(sqlite3@5.1.6))(kysely@0.24.2)(mysql2@3.9.7)(pg@8.11.3)(postgres@3.4.3)(react@18.3.1)(sqlite3@5.1.6) + version: 0.32.1(@cloudflare/workers-types@4.20240117.0)(@libsql/client@0.6.0)(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.7.0)(@prisma/client@6.0.0)(@types/better-sqlite3@7.6.9)(@types/pg@8.11.6)(@types/react@18.2.78)(@xata.io/client@0.28.0(typescript@5.3.3))(better-sqlite3@9.6.0)(knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.9.7)(pg@8.11.3)(sqlite3@5.1.6))(kysely@0.27.5)(mysql2@3.9.7)(pg@8.11.3)(postgres@3.4.3)(react@18.3.1)(sqlite3@5.1.6) libsql: specifier: ^0.3.18 version: 0.3.18 @@ -735,7 +735,7 @@ importers: specifier: workspace:* version: link:../core express: - specifier: ^4.18.2 + specifier: ^4.18.2 || ^5.0.0 version: 4.20.0 devDependencies: '@types/express': @@ -3647,14 +3647,14 @@ packages: '@next/env@13.5.6': resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==} - '@next/env@14.2.15': - resolution: {integrity: sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==} + '@next/env@14.2.21': + resolution: {integrity: sha512-lXcwcJd5oR01tggjWJ6SrNNYFGuOOMB9c251wUNkjCpkoXOPkDeF/15c3mnVlBqrW4JJXb2kVxDFhC4GduJt2A==} '@next/env@15.0.0-rc.1': resolution: {integrity: sha512-4neDwowyr+9DfgickGjNATp6Lm3rJ/Y83ulg8irVXUoN+mLikrInYSzFDnwpkflO/wokFR4z5A171RVPnapZ/Q==} - '@next/swc-darwin-arm64@14.2.15': - resolution: {integrity: sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==} + '@next/swc-darwin-arm64@14.2.21': + resolution: {integrity: sha512-HwEjcKsXtvszXz5q5Z7wCtrHeTTDSTgAbocz45PHMUjU3fBYInfvhR+ZhavDRUYLonm53aHZbB09QtJVJj8T7g==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -3665,8 +3665,8 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.15': - resolution: {integrity: sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==} + '@next/swc-darwin-x64@14.2.21': + resolution: {integrity: sha512-TSAA2ROgNzm4FhKbTbyJOBrsREOMVdDIltZ6aZiKvCi/v0UwFmwigBGeqXDA97TFMpR3LNNpw52CbVelkoQBxA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -3677,8 +3677,8 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.15': - resolution: {integrity: sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==} + '@next/swc-linux-arm64-gnu@14.2.21': + resolution: {integrity: sha512-0Dqjn0pEUz3JG+AImpnMMW/m8hRtl1GQCNbO66V1yp6RswSTiKmnHf3pTX6xMdJYSemf3O4Q9ykiL0jymu0TuA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -3689,8 +3689,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.15': - resolution: {integrity: sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==} + '@next/swc-linux-arm64-musl@14.2.21': + resolution: {integrity: sha512-Ggfw5qnMXldscVntwnjfaQs5GbBbjioV4B4loP+bjqNEb42fzZlAaK+ldL0jm2CTJga9LynBMhekNfV8W4+HBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -3701,8 +3701,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.15': - resolution: {integrity: sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==} + '@next/swc-linux-x64-gnu@14.2.21': + resolution: {integrity: sha512-uokj0lubN1WoSa5KKdThVPRffGyiWlm/vCc/cMkWOQHw69Qt0X1o3b2PyLLx8ANqlefILZh1EdfLRz9gVpG6tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -3713,8 +3713,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.15': - resolution: {integrity: sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==} + '@next/swc-linux-x64-musl@14.2.21': + resolution: {integrity: sha512-iAEBPzWNbciah4+0yI4s7Pce6BIoxTQ0AGCkxn/UBuzJFkYyJt71MadYQkjPqCQCJAFQ26sYh7MOKdU+VQFgPg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -3725,8 +3725,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.15': - resolution: {integrity: sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==} + '@next/swc-win32-arm64-msvc@14.2.21': + resolution: {integrity: sha512-plykgB3vL2hB4Z32W3ktsfqyuyGAPxqwiyrAi2Mr8LlEUhNn9VgkiAl5hODSBpzIfWweX3er1f5uNpGDygfQVQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -3737,14 +3737,14 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.15': - resolution: {integrity: sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==} + '@next/swc-win32-ia32-msvc@14.2.21': + resolution: {integrity: sha512-w5bacz4Vxqrh06BjWgua3Yf7EMDb8iMcVhNrNx8KnJXt8t+Uu0Zg4JHLDL/T7DkTCEEfKXO/Er1fcfWxn2xfPA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.15': - resolution: {integrity: sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==} + '@next/swc-win32-x64-msvc@14.2.21': + resolution: {integrity: sha512-sT6+llIkzpsexGYZq8cjjthRyRGe5cJVhqh12FmlbxHqna6zsDDK8UNaV7g41T6atFHCJUPeLb3uyAwrBwy0NA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -10222,8 +10222,8 @@ packages: react: ^16.8 || ^17 || ^18 react-dom: ^16.8 || ^17 || ^18 - next@14.2.15: - resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==} + next@14.2.21: + resolution: {integrity: sha512-rZmLwucLHr3/zfDMYbJXbw0ZeoBpirxkXuvsJbk7UPorvPYZhP7vq7aHbKnU7dQNCYIimRrbB2pp3xmf+wsYUg==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -13866,7 +13866,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@ark-ui/anatomy@0.1.0(@internationalized/date@3.5.6)': + '@ark-ui/anatomy@0.1.0(@internationalized/date@3.5.2)': dependencies: '@zag-js/accordion': 0.20.0 '@zag-js/anatomy': 0.20.0 @@ -13877,7 +13877,7 @@ snapshots: '@zag-js/color-utils': 0.20.0 '@zag-js/combobox': 0.20.0 '@zag-js/date-picker': 0.20.0 - '@zag-js/date-utils': 0.20.0(@internationalized/date@3.5.6) + '@zag-js/date-utils': 0.20.0(@internationalized/date@3.5.2) '@zag-js/dialog': 0.20.0 '@zag-js/editable': 0.20.0 '@zag-js/hover-card': 0.20.0 @@ -13903,7 +13903,7 @@ snapshots: transitivePeerDependencies: - '@internationalized/date' - '@ark-ui/react@0.15.0(@internationalized/date@3.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@ark-ui/react@0.15.0(@internationalized/date@3.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@zag-js/accordion': 0.19.1 '@zag-js/anatomy': 0.19.1 @@ -13915,7 +13915,7 @@ snapshots: '@zag-js/combobox': 0.19.1 '@zag-js/core': 0.19.1 '@zag-js/date-picker': 0.19.1 - '@zag-js/date-utils': 0.19.1(@internationalized/date@3.5.6) + '@zag-js/date-utils': 0.19.1(@internationalized/date@3.5.2) '@zag-js/dialog': 0.19.1 '@zag-js/editable': 0.19.1 '@zag-js/hover-card': 0.19.1 @@ -16689,11 +16689,11 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@inkeep/components@0.0.24(@ark-ui/react@0.15.0(@internationalized/date@3.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@internationalized/date@3.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@inkeep/components@0.0.24(@ark-ui/react@0.15.0(@internationalized/date@3.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@internationalized/date@3.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': dependencies: - '@ark-ui/react': 0.15.0(@internationalized/date@3.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@inkeep/preset': 0.0.24(@internationalized/date@3.5.6)(typescript@5.6.3) - '@inkeep/preset-chakra': 0.0.24(@internationalized/date@3.5.6)(typescript@5.6.3) + '@ark-ui/react': 0.15.0(@internationalized/date@3.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@inkeep/preset': 0.0.24(@internationalized/date@3.5.2)(typescript@5.6.3) + '@inkeep/preset-chakra': 0.0.24(@internationalized/date@3.5.2)(typescript@5.6.3) '@inkeep/shared': 0.0.25 '@inkeep/styled-system': 0.0.44 '@pandacss/dev': 0.22.1(typescript@5.6.3) @@ -16705,9 +16705,9 @@ snapshots: - jsdom - typescript - '@inkeep/preset-chakra@0.0.24(@internationalized/date@3.5.6)(typescript@5.6.3)': + '@inkeep/preset-chakra@0.0.24(@internationalized/date@3.5.2)(typescript@5.6.3)': dependencies: - '@ark-ui/anatomy': 0.1.0(@internationalized/date@3.5.6) + '@ark-ui/anatomy': 0.1.0(@internationalized/date@3.5.2) '@inkeep/shared': 0.0.25 '@pandacss/dev': 0.22.1(typescript@5.6.3) transitivePeerDependencies: @@ -16715,10 +16715,10 @@ snapshots: - jsdom - typescript - '@inkeep/preset@0.0.24(@internationalized/date@3.5.6)(typescript@5.6.3)': + '@inkeep/preset@0.0.24(@internationalized/date@3.5.2)(typescript@5.6.3)': dependencies: - '@ark-ui/anatomy': 0.1.0(@internationalized/date@3.5.6) - '@inkeep/preset-chakra': 0.0.24(@internationalized/date@3.5.6)(typescript@5.6.3) + '@ark-ui/anatomy': 0.1.0(@internationalized/date@3.5.2) + '@inkeep/preset-chakra': 0.0.24(@internationalized/date@3.5.2)(typescript@5.6.3) '@inkeep/shared': 0.0.25 '@pandacss/dev': 0.22.1(typescript@5.6.3) colorjs.io: 0.4.5 @@ -16733,14 +16733,14 @@ snapshots: '@inkeep/styled-system@0.0.46': {} - '@inkeep/widgets@0.2.289(@internationalized/date@3.5.6)(@types/react@18.2.78)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': + '@inkeep/widgets@0.2.289(@internationalized/date@3.5.2)(@types/react@18.2.78)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': dependencies: '@apollo/client': 3.9.5(@types/react@18.2.78)(graphql-ws@5.14.3(graphql@16.8.1))(graphql@16.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@ark-ui/react': 0.15.0(@internationalized/date@3.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ark-ui/react': 0.15.0(@internationalized/date@3.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@inkeep/color-mode': 0.0.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@inkeep/components': 0.0.24(@ark-ui/react@0.15.0(@internationalized/date@3.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@internationalized/date@3.5.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@inkeep/preset': 0.0.24(@internationalized/date@3.5.6)(typescript@5.6.3) - '@inkeep/preset-chakra': 0.0.24(@internationalized/date@3.5.6)(typescript@5.6.3) + '@inkeep/components': 0.0.24(@ark-ui/react@0.15.0(@internationalized/date@3.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@internationalized/date@3.5.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@inkeep/preset': 0.0.24(@internationalized/date@3.5.2)(typescript@5.6.3) + '@inkeep/preset-chakra': 0.0.24(@internationalized/date@3.5.2)(typescript@5.6.3) '@inkeep/shared': 0.0.25 '@inkeep/styled-system': 0.0.46 '@types/lodash.isequal': 4.5.8 @@ -17266,64 +17266,64 @@ snapshots: '@next/env@13.5.6': {} - '@next/env@14.2.15': {} + '@next/env@14.2.21': {} '@next/env@15.0.0-rc.1': {} - '@next/swc-darwin-arm64@14.2.15': + '@next/swc-darwin-arm64@14.2.21': optional: true '@next/swc-darwin-arm64@15.0.0-rc.1': optional: true - '@next/swc-darwin-x64@14.2.15': + '@next/swc-darwin-x64@14.2.21': optional: true '@next/swc-darwin-x64@15.0.0-rc.1': optional: true - '@next/swc-linux-arm64-gnu@14.2.15': + '@next/swc-linux-arm64-gnu@14.2.21': optional: true '@next/swc-linux-arm64-gnu@15.0.0-rc.1': optional: true - '@next/swc-linux-arm64-musl@14.2.15': + '@next/swc-linux-arm64-musl@14.2.21': optional: true '@next/swc-linux-arm64-musl@15.0.0-rc.1': optional: true - '@next/swc-linux-x64-gnu@14.2.15': + '@next/swc-linux-x64-gnu@14.2.21': optional: true '@next/swc-linux-x64-gnu@15.0.0-rc.1': optional: true - '@next/swc-linux-x64-musl@14.2.15': + '@next/swc-linux-x64-musl@14.2.21': optional: true '@next/swc-linux-x64-musl@15.0.0-rc.1': optional: true - '@next/swc-win32-arm64-msvc@14.2.15': + '@next/swc-win32-arm64-msvc@14.2.21': optional: true '@next/swc-win32-arm64-msvc@15.0.0-rc.1': optional: true - '@next/swc-win32-ia32-msvc@14.2.15': + '@next/swc-win32-ia32-msvc@14.2.21': optional: true - '@next/swc-win32-x64-msvc@14.2.15': + '@next/swc-win32-x64-msvc@14.2.21': optional: true '@next/swc-win32-x64-msvc@15.0.0-rc.1': optional: true - '@next/third-parties@14.2.15(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react@18.3.1)': + '@next/third-parties@14.2.15(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react@18.3.1)': dependencies: - next: 14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) + next: 14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) react: 18.3.1 third-party-capital: 1.0.20 @@ -19664,11 +19664,11 @@ snapshots: dependencies: crypto-js: 4.2.0 - '@vercel/analytics@1.3.1(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react@18.3.1)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) + next: 14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) react: 18.3.1 '@vercel/kv@1.0.1': @@ -20074,9 +20074,9 @@ snapshots: dependencies: '@internationalized/date': 3.5.2 - '@zag-js/date-utils@0.19.1(@internationalized/date@3.5.6)': + '@zag-js/date-utils@0.20.0(@internationalized/date@3.5.2)': dependencies: - '@internationalized/date': 3.5.6 + '@internationalized/date': 3.5.2 '@zag-js/date-utils@0.20.0(@internationalized/date@3.5.6)': dependencies: @@ -21235,7 +21235,7 @@ snapshots: browserslist@4.23.0: dependencies: - caniuse-lite: 1.0.30001609 + caniuse-lite: 1.0.30001668 electron-to-chromium: 1.4.693 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -22248,7 +22248,7 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.32.1(@cloudflare/workers-types@4.20240117.0)(@libsql/client@0.6.0)(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.7.0)(@prisma/client@5.20.0)(@types/better-sqlite3@7.6.9)(@types/pg@8.11.6)(@types/react@18.2.78)(@xata.io/client@0.28.0(typescript@5.3.3))(better-sqlite3@9.6.0)(knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.9.7)(pg@8.11.3)(sqlite3@5.1.6))(kysely@0.24.2)(mysql2@3.9.7)(pg@8.11.3)(postgres@3.4.3)(react@18.3.1)(sqlite3@5.1.6): + drizzle-orm@0.32.1(@cloudflare/workers-types@4.20240117.0)(@libsql/client@0.6.0)(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.7.0)(@prisma/client@6.0.0)(@types/better-sqlite3@7.6.9)(@types/pg@8.11.6)(@types/react@18.2.78)(@xata.io/client@0.28.0(typescript@5.3.3))(better-sqlite3@9.6.0)(knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.9.7)(pg@8.11.3)(sqlite3@5.1.6))(kysely@0.27.5)(mysql2@3.9.7)(pg@8.11.3)(postgres@3.4.3)(react@18.3.1)(sqlite3@5.1.6): optionalDependencies: '@cloudflare/workers-types': 4.20240117.0 '@libsql/client': 0.6.0 @@ -26072,22 +26072,22 @@ snapshots: neo4j-driver-core: 5.16.0 rxjs: 7.8.1 - next-sitemap@4.2.3(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0)): + next-sitemap@4.2.3(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0)): dependencies: '@corex/deepmerge': 4.0.43 '@next/env': 13.5.6 fast-glob: 3.3.2 minimist: 1.2.8 - next: 14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) + next: 14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0): + next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0): dependencies: - '@next/env': 14.2.15 + '@next/env': 14.2.21 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001668 @@ -26097,15 +26097,15 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.15 - '@next/swc-darwin-x64': 14.2.15 - '@next/swc-linux-arm64-gnu': 14.2.15 - '@next/swc-linux-arm64-musl': 14.2.15 - '@next/swc-linux-x64-gnu': 14.2.15 - '@next/swc-linux-x64-musl': 14.2.15 - '@next/swc-win32-arm64-msvc': 14.2.15 - '@next/swc-win32-ia32-msvc': 14.2.15 - '@next/swc-win32-x64-msvc': 14.2.15 + '@next/swc-darwin-arm64': 14.2.21 + '@next/swc-darwin-x64': 14.2.21 + '@next/swc-linux-arm64-gnu': 14.2.21 + '@next/swc-linux-arm64-musl': 14.2.21 + '@next/swc-linux-x64-gnu': 14.2.21 + '@next/swc-linux-x64-musl': 14.2.21 + '@next/swc-win32-arm64-msvc': 14.2.21 + '@next/swc-win32-ia32-msvc': 14.2.21 + '@next/swc-win32-x64-msvc': 14.2.21 '@opentelemetry/api': 1.7.0 '@playwright/test': 1.41.2 sass: 1.70.0 @@ -26169,21 +26169,21 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@3.0.15(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(nextra@3.0.15(@types/react@18.2.78)(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@3.0.15(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(nextra@3.0.15(@types/react@18.2.78)(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.0 escape-string-regexp: 5.0.0 flexsearch: 0.7.43 - next: 14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) + next: 14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) next-themes: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 3.0.15(@types/react@18.2.78)(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + nextra: 3.0.15(@types/react@18.2.78)(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.22.4 - nextra@3.0.15(@types/react@18.2.78)(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): + nextra@3.0.15(@types/react@18.2.78)(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): dependencies: '@formatjs/intl-localematcher': 0.5.5 '@headlessui/react': 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -26203,7 +26203,7 @@ snapshots: hast-util-to-estree: 3.1.0 katex: 0.16.9 negotiator: 0.6.3 - next: 14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) + next: 14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) p-limit: 6.1.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -27389,9 +27389,9 @@ snapshots: react: 18.3.1 use-sync-external-store: 1.2.2(react@18.3.1) - react-instantsearch-nextjs@0.2.5(next@14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-instantsearch@7.13.3(algoliasearch@4.24.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + react-instantsearch-nextjs@0.2.5(next@14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0))(react-instantsearch@7.13.3(algoliasearch@4.24.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: - next: 14.2.15(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) + next: 14.2.21(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.70.0) react-instantsearch: 7.13.3(algoliasearch@4.24.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-instantsearch@7.13.3(algoliasearch@4.24.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -29466,7 +29466,7 @@ snapshots: dependencies: browserslist: 4.23.0 escalade: 3.1.1 - picocolors: 1.0.0 + picocolors: 1.1.0 update-browserslist-db@1.1.1(browserslist@4.24.0): dependencies: @@ -29652,7 +29652,7 @@ snapshots: error-stack-parser-es: 0.1.1 fs-extra: 11.2.0 open: 9.1.0 - picocolors: 1.0.0 + picocolors: 1.1.0 sirv: 2.0.4 vite: 5.3.1(@types/node@18.11.10)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: