Merge branch 'main' into qa

This commit is contained in:
Max Peintner
2025-02-05 10:10:22 +01:00
committed by GitHub
10 changed files with 263 additions and 130 deletions

View File

@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '20'

View File

@@ -63,7 +63,7 @@ jobs:
uses: pnpm/action-setup@v4.0.0
- name: Setup Node.js 20.x
uses: actions/setup-node@v4.0.2
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'pnpm'

View File

@@ -46,7 +46,7 @@
"deepmerge": "^4.3.1",
"jose": "^5.3.0",
"moment": "^2.29.4",
"next": "15.0.4-canary.23",
"next": "15.2.0-canary.33",
"next-intl": "^3.25.1",
"next-themes": "^0.2.1",
"nice-grpc": "2.0.1",

View File

@@ -389,10 +389,6 @@ In future, self service options to jump to are shown below, like:
## Currently NOT Supported
Timebased features like the multifactor init prompt or password expiry, are not supported due to a current limitation in the API. Lockout settings which keeps track of the password retries, will also be implemented in a later stage.
- Lockout Settings
- Password Expiry Settings
- Login Settings: multifactor init prompt
- forceMFA on login settings is not checked for IDPs

View File

@@ -7,7 +7,12 @@ import {
getSession,
setSession,
} from "@/lib/zitadel";
import { Duration, timestampMs } from "@zitadel/client";
import { ConnectError, Duration, timestampMs } from "@zitadel/client";
import {
CredentialsCheckError,
CredentialsCheckErrorSchema,
ErrorDetail,
} from "@zitadel/proto/zitadel/message_pb";
import {
Challenges,
RequestChallenges,
@@ -28,6 +33,19 @@ type CustomCookieData = {
authRequestId?: string; // if its linked to an OIDC flow
};
const passwordAttemptsHandler = (error: ConnectError) => {
const details = error.findDetails(CredentialsCheckErrorSchema);
if (details[0] && "failedAttempts" in details[0]) {
const failedAttempts = details[0].failedAttempts;
throw {
error: `Failed to authenticate: You had ${failedAttempts} password attempts.`,
failedAttempts: failedAttempts,
};
}
throw error;
};
export async function createSessionAndUpdateCookie(
checks: Checks,
challenges: RequestChallenges | undefined,
@@ -107,6 +125,15 @@ export async function createSessionForIdpAndUpdateCookie(
userId,
idpIntent,
lifetime,
}).catch((error: ErrorDetail | CredentialsCheckError) => {
console.error("Could not set session", error);
if ("failedAttempts" in error && error.failedAttempts) {
throw {
error: `Failed to authenticate: You had ${error.failedAttempts} password attempts.`,
failedAttempts: error.failedAttempts,
};
}
throw error;
});
if (!createdSession) {
@@ -173,59 +200,61 @@ export async function setSessionAndUpdateCookie(
challenges,
checks,
lifetime,
}).then((updatedSession) => {
if (updatedSession) {
const sessionCookie: CustomCookieData = {
id: recentCookie.id,
token: updatedSession.sessionToken,
creationTs: recentCookie.creationTs,
expirationTs: recentCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
loginName: recentCookie.loginName,
organization: recentCookie.organization,
};
})
.then((updatedSession) => {
if (updatedSession) {
const sessionCookie: CustomCookieData = {
id: recentCookie.id,
token: updatedSession.sessionToken,
creationTs: recentCookie.creationTs,
expirationTs: recentCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
loginName: recentCookie.loginName,
organization: recentCookie.organization,
};
if (authRequestId) {
sessionCookie.authRequestId = authRequestId;
}
return getSession({
serviceUrl,
serviceRegion,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
if (response?.session && response.session.factors?.user?.loginName) {
const { session } = response;
const newCookie: CustomCookieData = {
id: sessionCookie.id,
token: updatedSession.sessionToken,
creationTs: sessionCookie.creationTs,
expirationTs: sessionCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
loginName: session.factors?.user?.loginName ?? "",
organization: session.factors?.user?.organizationId ?? "",
};
if (sessionCookie.authRequestId) {
newCookie.authRequestId = sessionCookie.authRequestId;
}
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
return { challenges: updatedSession.challenges, ...session };
});
} else {
throw "could not get session or session does not have loginName";
if (authRequestId) {
sessionCookie.authRequestId = authRequestId;
}
});
} else {
throw "Session not be set";
}
});
return getSession({
serviceUrl,
serviceRegion,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
if (response?.session && response.session.factors?.user?.loginName) {
const { session } = response;
const newCookie: CustomCookieData = {
id: sessionCookie.id,
token: updatedSession.sessionToken,
creationTs: sessionCookie.creationTs,
expirationTs: sessionCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
loginName: session.factors?.user?.loginName ?? "",
organization: session.factors?.user?.organizationId ?? "",
};
if (sessionCookie.authRequestId) {
newCookie.authRequestId = sessionCookie.authRequestId;
}
return updateSessionCookie(sessionCookie.id, newCookie).then(() => {
return { challenges: updatedSession.challenges, ...session };
});
} else {
throw "could not get session or session does not have loginName";
}
});
} else {
throw "Session not be set";
}
})
.catch(passwordAttemptsHandler);
}

View File

@@ -5,7 +5,9 @@ import {
setSessionAndUpdateCookie,
} from "@/lib/server/cookie";
import {
getLockoutSettings,
getLoginSettings,
getPasswordExpirySettings,
getSession,
getUserByID,
listAuthenticationMethodTypes,
@@ -122,24 +124,64 @@ export async function sendPassword(command: UpdateSessionCommand) {
organization: command.organization,
});
session = await createSessionAndUpdateCookie(
checks,
undefined,
command.authRequestId,
loginSettings?.passwordCheckLifetime,
);
try {
session = await createSessionAndUpdateCookie(
checks,
undefined,
command.authRequestId,
loginSettings?.passwordCheckLifetime,
);
} catch (error: any) {
if ("failedAttempts" in error && error.failedAttempts) {
const lockoutSettings = await getLockoutSettings({
serviceUrl,
serviceRegion,
orgId: command.organization,
});
return {
error:
`Failed to authenticate. You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.` +
(lockoutSettings?.maxPasswordAttempts &&
error.failedAttempts >= lockoutSettings?.maxPasswordAttempts
? "Contact your administrator to unlock your account"
: ""),
};
}
return { error: "Could not create session for user" };
}
}
// this is a fake error message to hide that the user does not even exist
return { error: "Could not verify password" };
} else {
session = await setSessionAndUpdateCookie(
sessionCookie,
command.checks,
undefined,
command.authRequestId,
loginSettings?.passwordCheckLifetime,
);
try {
session = await setSessionAndUpdateCookie(
sessionCookie,
command.checks,
undefined,
command.authRequestId,
loginSettings?.passwordCheckLifetime,
);
} catch (error: any) {
if ("failedAttempts" in error && error.failedAttempts) {
const lockoutSettings = await getLockoutSettings({
serviceUrl,
serviceRegion,
orgId: command.organization,
});
return {
error:
`Failed to authenticate. You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.` +
(lockoutSettings?.maxPasswordAttempts &&
error.failedAttempts >= lockoutSettings?.maxPasswordAttempts
? " Contact your administrator to unlock your account"
: ""),
};
}
throw error;
}
if (!session?.factors?.user?.id) {
return { error: "Could not create session for user" };
@@ -173,8 +215,15 @@ export async function sendPassword(command: UpdateSessionCommand) {
const humanUser = user.type.case === "human" ? user.type.value : undefined;
const expirySettings = await getPasswordExpirySettings({
serviceUrl,
serviceRegion,
orgId: command.organization ?? session.factors?.user?.organizationId,
});
// check if the user has to change password first
const passwordChangedCheck = checkPasswordChangeRequired(
expirySettings,
session,
humanUser,
command.organization,

View File

@@ -1,15 +1,29 @@
import { timestampDate } from "@zitadel/client";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { PasswordExpirySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
import { HumanUser } from "@zitadel/proto/zitadel/user/v2/user_pb";
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import moment from "moment";
export function checkPasswordChangeRequired(
expirySettings: PasswordExpirySettings | undefined,
session: Session,
humanUser: HumanUser | undefined,
organization?: string,
authRequestId?: string,
) {
if (humanUser?.passwordChangeRequired) {
let isOutdated = false;
if (expirySettings?.maxAgeDays && humanUser?.passwordChanged) {
const maxAgeDays = Number(expirySettings.maxAgeDays); // Convert bigint to number
const passwordChangedDate = moment(
timestampDate(humanUser.passwordChanged),
);
const outdatedPassword = passwordChangedDate.add(maxAgeDays, "days");
isOutdated = moment().isAfter(outdatedPassword);
}
if (humanUser?.passwordChangeRequired || isOutdated) {
const params = new URLSearchParams({
loginName: session.factors?.user?.loginName as string,
});

View File

@@ -91,6 +91,44 @@ export async function getLoginSettings({
return useCache ? cacheWrapper(callback) : callback;
}
export async function getLockoutSettings({
serviceUrl,
serviceRegion,
orgId,
}: {
serviceUrl: string;
serviceRegion: string;
orgId?: string;
}) {
const settingsService: Client<typeof SettingsService> =
await createServiceForHost(SettingsService, serviceUrl, serviceRegion);
const callback = settingsService
.getLockoutSettings({ ctx: makeReqCtx(orgId) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
return useCache ? cacheWrapper(callback) : callback;
}
export async function getPasswordExpirySettings({
serviceUrl,
serviceRegion,
orgId,
}: {
serviceUrl: string;
serviceRegion: string;
orgId?: string;
}) {
const settingsService: Client<typeof SettingsService> =
await createServiceForHost(SettingsService, serviceUrl, serviceRegion);
const callback = settingsService
.getPasswordExpirySettings({ ctx: makeReqCtx(orgId) }, {})
.then((resp) => (resp.settings ? resp.settings : undefined));
return useCache ? cacheWrapper(callback) : callback;
}
export async function listIDPLinks({
serviceUrl,
serviceRegion,

View File

@@ -7,4 +7,4 @@ export type { JsonObject } from "@bufbuild/protobuf";
export type { GenService } from "@bufbuild/protobuf/codegenv1";
export { TimestampSchema, timestampDate, timestampFromDate, timestampFromMs, timestampMs } from "@bufbuild/protobuf/wkt";
export type { Duration, Timestamp } from "@bufbuild/protobuf/wkt";
export type { Client } from "@connectrpc/connect";
export type { Client, Code, ConnectError } from "@connectrpc/connect";

115
pnpm-lock.yaml generated
View File

@@ -85,7 +85,7 @@ importers:
version: 0.5.7(tailwindcss@3.4.14)
'@vercel/analytics':
specifier: ^1.2.2
version: 1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)
version: 1.3.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)
'@zitadel/client':
specifier: workspace:*
version: link:../../packages/zitadel-client
@@ -108,14 +108,14 @@ importers:
specifier: ^2.29.4
version: 2.30.1
next:
specifier: 15.0.4-canary.23
version: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
specifier: 15.2.0-canary.33
version: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
next-intl:
specifier: ^3.25.1
version: 3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)
version: 3.25.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)
next-themes:
specifier: ^0.2.1
version: 0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
version: 0.2.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
nice-grpc:
specifier: 2.0.1
version: 2.0.1
@@ -1133,56 +1133,56 @@ packages:
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
'@next/env@15.0.4-canary.23':
resolution: {integrity: sha512-NfBMRPa10yaEzQ693kGEsgHL58Y27jSbGCDbyXy14dx3z6UeQZQfEVRAwJ4iG1V6gND9+CzzugtiXvJZfSlC9A==}
'@next/env@15.2.0-canary.33':
resolution: {integrity: sha512-y3EPM+JYKU8t2K+i6bc0QrotEZVGpqu9eVjprj4cfS8QZyZcL54s+W9aGB0TBuGavU9tQdZ50W186+toeMV+hw==}
'@next/eslint-plugin-next@14.2.18':
resolution: {integrity: sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==}
'@next/swc-darwin-arm64@15.0.4-canary.23':
resolution: {integrity: sha512-sX3MaDUiFiMT14KSx5mJz6B+IH9k7+buNniNrDP7iz4YG28jssm9e8uHbiWXsbn9jnkQUJu8PHoUOLhgjZgtsQ==}
'@next/swc-darwin-arm64@15.2.0-canary.33':
resolution: {integrity: sha512-+fCdK2KmR6lWoCTk1fSd5pvbiLZHfZF+D/Xdz3xrXw+pbnBtXWLKQrPT0bCtDseMxD31qcOywq5mAApvI3EGpA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@15.0.4-canary.23':
resolution: {integrity: sha512-KJRSDVvEPuvjRKe9IY3YMAv9KMOmB/U5+7g0c3OTT/50x1KL0XOlgnc+Af2GdZKIrkKiAdTFG54AHaSD584yHg==}
'@next/swc-darwin-x64@15.2.0-canary.33':
resolution: {integrity: sha512-GrrU+tSmeBRow+7bnn7i5M96g3tc28hPH5t5Y65qUXGmmrZwGZN1e1d+8QbXPdAGkvjEPcOkUNQuQVpp1qpYPA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@15.0.4-canary.23':
resolution: {integrity: sha512-0EqeqGdlG0MPDYGE/cPtTvBLtBiWDAd7fSRgRhIga6CkuaRVFKuTeRrsjTa0v+51C2OawjQp2N3ww1zBLuBhcg==}
'@next/swc-linux-arm64-gnu@15.2.0-canary.33':
resolution: {integrity: sha512-8RnGxnUpASHoUf6aHUifmZom5b4Ow5nTdCib/CNYXZ6VLuL5ocvmr+DXs/SKzi9h8OHR7JkLwKXHCcF8WyscSg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-arm64-musl@15.0.4-canary.23':
resolution: {integrity: sha512-O06Gw8HU0z9f1b4TiGb0u1o87hgLa0yEW1odyLPE1d3+JKwhkh4L1Ug9uLpeqEUnxCoIrwVomEUyQBPGNQtq0Q==}
'@next/swc-linux-arm64-musl@15.2.0-canary.33':
resolution: {integrity: sha512-COyE0LzMuLBZSR+Z/TOGilyJPdwSU588Vt0+o8GoECkoDEnjyuO2s2nHa2kDAcEfUEPkhlo0tErU3mF+8AVOTQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-x64-gnu@15.0.4-canary.23':
resolution: {integrity: sha512-BvERc3hri6eyUHnasZgwcRCdR8WpfCdKKe/M12Q+ZAkTeJeVkLXNakznaZbBWdlCc77F/NeHz/OWoQWUTpKm3g==}
'@next/swc-linux-x64-gnu@15.2.0-canary.33':
resolution: {integrity: sha512-3Y9lqJs+ftU9jgbLdCtvAvF8MNJsJYGMH7icb8QMs1+yOyHHbmwkZoElKdjwfUWzQ2sX28ywp73GWq4HbrsoUg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-linux-x64-musl@15.0.4-canary.23':
resolution: {integrity: sha512-FF5LNTdra/tHxdHjRR3lb+UxFgRVT+v3EMruueQg6BpOqpciodyCkkYQFrx2DitpADojQ6bBBFBDs6KIb8jB5w==}
'@next/swc-linux-x64-musl@15.2.0-canary.33':
resolution: {integrity: sha512-FS9iA+RkZlhdWGQEKtsplVBXIYZJUn5nsRB+1UY46b3uaL6dDypu13ODaSwYuAwXGgkrZBVF9AFO3y4biBnPlA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-win32-arm64-msvc@15.0.4-canary.23':
resolution: {integrity: sha512-XnHD7fqQYZR1XCCuAf8+yAdkMpzAFz2pWmny2K6g5C7BalrwNuxWLsM5LycW1PTMzSqkzLJeXCG6AZu099u7/w==}
'@next/swc-win32-arm64-msvc@15.2.0-canary.33':
resolution: {integrity: sha512-Ji9CtBbUx06qvvN/rPohJN2FEFGsUv26F50f2nMRYRwrq3POXDjloGOiRocrjU0ty/cUzCz71qTUfKdmv/ajmg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-x64-msvc@15.0.4-canary.23':
resolution: {integrity: sha512-HGoW8LjYxbUhkND+vJ/21dWQ7sdv4SIUQDv2r/FpcdHFMzb5M/jgQVqcMFkqg2ibH65ZAcVBM0ICcUnTLlX7PQ==}
'@next/swc-win32-x64-msvc@15.2.0-canary.33':
resolution: {integrity: sha512-hjdbGnkwIZ8zN2vlS6lNsEJO37HRtcEGimzfkruBMsi/DwJBqkJvZbNC/XCJy3HFcU58igncqV52p1IPjmAJAw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -1479,6 +1479,9 @@ packages:
'@swc/helpers@0.5.13':
resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==}
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
@@ -3468,16 +3471,16 @@ packages:
react: '*'
react-dom: '*'
next@15.0.4-canary.23:
resolution: {integrity: sha512-xCjjBx4csWdG4MP9tKV/C25OIDbN0o+zovMC5zd4yvE4lrd43Y5tt+w171IGUueb6VbPLTSlDaXvqOtrxKJXzQ==}
next@15.2.0-canary.33:
resolution: {integrity: sha512-WF8QLeYkakuYwksdWY/F+Bi8tNJfIbiSYk9hCmldn9sNp1lU3lqI1hrW1ynbcMSaXC+qQEr7yol2OdvVZ4nZYQ==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
'@playwright/test': ^1.41.2
babel-plugin-react-compiler: '*'
react: ^18.2.0 || 19.0.0-rc-380f5d67-20241113
react-dom: ^18.2.0 || 19.0.0-rc-380f5d67-20241113
react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
sass: ^1.3.0
peerDependenciesMeta:
'@opentelemetry/api':
@@ -5250,7 +5253,7 @@ snapshots:
'@emnapi/runtime@1.3.1':
dependencies:
tslib: 2.7.0
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.21.5':
@@ -5657,34 +5660,34 @@ snapshots:
- encoding
- supports-color
'@next/env@15.0.4-canary.23': {}
'@next/env@15.2.0-canary.33': {}
'@next/eslint-plugin-next@14.2.18':
dependencies:
glob: 10.3.10
'@next/swc-darwin-arm64@15.0.4-canary.23':
'@next/swc-darwin-arm64@15.2.0-canary.33':
optional: true
'@next/swc-darwin-x64@15.0.4-canary.23':
'@next/swc-darwin-x64@15.2.0-canary.33':
optional: true
'@next/swc-linux-arm64-gnu@15.0.4-canary.23':
'@next/swc-linux-arm64-gnu@15.2.0-canary.33':
optional: true
'@next/swc-linux-arm64-musl@15.0.4-canary.23':
'@next/swc-linux-arm64-musl@15.2.0-canary.33':
optional: true
'@next/swc-linux-x64-gnu@15.0.4-canary.23':
'@next/swc-linux-x64-gnu@15.2.0-canary.33':
optional: true
'@next/swc-linux-x64-musl@15.0.4-canary.23':
'@next/swc-linux-x64-musl@15.2.0-canary.33':
optional: true
'@next/swc-win32-arm64-msvc@15.0.4-canary.23':
'@next/swc-win32-arm64-msvc@15.2.0-canary.33':
optional: true
'@next/swc-win32-x64-msvc@15.0.4-canary.23':
'@next/swc-win32-x64-msvc@15.2.0-canary.33':
optional: true
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
@@ -5919,6 +5922,10 @@ snapshots:
dependencies:
tslib: 2.7.0
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
'@swc/helpers@0.5.5':
dependencies:
'@swc/counter': 0.1.3
@@ -6140,11 +6147,11 @@ snapshots:
'@ungap/structured-clone@1.2.0': {}
'@vercel/analytics@1.3.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)':
'@vercel/analytics@1.3.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0)':
dependencies:
server-only: 0.0.1
optionalDependencies:
next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
react: 19.0.0
'@vercel/git-hooks@1.0.0': {}
@@ -8210,25 +8217,25 @@ snapshots:
negotiator@1.0.0: {}
next-intl@3.25.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0):
next-intl@3.25.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react@19.0.0):
dependencies:
'@formatjs/intl-localematcher': 0.5.4
negotiator: 1.0.0
next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
react: 19.0.0
use-intl: 3.25.1(react@19.0.0)
next-themes@0.2.1(next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
next-themes@0.2.1(next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
next: 15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
next: 15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
next@15.0.4-canary.23(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7):
next@15.2.0-canary.33(@babel/core@7.26.0)(@playwright/test@1.48.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.80.7):
dependencies:
'@next/env': 15.0.4-canary.23
'@next/env': 15.2.0-canary.33
'@swc/counter': 0.1.3
'@swc/helpers': 0.5.13
'@swc/helpers': 0.5.15
busboy: 1.6.0
caniuse-lite: 1.0.30001680
postcss: 8.4.31
@@ -8236,14 +8243,14 @@ snapshots:
react-dom: 19.0.0(react@19.0.0)
styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0)
optionalDependencies:
'@next/swc-darwin-arm64': 15.0.4-canary.23
'@next/swc-darwin-x64': 15.0.4-canary.23
'@next/swc-linux-arm64-gnu': 15.0.4-canary.23
'@next/swc-linux-arm64-musl': 15.0.4-canary.23
'@next/swc-linux-x64-gnu': 15.0.4-canary.23
'@next/swc-linux-x64-musl': 15.0.4-canary.23
'@next/swc-win32-arm64-msvc': 15.0.4-canary.23
'@next/swc-win32-x64-msvc': 15.0.4-canary.23
'@next/swc-darwin-arm64': 15.2.0-canary.33
'@next/swc-darwin-x64': 15.2.0-canary.33
'@next/swc-linux-arm64-gnu': 15.2.0-canary.33
'@next/swc-linux-arm64-musl': 15.2.0-canary.33
'@next/swc-linux-x64-gnu': 15.2.0-canary.33
'@next/swc-linux-x64-musl': 15.2.0-canary.33
'@next/swc-win32-arm64-msvc': 15.2.0-canary.33
'@next/swc-win32-x64-msvc': 15.2.0-canary.33
'@playwright/test': 1.48.2
sass: 1.80.7
sharp: 0.33.5