From d31f3eb9934703125afb6342db9d6bd5ddb380c7 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Sat, 1 Mar 2025 16:35:09 +0700 Subject: [PATCH] Add support for OTPs and enhance sensitive apis with OTP verification (#191) One-Time Password (OTP) package added with comprehensive token management, including OTP verification for team account deletion and ownership transfer. --- apps/dev-tool/app/emails/[id]/page.tsx | 1 + apps/dev-tool/app/emails/lib/email-loader.tsx | 63 +- apps/dev-tool/app/emails/page.tsx | 8 + apps/e2e/playwright.config.ts | 5 +- apps/e2e/tests/account/account.po.ts | 33 +- apps/e2e/tests/account/account.spec.ts | 27 +- apps/e2e/tests/authentication/auth.po.ts | 9 +- apps/e2e/tests/authentication/auth.spec.ts | 17 + .../authentication/password-reset.spec.ts | 61 +- apps/e2e/tests/invitations/invitations.po.ts | 2 +- .../tests/team-accounts/team-accounts.po.ts | 77 +- .../tests/team-accounts/team-accounts.spec.ts | 132 +- apps/e2e/tests/utils/mailbox.ts | 75 +- apps/e2e/tests/utils/otp.po.ts | 63 + apps/web/app/home/(user)/layout.tsx | 2 +- apps/web/app/home/[account]/layout.tsx | 3 +- apps/web/lib/database.types.ts | 178 ++- apps/web/public/locales/en/common.json | 14 + .../20240610000000_one_time_tokens.sql | 346 +++++ .../tests/database/00000-makerkit-helpers.sql | 13 + apps/web/supabase/tests/database/otp.test.sql | 853 ++++++++++++ .../tests/database/team-accounts.test.sql | 20 + package.json | 2 +- .../src/components/cta-button.tsx | 2 +- .../email-templates/src/components/header.tsx | 4 +- .../src/emails/account-delete.email.tsx | 10 +- .../src/emails/invite.email.tsx | 6 +- .../email-templates/src/emails/otp.email.tsx | 97 ++ packages/email-templates/src/index.ts | 1 + .../src/locales/en/otp-email.json | 7 + packages/features/accounts/package.json | 1 + .../account-danger-zone.tsx | 83 +- .../mfa/multi-factor-auth-setup-dialog.tsx | 11 +- .../schema/delete-personal-account.schema.ts | 2 +- .../personal-accounts-server-actions.ts | 35 +- .../delete-personal-account.service.ts | 8 +- packages/features/team-accounts/package.json | 1 + .../members/transfer-ownership-dialog.tsx | 95 +- .../settings/team-account-danger-zone.tsx | 107 +- .../src/schema/delete-team-account.schema.ts | 1 + .../transfer-ownership-confirmation.schema.ts | 10 +- .../delete-team-account-server-actions.ts | 36 +- .../actions/team-members-server-actions.ts | 51 +- packages/otp/README.md | 3 + packages/otp/eslint.config.mjs | 3 + packages/otp/package.json | 43 + packages/otp/src/api/index.ts | 117 ++ packages/otp/src/components/index.ts | 1 + .../otp/src/components/verify-otp-form.tsx | 257 ++++ packages/otp/src/server/index.ts | 1 + packages/otp/src/server/otp-email.service.ts | 62 + packages/otp/src/server/otp.service.ts | 267 ++++ packages/otp/src/server/server-actions.ts | 88 ++ packages/otp/src/types/index.ts | 115 ++ packages/otp/tsconfig.json | 8 + packages/supabase/src/database.types.ts | 206 ++- packages/ui/src/shadcn/alert.tsx | 7 +- packages/ui/src/shadcn/switch.tsx | 2 +- pnpm-lock.yaml | 1152 +++-------------- tooling/eslint/nextjs.js | 2 +- 60 files changed, 3543 insertions(+), 1363 deletions(-) create mode 100644 apps/e2e/tests/utils/otp.po.ts create mode 100644 apps/web/supabase/migrations/20240610000000_one_time_tokens.sql create mode 100644 apps/web/supabase/tests/database/otp.test.sql create mode 100644 packages/email-templates/src/emails/otp.email.tsx create mode 100644 packages/email-templates/src/locales/en/otp-email.json create mode 100644 packages/otp/README.md create mode 100644 packages/otp/eslint.config.mjs create mode 100644 packages/otp/package.json create mode 100644 packages/otp/src/api/index.ts create mode 100644 packages/otp/src/components/index.ts create mode 100644 packages/otp/src/components/verify-otp-form.tsx create mode 100644 packages/otp/src/server/index.ts create mode 100644 packages/otp/src/server/otp-email.service.ts create mode 100644 packages/otp/src/server/otp.service.ts create mode 100644 packages/otp/src/server/server-actions.ts create mode 100644 packages/otp/src/types/index.ts create mode 100644 packages/otp/tsconfig.json diff --git a/apps/dev-tool/app/emails/[id]/page.tsx b/apps/dev-tool/app/emails/[id]/page.tsx index 514cd316c..8b4df071c 100644 --- a/apps/dev-tool/app/emails/[id]/page.tsx +++ b/apps/dev-tool/app/emails/[id]/page.tsx @@ -48,6 +48,7 @@ export default async function EmailPage(props: EmailPageProps) { 'change-email-address-email': 'Change Email Address Email', 'reset-password-email': 'Reset Password Email', 'magic-link-email': 'Magic Link Email', + 'otp-email': 'OTP Email', }} /> } diff --git a/apps/dev-tool/app/emails/lib/email-loader.tsx b/apps/dev-tool/app/emails/lib/email-loader.tsx index 28377eebd..44bc9e29d 100644 --- a/apps/dev-tool/app/emails/lib/email-loader.tsx +++ b/apps/dev-tool/app/emails/lib/email-loader.tsx @@ -1,45 +1,48 @@ import { renderAccountDeleteEmail, renderInviteEmail, + renderOtpEmail, } from '@kit/email-templates'; export async function loadEmailTemplate(id: string) { - if (id === 'account-delete-email') { - return renderAccountDeleteEmail({ - productName: 'Makerkit', - userDisplayName: 'Giancarlo', - }); - } + switch (id) { + case 'account-delete-email': + return renderAccountDeleteEmail({ + productName: 'Makerkit', + userDisplayName: 'Giancarlo', + }); - if (id === 'invite-email') { - return renderInviteEmail({ - teamName: 'Makerkit', - teamLogo: - '', - inviter: 'Giancarlo', - invitedUserEmail: 'test@makerkit.dev', - link: 'https://makerkit.dev', - productName: 'Makerkit', - }); - } + case 'invite-email': + return renderInviteEmail({ + teamName: 'Makerkit', + teamLogo: '', + inviter: 'Giancarlo', + invitedUserEmail: 'test@makerkit.dev', + link: 'https://makerkit.dev', + productName: 'Makerkit', + }); - if (id === 'magic-link-email') { - return loadFromFileSystem('magic-link'); - } + case 'otp-email': + return renderOtpEmail({ + productName: 'Makerkit', + otp: '123456', + }); - if (id === 'reset-password-email') { - return loadFromFileSystem('reset-password'); - } + case 'magic-link-email': + return loadFromFileSystem('magic-link'); - if (id === 'change-email-address-email') { - return loadFromFileSystem('change-email-address'); - } + case 'reset-password-email': + return loadFromFileSystem('reset-password'); - if (id === 'confirm-email') { - return loadFromFileSystem('confirm-email'); - } + case 'change-email-address-email': + return loadFromFileSystem('change-email-address'); - throw new Error(`Email template not found: ${id}`); + case 'confirm-email': + return loadFromFileSystem('confirm-email'); + + default: + throw new Error(`Email template not found: ${id}`); + } } async function loadFromFileSystem(fileName: string) { diff --git a/apps/dev-tool/app/emails/page.tsx b/apps/dev-tool/app/emails/page.tsx index 908499bd8..7718c662c 100644 --- a/apps/dev-tool/app/emails/page.tsx +++ b/apps/dev-tool/app/emails/page.tsx @@ -75,6 +75,14 @@ export default async function EmailsPage() { + + + + + OTP Email + + + diff --git a/apps/e2e/playwright.config.ts b/apps/e2e/playwright.config.ts index 10fd7eaef..b51aab9f7 100644 --- a/apps/e2e/playwright.config.ts +++ b/apps/e2e/playwright.config.ts @@ -7,7 +7,7 @@ const testIgnore: string[] = []; if (!enableBillingTests) { console.log( `Billing tests are disabled. To enable them, set the environment variable ENABLE_BILLING_TESTS=true.`, - `Current value: "${process.env.ENABLE_BILLING_TESTS}"` + `Current value: "${process.env.ENABLE_BILLING_TESTS}"`, ); testIgnore.push('*-billing.spec.ts'); @@ -45,15 +45,14 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + navigationTimeout: 5000, }, - // test timeout set to 1 minutes timeout: 60 * 1000, expect: { // expect timeout set to 10 seconds timeout: 10 * 1000, }, - /* Configure projects for major browsers */ projects: [ { diff --git a/apps/e2e/tests/account/account.po.ts b/apps/e2e/tests/account/account.po.ts index 2ebe53e9a..38f0edeb6 100644 --- a/apps/e2e/tests/account/account.po.ts +++ b/apps/e2e/tests/account/account.po.ts @@ -1,14 +1,17 @@ import { Page, expect } from '@playwright/test'; import { AuthPageObject } from '../authentication/auth.po'; +import { OtpPo } from '../utils/otp.po'; export class AccountPageObject { private readonly page: Page; public auth: AuthPageObject; + private otp: OtpPo; constructor(page: Page) { this.page = page; this.auth = new AuthPageObject(page); + this.otp = new OtpPo(page); } async setup() { @@ -58,32 +61,16 @@ export class AccountPageObject { await this.page.click('[data-test="account-password-form"] button'); } - async deleteAccount() { - await expect(async () => { - await this.page.click('[data-test="delete-account-button"]'); + async deleteAccount(email: string) { + // Click the delete account button to open the modal + await this.page.click('[data-test="delete-account-button"]'); - await this.page.fill( - '[data-test="delete-account-input-field"]', - 'DELETE', - ); + // Complete the OTP verification process + await this.otp.completeOtpVerification(email); - const click = this.page.click( - '[data-test="confirm-delete-account-button"]', - ); + await this.page.waitForTimeout(500); - const response = await this.page - .waitForResponse((resp) => { - return ( - resp.url().includes('home/settings') && - resp.request().method() === 'POST' - ); - }) - .then((response) => { - expect(response.status()).toBe(303); - }); - - await Promise.all([click, response]); - }).toPass(); + await this.page.click('[data-test="confirm-delete-account-button"]'); } getProfileName() { diff --git a/apps/e2e/tests/account/account.spec.ts b/apps/e2e/tests/account/account.spec.ts index 21297fff8..96d3e17ba 100644 --- a/apps/e2e/tests/account/account.spec.ts +++ b/apps/e2e/tests/account/account.spec.ts @@ -1,6 +1,7 @@ import { Page, expect, test } from '@playwright/test'; import { AccountPageObject } from './account.po'; +import {AuthPageObject} from "../authentication/auth.po"; test.describe('Account Settings', () => { let page: Page; @@ -51,22 +52,22 @@ test.describe('Account Settings', () => { test.describe('Account Deletion', () => { test('user can delete their own account', async ({ page }) => { const account = new AccountPageObject(page); + const auth = new AuthPageObject(page); - await account.setup(); + const { email } = await account.setup(); - const request = account.deleteAccount(); + await account.deleteAccount(email); - const response = page - .waitForResponse((resp) => { - return ( - resp.url().includes('home/settings') && - resp.request().method() === 'POST' - ); - }) - .then((response) => { - expect(response.status()).toBe(303); - }); + await page.waitForURL('/'); - await Promise.all([request, response]); + await page.goto('/auth/sign-in'); + + // sign in will now fail + await auth.signIn({ + email, + password: 'testingpassword', + }); + + await expect(page.locator('[data-test="auth-error-message"]')).toBeVisible(); }); }); diff --git a/apps/e2e/tests/authentication/auth.po.ts b/apps/e2e/tests/authentication/auth.po.ts index 6d1f98430..b7c29f32e 100644 --- a/apps/e2e/tests/authentication/auth.po.ts +++ b/apps/e2e/tests/authentication/auth.po.ts @@ -25,7 +25,7 @@ export class AuthPageObject { } async signIn(params: { email: string; password: string }) { - await this.page.waitForTimeout(1000); + await this.page.waitForTimeout(500); await this.page.fill('input[name="email"]', params.email); await this.page.fill('input[name="password"]', params.password); @@ -37,7 +37,7 @@ export class AuthPageObject { password: string; repeatPassword: string; }) { - await this.page.waitForTimeout(1000); + await this.page.waitForTimeout(500); await this.page.fill('input[name="email"]', params.email); await this.page.fill('input[name="password"]', params.password); @@ -50,6 +50,7 @@ export class AuthPageObject { email: string, params: { deleteAfter: boolean; + subject?: string; } = { deleteAfter: true, }, @@ -79,6 +80,10 @@ export class AuthPageObject { }); await this.visitConfirmEmailLink(email); + + return { + email, + }; } async updatePassword(password: string) { diff --git a/apps/e2e/tests/authentication/auth.spec.ts b/apps/e2e/tests/authentication/auth.spec.ts index 26a4ac265..485405353 100644 --- a/apps/e2e/tests/authentication/auth.spec.ts +++ b/apps/e2e/tests/authentication/auth.spec.ts @@ -51,6 +51,23 @@ test.describe('Auth flow', () => { expect(page.url()).toContain('/'); }); + + test('will sign out using the dropdown', async ({ page }) => { + const auth = new AuthPageObject(page); + + await page.goto('/home/settings'); + + await auth.signIn({ + email: 'test@makerkit.dev', + password: 'testingpassword', + }); + + await page.waitForURL('/home/settings'); + + await auth.signOut(); + + await page.waitForURL('/'); + }); }); test.describe('Protected routes', () => { diff --git a/apps/e2e/tests/authentication/password-reset.spec.ts b/apps/e2e/tests/authentication/password-reset.spec.ts index 037f4c581..101f5b56f 100644 --- a/apps/e2e/tests/authentication/password-reset.spec.ts +++ b/apps/e2e/tests/authentication/password-reset.spec.ts @@ -2,35 +2,60 @@ import { expect, test } from '@playwright/test'; import { AuthPageObject } from './auth.po'; -const email = 'owner@makerkit.dev'; const newPassword = (Math.random() * 10000).toString(); test.describe('Password Reset Flow', () => { - test.describe.configure({ mode: 'serial' }); - test('will reset the password and sign in with new one', async ({ page }) => { const auth = new AuthPageObject(page); - await page.goto('/auth/password-reset'); + let email = ''; - await page.fill('[name="email"]', email); - await page.click('[type="submit"]'); + await expect(async () => { + email = `test-${Math.random() * 10000}@makerkit.dev`; - await auth.visitConfirmEmailLink(email); + await page.goto('/auth/sign-up'); - await page.waitForURL('/update-password'); + await auth.signUp({ + email, + password: 'password', + repeatPassword: 'password', + }); - await auth.updatePassword(newPassword); + await auth.visitConfirmEmailLink(email, { + deleteAfter: true, + subject: 'Confirm your email', + }); - await page - .locator('a', { - hasText: 'Back to Home Page', - }) - .click(); + await page.context().clearCookies(); + await page.reload(); - await page.waitForURL('/home'); + await page.goto('/auth/password-reset'); - await auth.signOut(); + await page.fill('[name="email"]', email); + await page.click('[type="submit"]'); + + await auth.visitConfirmEmailLink(email, { + deleteAfter: true, + subject: 'Reset your password', + }); + + await page.waitForURL('/update-password', { + timeout: 1000, + }); + + await auth.updatePassword(newPassword); + + await page + .locator('a', { + hasText: 'Back to Home Page', + }) + .click(); + + await page.waitForURL('/home'); + }).toPass(); + + await page.context().clearCookies(); + await page.reload(); await page .locator('a', { @@ -43,6 +68,8 @@ test.describe('Password Reset Flow', () => { password: newPassword, }); - await page.waitForURL('/home'); + await page.waitForURL('/home', { + timeout: 2000, + }); }); }); diff --git a/apps/e2e/tests/invitations/invitations.po.ts b/apps/e2e/tests/invitations/invitations.po.ts index 9deac3c02..582e8f843 100644 --- a/apps/e2e/tests/invitations/invitations.po.ts +++ b/apps/e2e/tests/invitations/invitations.po.ts @@ -64,7 +64,7 @@ export class InvitationsPageObject { }) .click(); - return expect(this.page.url()).toContain('members'); + await this.page.waitForURL('**/home/*/members'); }).toPass() } diff --git a/apps/e2e/tests/team-accounts/team-accounts.po.ts b/apps/e2e/tests/team-accounts/team-accounts.po.ts index dbf9d1910..1d71030ae 100644 --- a/apps/e2e/tests/team-accounts/team-accounts.po.ts +++ b/apps/e2e/tests/team-accounts/team-accounts.po.ts @@ -1,20 +1,25 @@ import { Page, expect } from '@playwright/test'; import { AuthPageObject } from '../authentication/auth.po'; +import { OtpPo } from '../utils/otp.po'; export class TeamAccountsPageObject { private readonly page: Page; public auth: AuthPageObject; + public otp: OtpPo; constructor(page: Page) { this.page = page; this.auth = new AuthPageObject(page); + this.otp = new OtpPo(page); } async setup(params = this.createTeamName()) { - await this.auth.signUpFlow('/home'); - + const { email } = await this.auth.signUpFlow('/home'); + await this.createTeam(params); + + return { email, teamName: params.teamName, slug: params.slug }; } getTeamFromSelector(teamName: string) { @@ -39,6 +44,18 @@ export class TeamAccountsPageObject { }).toPass(); } + goToMembers() { + return expect(async () => { + await this.page + .locator('a', { + hasText: 'Members', + }) + .click(); + + await this.page.waitForURL('**/home/*/members'); + }).toPass(); + } + goToBilling() { return expect(async () => { await this.page @@ -94,18 +111,11 @@ export class TeamAccountsPageObject { }).toPass(); } - async deleteAccount(teamName: string) { + async deleteAccount(email: string) { await expect(async () => { await this.page.click('[data-test="delete-team-trigger"]'); - await expect( - this.page.locator('[data-test="delete-team-form-confirm-input"]'), - ).toBeVisible(); - - await this.page.fill( - '[data-test="delete-team-form-confirm-input"]', - teamName, - ); + await this.otp.completeOtpVerification(email); const click = this.page.click( '[data-test="delete-team-form-confirm-button"]', @@ -117,6 +127,51 @@ export class TeamAccountsPageObject { }).toPass(); } + async updateMemberRole(memberEmail: string, newRole: string) { + await expect(async () => { + // Find the member row and click the actions button + const memberRow = this.page.getByRole('row', { name: memberEmail }); + await memberRow.getByRole('button').click(); + + // Click the update role option in the dropdown menu + await this.page.getByText('Update Role').click(); + + // Select the new role + await this.page.click('[data-test="role-selector-trigger"]'); + await this.page.click(`[data-test="role-option-${newRole}"]`); + + // Click the confirm button + const click = this.page.click('[data-test="confirm-update-member-role"]'); + + // Wait for the update to complete and page to reload + const response = this.page.waitForURL('**/home/*/members'); + + return Promise.all([click, response]); + }).toPass(); + } + + async transferOwnership(memberEmail: string, ownerEmail: string) { + await expect(async () => { + // Find the member row and click the actions button + const memberRow = this.page.getByRole('row', { name: memberEmail }); + await memberRow.getByRole('button').click(); + + // Click the transfer ownership option in the dropdown menu + await this.page.getByText('Transfer Ownership').click(); + + // Complete OTP verification + await this.otp.completeOtpVerification(ownerEmail); + + // Click the confirm button + const click = this.page.click('[data-test="confirm-transfer-ownership-button"]'); + + // Wait for the transfer to complete and page to reload + const response = this.page.waitForURL('**/home/*/members'); + + return Promise.all([click, response]); + }).toPass(); + } + createTeamName() { const random = (Math.random() * 100000000).toFixed(0); diff --git a/apps/e2e/tests/team-accounts/team-accounts.spec.ts b/apps/e2e/tests/team-accounts/team-accounts.spec.ts index dd8f91d7a..2d9f6748e 100644 --- a/apps/e2e/tests/team-accounts/team-accounts.spec.ts +++ b/apps/e2e/tests/team-accounts/team-accounts.spec.ts @@ -1,7 +1,74 @@ import { Page, expect, test } from '@playwright/test'; +import { InvitationsPageObject } from '../invitations/invitations.po'; import { TeamAccountsPageObject } from './team-accounts.po'; +// Helper function to set up a team with a member +async function setupTeamWithMember(page: Page, memberRole = 'member') { + // Setup invitations page object + const invitations = new InvitationsPageObject(page); + const teamAccounts = invitations.teamAccounts; + + // Setup team with owner + const { email: ownerEmail, slug } = await invitations.setup(); + + // Navigate to members page + await invitations.navigateToMembers(); + + // Create a new member email and invite them with the specified role + const memberEmail = invitations.auth.createRandomEmail(); + + const invites = [ + { + email: memberEmail, + role: memberRole, + }, + ]; + + await invitations.openInviteForm(); + await invitations.inviteMembers(invites); + + // Verify the invitation was sent + await expect(invitations.getInvitations()).toHaveCount(1); + + // Sign out the current user + await page.context().clearCookies(); + + // Sign up with the new member email and accept the invitation + await invitations.auth.visitConfirmEmailLink(memberEmail); + + await invitations.auth.signUp({ + email: memberEmail, + password: 'password', + repeatPassword: 'password', + }); + + await invitations.auth.visitConfirmEmailLink(memberEmail); + + await invitations.acceptInvitation(); + + await invitations.teamAccounts.openAccountsSelector(); + + await expect(invitations.teamAccounts.getTeams()).toHaveCount(1); + + // Sign out and sign back in as the original owner + await page.context().clearCookies(); + + await page.goto('/auth/sign-in'); + + await invitations.auth.signIn({ + email: ownerEmail, + password: 'password', + }); + + await page.waitForURL('/home'); + + // Navigate to the team members page + await page.goto(`/home/${slug}/members`); + + return { invitations, teamAccounts, ownerEmail, memberEmail, slug }; +} + test.describe('Team Accounts', () => { let page: Page; let teamAccounts: TeamAccountsPageObject; @@ -31,15 +98,15 @@ test.describe('Team Accounts', () => { }); }); -test.describe('Account Deletion', () => { +test.describe('Team Account Deletion', () => { test('user can delete their team account', async ({ page }) => { const teamAccounts = new TeamAccountsPageObject(page); const params = teamAccounts.createTeamName(); - await teamAccounts.setup(params); + const { email } = await teamAccounts.setup(params); await teamAccounts.goToSettings(); - await teamAccounts.deleteAccount(params.teamName); + await teamAccounts.deleteAccount(email); await teamAccounts.openAccountsSelector(); await expect( @@ -47,3 +114,62 @@ test.describe('Account Deletion', () => { ).not.toBeVisible(); }); }); + +test.describe('Team Member Role Management', () => { + test("owner can update a team member's role", async ({ page }) => { + // Setup team with a regular member + const { teamAccounts, memberEmail } = await setupTeamWithMember(page); + + // Get the current role badge text + const memberRow = page.getByRole('row', { name: memberEmail }); + + const initialRoleBadge = memberRow.locator( + '[data-test="member-role-badge"]', + ); + + await expect(initialRoleBadge).toHaveText('Member'); + + // Update the member's role to admin + await teamAccounts.updateMemberRole(memberEmail, 'owner'); + + // Wait for the page to fully load after the update + await page.waitForTimeout(1000); + + // Verify the role was updated successfully + const updatedRoleBadge = page + .getByRole('row', { name: memberEmail }) + .locator('[data-test="member-role-badge"]'); + await expect(updatedRoleBadge).toHaveText('Owner'); + }); +}); + +test.describe('Team Ownership Transfer', () => { + test('owner can transfer ownership to another team member', async ({ + page, + }) => { + // Setup team with an owner member (required for ownership transfer) + const { teamAccounts, ownerEmail, memberEmail } = await setupTeamWithMember( + page, + 'owner', + ); + + // Transfer ownership to the member + await teamAccounts.transferOwnership(memberEmail, ownerEmail); + + // Wait for the page to fully load after the transfer + await page.waitForTimeout(1000); + + // Verify the transfer was successful by checking if the primary owner badge + // is now on the new owner's row + const memberRow = page.getByRole('row', { name: memberEmail }); + + // Check for the primary owner badge on the member's row + await expect(memberRow.locator('text=Primary Owner')).toBeVisible({ + timeout: 5000, + }); + + // The original owner should no longer have the primary owner badge + const ownerRow = page.getByRole('row', { name: ownerEmail.split('@')[0] }); + await expect(ownerRow.locator('text=Primary Owner')).not.toBeVisible(); + }); +}); diff --git a/apps/e2e/tests/utils/mailbox.ts b/apps/e2e/tests/utils/mailbox.ts index 276d3444e..7ea285460 100644 --- a/apps/e2e/tests/utils/mailbox.ts +++ b/apps/e2e/tests/utils/mailbox.ts @@ -8,6 +8,7 @@ export class Mailbox { email: string, params: { deleteAfter: boolean; + subject?: string; }, ) { const mailbox = email.split('@')[0]; @@ -18,13 +19,17 @@ export class Mailbox { throw new Error('Invalid email'); } - const json = await this.getInviteEmail(mailbox, params); + const json = await this.getEmail(mailbox, params); if (!json?.body) { throw new Error('Email body was not found'); } - console.log('Email found'); + console.log(`Email found for ${email}`, { + id: json.id, + subject: json.subject, + date: json.date, + }); const html = (json.body as { html: string }).html; const el = parse(html); @@ -40,10 +45,49 @@ export class Mailbox { return this.page.goto(linkHref); } - async getInviteEmail( + /** + * Retrieves an OTP code from an email + * @param email The email address to check for the OTP + * @param deleteAfter Whether to delete the email after retrieving the OTP + * @returns The OTP code + */ + async getOtpFromEmail(email: string, deleteAfter: boolean = true) { + const mailbox = email.split('@')[0]; + + console.log(`Retrieving OTP from mailbox ${email} ...`); + + if (!mailbox) { + throw new Error('Invalid email'); + } + + const json = await this.getEmail(mailbox, { + deleteAfter, + subject: `One-time password for Makerkit`, + }); + + if (!json?.body) { + throw new Error('Email body was not found'); + } + + const html = (json.body as { html: string }).html; + + const text = html.match( + new RegExp(`Your one-time password is: (\\d{6})`), + )?.[1]; + + if (text) { + console.log(`OTP code found in text: ${text}`); + return text; + } + + throw new Error('Could not find OTP code in email'); + } + + async getEmail( mailbox: string, params: { deleteAfter: boolean; + subject?: string; }, ) { const url = `http://127.0.0.1:54324/api/v1/mailbox/${mailbox}`; @@ -54,13 +98,34 @@ export class Mailbox { throw new Error(`Failed to fetch emails: ${response.statusText}`); } - const json = (await response.json()) as Array<{ id: string }>; + const json = (await response.json()) as Array<{ + id: string; + subject: string; + }>; if (!json || !json.length) { + console.log(`No emails found for mailbox ${mailbox}`); + return; } - const messageId = json[0]?.id; + const message = params.subject + ? (() => { + const filtered = json.filter( + (item) => item.subject === params.subject, + ); + + console.log( + `Found ${filtered.length} emails with subject ${params.subject}`, + ); + + return filtered[filtered.length - 1]; + })() + : json[0]; + + console.log(`Message: ${JSON.stringify(message)}`); + + const messageId = message?.id; const messageUrl = `${url}/${messageId}`; const messageResponse = await fetch(messageUrl); diff --git a/apps/e2e/tests/utils/otp.po.ts b/apps/e2e/tests/utils/otp.po.ts new file mode 100644 index 000000000..2702ba05e --- /dev/null +++ b/apps/e2e/tests/utils/otp.po.ts @@ -0,0 +1,63 @@ +import { Page, expect } from '@playwright/test'; + +import { Mailbox } from './mailbox'; + +export class OtpPo { + private readonly page: Page; + private readonly mailbox: Mailbox; + + constructor(page: Page) { + this.page = page; + this.mailbox = new Mailbox(page); + } + + /** + * Completes the OTP verification process + * @param email The email address to send the OTP to + */ + async completeOtpVerification(email: string) { + // Click the "Send Verification Code" button + await this.page.click('[data-test="otp-send-verification-button"]'); + + // wait for the OTP to be sent + await this.page.waitForTimeout(500); + + await expect(async () => { + // Get the OTP code from the email + const otpCode = await this.getOtpCodeFromEmail(email); + + expect(otpCode).not.toBeNull(); + + // Enter the OTP code + await this.enterOtpCode(otpCode); + }).toPass(); + + // Click the "Verify Code" button + await this.page.click('[data-test="otp-verify-button"]'); + } + + /** + * Retrieves the OTP code from an email + * @param email The email address to check for the OTP + * @returns The OTP code + */ + async getOtpCodeFromEmail(email: string) { + // Get the OTP from the email + const otpCode = await this.mailbox.getOtpFromEmail(email); + + if (!otpCode) { + throw new Error('Failed to retrieve OTP code from email'); + } + + return otpCode; + } + + /** + * Enters the OTP code into the input fields + * @param otpCode The 6-digit OTP code + */ + async enterOtpCode(otpCode: string) { + console.log(`Entering OTP code: ${otpCode}`); + await this.page.fill('[data-input-otp]', otpCode); + } +} diff --git a/apps/web/app/home/(user)/layout.tsx b/apps/web/app/home/(user)/layout.tsx index 2360eccd9..26b423d1a 100644 --- a/apps/web/app/home/(user)/layout.tsx +++ b/apps/web/app/home/(user)/layout.tsx @@ -93,7 +93,7 @@ async function getLayoutState() { const sidebarOpenCookieValue = sidebarOpenCookie ? sidebarOpenCookie.value === 'false' - : personalAccountNavigationConfig.sidebarCollapsed; + : !personalAccountNavigationConfig.sidebarCollapsed; const style = layoutStyleCookie?.value ?? personalAccountNavigationConfig.style; diff --git a/apps/web/app/home/[account]/layout.tsx b/apps/web/app/home/[account]/layout.tsx index 0bb3c6693..1bd21ef5e 100644 --- a/apps/web/app/home/[account]/layout.tsx +++ b/apps/web/app/home/[account]/layout.tsx @@ -126,12 +126,13 @@ async function getLayoutState(account: string) { const cookieStore = await cookies(); const sidebarOpenCookie = cookieStore.get('sidebar:state'); const layoutCookie = cookieStore.get('layout-style'); + const layoutStyle = layoutCookie?.value as PageLayoutStyle; const config = getTeamAccountSidebarConfig(account); const sidebarOpenCookieValue = sidebarOpenCookie ? sidebarOpenCookie.value === 'false' - : config.sidebarCollapsed; + : !config.sidebarCollapsed; return { open: sidebarOpenCookieValue, diff --git a/apps/web/lib/database.types.ts b/apps/web/lib/database.types.ts index 1f7aaa5b2..6cf4660ab 100644 --- a/apps/web/lib/database.types.ts +++ b/apps/web/lib/database.types.ts @@ -77,29 +77,7 @@ export type Database = { updated_at?: string | null; updated_by?: string | null; }; - Relationships: [ - { - foreignKeyName: 'accounts_created_by_fkey'; - columns: ['created_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'accounts_primary_owner_user_id_fkey'; - columns: ['primary_owner_user_id']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'accounts_updated_by_fkey'; - columns: ['updated_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - ]; + Relationships: []; }; accounts_memberships: { Row: { @@ -158,27 +136,6 @@ export type Database = { referencedRelation: 'roles'; referencedColumns: ['name']; }, - { - foreignKeyName: 'accounts_memberships_created_by_fkey'; - columns: ['created_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'accounts_memberships_updated_by_fkey'; - columns: ['updated_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'accounts_memberships_user_id_fkey'; - columns: ['user_id']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, ]; }; billing_customers: { @@ -304,13 +261,6 @@ export type Database = { referencedRelation: 'user_accounts'; referencedColumns: ['id']; }, - { - foreignKeyName: 'invitations_invited_by_fkey'; - columns: ['invited_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, { foreignKeyName: 'invitations_role_fkey'; columns: ['role']; @@ -320,6 +270,69 @@ export type Database = { }, ]; }; + nonces: { + Row: { + client_token: string; + created_at: string; + description: string | null; + expires_at: string; + id: string; + last_verification_at: string | null; + last_verification_ip: unknown | null; + last_verification_user_agent: string | null; + metadata: Json | null; + nonce: string; + purpose: string; + revoked: boolean; + revoked_reason: string | null; + scopes: string[] | null; + tags: string[] | null; + used_at: string | null; + user_id: string | null; + verification_attempts: number; + }; + Insert: { + client_token: string; + created_at?: string; + description?: string | null; + expires_at: string; + id?: string; + last_verification_at?: string | null; + last_verification_ip?: unknown | null; + last_verification_user_agent?: string | null; + metadata?: Json | null; + nonce: string; + purpose: string; + revoked?: boolean; + revoked_reason?: string | null; + scopes?: string[] | null; + tags?: string[] | null; + used_at?: string | null; + user_id?: string | null; + verification_attempts?: number; + }; + Update: { + client_token?: string; + created_at?: string; + description?: string | null; + expires_at?: string; + id?: string; + last_verification_at?: string | null; + last_verification_ip?: unknown | null; + last_verification_user_agent?: string | null; + metadata?: Json | null; + nonce?: string; + purpose?: string; + revoked?: boolean; + revoked_reason?: string | null; + scopes?: string[] | null; + tags?: string[] | null; + used_at?: string | null; + user_id?: string | null; + verification_attempts?: number; + }; + Relationships: []; + }; notifications: { Row: { account_id: string; @@ -727,6 +740,19 @@ export type Database = { updated_at: string; }; }; + create_nonce: { + Args: { + p_user_id?: string; + p_purpose?: string; + p_expires_in_seconds?: number; + p_metadata?: Json; + p_description?: string; + p_tags?: string[]; + p_scopes?: string[]; + p_revoke_previous?: boolean; + }; + Returns: Json; + }; create_team_account: { Args: { account_name: string; @@ -785,6 +811,12 @@ export type Database = { Args: Record; Returns: Json; }; + get_nonce_status: { + Args: { + p_id: string; + }; + Returns: Json; + }; get_upper_system_role: { Args: Record; Returns: string; @@ -851,6 +883,13 @@ export type Database = { }; Returns: boolean; }; + revoke_nonce: { + Args: { + p_id: string; + p_reason?: string; + }; + Returns: boolean; + }; team_account_workspace: { Args: { account_slug: string; @@ -930,6 +969,18 @@ export type Database = { updated_at: string; }; }; + verify_nonce: { + Args: { + p_token: string; + p_purpose: string; + p_user_id?: string; + p_required_scopes?: string[]; + p_max_verification_attempts?: number; + p_ip?: unknown; + p_user_agent?: string; + }; + Returns: Json; + }; }; Enums: { app_permissions: @@ -1034,6 +1085,7 @@ export type Database = { owner_id: string | null; path_tokens: string[] | null; updated_at: string | null; + user_metadata: Json | null; version: string | null; }; Insert: { @@ -1047,6 +1099,7 @@ export type Database = { owner_id?: string | null; path_tokens?: string[] | null; updated_at?: string | null; + user_metadata?: Json | null; version?: string | null; }; Update: { @@ -1060,6 +1113,7 @@ export type Database = { owner_id?: string | null; path_tokens?: string[] | null; updated_at?: string | null; + user_metadata?: Json | null; version?: string | null; }; Relationships: [ @@ -1081,6 +1135,7 @@ export type Database = { key: string; owner_id: string | null; upload_signature: string; + user_metadata: Json | null; version: string; }; Insert: { @@ -1091,6 +1146,7 @@ export type Database = { key: string; owner_id?: string | null; upload_signature: string; + user_metadata?: Json | null; version: string; }; Update: { @@ -1101,6 +1157,7 @@ export type Database = { key?: string; owner_id?: string | null; upload_signature?: string; + user_metadata?: Json | null; version?: string; }; Relationships: [ @@ -1237,6 +1294,10 @@ export type Database = { updated_at: string; }[]; }; + operation: { + Args: Record; + Returns: string; + }; search: { Args: { prefix: string; @@ -1348,3 +1409,18 @@ export type Enums< : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] ? PublicSchema['Enums'][PublicEnumNameOrOptions] : never; + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof PublicSchema['CompositeTypes'] + | { schema: keyof Database }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof Database; + } + ? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } + ? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof PublicSchema['CompositeTypes'] + ? PublicSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] + : never; diff --git a/apps/web/public/locales/en/common.json b/apps/web/public/locales/en/common.json index ecea6e78d..2bff73b5e 100644 --- a/apps/web/public/locales/en/common.json +++ b/apps/web/public/locales/en/common.json @@ -73,6 +73,20 @@ "label": "Member" } }, + "otp": { + "requestVerificationCode": "Request Verification Code", + "requestVerificationCodeDescription": "We must verify your identity to continue with this action. We'll send a verification code to the email address {{email}}.", + "sendingCode": "Sending Code...", + "sendVerificationCode": "Send Verification Code", + "enterVerificationCode": "Enter Verification Code", + "codeSentToEmail": "We've sent a verification code to the email address {{email}}.", + "verificationCode": "Verification Code", + "enterCodeFromEmail": "Enter the 6-digit code we sent to your email.", + "verifying": "Verifying...", + "verifyCode": "Verify Code", + "requestNewCode": "Request New Code", + "errorSendingCode": "Error sending code. Please try again." + }, "cookieBanner": { "title": "Hey, we use cookies \uD83C\uDF6A", "description": "This website uses cookies to ensure you get the best experience on our website.", diff --git a/apps/web/supabase/migrations/20240610000000_one_time_tokens.sql b/apps/web/supabase/migrations/20240610000000_one_time_tokens.sql new file mode 100644 index 000000000..02cfc4b6e --- /dev/null +++ b/apps/web/supabase/migrations/20240610000000_one_time_tokens.sql @@ -0,0 +1,346 @@ +create extension if not exists pg_cron; + +-- Create a table to store one-time tokens (nonces) +CREATE TABLE IF NOT EXISTS public.nonces ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + client_token TEXT NOT NULL, -- token sent to client (hashed) + nonce TEXT NOT NULL, -- token stored in DB (hashed) + user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NULL, -- Optional to support anonymous tokens + purpose TEXT NOT NULL, -- e.g., 'password-reset', 'email-verification', etc. + + -- Status fields + expires_at TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + used_at TIMESTAMPTZ, + revoked BOOLEAN NOT NULL DEFAULT FALSE, -- For administrative revocation + revoked_reason TEXT, -- Reason for revocation if applicable + + -- Audit fields + verification_attempts INTEGER NOT NULL DEFAULT 0, -- Track attempted uses + last_verification_at TIMESTAMPTZ, -- Timestamp of last verification attempt + last_verification_ip INET, -- For tracking verification source + last_verification_user_agent TEXT, -- For tracking client information + + -- Extensibility fields + metadata JSONB DEFAULT '{}'::JSONB, -- optional metadata + scopes TEXT[] DEFAULT '{}' -- OAuth-style authorized scopes +); + +-- Create indexes for efficient lookups +CREATE INDEX IF NOT EXISTS idx_nonces_status ON public.nonces (client_token, user_id, purpose, expires_at) + WHERE used_at IS NULL AND revoked = FALSE; + +-- Enable Row Level Security (RLS) +ALTER TABLE public.nonces ENABLE ROW LEVEL SECURITY; + +-- RLS policies +-- Users can view their own nonces for verification +CREATE POLICY "Users can read their own nonces" + ON public.nonces + FOR SELECT + USING ( + user_id = (select auth.uid()) + ); + +-- Create a function to create a nonce +CREATE OR REPLACE FUNCTION public.create_nonce( + p_user_id UUID DEFAULT NULL, + p_purpose TEXT DEFAULT NULL, + p_expires_in_seconds INTEGER DEFAULT 3600, -- 1 hour by default + p_metadata JSONB DEFAULT NULL, + p_scopes TEXT[] DEFAULT NULL, + p_revoke_previous BOOLEAN DEFAULT TRUE -- New parameter to control automatic revocation +) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_client_token TEXT; + v_nonce TEXT; + v_expires_at TIMESTAMPTZ; + v_id UUID; + v_plaintext_token TEXT; + v_revoked_count INTEGER; +BEGIN + -- Revoke previous tokens for the same user and purpose if requested + -- This only applies if a user ID is provided (not for anonymous tokens) + IF p_revoke_previous = TRUE AND p_user_id IS NOT NULL THEN + WITH revoked AS ( + UPDATE public.nonces + SET + revoked = TRUE, + revoked_reason = 'Superseded by new token with same purpose' + WHERE + user_id = p_user_id + AND purpose = p_purpose + AND used_at IS NULL + AND revoked = FALSE + AND expires_at > NOW() + RETURNING 1 + ) + SELECT COUNT(*) INTO v_revoked_count FROM revoked; + END IF; + + -- Generate a 6-digit token + v_plaintext_token := (100000 + floor(random() * 900000))::text; + v_client_token := crypt(v_plaintext_token, gen_salt('bf')); + + -- Still generate a secure nonce for internal use + v_nonce := encode(gen_random_bytes(24), 'base64'); + v_nonce := crypt(v_nonce, gen_salt('bf')); + + -- Calculate expiration time + v_expires_at := NOW() + (p_expires_in_seconds * interval '1 second'); + + -- Insert the new nonce + INSERT INTO public.nonces ( + client_token, + nonce, + user_id, + expires_at, + metadata, + purpose, + scopes + ) + VALUES ( + v_client_token, + v_nonce, + p_user_id, + v_expires_at, + COALESCE(p_metadata, '{}'::JSONB), + p_purpose, + COALESCE(p_scopes, '{}'::TEXT[]) + ) + RETURNING id INTO v_id; + + -- Return the token information + -- Note: returning the plaintext token, not the hash + RETURN jsonb_build_object( + 'id', v_id, + 'token', v_plaintext_token, + 'expires_at', v_expires_at, + 'revoked_previous_count', COALESCE(v_revoked_count, 0) + ); +END; +$$; + +grant execute on function public.create_nonce to service_role; + +-- Create a function to verify a nonce +CREATE OR REPLACE FUNCTION public.verify_nonce( + p_token TEXT, + p_purpose TEXT, + p_user_id UUID DEFAULT NULL, + p_required_scopes TEXT[] DEFAULT NULL, + p_max_verification_attempts INTEGER DEFAULT 5, + p_ip INET DEFAULT NULL, + p_user_agent TEXT DEFAULT NULL +) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_nonce RECORD; + v_matching_count INTEGER; +BEGIN + -- Add debugging info + RAISE NOTICE 'Verifying token: %, purpose: %, user_id: %', p_token, p_purpose, p_user_id; + + -- Count how many matching tokens exist before verification attempt + SELECT COUNT(*) INTO v_matching_count + FROM public.nonces + WHERE purpose = p_purpose; + + -- Update verification attempt counter and tracking info for all matching tokens + UPDATE public.nonces + SET + verification_attempts = verification_attempts + 1, + last_verification_at = NOW(), + last_verification_ip = COALESCE(p_ip, last_verification_ip), + last_verification_user_agent = COALESCE(p_user_agent, last_verification_user_agent) + WHERE + client_token = crypt(p_token, client_token) + AND purpose = p_purpose; + + -- Find the nonce by token and purpose + -- Modified to handle user-specific tokens better + SELECT * + INTO v_nonce + FROM public.nonces + WHERE + client_token = crypt(p_token, client_token) + AND purpose = p_purpose + -- Only apply user_id filter if the token was created for a specific user + AND ( + -- Case 1: Anonymous token (user_id is NULL in DB) + (user_id IS NULL) + OR + -- Case 2: User-specific token (check if user_id matches) + (user_id = p_user_id) + ) + AND used_at IS NULL + AND NOT revoked + AND expires_at > NOW(); + + -- Check if nonce exists + IF v_nonce.id IS NULL THEN + RETURN jsonb_build_object( + 'valid', false, + 'message', 'Invalid or expired token' + ); + END IF; + + -- Check if max verification attempts exceeded + IF p_max_verification_attempts > 0 AND v_nonce.verification_attempts > p_max_verification_attempts THEN + -- Automatically revoke the token + UPDATE public.nonces + SET + revoked = TRUE, + revoked_reason = 'Maximum verification attempts exceeded' + WHERE id = v_nonce.id; + + RETURN jsonb_build_object( + 'valid', false, + 'message', 'Token revoked due to too many verification attempts', + 'max_attempts_exceeded', true + ); + END IF; + + -- Check scopes if required + IF p_required_scopes IS NOT NULL AND array_length(p_required_scopes, 1) > 0 THEN + -- Fix scope validation to properly check if token scopes contain all required scopes + -- Using array containment check: array1 @> array2 (array1 contains array2) + IF NOT (v_nonce.scopes @> p_required_scopes) THEN + RETURN jsonb_build_object( + 'valid', false, + 'message', 'Token does not have required permissions', + 'token_scopes', v_nonce.scopes, + 'required_scopes', p_required_scopes + ); + END IF; + END IF; + + -- Mark nonce as used + UPDATE public.nonces + SET used_at = NOW() + WHERE id = v_nonce.id; + + -- Return success with metadata + RETURN jsonb_build_object( + 'valid', true, + 'user_id', v_nonce.user_id, + 'metadata', v_nonce.metadata, + 'scopes', v_nonce.scopes, + 'purpose', v_nonce.purpose + ); +END; +$$; + +grant execute on function public.verify_nonce to authenticated,service_role; + +-- Create a function to revoke a nonce +CREATE OR REPLACE FUNCTION public.revoke_nonce( + p_id UUID, + p_reason TEXT DEFAULT NULL +) +RETURNS BOOLEAN +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_affected_rows INTEGER; +BEGIN + UPDATE public.nonces + SET + revoked = TRUE, + revoked_reason = p_reason + WHERE + id = p_id + AND used_at IS NULL + AND NOT revoked + RETURNING 1 INTO v_affected_rows; + + RETURN v_affected_rows > 0; +END; +$$; + +grant execute on function public.revoke_nonce to service_role; + +-- Create a function to clean up expired nonces +CREATE OR REPLACE FUNCTION kit.cleanup_expired_nonces( + p_older_than_days INTEGER DEFAULT 1, + p_include_used BOOLEAN DEFAULT TRUE, + p_include_revoked BOOLEAN DEFAULT TRUE +) +RETURNS INTEGER +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_count INTEGER; +BEGIN + -- Count and delete expired or used nonces based on parameters + WITH deleted AS ( + DELETE FROM public.nonces + WHERE + ( + -- Expired and unused tokens + (expires_at < NOW() AND used_at IS NULL) + + -- Used tokens older than specified days (if enabled) + OR (p_include_used = TRUE AND used_at < NOW() - (p_older_than_days * interval '1 day')) + + -- Revoked tokens older than specified days (if enabled) + OR (p_include_revoked = TRUE AND revoked = TRUE AND created_at < NOW() - (p_older_than_days * interval '1 day')) + ) + RETURNING 1 + ) + SELECT COUNT(*) INTO v_count FROM deleted; + + RETURN v_count; +END; +$$; + +-- Create a function to get token status (for administrative use) +CREATE OR REPLACE FUNCTION public.get_nonce_status( + p_id UUID +) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + v_nonce public.nonces; +BEGIN + SELECT * INTO v_nonce FROM public.nonces WHERE id = p_id; + + IF v_nonce.id IS NULL THEN + RETURN jsonb_build_object('exists', false); + END IF; + + RETURN jsonb_build_object( + 'exists', true, + 'purpose', v_nonce.purpose, + 'user_id', v_nonce.user_id, + 'created_at', v_nonce.created_at, + 'expires_at', v_nonce.expires_at, + 'used_at', v_nonce.used_at, + 'revoked', v_nonce.revoked, + 'revoked_reason', v_nonce.revoked_reason, + 'verification_attempts', v_nonce.verification_attempts, + 'last_verification_at', v_nonce.last_verification_at, + 'last_verification_ip', v_nonce.last_verification_ip, + 'is_valid', (v_nonce.used_at IS NULL AND NOT v_nonce.revoked AND v_nonce.expires_at > NOW()) + ); +END; +$$; + +-- Comments for documentation +COMMENT ON TABLE public.nonces IS 'Table for storing one-time tokens with enhanced security and audit features'; +COMMENT ON FUNCTION public.create_nonce IS 'Creates a new one-time token for a specific purpose with enhanced options'; +COMMENT ON FUNCTION public.verify_nonce IS 'Verifies a one-time token, checks scopes, and marks it as used'; +COMMENT ON FUNCTION public.revoke_nonce IS 'Administratively revokes a token to prevent its use'; +COMMENT ON FUNCTION kit.cleanup_expired_nonces IS 'Cleans up expired, used, or revoked tokens based on parameters'; +COMMENT ON FUNCTION public.get_nonce_status IS 'Retrieves the status of a token for administrative purposes'; \ No newline at end of file diff --git a/apps/web/supabase/tests/database/00000-makerkit-helpers.sql b/apps/web/supabase/tests/database/00000-makerkit-helpers.sql index aa331d647..6635f7af5 100644 --- a/apps/web/supabase/tests/database/00000-makerkit-helpers.sql +++ b/apps/web/supabase/tests/database/00000-makerkit-helpers.sql @@ -10,6 +10,19 @@ alter default PRIVILEGES in schema makerkit revoke execute on FUNCTIONS from pub alter default PRIVILEGES in schema makerkit grant execute on FUNCTIONS to anon, authenticated, service_role; +create or replace function makerkit.get_id_by_identifier( + identifier text +) + returns uuid + as $$ +begin + + return (select id from auth.users where raw_user_meta_data->>'test_identifier' = identifier); + +end; + +$$ language PLPGSQL; + create or replace function makerkit.set_identifier( identifier text, user_email text diff --git a/apps/web/supabase/tests/database/otp.test.sql b/apps/web/supabase/tests/database/otp.test.sql new file mode 100644 index 000000000..61aa6bf32 --- /dev/null +++ b/apps/web/supabase/tests/database/otp.test.sql @@ -0,0 +1,853 @@ +begin; +create extension "basejump-supabase_test_helpers" version '0.0.6'; + +select no_plan(); -- Use no_plan for flexibility + +select tests.create_supabase_user('token_creator', 'creator@example.com'); +select tests.create_supabase_user('token_verifier', 'verifier@example.com'); + +-- ========================================== +-- Test 1: Permission Tests +-- ========================================== + +-- Test 1.1: Regular users cannot create nonces directly +select tests.authenticate_as('token_creator'); + +select throws_ok( + $$ select public.create_nonce(auth.uid(), 'password-reset', 3600) $$, + 'permission denied for function create_nonce', + 'Regular users should not be able to create nonces directly' +); + +-- Test 1.2: Regular users cannot revoke nonces +select throws_ok( + $$ select public.revoke_nonce('00000000-0000-0000-0000-000000000000'::uuid, 'test') $$, + 'permission denied for function revoke_nonce', + 'Regular users should not be able to revoke tokens' +); + +-- Test 1.3: Service role can create nonces +set local role service_role; + +-- Create a token and store it for later verification +do $$ +declare + token_result jsonb; +begin + token_result := public.create_nonce( + null, + 'password-reset', + 3600, + '{"redirect_url": "/reset-password"}'::jsonb, + ARRAY['auth:reset'] + ); + + -- Store the token for later verification + perform set_config('app.settings.test_token', token_result->>'token', false); + perform set_config('app.settings.test_token_id', token_result->>'id', false); + perform set_config('app.settings.test_token_json', token_result::text, false); +end $$; + +-- Check token result properties +select ok( + (current_setting('app.settings.test_token_json', false)::jsonb) ? 'id', + 'Token result should contain an id' +); +select ok( + (current_setting('app.settings.test_token_json', false)::jsonb) ? 'token', + 'Token result should contain a token' +); +select ok( + (current_setting('app.settings.test_token_json', false)::jsonb) ? 'expires_at', + 'Token result should contain an expiration time' +); + +set local role postgres; + +-- Create a token for an authenticated user +do $$ +declare + token_result jsonb; + auth_user_id uuid; +begin + auth_user_id := makerkit.get_id_by_identifier('token_creator'); + + token_result := public.create_nonce( + auth_user_id, + 'email-verification', + 3600 + ); + + -- Store the token for later user verification + perform set_config('app.settings.user_token', token_result->>'token', false); + perform set_config('app.settings.user_token_id', token_result->>'id', false); + perform set_config('app.settings.user_token_json', token_result::text, false); +end $$; + +-- Check user token result properties +select ok( + (current_setting('app.settings.user_token_json', false)::jsonb) ? 'id', + 'Token result with minimal params should contain an id' +); +select ok( + (current_setting('app.settings.user_token_json', false)::jsonb) ? 'token', + 'Token result with minimal params should contain a token' +); + +-- Create an anonymous token (no user_id) +do $$ +declare + token_result jsonb; +begin + token_result := public.create_nonce( + null, + 'user-invitation', + 7200, + '{"team_id": "123456"}'::jsonb + ); + + -- Store the anonymous token for later verification + perform set_config('app.settings.anonymous_token', token_result->>'token', false); + + perform set_config('app.settings.anonymous_token_id', token_result->>'id', false); + + perform set_config('app.settings.anonymous_token_json', token_result::text, false); +end $$; + +-- Check anonymous token result properties +select ok( + (current_setting('app.settings.anonymous_token_json', false)::jsonb) ? 'id', + 'Anonymous token result should contain an id' +); + +select ok( + (current_setting('app.settings.anonymous_token_json', false)::jsonb) ? 'token', + 'Anonymous token result should contain a token' +); + +-- ========================================== +-- Test 2: Verify Tokens +-- ========================================== + +-- Test 2.1: Authenticated users can verify tokens +select tests.authenticate_as('token_creator'); + +-- Verify token and store result +do $$ +declare + test_token text; + verification_result jsonb; +begin + test_token := current_setting('app.settings.test_token', false); + + verification_result := public.verify_nonce( + test_token, + 'password-reset' + ); + + perform set_config('app.settings.verification_result', verification_result::text, false); +end $$; + +-- Check verification result +select is( + (current_setting('app.settings.verification_result', false)::jsonb)->>'valid', + 'true', + 'Token should be valid' +); + +select ok( + (current_setting('app.settings.verification_result', false)::jsonb) ? 'metadata', + 'Result should contain metadata' +); + +select ok( + (current_setting('app.settings.verification_result', false)::jsonb) ? 'scopes', + 'Result should contain scopes' +); + +-- Test 2.2: Users can verify tokens assigned to them +do $$ +declare + user_token text; + verification_result jsonb; + user_id uuid; +begin + user_token := current_setting('app.settings.user_token', false); + + set local role postgres; + user_id := makerkit.get_id_by_identifier('token_creator'); + + perform tests.authenticate_as('token_creator'); + + verification_result := public.verify_nonce( + user_token, + 'email-verification', + user_id + ); + + perform set_config('app.settings.user_verification_result', verification_result::text, false); +end $$; + +-- Check user verification result +select is( + (current_setting('app.settings.user_verification_result', false)::jsonb)->>'valid', + 'true', + 'User-specific token should be valid' +); + +select isnt( + (current_setting('app.settings.user_verification_result', false)::jsonb)->>'user_id', + null, + 'User-specific token should have user_id' +); + +-- Test 2.3: Verify token with scopes +set local role service_role; + +-- Create token with scopes +do $$ +declare + scope_token_result jsonb; +begin + -- Create token with scopes + scope_token_result := public.create_nonce( + null, + 'api-access', + 3600, + '{"permissions": "read-only"}'::jsonb, + ARRAY['read:profile', 'read:posts'] + ); + + -- Store for verification + perform set_config('app.settings.scope_token', scope_token_result->>'token', false); +end $$; + +-- Verify with correct scope +do $$ +declare + scope_token text; + verification_result jsonb; + user_id uuid; +begin + set local role postgres; + scope_token := current_setting('app.settings.scope_token', false); + user_id := makerkit.get_id_by_identifier('token_verifier'); + + perform tests.authenticate_as('token_verifier'); + + -- Verify with correct required scope + verification_result := public.verify_nonce( + scope_token, + 'api-access', + null, + ARRAY['read:profile'] + ); + + perform set_config('app.settings.correct_scope_result', verification_result::text, false); + + -- Verify with incorrect required scope + verification_result := public.verify_nonce( + scope_token, + 'api-access', + null, + ARRAY['write:posts'] + ); + + perform set_config('app.settings.incorrect_scope_result', verification_result::text, false); +end $$; + +-- Check scope verification results +select is( + (current_setting('app.settings.correct_scope_result', false)::jsonb)->>'valid', + 'true', + 'Token with correct scope should be valid' +); + +select is( + (current_setting('app.settings.incorrect_scope_result', false)::jsonb)->>'valid', + 'false', + 'Token with incorrect scope should be invalid' +); + +-- Test 2.4: Once used, token becomes invalid +do $$ +declare + token_result jsonb; + first_verification jsonb; + second_verification jsonb; +begin + -- Use service role to create a token + set local role service_role; + + -- Create a token + token_result := public.create_nonce( + null, + 'one-time-action', + 3600 + ); + + set local role authenticated; + + -- Verify it once (uses it) + first_verification := public.verify_nonce( + token_result->>'token', + 'one-time-action' + ); + + -- Try to verify again + second_verification := public.verify_nonce( + token_result->>'token', + 'one-time-action' + ); + + perform set_config('app.settings.first_verification', first_verification::text, false); + perform set_config('app.settings.second_verification', second_verification::text, false); +end $$; + +-- Check first and second verification results +select is( + (current_setting('app.settings.first_verification', false)::jsonb)->>'valid', + 'true', + 'First verification should succeed' +); +select is( + (current_setting('app.settings.second_verification', false)::jsonb)->>'valid', + 'false', + 'Token should not be valid on second use' +); + +-- Test 2.5: Verify with incorrect purpose +do $$ +declare + token_result jsonb; + verification_result jsonb; +begin + -- Use service role to create a token + set local role service_role; + + -- Create a token + token_result := public.create_nonce( + null, + 'specific-purpose', + 3600 + ); + + set local role authenticated; + + -- Verify with wrong purpose + verification_result := public.verify_nonce( + token_result->>'token', + 'different-purpose' + ); + + perform set_config('app.settings.wrong_purpose_result', verification_result::text, false); +end $$; + +-- Check wrong purpose verification result +select is( + (current_setting('app.settings.wrong_purpose_result', false)::jsonb)->>'valid', + 'false', + 'Token with incorrect purpose should be invalid' +); + +-- ========================================== +-- Test 3: Revoke Tokens +-- ========================================== + +-- Test 3.1: Only service_role can revoke tokens +select tests.authenticate_as('token_creator'); + +select + has_function( + 'public', + 'revoke_nonce', + ARRAY['uuid', 'text'], + 'revoke_nonce function should exist' + ); + +select throws_ok( + $$ select public.revoke_nonce('00000000-0000-0000-0000-000000000000'::uuid, 'test reason') $$, + 'permission denied for function revoke_nonce', + 'Regular users should not be able to revoke tokens' +); + +-- Test 3.2: Service role can revoke tokens +set local role service_role; + +do $$ +declare + token_result jsonb; + revoke_result boolean; + verification_result jsonb; + token_id uuid; +begin + -- Create a token + token_result := public.create_nonce( + null, + 'revokable-action', + 3600 + ); + + token_id := token_result->>'id'; + + -- Revoke the token + revoke_result := public.revoke_nonce( + token_id, + 'Security concern' + ); + + -- Switch to regular user to try to verify the revoked token + set local role authenticated; + + -- Try to verify the revoked token + verification_result := public.verify_nonce( + token_result->>'token', + 'revokable-action' + ); + + perform set_config('app.settings.revoke_result', revoke_result::text, false); + perform set_config('app.settings.revoked_verification', verification_result::text, false); +end $$; + +-- Check revocation results +select is( + current_setting('app.settings.revoke_result', false)::boolean, + true, + 'Token revocation should succeed' +); +select is( + (current_setting('app.settings.revoked_verification', false)::jsonb)->>'valid', + 'false', + 'Revoked token should be invalid' +); + +-- ========================================== +-- Test 4: Get Token Status +-- ========================================== + +-- Test 4.1: Verify permission on get_nonce_status +select tests.authenticate_as('token_creator'); + +select throws_ok( + $$ select public.get_nonce_status('00000000-0000-0000-0000-000000000000'::uuid) $$, + 'permission denied for function get_nonce_status', + 'Regular users should not be able to check token status' +); + +-- Test 4.2: Service role can check token status +set local role service_role; + +select + has_function( + 'public', + 'get_nonce_status', + ARRAY['uuid'], + 'get_nonce_status function should exist' + ); + +do $$ +declare + token_result jsonb; + status_result jsonb; + token_id uuid; +begin + -- Create a token + token_result := public.create_nonce( + null, + 'status-check-test', + 3600 + ); + + token_id := token_result->>'id'; + + -- Get status + status_result := public.get_nonce_status(token_id); + + perform set_config('app.settings.status_result', status_result::text, false); +end $$; + +-- Check status result +select is( + (current_setting('app.settings.status_result', false)::jsonb)->>'exists', + 'true', + 'Token should exist' +); +select is( + (current_setting('app.settings.status_result', false)::jsonb)->>'purpose', + 'status-check-test', + 'Purpose should match' +); +select is( + (current_setting('app.settings.status_result', false)::jsonb)->>'is_valid', + 'true', + 'Token should be valid' +); +select is( + (current_setting('app.settings.status_result', false)::jsonb)->>'verification_attempts', + '0', + 'New token should have 0 verification attempts' +); +select is( + (current_setting('app.settings.status_result', false)::jsonb)->>'used_at', + null, + 'New token should not be used' +); +select is( + (current_setting('app.settings.status_result', false)::jsonb)->>'revoked', + 'false', + 'New token should not be revoked' +); + +-- ========================================== +-- Test 5: Cleanup Expired Tokens +-- ========================================== + +-- Test 5.1: Regular users cannot access cleanup function +select tests.authenticate_as('token_creator'); + +select throws_ok( + $$ select kit.cleanup_expired_nonces() $$, + 'permission denied for schema kit', + 'Regular users should not be able to clean up tokens' +); + +-- Test 5.2: Postgres can clean up expired tokens +set local role postgres; + +select + has_function( + 'kit', + 'cleanup_expired_nonces', + ARRAY['integer', 'boolean', 'boolean'], + 'cleanup_expired_nonces function should exist' + ); + +do $$ +declare + token_result jsonb; + cleanup_result integer; + token_count integer; +begin + -- Create an expired token (expiring in -10 seconds from now) + token_result := public.create_nonce( + null, + 'expired-token-test', + -10 -- Negative value to create an already expired token + ); + + -- Run cleanup + cleanup_result := kit.cleanup_expired_nonces(); + + -- Verify the token is gone + select count(*) into token_count from public.nonces where id = (token_result->>'id')::uuid; + + perform set_config('app.settings.cleanup_result', cleanup_result::text, false); + perform set_config('app.settings.token_count_after_cleanup', token_count::text, false); +end $$; + +-- Check cleanup results +select cmp_ok( + current_setting('app.settings.cleanup_result', false)::integer, + '>=', + 1, + 'Cleanup should remove at least one expired token' +); +select is( + current_setting('app.settings.token_count_after_cleanup', false)::integer, + 0, + 'Expired token should be removed after cleanup' +); + +-- ========================================== +-- Test 6: Security Tests +-- ========================================== + +-- Test 6.1: Regular users cannot view tokens directly from the nonces table +select tests.authenticate_as('token_creator'); + +set local role postgres; + +do $$ +declare + creator_id uuid; + token_id uuid; +begin + -- Get the user id + creator_id := makerkit.get_id_by_identifier('token_creator'); + + -- Create a token for this user + token_id := (public.create_nonce(creator_id, 'security-test', 3600))->>'id'; + perform set_config('app.settings.security_test_token_id', token_id::text, false); +end $$; + +select tests.authenticate_as('token_creator'); +do $$ +declare + token_id uuid; + token_count integer; +begin + -- Get the token ID created by service role + token_id := (current_setting('app.settings.security_test_token_id', false))::uuid; + + -- Try to view token directly from nonces table + select count(*) into token_count from public.nonces where id = token_id; + + perform set_config('app.settings.creator_token_count', token_count::text, false); +end $$; + +-- Check creator can see their own token +select is( + current_setting('app.settings.creator_token_count', false)::integer, + 1, + 'User should be able to see their own tokens in the table' +); + +-- Test 6.2: Users cannot see tokens belonging to other users +select tests.authenticate_as('token_verifier'); +do $$ +declare + token_id uuid; + token_count integer; +begin + -- Get the token ID created for the creator user + token_id := (current_setting('app.settings.security_test_token_id', false))::uuid; + + -- Verifier tries to view token created for creator + select count(*) into token_count from public.nonces where id = token_id; + + perform set_config('app.settings.verifier_token_count', token_count::text, false); +end $$; + +-- Check verifier cannot see creator's token +select is( + current_setting('app.settings.verifier_token_count', false)::integer, + 0, + 'User should not be able to see tokens belonging to other users' +); + +-- ========================================== +-- Test 7: Auto-Revocation of Previous Tokens +-- ========================================== + +-- Test 7.1: Creating a new token should revoke previous tokens with the same purpose by default +set local role postgres; + +do $$ +declare + auth_user_id uuid; + first_token_result jsonb; + second_token_result jsonb; + first_token_id uuid; + first_token_status jsonb; +begin + -- Get user ID + auth_user_id := makerkit.get_id_by_identifier('token_creator'); + + -- Create first token + first_token_result := public.create_nonce( + auth_user_id, + 'password-reset', + 3600, + '{"first": true}'::jsonb + ); + + first_token_id := first_token_result->>'id'; + + -- Verify first token is valid + first_token_status := public.get_nonce_status(first_token_id); + + -- Create second token with same purpose + second_token_result := public.create_nonce( + auth_user_id, + 'password-reset', + 3600, + '{"second": true}'::jsonb + ); + + -- Check that first token is now revoked + first_token_status := public.get_nonce_status(first_token_id); + + perform set_config('app.settings.first_token_valid_before', 'true', false); + perform set_config('app.settings.revoked_previous_count', (second_token_result->>'revoked_previous_count')::text, false); + perform set_config('app.settings.first_token_revoked', (first_token_status->>'revoked')::text, false); + perform set_config('app.settings.first_token_revoked_reason', (first_token_status->>'revoked_reason')::text, false); + perform set_config('app.settings.first_token_valid_after', (first_token_status->>'is_valid')::text, false); +end $$; + +-- Check auto-revocation results +select is( + current_setting('app.settings.first_token_valid_before', false), + 'true', + 'First token should be valid initially' +); + +select is( + current_setting('app.settings.revoked_previous_count', false)::integer, + 1, + 'Should report one revoked token' +); + +select is( + current_setting('app.settings.first_token_revoked', false), + 'true', + 'First token should be revoked' +); + +select is( + current_setting('app.settings.first_token_revoked_reason', false), + 'Superseded by new token with same purpose', + 'Revocation reason should be set' +); + +select is( + current_setting('app.settings.first_token_valid_after', false), + 'false', + 'First token should be invalid' +); + +-- ========================================== +-- Test 8: Maximum Verification Attempts +-- ========================================== + +-- Test 8.1: Token should be revoked after exceeding max verification attempts +set local role service_role; + +do $$ +declare + token_result jsonb; + verification_result jsonb; + status_result jsonb; + token_id uuid; + token_text text; +begin + -- Create a token + token_result := public.create_nonce( + null, + 'max-attempts-test', + 3600 + ); + + token_id := token_result->>'id'; + token_text := token_result->>'token'; + + -- Manually set verification_attempts to just below the limit (3) + UPDATE public.nonces + SET verification_attempts = 3 + WHERE id = token_id; + + -- Get status after manual update + status_result := public.get_nonce_status(token_id); + + -- Now perform a verification with an incorrect token - this should trigger max attempts exceeded + verification_result := public.verify_nonce( + 'wrong-token', -- Wrong token + 'max-attempts-test', -- Correct purpose, + NULL, -- No user id + NULL, -- No required scopes + 3 -- Max 3 attempts + ); + + -- The above won't increment the counter, so we need to make one more attempt with the correct token + verification_result := public.verify_nonce( + token_text, -- Correct token + 'max-attempts-test', -- Correct purpose, + NULL, -- No user id + NULL, -- No required scopes + 3 -- Max 3 attempts + ); + + -- Check token status to verify it was revoked + status_result := public.get_nonce_status(token_id); + + -- Store results for assertions outside the DO block + perform set_config('app.settings.max_attempts_verification_result', verification_result::text, false); + perform set_config('app.settings.max_attempts_status_result', status_result::text, false); +end $$; + +-- Check max attempts results outside the DO block +select is( + (current_setting('app.settings.max_attempts_verification_result', false)::jsonb)->>'valid', + 'false', + 'Token should be invalid after exceeding max attempts' +); + +select is( + (current_setting('app.settings.max_attempts_verification_result', false)::jsonb)->>'max_attempts_exceeded', + 'true', + 'Max attempts exceeded flag should be set' +); + +select is( + (current_setting('app.settings.max_attempts_status_result', false)::jsonb)->>'revoked', + 'true', + 'Token should be revoked after exceeding max attempts' +); + +select is( + (current_setting('app.settings.max_attempts_status_result', false)::jsonb)->>'revoked_reason', + 'Maximum verification attempts exceeded', + 'Revocation reason should indicate max attempts exceeded' +); + +-- Test 8.2: Setting max attempts to 0 should disable the limit +do $$ +declare + token_result jsonb; + verification_result jsonb; + status_result jsonb; + token_id uuid; + token_text text; +begin + -- Create a token + token_result := public.create_nonce( + null, + 'unlimited-attempts-test', + 3600 + ); + + token_id := token_result->>'id'; + token_text := token_result->>'token'; + + -- Manually set verification_attempts to a high number + UPDATE public.nonces + SET verification_attempts = 10 + WHERE id = token_id; + + -- Get status after manual update + status_result := public.get_nonce_status(token_id); + + -- Now perform a verification with the correct token and unlimited attempts + verification_result := public.verify_nonce( + token_text, -- Correct token + 'unlimited-attempts-test', -- Correct purpose, + NULL, -- No user id + NULL, -- No required scopes + 0 -- Unlimited attempts (disabled) + ); + + -- Check token status to verify it was not revoked + status_result := public.get_nonce_status(token_id); + + -- Store results for assertions outside the DO block + perform set_config('app.settings.unlimited_attempts_status', status_result::text, false); +end $$; + +-- Check unlimited attempts results outside the DO block +select is( + (current_setting('app.settings.unlimited_attempts_status', false)::jsonb)->>'revoked', + 'false', + 'Token should not be revoked when max attempts is disabled' +); + +select cmp_ok( + (current_setting('app.settings.unlimited_attempts_status', false)::jsonb)->>'verification_attempts', + '>=', + '10', + 'Token should record at least 10 verification attempts' +); + +-- Finish tests +select * from finish(); + +rollback; + diff --git a/apps/web/supabase/tests/database/team-accounts.test.sql b/apps/web/supabase/tests/database/team-accounts.test.sql index 16fba4456..686247ea6 100644 --- a/apps/web/supabase/tests/database/team-accounts.test.sql +++ b/apps/web/supabase/tests/database/team-accounts.test.sql @@ -44,6 +44,16 @@ select $$, row ('owner'::varchar), 'The primary owner should have the owner role for the team account'); +select is( + public.is_account_owner((select + id + from public.accounts + where + slug = 'test')), + true, + 'The current user should be the owner of the team account' +); + -- Should be able to see the team account select isnt_empty($$ @@ -58,6 +68,16 @@ select select tests.authenticate_as('test2'); +select is( + public.is_account_owner((select + id + from public.accounts + where + slug = 'test')), + false, + 'The current user should not be the owner of the team account' +); + select is_empty($$ select diff --git a/package.json b/package.json index e69dd9f19..88da6903a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-supabase-saas-kit-turbo", - "version": "2.3.0", + "version": "2.4.0", "private": true, "sideEffects": false, "engines": { diff --git a/packages/email-templates/src/components/cta-button.tsx b/packages/email-templates/src/components/cta-button.tsx index 72a84a138..ad4219464 100644 --- a/packages/email-templates/src/components/cta-button.tsx +++ b/packages/email-templates/src/components/cta-button.tsx @@ -7,7 +7,7 @@ export function CtaButton( ) { return ( + + + + + + {props.productName} + + + + , + ); + + return { + html, + subject, + }; +} diff --git a/packages/email-templates/src/index.ts b/packages/email-templates/src/index.ts index 193eafa2c..c7c2eb418 100644 --- a/packages/email-templates/src/index.ts +++ b/packages/email-templates/src/index.ts @@ -1,2 +1,3 @@ export * from './emails/invite.email'; export * from './emails/account-delete.email'; +export * from './emails/otp.email'; diff --git a/packages/email-templates/src/locales/en/otp-email.json b/packages/email-templates/src/locales/en/otp-email.json new file mode 100644 index 000000000..9439b35eb --- /dev/null +++ b/packages/email-templates/src/locales/en/otp-email.json @@ -0,0 +1,7 @@ +{ + "subject": "One-time password for {{productName}}", + "heading": "One-time password for {{productName}}", + "otpText": "Your one-time password is: {{otp}}", + "footerText": "Please enter the one-time password in the app to continue.", + "mainText": "You're receiving this email because you need to verify your identity using a one-time password." +} diff --git a/packages/features/accounts/package.json b/packages/features/accounts/package.json index 8ab7e460a..6ab2bc1c6 100644 --- a/packages/features/accounts/package.json +++ b/packages/features/accounts/package.json @@ -27,6 +27,7 @@ "@kit/mailers": "workspace:*", "@kit/monitoring": "workspace:*", "@kit/next": "workspace:*", + "@kit/otp": "workspace:*", "@kit/prettier-config": "workspace:*", "@kit/shared": "workspace:*", "@kit/supabase": "workspace:*", diff --git a/packages/features/accounts/src/components/personal-account-settings/account-danger-zone.tsx b/packages/features/accounts/src/components/personal-account-settings/account-danger-zone.tsx index 36be0fcd8..38b945edf 100644 --- a/packages/features/accounts/src/components/personal-account-settings/account-danger-zone.tsx +++ b/packages/features/accounts/src/components/personal-account-settings/account-danger-zone.tsx @@ -4,9 +4,11 @@ import { useFormStatus } from 'react-dom'; import { zodResolver } from '@hookform/resolvers/zod'; import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; -import { useForm } from 'react-hook-form'; +import { useForm, useWatch } from 'react-hook-form'; import { ErrorBoundary } from '@kit/monitoring/components'; +import { VerifyOtpForm } from '@kit/otp/components'; +import { useUser } from '@kit/supabase/hooks/use-user'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { AlertDialog, @@ -18,8 +20,7 @@ import { AlertDialogTrigger, } from '@kit/ui/alert-dialog'; import { Button } from '@kit/ui/button'; -import { Form, FormControl, FormItem, FormLabel } from '@kit/ui/form'; -import { Input } from '@kit/ui/input'; +import { Form } from '@kit/ui/form'; import { Trans } from '@kit/ui/trans'; import { DeletePersonalAccountSchema } from '../../schema/delete-personal-account.schema'; @@ -46,6 +47,12 @@ export function AccountDangerZone() { } function DeleteAccountModal() { + const { data: user } = useUser(); + + if (!user?.email) { + return null; + } + return ( @@ -61,22 +68,39 @@ function DeleteAccountModal() { - }> - + }> + ); } -function DeleteAccountForm() { +function DeleteAccountForm(props: { email: string }) { const form = useForm({ resolver: zodResolver(DeletePersonalAccountSchema), defaultValues: { - confirmation: '' as 'DELETE' + otp: '', }, }); + const { otp } = useWatch({ control: form.control }); + + if (!otp) { + return ( + form.setValue('otp', otp, { shouldValidate: true })} + CancelButton={ + + + + } + /> + ); + } + return (
+ +
@@ -98,25 +126,6 @@ function DeleteAccountForm() {
- - - - - - - - - -
@@ -124,21 +133,21 @@ function DeleteAccountForm() { - +
); } -function DeleteAccountSubmitButton() { +function DeleteAccountSubmitButton(props: { disabled: boolean }) { const { pending } = useFormStatus(); return ( + + + + ) : ( +
+
+
+ +
+ + + + + + + + + + + {error} + + + + ( + + + + + + + + + + + + + + + + + + + + + + + )} + /> + +
+ {CancelButton} + +
+ + + +
+
+ +
+ + )} + + ); +} diff --git a/packages/otp/src/server/index.ts b/packages/otp/src/server/index.ts new file mode 100644 index 000000000..88ec6358a --- /dev/null +++ b/packages/otp/src/server/index.ts @@ -0,0 +1 @@ +export * from './otp.service'; diff --git a/packages/otp/src/server/otp-email.service.ts b/packages/otp/src/server/otp-email.service.ts new file mode 100644 index 000000000..fc14fb027 --- /dev/null +++ b/packages/otp/src/server/otp-email.service.ts @@ -0,0 +1,62 @@ +import { z } from 'zod'; + +import { renderOtpEmail } from '@kit/email-templates'; +import { getMailer } from '@kit/mailers'; +import { getLogger } from '@kit/shared/logger'; + +const EMAIL_SENDER = z + .string({ + required_error: 'EMAIL_SENDER is required', + }) + .min(1) + .parse(process.env.EMAIL_SENDER); + +const PRODUCT_NAME = z + .string({ + required_error: 'PRODUCT_NAME is required', + }) + .min(1) + .parse(process.env.NEXT_PUBLIC_PRODUCT_NAME); + +/** + * @name createOtpEmailService + * @description Creates a new OtpEmailService + * @returns {OtpEmailService} + */ +export function createOtpEmailService() { + return new OtpEmailService(); +} + +/** + * @name OtpEmailService + * @description Service for sending OTP emails + */ +class OtpEmailService { + async sendOtpEmail(params: { email: string; otp: string }) { + const logger = await getLogger(); + const { email, otp } = params; + const mailer = await getMailer(); + + const { html, subject } = await renderOtpEmail({ + otp, + productName: PRODUCT_NAME, + }); + + try { + logger.info({ otp }, 'Sending OTP email...'); + + await mailer.sendEmail({ + to: email, + subject, + html, + from: EMAIL_SENDER, + }); + + logger.info({ otp }, 'OTP email sent'); + } catch (error) { + logger.error({ otp, error }, 'Error sending OTP email'); + + throw error; + } + } +} diff --git a/packages/otp/src/server/otp.service.ts b/packages/otp/src/server/otp.service.ts new file mode 100644 index 000000000..c0f69c8ed --- /dev/null +++ b/packages/otp/src/server/otp.service.ts @@ -0,0 +1,267 @@ +import 'server-only'; + +import { SupabaseClient } from '@supabase/supabase-js'; + +import { getLogger } from '@kit/shared/logger'; +import { Database, Json } from '@kit/supabase/database'; + +import { + CreateNonceParams, + CreateNonceResult, + GetNonceStatusParams, + GetNonceStatusResult, + RevokeNonceParams, + VerifyNonceParams, + VerifyNonceResult, +} from '../types'; + +/** + * @name createOtpService + * @description Creates an instance of the OtpService + * @param client + */ +export function createOtpService(client: SupabaseClient) { + return new OtpService(client); +} + +// Type declarations for RPC parameters +type CreateNonceRpcParams = { + p_user_id?: string; + p_purpose?: string; + p_expires_in_seconds?: number; + p_metadata?: Json; + p_description?: string; + p_tags?: string[]; + p_scopes?: string[]; + p_revoke_previous?: boolean; +}; + +type VerifyNonceRpcParams = { + p_token: string; + p_purpose: string; + p_required_scopes?: string[]; + p_max_verification_attempts?: number; +}; + +/** + * @name OtpService + * @description Service for creating and verifying one-time tokens/passwords + */ +class OtpService { + constructor(private readonly client: SupabaseClient) {} + + /** + * @name createNonce + * @description Creates a new one-time token for a user + * @param params + */ + async createNonce(params: CreateNonceParams) { + const logger = await getLogger(); + + const { + userId, + purpose, + expiresInSeconds = 3600, + metadata = {}, + description, + tags, + scopes, + revokePrevious = true, + } = params; + + const ctx = { userId, purpose, name: 'nonce' }; + + logger.info(ctx, 'Creating one-time token'); + + try { + const result = await this.client.rpc('create_nonce', { + p_user_id: userId, + p_purpose: purpose, + p_expires_in_seconds: expiresInSeconds, + p_metadata: metadata as Json, + p_description: description, + p_tags: tags, + p_scopes: scopes, + p_revoke_previous: revokePrevious, + } as CreateNonceRpcParams); + + if (result.error) { + logger.error( + { ...ctx, error: result.error.message }, + 'Failed to create one-time token', + ); + + throw new Error( + `Failed to create one-time token: ${result.error.message}`, + ); + } + + const data = result.data as unknown as CreateNonceResult; + + logger.info( + { ...ctx, revokedPreviousCount: data.revoked_previous_count }, + 'One-time token created successfully', + ); + + return { + id: data.id, + token: data.token, + expiresAt: data.expires_at, + revokedPreviousCount: data.revoked_previous_count, + }; + } catch (error) { + logger.error({ ...ctx, error }, 'Error creating one-time token'); + throw error; + } + } + + /** + * @name verifyNonce + * @description Verifies a one-time token + * @param params + */ + async verifyNonce(params: VerifyNonceParams) { + const logger = await getLogger(); + const { token, purpose, requiredScopes, maxVerificationAttempts } = params; + const ctx = { purpose, name: 'verify-nonce' }; + + logger.info(ctx, 'Verifying one-time token'); + + try { + const result = await this.client.rpc('verify_nonce', { + p_token: token, + p_user_id: params.userId, + p_purpose: purpose, + p_required_scopes: requiredScopes, + p_max_verification_attempts: maxVerificationAttempts, + } as VerifyNonceRpcParams); + + if (result.error) { + logger.error( + { ...ctx, error: result.error.message }, + 'Failed to verify one-time token', + ); + + throw new Error( + `Failed to verify one-time token: ${result.error.message}`, + ); + } + + const data = result.data as unknown as VerifyNonceResult; + + logger.info( + { + ...ctx, + ...data, + }, + 'One-time token verification complete', + ); + + return data; + } catch (error) { + logger.error({ ...ctx, error }, 'Error verifying one-time token'); + throw error; + } + } + + /** + * @name revokeNonce + * @description Revokes a one-time token to prevent its use + * @param params + */ + async revokeNonce(params: RevokeNonceParams) { + const logger = await getLogger(); + const { id, reason } = params; + const ctx = { id, reason, name: 'revoke-nonce' }; + + logger.info(ctx, 'Revoking one-time token'); + + try { + const { data, error } = await this.client.rpc('revoke_nonce', { + p_id: id, + p_reason: reason, + }); + + if (error) { + logger.error( + { ...ctx, error: error.message }, + 'Failed to revoke one-time token', + ); + throw new Error(`Failed to revoke one-time token: ${error.message}`); + } + + logger.info( + { ...ctx, success: data }, + 'One-time token revocation complete', + ); + + return { + success: data, + }; + } catch (error) { + logger.error({ ...ctx, error }, 'Error revoking one-time token'); + throw error; + } + } + + /** + * @name getNonceStatus + * @description Gets the status of a one-time token + * @param params + */ + async getNonceStatus(params: GetNonceStatusParams) { + const logger = await getLogger(); + const { id } = params; + const ctx = { id, name: 'get-nonce-status' }; + + logger.info(ctx, 'Getting one-time token status'); + + try { + const result = await this.client.rpc('get_nonce_status', { + p_id: id, + }); + + if (result.error) { + logger.error( + { ...ctx, error: result.error.message }, + 'Failed to get one-time token status', + ); + + throw new Error( + `Failed to get one-time token status: ${result.error.message}`, + ); + } + + const data = result.data as unknown as GetNonceStatusResult; + + logger.info( + { ...ctx, exists: data.exists }, + 'Retrieved one-time token status', + ); + + if (!data.exists) { + return { + exists: false, + }; + } + + return { + exists: data.exists, + purpose: data.purpose, + userId: data.user_id, + createdAt: data.created_at, + expiresAt: data.expires_at, + usedAt: data.used_at, + revoked: data.revoked, + revokedReason: data.revoked_reason, + verificationAttempts: data.verification_attempts, + lastVerificationAt: data.last_verification_at, + lastVerificationIp: data.last_verification_ip, + isValid: data.is_valid, + }; + } catch (error) { + logger.error({ ...ctx, error }, 'Error getting one-time token status'); + throw error; + } + } +} diff --git a/packages/otp/src/server/server-actions.ts b/packages/otp/src/server/server-actions.ts new file mode 100644 index 000000000..0a53c249b --- /dev/null +++ b/packages/otp/src/server/server-actions.ts @@ -0,0 +1,88 @@ +'use server'; + +import { z } from 'zod'; + +import { enhanceAction } from '@kit/next/actions'; +import { getLogger } from '@kit/shared/logger'; +import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; + +import { createOtpApi } from '../api'; + +// Schema for sending OTP email +const SendOtpEmailSchema = z.object({ + email: z.string().email({ message: 'Please enter a valid email address' }), + // Purpose of the OTP (e.g., 'email-verification', 'password-reset') + purpose: z.string().min(1).max(1000), + // how long the OTP should be valid for. Defaults to 1 hour. Max is 7 days. Min is 30 seconds. + expiresInSeconds: z.number().min(30).max(86400 * 7).default(3600).optional(), +}); + +/** + * Server action to generate an OTP and send it via email + */ +export const sendOtpEmailAction = enhanceAction( + async function (data: z.infer, user) { + const logger = await getLogger(); + const ctx = { name: 'send-otp-email', userId: user.id }; + const email = user.email; + + // validate edge case where user has no email + if (!email) { + throw new Error('User has no email. OTP verification is not possible.'); + } + + // validate edge case where email is not the same as the one provided + // this is highly unlikely to happen, but we want to make sure the client-side code is correct in + // sending the correct user email + if (data.email !== email) { + throw new Error('User email does not match the email provided. This is likely an error in the client.'); + } + + try { + const { purpose, expiresInSeconds } = data; + + logger.info( + { ...ctx, email, purpose }, + 'Creating OTP token and sending email', + ); + + const client = getSupabaseServerAdminClient(); + const otpApi = createOtpApi(client); + + // Create a token that will be verified later + const tokenResult = await otpApi.createToken({ + userId: user.id, + purpose, + expiresInSeconds, + }); + + // Send the email with the OTP + await otpApi.sendOtpEmail({ + email, + otp: tokenResult.token, + }); + + logger.info( + { ...ctx, tokenId: tokenResult.id }, + 'OTP email sent successfully', + ); + + return { + success: true, + tokenId: tokenResult.id, + }; + } catch (error) { + logger.error({ ...ctx, error }, 'Failed to send OTP email'); + + return { + success: false, + error: + error instanceof Error ? error.message : 'Failed to send OTP email', + }; + } + }, + { + schema: SendOtpEmailSchema, + auth: true, + }, +); diff --git a/packages/otp/src/types/index.ts b/packages/otp/src/types/index.ts new file mode 100644 index 000000000..f57bcd7a2 --- /dev/null +++ b/packages/otp/src/types/index.ts @@ -0,0 +1,115 @@ +/** + * @name CreateNonceParams - Parameters for creating a nonce + */ +export interface CreateNonceParams { + userId?: string; + purpose: string; + expiresInSeconds?: number; + metadata?: Record; + description?: string; + tags?: string[]; + scopes?: string[]; + revokePrevious?: boolean; +} + +/** + * @name VerifyNonceParams - Parameters for verifying a nonce + */ +export interface VerifyNonceParams { + token: string; + purpose: string; + userId?: string; + requiredScopes?: string[]; + maxVerificationAttempts?: number; +} + +/** + * @name RevokeNonceParams - Parameters for revoking a nonce + */ +export interface RevokeNonceParams { + id: string; + reason?: string; +} + +/** + * @name CreateNonceResult - Result of creating a nonce + */ +export interface CreateNonceResult { + id: string; + token: string; + expires_at: string; + revoked_previous_count?: number; +} + +/** + * @name ValidNonceResult - Result of verifying a nonce + */ +type ValidNonceResult = { + valid: boolean; + user_id?: string; + metadata?: Record; + message?: string; + scopes?: string[]; + purpose?: string; +}; + +/** + * @name InvalidNonceResult - Result of verifying a nonce + */ +type InvalidNonceResult = { + valid: false; + message: string; + max_attempts_exceeded?: boolean; +}; + +/** + * @name VerifyNonceResult - Result of verifying a nonce + */ +export type VerifyNonceResult = ValidNonceResult | InvalidNonceResult; + +/** + * @name GetNonceStatusParams - Parameters for getting nonce status + */ +export interface GetNonceStatusParams { + id: string; +} + +/** + * @name SuccessGetNonceStatusResult - Result of getting nonce status + */ +type SuccessGetNonceStatusResult = { + exists: true; + purpose?: string; + user_id?: string; + created_at?: string; + expires_at?: string; + used_at?: string; + revoked?: boolean; + revoked_reason?: string; + verification_attempts?: number; + last_verification_at?: string; + last_verification_ip?: string; + is_valid?: boolean; +}; + +/** + * @name FailedGetNonceStatusResult - Result of getting nonce status + */ +type FailedGetNonceStatusResult = { + exists: false; +}; + +/** + * @name GetNonceStatusResult - Result of getting nonce status + */ +export type GetNonceStatusResult = + | SuccessGetNonceStatusResult + | FailedGetNonceStatusResult; + +/** + * @name SendOtpEmailParams - Parameters for sending an OTP email + */ +export interface SendOtpEmailParams { + email: string; + otp: string; +} diff --git a/packages/otp/tsconfig.json b/packages/otp/tsconfig.json new file mode 100644 index 000000000..c4697e934 --- /dev/null +++ b/packages/otp/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@kit/tsconfig/base.json", + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["*.ts", "src"], + "exclude": ["node_modules"] +} diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 328e008dc..6cf4660ab 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -8,8 +8,12 @@ export type Json = export type Database = { graphql_public: { - Tables: Record; - Views: Record; + Tables: { + [_ in never]: never; + }; + Views: { + [_ in never]: never; + }; Functions: { graphql: { Args: { @@ -21,8 +25,12 @@ export type Database = { Returns: Json; }; }; - Enums: Record; - CompositeTypes: Record; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; }; public: { Tables: { @@ -69,29 +77,7 @@ export type Database = { updated_at?: string | null; updated_by?: string | null; }; - Relationships: [ - { - foreignKeyName: 'accounts_created_by_fkey'; - columns: ['created_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'accounts_primary_owner_user_id_fkey'; - columns: ['primary_owner_user_id']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'accounts_updated_by_fkey'; - columns: ['updated_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - ]; + Relationships: []; }; accounts_memberships: { Row: { @@ -150,27 +136,6 @@ export type Database = { referencedRelation: 'roles'; referencedColumns: ['name']; }, - { - foreignKeyName: 'accounts_memberships_created_by_fkey'; - columns: ['created_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'accounts_memberships_updated_by_fkey'; - columns: ['updated_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, - { - foreignKeyName: 'accounts_memberships_user_id_fkey'; - columns: ['user_id']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, ]; }; billing_customers: { @@ -296,13 +261,6 @@ export type Database = { referencedRelation: 'user_accounts'; referencedColumns: ['id']; }, - { - foreignKeyName: 'invitations_invited_by_fkey'; - columns: ['invited_by']; - isOneToOne: false; - referencedRelation: 'users'; - referencedColumns: ['id']; - }, { foreignKeyName: 'invitations_role_fkey'; columns: ['role']; @@ -312,6 +270,69 @@ export type Database = { }, ]; }; + nonces: { + Row: { + client_token: string; + created_at: string; + description: string | null; + expires_at: string; + id: string; + last_verification_at: string | null; + last_verification_ip: unknown | null; + last_verification_user_agent: string | null; + metadata: Json | null; + nonce: string; + purpose: string; + revoked: boolean; + revoked_reason: string | null; + scopes: string[] | null; + tags: string[] | null; + used_at: string | null; + user_id: string | null; + verification_attempts: number; + }; + Insert: { + client_token: string; + created_at?: string; + description?: string | null; + expires_at: string; + id?: string; + last_verification_at?: string | null; + last_verification_ip?: unknown | null; + last_verification_user_agent?: string | null; + metadata?: Json | null; + nonce: string; + purpose: string; + revoked?: boolean; + revoked_reason?: string | null; + scopes?: string[] | null; + tags?: string[] | null; + used_at?: string | null; + user_id?: string | null; + verification_attempts?: number; + }; + Update: { + client_token?: string; + created_at?: string; + description?: string | null; + expires_at?: string; + id?: string; + last_verification_at?: string | null; + last_verification_ip?: unknown | null; + last_verification_user_agent?: string | null; + metadata?: Json | null; + nonce?: string; + purpose?: string; + revoked?: boolean; + revoked_reason?: string | null; + scopes?: string[] | null; + tags?: string[] | null; + used_at?: string | null; + user_id?: string | null; + verification_attempts?: number; + }; + Relationships: []; + }; notifications: { Row: { account_id: string; @@ -719,6 +740,19 @@ export type Database = { updated_at: string; }; }; + create_nonce: { + Args: { + p_user_id?: string; + p_purpose?: string; + p_expires_in_seconds?: number; + p_metadata?: Json; + p_description?: string; + p_tags?: string[]; + p_scopes?: string[]; + p_revoke_previous?: boolean; + }; + Returns: Json; + }; create_team_account: { Args: { account_name: string; @@ -777,6 +811,12 @@ export type Database = { Args: Record; Returns: Json; }; + get_nonce_status: { + Args: { + p_id: string; + }; + Returns: Json; + }; get_upper_system_role: { Args: Record; Returns: string; @@ -843,6 +883,13 @@ export type Database = { }; Returns: boolean; }; + revoke_nonce: { + Args: { + p_id: string; + p_reason?: string; + }; + Returns: boolean; + }; team_account_workspace: { Args: { account_slug: string; @@ -922,6 +969,18 @@ export type Database = { updated_at: string; }; }; + verify_nonce: { + Args: { + p_token: string; + p_purpose: string; + p_user_id?: string; + p_required_scopes?: string[]; + p_max_verification_attempts?: number; + p_ip?: unknown; + p_user_agent?: string; + }; + Returns: Json; + }; }; Enums: { app_permissions: @@ -1026,6 +1085,7 @@ export type Database = { owner_id: string | null; path_tokens: string[] | null; updated_at: string | null; + user_metadata: Json | null; version: string | null; }; Insert: { @@ -1039,6 +1099,7 @@ export type Database = { owner_id?: string | null; path_tokens?: string[] | null; updated_at?: string | null; + user_metadata?: Json | null; version?: string | null; }; Update: { @@ -1052,6 +1113,7 @@ export type Database = { owner_id?: string | null; path_tokens?: string[] | null; updated_at?: string | null; + user_metadata?: Json | null; version?: string | null; }; Relationships: [ @@ -1073,6 +1135,7 @@ export type Database = { key: string; owner_id: string | null; upload_signature: string; + user_metadata: Json | null; version: string; }; Insert: { @@ -1083,6 +1146,7 @@ export type Database = { key: string; owner_id?: string | null; upload_signature: string; + user_metadata?: Json | null; version: string; }; Update: { @@ -1093,6 +1157,7 @@ export type Database = { key?: string; owner_id?: string | null; upload_signature?: string; + user_metadata?: Json | null; version?: string; }; Relationships: [ @@ -1160,7 +1225,9 @@ export type Database = { ]; }; }; - Views: Record; + Views: { + [_ in never]: never; + }; Functions: { can_insert_object: { Args: { @@ -1227,6 +1294,10 @@ export type Database = { updated_at: string; }[]; }; + operation: { + Args: Record; + Returns: string; + }; search: { Args: { prefix: string; @@ -1248,8 +1319,12 @@ export type Database = { }[]; }; }; - Enums: Record; - CompositeTypes: Record; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; }; }; @@ -1334,3 +1409,18 @@ export type Enums< : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] ? PublicSchema['Enums'][PublicEnumNameOrOptions] : never; + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof PublicSchema['CompositeTypes'] + | { schema: keyof Database }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof Database; + } + ? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } + ? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof PublicSchema['CompositeTypes'] + ? PublicSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] + : never; diff --git a/packages/ui/src/shadcn/alert.tsx b/packages/ui/src/shadcn/alert.tsx index 0a0a8ce86..a01ca93fa 100644 --- a/packages/ui/src/shadcn/alert.tsx +++ b/packages/ui/src/shadcn/alert.tsx @@ -47,9 +47,10 @@ const AlertTitle: React.FC> = ({ ); AlertTitle.displayName = 'AlertTitle'; -const AlertDescription: React.FC< - React.ComponentPropsWithRef<'div'> -> = ({ className, ...props }) => ( +const AlertDescription: React.FC> = ({ + className, + ...props +}) => (
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c060e7c67..50ff9a293 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,7 +43,7 @@ importers: version: 0.475.0(react@19.0.0) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nodemailer: specifier: ^6.10.0 version: 6.10.0 @@ -74,7 +74,7 @@ importers: version: 4.0.8 '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 '@types/nodemailer': specifier: 6.4.17 version: 6.4.17 @@ -86,7 +86,7 @@ importers: version: 19.0.4(@types/react@19.0.10) babel-plugin-react-compiler: specifier: beta - version: 19.0.0-beta-21e868a-20250216 + version: 19.0.0-beta-e1e972c-20250221 pino-pretty: specifier: ^13.0.0 version: 13.0.0 @@ -113,7 +113,7 @@ importers: version: 1.50.1 '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 node-html-parser: specifier: ^7.0.1 version: 7.0.1 @@ -122,7 +122,7 @@ importers: dependencies: '@edge-csrf/nextjs': specifier: 2.5.3-cloudflare-rc1 - version: 2.5.3-cloudflare-rc1(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + version: 2.5.3-cloudflare-rc1(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) '@hookform/resolvers': specifier: ^4.1.1 version: 4.1.1(react-hook-form@7.54.2(react@19.0.0)) @@ -182,10 +182,10 @@ importers: version: link:../../packages/ui '@makerkit/data-loader-supabase-core': specifier: ^0.0.8 - version: 0.0.8(@supabase/postgrest-js@1.19.1)(@supabase/supabase-js@2.48.1) + version: 0.0.8(@supabase/postgrest-js@1.19.2)(@supabase/supabase-js@2.48.1) '@makerkit/data-loader-supabase-nextjs': specifier: ^1.2.3 - version: 1.2.3(@supabase/postgrest-js@1.19.1)(@supabase/supabase-js@2.48.1)(@tanstack/react-query@5.66.9(react@19.0.0))(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + version: 1.2.3(@supabase/postgrest-js@1.19.2)(@supabase/supabase-js@2.48.1)(@tanstack/react-query@5.66.9(react@19.0.0))(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) '@marsidev/react-turnstile': specifier: ^1.1.0 version: 1.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -209,10 +209,10 @@ importers: version: 0.475.0(react@19.0.0) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + version: 4.2.3(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) next-themes: specifier: 0.4.4 version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -258,7 +258,7 @@ importers: version: 4.0.8 '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 '@types/react': specifier: 19.0.10 version: 19.0.10 @@ -270,7 +270,7 @@ importers: version: 10.4.20(postcss@8.5.3) babel-plugin-react-compiler: specifier: beta - version: 19.0.0-beta-21e868a-20250216 + version: 19.0.0-beta-e1e972c-20250221 dotenv-cli: specifier: ^8.0.0 version: 8.0.0 @@ -306,7 +306,7 @@ importers: version: link:../../tooling/typescript '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 packages/billing/core: devDependencies: @@ -375,7 +375,7 @@ importers: version: 0.475.0(react@19.0.0) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -421,7 +421,7 @@ importers: version: 19.0.10 next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -470,7 +470,7 @@ importers: version: 4.1.0 next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -503,7 +503,7 @@ importers: version: link:../wordpress '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 packages/cms/keystatic: dependencies: @@ -534,7 +534,7 @@ importers: version: link:../../ui '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 '@types/react': specifier: 19.0.10 version: 19.0.10 @@ -576,7 +576,7 @@ importers: version: link:../../ui '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 '@types/react': specifier: 19.0.10 version: 19.0.10 @@ -666,6 +666,9 @@ importers: '@kit/next': specifier: workspace:* version: link:../../next + '@kit/otp': + specifier: workspace:* + version: link:../../otp '@kit/prettier-config': specifier: workspace:* version: link:../../../tooling/prettier @@ -701,7 +704,7 @@ importers: version: 0.475.0(react@19.0.0) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes: specifier: 0.4.4 version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -752,10 +755,10 @@ importers: version: link:../../ui '@makerkit/data-loader-supabase-core': specifier: ^0.0.8 - version: 0.0.8(@supabase/postgrest-js@1.19.1)(@supabase/supabase-js@2.48.1) + version: 0.0.8(@supabase/postgrest-js@1.19.2)(@supabase/supabase-js@2.48.1) '@makerkit/data-loader-supabase-nextjs': specifier: ^1.2.3 - version: 1.2.3(@supabase/postgrest-js@1.19.1)(@supabase/supabase-js@2.48.1)(@tanstack/react-query@5.66.9(react@19.0.0))(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + version: 1.2.3(@supabase/postgrest-js@1.19.2)(@supabase/supabase-js@2.48.1)(@tanstack/react-query@5.66.9(react@19.0.0))(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) '@supabase/supabase-js': specifier: 2.48.1 version: 2.48.1 @@ -773,7 +776,7 @@ importers: version: 0.475.0(react@19.0.0) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -830,7 +833,7 @@ importers: version: 0.475.0(react@19.0.0) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-hook-form: specifier: ^7.54.2 version: 7.54.2(react@19.0.0) @@ -913,6 +916,9 @@ importers: '@kit/next': specifier: workspace:* version: link:../../next + '@kit/otp': + specifier: workspace:* + version: link:../../otp '@kit/prettier-config': specifier: workspace:* version: link:../../../tooling/prettier @@ -954,7 +960,7 @@ importers: version: 0.475.0(react@19.0.0) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -1003,7 +1009,7 @@ importers: version: 5.66.9(react@19.0.0) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -1039,7 +1045,7 @@ importers: version: link:../../../tooling/typescript '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 zod: specifier: ^3.24.2 version: 3.24.2 @@ -1085,7 +1091,7 @@ importers: version: link:../../../tooling/typescript '@types/node': specifier: ^22.13.4 - version: 22.13.4 + version: 22.13.5 zod: specifier: ^3.24.2 version: 3.24.2 @@ -1240,7 +1246,64 @@ importers: version: 2.48.1 next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + zod: + specifier: ^3.24.2 + version: 3.24.2 + + packages/otp: + devDependencies: + '@hookform/resolvers': + specifier: ^4.1.1 + version: 4.1.1(react-hook-form@7.54.2(react@19.0.0)) + '@kit/email-templates': + specifier: workspace:* + version: link:../email-templates + '@kit/eslint-config': + specifier: workspace:* + version: link:../../tooling/eslint + '@kit/mailers': + specifier: workspace:* + version: link:../mailers/core + '@kit/next': + specifier: workspace:* + version: link:../next + '@kit/prettier-config': + specifier: workspace:* + version: link:../../tooling/prettier + '@kit/shared': + specifier: workspace:* + version: link:../shared + '@kit/supabase': + specifier: workspace:* + version: link:../supabase + '@kit/tsconfig': + specifier: workspace:* + version: link:../../tooling/typescript + '@kit/ui': + specifier: workspace:* + version: link:../ui + '@radix-ui/react-icons': + specifier: ^1.3.2 + version: 1.3.2(react@19.0.0) + '@supabase/supabase-js': + specifier: 2.48.1 + version: 2.48.1 + '@types/react': + specifier: 19.0.10 + version: 19.0.10 + '@types/react-dom': + specifier: 19.0.4 + version: 19.0.4(@types/react@19.0.10) + react: + specifier: 19.0.0 + version: 19.0.0 + react-dom: + specifier: 19.0.0 + version: 19.0.0(react@19.0.0) + react-hook-form: + specifier: ^7.54.2 + version: 7.54.2(react@19.0.0) zod: specifier: ^3.24.2 version: 3.24.2 @@ -1289,7 +1352,7 @@ importers: version: 19.0.10 next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -1422,7 +1485,7 @@ importers: version: 9.20.1(jiti@2.4.2) next: specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes: specifier: 0.4.4 version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -1538,10 +1601,6 @@ packages: resolution: {integrity: sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.5': - resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.26.8': resolution: {integrity: sha512-ef383X5++iZHWAXX0SXQR6ZyQhw/0KtTkrTz61WXRhFM6dhpHulO/RJz79L8S6ugZHJkOOkUrUdxgdF2YiPFnA==} engines: {node: '>=6.9.0'} @@ -1576,16 +1635,6 @@ packages: resolution: {integrity: sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==} engines: {node: '>=6.9.0'} - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/parser@7.26.7': - resolution: {integrity: sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.26.8': resolution: {integrity: sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==} engines: {node: '>=6.0.0'} @@ -1595,38 +1644,18 @@ packages: resolution: {integrity: sha512-55gRV8vGrCIYZnaQHQrD92Lo/hYE3Sj5tmbuf0hhHR7sj2CWhEhHU89hbq+UVDXvFG1zUVXJhUkEq1eAfqXtFw==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.26.0': - resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} - engines: {node: '>=6.9.0'} - - '@babel/runtime@7.26.7': - resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.26.9': resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} - engines: {node: '>=6.9.0'} - '@babel/template@7.26.8': resolution: {integrity: sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.7': - resolution: {integrity: sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.8': resolution: {integrity: sha512-nic9tRkjYH0oB2dzr/JoGIm+4Q6SuYeLEiIiZDwBscRMYFJ+tMAz98fuel9ZnbXViA2I0HVSSRRK8DW5fjXStA==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.7': - resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==} - engines: {node: '>=6.9.0'} - '@babel/types@7.26.8': resolution: {integrity: sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==} engines: {node: '>=6.9.0'} @@ -2680,15 +2709,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.1.0': - resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-compose-refs@1.1.1': resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} peerDependencies: @@ -2701,7 +2721,7 @@ packages: '@radix-ui/react-context@1.1.1': resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2758,7 +2778,7 @@ packages: '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2882,19 +2902,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.0.0': - resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-primitive@2.0.2': resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} peerDependencies: @@ -2986,15 +2993,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.1.0': - resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-slot@1.1.2': resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} peerDependencies: @@ -3086,7 +3084,7 @@ packages: '@radix-ui/react-use-layout-effect@1.1.0': resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3095,7 +3093,7 @@ packages: '@radix-ui/react-use-previous@1.1.0': resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3113,7 +3111,7 @@ packages: '@radix-ui/react-use-size@1.1.0': resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -3957,8 +3955,8 @@ packages: '@supabase/postgrest-js@1.18.1': resolution: {integrity: sha512-dWDnoC0MoDHKhaEOrsEKTadWQcBNknZVQcSgNE/Q2wXh05mhCL1ut/jthRUrSbYcqIw/CEjhaeIPp7dLarT0bg==} - '@supabase/postgrest-js@1.19.1': - resolution: {integrity: sha512-SJ6R71jICD3HizEtu37YqOL20duUZwrhOCHPe0DFl51byHoL221qE4+fOQiJtvd4kXDR16rXtWqrccrUW/oNQw==} + '@supabase/postgrest-js@1.19.2': + resolution: {integrity: sha512-MXRbk4wpwhWl9IN6rIY1mR8uZCCG4MZAEji942ve6nMwIqnBgBnZhZlON6zTTs6fgveMnoCILpZv1+K91jN+ow==} '@supabase/realtime-js@2.11.2': resolution: {integrity: sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==} @@ -4226,9 +4224,6 @@ packages: '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/node@22.13.4': - resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==} - '@types/node@22.13.5': resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==} @@ -4276,14 +4271,6 @@ packages: '@types/ws@8.5.14': resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} - '@typescript-eslint/eslint-plugin@8.24.0': - resolution: {integrity: sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/eslint-plugin@8.24.1': resolution: {integrity: sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4292,13 +4279,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.24.0': - resolution: {integrity: sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.24.1': resolution: {integrity: sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4306,21 +4286,10 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/scope-manager@8.24.0': - resolution: {integrity: sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.24.1': resolution: {integrity: sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.24.0': - resolution: {integrity: sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/type-utils@8.24.1': resolution: {integrity: sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4328,33 +4297,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/types@8.24.0': - resolution: {integrity: sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.24.1': resolution: {integrity: sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.24.0': - resolution: {integrity: sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/typescript-estree@8.24.1': resolution: {integrity: sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.24.0': - resolution: {integrity: sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.24.1': resolution: {integrity: sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4362,10 +4314,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/visitor-keys@8.24.0': - resolution: {integrity: sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.24.1': resolution: {integrity: sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4537,10 +4485,6 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} - array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -4573,10 +4517,6 @@ packages: resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} engines: {node: '>= 0.4'} - arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} - engines: {node: '>= 0.4'} - arraybuffer.prototype.slice@1.0.4: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} @@ -4621,8 +4561,8 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} - babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216: - resolution: {integrity: sha512-WDOBsm9t9P0RADm8CSlav5OqWvs+3mZFvrBo/qf3vuNtdz78OG5TFxOy7De8ePR3rA6qg1Qmcjjae6nR1pOpCA==} + babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221: + resolution: {integrity: sha512-m3Y8KdwBwKj9l6bf1XPO2xm0WWzv/cYJPurkwP5j8SADGor6l9CdQVksrcOGzU/4Rylfa+tXW6+xaR3vAKs7Hg==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -4658,11 +4598,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.2: - resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - browserslist@4.24.4: resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -4682,10 +4617,6 @@ packages: resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} - call-bind@1.0.8: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} @@ -4701,9 +4632,6 @@ packages: camel-case@3.0.0: resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==} - caniuse-lite@1.0.30001677: - resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==} - caniuse-lite@1.0.30001700: resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==} @@ -4884,10 +4812,6 @@ packages: engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} hasBin: true - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -4957,26 +4881,14 @@ packages: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} - engines: {node: '>= 0.4'} - data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} - engines: {node: '>= 0.4'} - data-view-byte-length@1.0.2: resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} engines: {node: '>= 0.4'} - data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} - engines: {node: '>= 0.4'} - data-view-byte-offset@1.0.1: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} @@ -5134,9 +5046,6 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.51: - resolution: {integrity: sha512-kKeWV57KSS8jH4alKt/jKnvHPmJgBxXzGUSbMd4eQF+iOsVPl7bz2KUmu6eo80eMP8wVioTfTyTzdMgM15WXNg==} - electron-to-chromium@1.5.99: resolution: {integrity: sha512-77c/+fCyL2U+aOyqfIFi89wYLBeSTCs55xCZL0oFH0KjqsvSvyh6AdQ+UIl1vgpnQQE6g+/KK8hOIupH6VwPtg==} @@ -5163,18 +5072,10 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} - engines: {node: '>= 0.4'} - es-abstract@1.23.9: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -5190,18 +5091,10 @@ packages: es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} - es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} - es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} - es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} @@ -5209,10 +5102,6 @@ packages: es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - es-to-primitive@1.3.0: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} @@ -5431,10 +5320,6 @@ packages: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -5551,10 +5436,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} - function.prototype.name@1.1.8: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} @@ -5570,14 +5451,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} - - get-intrinsic@1.2.6: - resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} - engines: {node: '>= 0.4'} - get-intrinsic@1.2.7: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} @@ -5594,10 +5467,6 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} - engines: {node: '>= 0.4'} - get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} @@ -5648,9 +5517,6 @@ packages: resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} engines: {node: '>=8'} - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -5687,9 +5553,6 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -5705,18 +5568,10 @@ packages: has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - has-proto@1.2.0: resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} engines: {node: '>= 0.4'} - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -5847,10 +5702,6 @@ packages: resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} engines: {node: '>=12.0.0'} - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} - engines: {node: '>= 0.4'} - internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -5872,10 +5723,6 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} - engines: {node: '>= 0.4'} - is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -5890,9 +5737,6 @@ packages: resolution: {integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==} engines: {node: '>= 0.4'} - is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - is-bigint@1.1.0: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} @@ -5901,10 +5745,6 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - is-boolean-object@1.2.1: resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==} engines: {node: '>= 0.4'} @@ -5920,18 +5760,10 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} - engines: {node: '>= 0.4'} - is-data-view@1.0.2: resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - is-date-object@1.1.0: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} @@ -5979,14 +5811,6 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} @@ -6010,10 +5834,6 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -6022,10 +5842,6 @@ packages: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} - engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.4: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} @@ -6034,26 +5850,14 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - is-symbol@1.1.1: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} - is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} - engines: {node: '>= 0.4'} - is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} @@ -6069,9 +5873,6 @@ packages: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} - is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - is-weakref@1.1.0: resolution: {integrity: sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==} engines: {node: '>= 0.4'} @@ -6635,9 +6436,6 @@ packages: resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==} engines: {node: '>=8.9.4'} - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -6668,10 +6466,6 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} - engines: {node: '>= 0.4'} - object-inspect@1.13.3: resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} engines: {node: '>= 0.4'} @@ -6680,10 +6474,6 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} - object.assign@4.1.7: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} @@ -7189,10 +6979,6 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - regexp.prototype.flags@1.5.3: - resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} - engines: {node: '>= 0.4'} - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -7286,10 +7072,6 @@ packages: rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} - engines: {node: '>=0.4'} - safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -7301,10 +7083,6 @@ packages: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} - safe-regex-test@1.1.0: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} @@ -7347,11 +7125,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -7405,10 +7178,6 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - side-channel@1.1.0: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} @@ -7526,13 +7295,6 @@ packages: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} - string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - string.prototype.trimend@1.0.9: resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} engines: {node: '>= 0.4'} @@ -7782,34 +7544,18 @@ packages: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} - typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} - engines: {node: '>= 0.4'} - typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} - typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} - typed-array-byte-length@1.0.3: resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} - typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} - typed-array-byte-offset@1.0.4: resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} - engines: {node: '>= 0.4'} - typed-array-length@1.0.7: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} @@ -7831,9 +7577,6 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -7867,12 +7610,6 @@ packages: unplugin@1.0.1: resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} - update-browserslist-db@1.1.1: - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - update-browserslist-db@1.1.2: resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true @@ -7917,11 +7654,6 @@ packages: '@types/react': optional: true - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - use-sync-external-store@1.4.0: resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} peerDependencies: @@ -7996,9 +7728,6 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -8011,10 +7740,6 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} - engines: {node: '>= 0.4'} - which-typed-array@1.1.18: resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} engines: {node: '>= 0.4'} @@ -8188,14 +7913,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.26.5': - dependencies: - '@babel/parser': 7.26.7 - '@babel/types': 7.26.8 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - '@babel/generator@7.26.8': dependencies: '@babel/parser': 7.26.8 @@ -8239,14 +7956,6 @@ snapshots: '@babel/template': 7.26.8 '@babel/types': 7.26.8 - '@babel/parser@7.26.2': - dependencies: - '@babel/types': 7.26.8 - - '@babel/parser@7.26.7': - dependencies: - '@babel/types': 7.26.8 - '@babel/parser@7.26.8': dependencies: '@babel/types': 7.26.8 @@ -8256,42 +7965,16 @@ snapshots: core-js-pure: 3.40.0 regenerator-runtime: 0.14.1 - '@babel/runtime@7.26.0': - dependencies: - regenerator-runtime: 0.14.1 - - '@babel/runtime@7.26.7': - dependencies: - regenerator-runtime: 0.14.1 - '@babel/runtime@7.26.9': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.25.9': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.2 - '@babel/types': 7.26.8 - '@babel/template@7.26.8': dependencies: '@babel/code-frame': 7.26.2 '@babel/parser': 7.26.8 '@babel/types': 7.26.8 - '@babel/traverse@7.26.7': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.5 - '@babel/parser': 7.26.7 - '@babel/template': 7.25.9 - '@babel/types': 7.26.8 - debug: 4.4.0 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.26.8': dependencies: '@babel/code-frame': 7.26.2 @@ -8304,11 +7987,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.26.7': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/types@7.26.8': dependencies: '@babel/helper-string-parser': 7.25.9 @@ -8321,7 +7999,7 @@ snapshots: '@opentelemetry/instrumentation': 0.50.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation-http': 0.50.0(@opentelemetry/api@1.9.0) '@opentelemetry/resource-detector-aws': 1.7.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-node': 0.50.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 1.27.0(@opentelemetry/api@1.9.0) '@trpc/server': 10.45.2 @@ -8350,9 +8028,9 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))': + '@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))': dependencies: - next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@emnapi/runtime@1.3.1': dependencies: @@ -8362,7 +8040,7 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.25.9 - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.26.9 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -8684,7 +8362,7 @@ snapshots: '@keystar/ui@0.7.17(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.26.9 '@emotion/css': 11.13.5 '@floating-ui/react': 0.24.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@internationalized/date': 3.7.0 @@ -8775,13 +8453,13 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) optionalDependencies: - next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) transitivePeerDependencies: - supports-color '@keystatic/core@0.5.45(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.26.9 '@braintree/sanitize-url': 6.0.4 '@emotion/weak-memoize': 0.3.1 '@floating-ui/react': 0.24.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -8859,29 +8537,29 @@ snapshots: '@keystatic/next@5.0.3(@keystatic/core@0.5.45(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.26.9 '@keystatic/core': 0.5.45(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/react': 19.0.10 chokidar: 3.6.0 - next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) server-only: 0.0.1 '@lemonsqueezy/lemonsqueezy.js@4.0.0': {} - '@makerkit/data-loader-supabase-core@0.0.8(@supabase/postgrest-js@1.19.1)(@supabase/supabase-js@2.48.1)': + '@makerkit/data-loader-supabase-core@0.0.8(@supabase/postgrest-js@1.19.2)(@supabase/supabase-js@2.48.1)': dependencies: - '@supabase/postgrest-js': 1.19.1 + '@supabase/postgrest-js': 1.19.2 '@supabase/supabase-js': 2.48.1 ts-case-convert: 2.1.0 - '@makerkit/data-loader-supabase-nextjs@1.2.3(@supabase/postgrest-js@1.19.1)(@supabase/supabase-js@2.48.1)(@tanstack/react-query@5.66.9(react@19.0.0))(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': + '@makerkit/data-loader-supabase-nextjs@1.2.3(@supabase/postgrest-js@1.19.2)(@supabase/supabase-js@2.48.1)(@tanstack/react-query@5.66.9(react@19.0.0))(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': dependencies: - '@makerkit/data-loader-supabase-core': 0.0.8(@supabase/postgrest-js@1.19.1)(@supabase/supabase-js@2.48.1) + '@makerkit/data-loader-supabase-core': 0.0.8(@supabase/postgrest-js@1.19.2)(@supabase/supabase-js@2.48.1) '@supabase/supabase-js': 2.48.1 '@tanstack/react-query': 5.66.9(react@19.0.0) - next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 transitivePeerDependencies: - '@supabase/postgrest-js' @@ -8896,7 +8574,7 @@ snapshots: parse-github-url: 1.0.3 picocolors: 1.1.1 sembear: 0.7.0 - semver: 7.6.3 + semver: 7.7.1 tinyexec: 0.3.1 validate-npm-package-name: 5.0.1 @@ -9149,7 +8827,7 @@ snapshots: '@opentelemetry/core': 1.23.0(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.50.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.23.0 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color @@ -9285,7 +8963,7 @@ snapshots: '@types/shimmer': 1.2.0 import-in-the-middle: 1.7.1 require-in-the-middle: 7.5.1 - semver: 7.6.3 + semver: 7.7.1 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -9381,9 +9059,9 @@ snapshots: '@opentelemetry/resource-detector-aws@1.7.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.30.0 '@opentelemetry/resources@1.23.0(@opentelemetry/api@1.9.0)': dependencies: @@ -9465,7 +9143,7 @@ snapshots: '@opentelemetry/propagator-b3': 1.23.0(@opentelemetry/api@1.9.0) '@opentelemetry/propagator-jaeger': 1.23.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.23.0(@opentelemetry/api@1.9.0) - semver: 7.6.3 + semver: 7.7.1 '@opentelemetry/sdk-trace-node@1.27.0(@opentelemetry/api@1.9.0)': dependencies: @@ -9475,7 +9153,7 @@ snapshots: '@opentelemetry/propagator-b3': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/propagator-jaeger': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) - semver: 7.6.3 + semver: 7.7.1 '@opentelemetry/semantic-conventions@1.23.0': {} @@ -9641,12 +9319,6 @@ snapshots: '@types/react': 19.0.10 '@types/react-dom': 19.0.4(@types/react@19.0.10) - '@radix-ui/react-compose-refs@1.1.0(@types/react@19.0.10)(react@19.0.0)': - dependencies: - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.10)(react@19.0.0)': dependencies: react: 19.0.0 @@ -9861,15 +9533,6 @@ snapshots: '@types/react': 19.0.10 '@types/react-dom': 19.0.4(@types/react@19.0.10) - '@radix-ui/react-primitive@2.0.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@radix-ui/react-slot': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - '@types/react-dom': 19.0.4(@types/react@19.0.10) - '@radix-ui/react-primitive@2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-slot': 1.1.2(@types/react@19.0.10)(react@19.0.0) @@ -9979,13 +9642,6 @@ snapshots: '@types/react': 19.0.10 '@types/react-dom': 19.0.4(@types/react@19.0.10) - '@radix-ui/react-slot@1.1.0(@types/react@19.0.10)(react@19.0.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - '@radix-ui/react-slot@1.1.2(@types/react@19.0.10)(react@19.0.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0) @@ -11407,7 +11063,7 @@ snapshots: dependencies: '@supabase/node-fetch': 2.6.15 - '@supabase/postgrest-js@1.19.1': + '@supabase/postgrest-js@1.19.2': dependencies: '@supabase/node-fetch': 2.6.15 @@ -11537,10 +11193,10 @@ snapshots: '@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.5.2)': dependencies: - '@babel/generator': 7.26.5 - '@babel/parser': 7.26.7 - '@babel/traverse': 7.26.7 - '@babel/types': 7.26.7 + '@babel/generator': 7.26.8 + '@babel/parser': 7.26.8 + '@babel/traverse': 7.26.8 + '@babel/types': 7.26.8 javascript-natural-sort: 0.7.1 lodash: 4.17.21 prettier: 3.5.2 @@ -11605,7 +11261,7 @@ snapshots: '@types/connect@3.4.36': dependencies: - '@types/node': 22.13.4 + '@types/node': 22.13.5 '@types/cookie@0.6.0': {} @@ -11658,7 +11314,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 22.13.4 + '@types/node': 22.13.5 '@types/hast@3.0.4': dependencies: @@ -11699,11 +11355,7 @@ snapshots: '@types/mysql@2.15.26': dependencies: - '@types/node': 22.13.4 - - '@types/node@22.13.4': - dependencies: - undici-types: 6.20.0 + '@types/node': 22.13.5 '@types/node@22.13.5': dependencies: @@ -11711,7 +11363,7 @@ snapshots: '@types/nodemailer@6.4.17': dependencies: - '@types/node': 22.13.4 + '@types/node': 22.13.5 '@types/parse-json@4.0.2': {} @@ -11721,7 +11373,7 @@ snapshots: '@types/pg@8.6.1': dependencies: - '@types/node': 22.13.4 + '@types/node': 22.13.5 pg-protocol: 1.7.1 pg-types: 2.2.0 @@ -11739,11 +11391,11 @@ snapshots: '@types/tedious@4.0.14': dependencies: - '@types/node': 22.13.4 + '@types/node': 22.13.5 '@types/through@0.0.33': dependencies: - '@types/node': 22.13.4 + '@types/node': 22.13.5 '@types/tinycolor2@1.4.6': {} @@ -11753,24 +11405,7 @@ snapshots: '@types/ws@8.5.14': dependencies: - '@types/node': 22.13.4 - - '@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/type-utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.24.0 - eslint: 9.20.1(jiti@2.4.2) - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 2.0.1(typescript@5.7.3) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color + '@types/node': 22.13.5 '@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: @@ -11789,18 +11424,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.24.0 - debug: 4.4.0 - eslint: 9.20.1(jiti@2.4.2) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 8.24.1 @@ -11813,27 +11436,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.24.0': - dependencies: - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/visitor-keys': 8.24.0 - '@typescript-eslint/scope-manager@8.24.1': dependencies: '@typescript-eslint/types': 8.24.1 '@typescript-eslint/visitor-keys': 8.24.1 - '@typescript-eslint/type-utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - debug: 4.4.0 - eslint: 9.20.1(jiti@2.4.2) - ts-api-utils: 2.0.1(typescript@5.7.3) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/type-utils@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) @@ -11845,24 +11452,8 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.24.0': {} - '@typescript-eslint/types@8.24.1': {} - '@typescript-eslint/typescript-estree@8.24.0(typescript@5.7.3)': - dependencies: - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/visitor-keys': 8.24.0 - debug: 4.4.0 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.0.1(typescript@5.7.3) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.24.1(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 8.24.1 @@ -11877,17 +11468,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/types': 8.24.0 - '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - eslint: 9.20.1(jiti@2.4.2) - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1(jiti@2.4.2)) @@ -11899,11 +11479,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.24.0': - dependencies: - '@typescript-eslint/types': 8.24.0 - eslint-visitor-keys: 4.2.0 - '@typescript-eslint/visitor-keys@8.24.1': dependencies: '@typescript-eslint/types': 8.24.1 @@ -12101,11 +11676,6 @@ snapshots: aria-query@5.3.2: {} - array-buffer-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.3 @@ -12113,12 +11683,12 @@ snapshots: array-includes@3.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - is-string: 1.0.7 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.2.7 + is-string: 1.1.1 array-union@2.1.0: {} @@ -12162,17 +11732,6 @@ snapshots: es-errors: 1.3.0 es-shim-unscopables: 1.0.2 - arraybuffer.prototype.slice@1.0.3: - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.4 - is-shared-array-buffer: 1.0.3 - arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 @@ -12195,8 +11754,8 @@ snapshots: autoprefixer@10.4.20(postcss@8.5.3): dependencies: - browserslist: 4.24.2 - caniuse-lite: 1.0.30001677 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001700 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -12221,11 +11780,11 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.26.9 cosmiconfig: 7.1.0 resolve: 1.22.10 - babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216: + babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221: dependencies: '@babel/types': 7.26.8 @@ -12266,13 +11825,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.2: - dependencies: - caniuse-lite: 1.0.30001677 - electron-to-chromium: 1.5.51 - node-releases: 2.0.18 - update-browserslist-db: 1.1.1(browserslist@4.24.2) - browserslist@4.24.4: dependencies: caniuse-lite: 1.0.30001700 @@ -12296,14 +11848,6 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.7: - dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - set-function-length: 1.2.2 - call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.1 @@ -12314,7 +11858,7 @@ snapshots: call-bound@1.0.3: dependencies: call-bind-apply-helpers: 1.0.1 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 callsites@3.1.0: {} @@ -12323,8 +11867,6 @@ snapshots: no-case: 2.3.2 upper-case: 1.1.3 - caniuse-lite@1.0.30001677: {} - caniuse-lite@1.0.30001700: {} ccount@2.0.1: {} @@ -12426,10 +11968,10 @@ snapshots: dependencies: '@radix-ui/react-dialog': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-id': 1.1.0(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - use-sync-external-store: 1.2.2(react@19.0.0) + use-sync-external-store: 1.4.0(react@19.0.0) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -12510,13 +12052,7 @@ snapshots: cross-env@7.0.3: dependencies: - cross-spawn: 7.0.3 - - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + cross-spawn: 7.0.6 cross-spawn@7.0.6: dependencies: @@ -12580,36 +12116,18 @@ snapshots: data-uri-to-buffer@6.0.2: {} - data-view-buffer@1.0.1: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - data-view-buffer@1.0.2: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 is-data-view: 1.0.2 - data-view-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - data-view-byte-length@1.0.2: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 is-data-view: 1.0.2 - data-view-byte-offset@1.0.0: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - data-view-byte-offset@1.0.1: dependencies: call-bound: 1.0.3 @@ -12650,9 +12168,9 @@ snapshots: define-data-property@1.1.4: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 - gopd: 1.0.1 + gopd: 1.2.0 define-properties@1.2.1: dependencies: @@ -12756,8 +12274,6 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.51: {} - electron-to-chromium@1.5.99: {} emery@1.4.3: {} @@ -12781,55 +12297,6 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract@1.23.3: - dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.3 - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.2 - globalthis: 1.0.4 - gopd: 1.0.1 - has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - internal-slot: 1.0.7 - is-array-buffer: 3.0.4 - is-callable: 1.2.7 - is-data-view: 1.0.1 - is-negative-zero: 2.0.3 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - is-string: 1.0.7 - is-typed-array: 1.1.13 - is-weakref: 1.0.2 - object-inspect: 1.13.2 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.3 - safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 - typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.6 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.15 - es-abstract@1.23.9: dependencies: array-buffer-byte-length: 1.0.2 @@ -12884,10 +12351,6 @@ snapshots: unbox-primitive: 1.1.0 which-typed-array: 1.1.18 - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.2.4 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -12913,20 +12376,10 @@ snapshots: es-module-lexer@1.6.0: {} - es-object-atoms@1.0.0: - dependencies: - es-errors: 1.3.0 - es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.0.3: - dependencies: - get-intrinsic: 1.2.4 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 @@ -12938,12 +12391,6 @@ snapshots: dependencies: hasown: 2.0.2 - es-to-primitive@1.2.1: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 @@ -12972,12 +12419,12 @@ snapshots: dependencies: '@next/eslint-plugin-next': 15.1.7 '@rushstack/eslint-patch': 1.10.5 - '@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/eslint-plugin': 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) eslint: 9.20.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.1(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-react: 7.37.4(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-react-hooks: 5.1.0(eslint@9.20.1(jiti@2.4.2)) @@ -13014,22 +12461,22 @@ snapshots: is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/parser': 8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) eslint: 9.20.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.20.1(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -13040,7 +12487,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.20.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.20.1(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -13237,14 +12684,6 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -13345,13 +12784,6 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.6: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - functions-have-names: 1.2.3 - function.prototype.name@1.1.8: dependencies: call-bind: 1.0.8 @@ -13367,27 +12799,6 @@ snapshots: get-caller-file@2.0.5: {} - get-intrinsic@1.2.4: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - - get-intrinsic@1.2.6: - dependencies: - call-bind-apply-helpers: 1.0.1 - dunder-proto: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - function-bind: 1.1.2 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - get-intrinsic@1.2.7: dependencies: call-bind-apply-helpers: 1.0.1 @@ -13410,12 +12821,6 @@ snapshots: get-stream@6.0.1: {} - get-symbol-description@1.0.2: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - get-symbol-description@1.1.0: dependencies: call-bound: 1.0.3 @@ -13476,7 +12881,7 @@ snapshots: globalthis@1.0.4: dependencies: define-properties: 1.2.1 - gopd: 1.0.1 + gopd: 1.2.0 globby@10.0.2: dependencies: @@ -13489,10 +12894,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - gopd@1.0.1: - dependencies: - get-intrinsic: 1.2.4 - gopd@1.2.0: {} graceful-fs@4.2.10: {} @@ -13526,8 +12927,6 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - has-bigints@1.0.2: {} - has-bigints@1.1.0: {} has-flag@3.0.0: {} @@ -13536,21 +12935,17 @@ snapshots: has-property-descriptors@1.0.2: dependencies: - es-define-property: 1.0.0 - - has-proto@1.0.3: {} + es-define-property: 1.0.1 has-proto@1.2.0: dependencies: dunder-proto: 1.0.1 - has-symbols@1.0.3: {} - has-symbols@1.1.0: {} has-tostringtag@1.0.2: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 hasown@2.0.2: dependencies: @@ -13619,11 +13014,11 @@ snapshots: i18next-resources-to-backend@1.2.1: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 i18next@24.2.2(typescript@5.7.3): dependencies: - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.26.9 optionalDependencies: typescript: 5.7.3 @@ -13712,12 +13107,6 @@ snapshots: through: 2.3.8 wrap-ansi: 6.2.0 - internal-slot@1.0.7: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.0.6 - internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -13745,11 +13134,6 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 - is-array-buffer@3.0.4: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -13768,10 +13152,6 @@ snapshots: has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 - is-bigint@1.0.4: - dependencies: - has-bigints: 1.0.2 - is-bigint@1.1.0: dependencies: has-bigints: 1.1.0 @@ -13780,11 +13160,6 @@ snapshots: dependencies: binary-extensions: 2.3.0 - is-boolean-object@1.1.2: - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - is-boolean-object@1.2.1: dependencies: call-bound: 1.0.3 @@ -13800,20 +13175,12 @@ snapshots: dependencies: hasown: 2.0.2 - is-data-view@1.0.1: - dependencies: - is-typed-array: 1.1.13 - is-data-view@1.0.2: dependencies: call-bound: 1.0.3 get-intrinsic: 1.2.7 is-typed-array: 1.1.15 - is-date-object@1.0.5: - dependencies: - has-tostringtag: 1.0.2 - is-date-object@1.1.0: dependencies: call-bound: 1.0.3 @@ -13854,12 +13221,6 @@ snapshots: is-map@2.0.3: {} - is-negative-zero@2.0.3: {} - - is-number-object@1.0.7: - dependencies: - has-tostringtag: 1.0.2 - is-number-object@1.1.1: dependencies: call-bound: 1.0.3 @@ -13877,11 +13238,6 @@ snapshots: dependencies: '@types/estree': 1.0.6 - is-regex@1.1.4: - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - is-regex@1.2.1: dependencies: call-bound: 1.0.3 @@ -13891,39 +13247,23 @@ snapshots: is-set@2.0.3: {} - is-shared-array-buffer@1.0.3: - dependencies: - call-bind: 1.0.7 - is-shared-array-buffer@1.0.4: dependencies: call-bound: 1.0.3 is-stream@2.0.1: {} - is-string@1.0.7: - dependencies: - has-tostringtag: 1.0.2 - is-string@1.1.1: dependencies: call-bound: 1.0.3 has-tostringtag: 1.0.2 - is-symbol@1.0.4: - dependencies: - has-symbols: 1.0.3 - is-symbol@1.1.1: dependencies: call-bound: 1.0.3 has-symbols: 1.1.0 safe-regex-test: 1.1.0 - is-typed-array@1.1.13: - dependencies: - which-typed-array: 1.1.15 - is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.18 @@ -13936,10 +13276,6 @@ snapshots: is-weakmap@2.0.2: {} - is-weakref@1.0.2: - dependencies: - call-bind: 1.0.7 - is-weakref@1.1.0: dependencies: call-bound: 1.0.3 @@ -14164,7 +13500,7 @@ snapshots: match-sorter@6.3.4: dependencies: - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.26.9 remove-accents: 0.5.0 math-intrinsics@1.1.0: {} @@ -14590,13 +13926,13 @@ snapshots: netmask@2.0.2: {} - next-sitemap@4.2.3(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): + next-sitemap@4.2.3(next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): dependencies: '@corex/deepmerge': 4.0.43 '@next/env': 13.5.7 - fast-glob: 3.3.2 + fast-glob: 3.3.3 minimist: 1.2.8 - next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes@0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: @@ -14630,7 +13966,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-21e868a-20250216)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(babel-plugin-react-compiler@19.0.0-beta-e1e972c-20250221)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 15.1.7 '@swc/counter': 0.1.3 @@ -14652,7 +13988,7 @@ snapshots: '@next/swc-win32-x64-msvc': 15.1.7 '@opentelemetry/api': 1.9.0 '@playwright/test': 1.50.1 - babel-plugin-react-compiler: 19.0.0-beta-21e868a-20250216 + babel-plugin-react-compiler: 19.0.0-beta-e1e972c-20250221 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' @@ -14693,8 +14029,6 @@ snapshots: mkdirp: 0.5.6 resolve: 1.22.10 - node-releases@2.0.18: {} - node-releases@2.0.19: {} nodemailer@6.10.0: {} @@ -14715,19 +14049,10 @@ snapshots: object-assign@4.1.1: {} - object-inspect@1.13.2: {} - object-inspect@1.13.3: {} object-keys@1.1.1: {} - object.assign@4.1.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - object.assign@4.1.7: dependencies: call-bind: 1.0.8 @@ -14745,10 +14070,10 @@ snapshots: object.fromentries@2.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: @@ -14860,7 +14185,7 @@ snapshots: ky: 1.7.2 registry-auth-token: 5.0.2 registry-url: 6.0.1 - semver: 7.6.3 + semver: 7.7.1 param-case@2.1.1: dependencies: @@ -15100,7 +14425,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.13.4 + '@types/node': 22.13.5 long: 5.2.3 proxy-agent@6.5.0: @@ -15156,7 +14481,7 @@ snapshots: react-error-boundary@4.1.2(react@19.0.0): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 react: 19.0.0 react-hook-form@7.54.2(react@19.0.0): @@ -15272,13 +14597,6 @@ snapshots: regenerator-runtime@0.14.1: {} - regexp.prototype.flags@1.5.3: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-errors: 1.3.0 - set-function-name: 2.0.2 - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -15376,13 +14694,6 @@ snapshots: dependencies: tslib: 2.8.1 - safe-array-concat@1.1.2: - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - isarray: 2.0.5 - safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -15398,12 +14709,6 @@ snapshots: es-errors: 1.3.0 isarray: 2.0.5 - safe-regex-test@1.0.3: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-regex: 1.1.4 - safe-regex-test@1.1.0: dependencies: call-bound: 1.0.3 @@ -15439,14 +14744,12 @@ snapshots: sembear@0.7.0: dependencies: - semver: 7.6.3 + semver: 7.7.1 semver@6.3.1: {} semver@7.6.2: {} - semver@7.6.3: {} - semver@7.7.1: {} sentence-case@2.1.1: @@ -15465,8 +14768,8 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 - gopd: 1.0.1 + get-intrinsic: 1.2.7 + gopd: 1.2.0 has-property-descriptors: 1.0.2 set-function-name@2.0.2: @@ -15526,24 +14829,17 @@ snapshots: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 object-inspect: 1.13.3 side-channel-weakmap@1.0.2: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.6 + get-intrinsic: 1.2.7 object-inspect: 1.13.3 side-channel-map: 1.0.1 - side-channel@1.0.6: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.2 - side-channel@1.1.0: dependencies: es-errors: 1.3.0 @@ -15695,19 +14991,6 @@ snapshots: es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 - string.prototype.trim@1.2.9: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - - string.prototype.trimend@1.0.8: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - string.prototype.trimend@1.0.9: dependencies: call-bind: 1.0.8 @@ -15717,9 +15000,9 @@ snapshots: string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 string_decoder@1.3.0: dependencies: @@ -15748,7 +15031,7 @@ snapshots: stripe@17.6.0: dependencies: - '@types/node': 22.13.4 + '@types/node': 22.13.5 qs: 6.14.0 styled-jsx@5.1.6(@babel/core@7.26.8)(react@19.0.0): @@ -15935,26 +15218,12 @@ snapshots: type-fest@0.7.1: {} - typed-array-buffer@1.0.2: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-typed-array: 1.1.13 - typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 is-typed-array: 1.1.15 - typed-array-byte-length@1.0.1: - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - typed-array-byte-length@1.0.3: dependencies: call-bind: 1.0.8 @@ -15963,15 +15232,6 @@ snapshots: has-proto: 1.2.0 is-typed-array: 1.1.15 - typed-array-byte-offset@1.0.2: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 @@ -15982,15 +15242,6 @@ snapshots: is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.6: - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - possible-typed-array-names: 1.0.0 - typed-array-length@1.0.7: dependencies: call-bind: 1.0.8 @@ -16015,13 +15266,6 @@ snapshots: uglify-js@3.19.3: optional: true - unbox-primitive@1.0.2: - dependencies: - call-bind: 1.0.7 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - unbox-primitive@1.1.0: dependencies: call-bound: 1.0.3 @@ -16065,12 +15309,6 @@ snapshots: webpack-sources: 3.2.3 webpack-virtual-modules: 0.5.0 - update-browserslist-db@1.1.1(browserslist@4.24.2): - dependencies: - browserslist: 4.24.2 - escalade: 3.2.0 - picocolors: 1.1.1 - update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: browserslist: 4.24.4 @@ -16113,10 +15351,6 @@ snapshots: optionalDependencies: '@types/react': 19.0.10 - use-sync-external-store@1.2.2(react@19.0.0): - dependencies: - react: 19.0.0 - use-sync-external-store@1.4.0(react@19.0.0): dependencies: react: 19.0.0 @@ -16228,14 +15462,6 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - which-boxed-primitive@1.0.2: - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -16267,14 +15493,6 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-typed-array@1.1.15: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.2 - which-typed-array@1.1.18: dependencies: available-typed-arrays: 1.0.7 diff --git a/tooling/eslint/nextjs.js b/tooling/eslint/nextjs.js index acdc685e4..bc1cd04a7 100644 --- a/tooling/eslint/nextjs.js +++ b/tooling/eslint/nextjs.js @@ -9,7 +9,7 @@ const nextEslintConfig = [ extends: ['next/core-web-vitals', 'next/typescript'], rules: { '@next/next/no-html-link-for-pages': 'off', - 'no-undef': 'off' + 'no-undef': 'off', }, }), ];