From 24a70f123a485473a26c35df5d5737d773da6125 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:01:49 +0100 Subject: [PATCH 01/47] refactor(#48): Improve security by moving create customer functionality to not be exposed in an endpoint --- .../api/checkout/create-customer/+server.ts | 16 ---------------- src/routes/checkout/success/+page.server.ts | 18 +++++++++--------- 2 files changed, 9 insertions(+), 25 deletions(-) delete mode 100644 src/routes/api/checkout/create-customer/+server.ts diff --git a/src/routes/api/checkout/create-customer/+server.ts b/src/routes/api/checkout/create-customer/+server.ts deleted file mode 100644 index e8926f4..0000000 --- a/src/routes/api/checkout/create-customer/+server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { upsertCustomerToSupabase } from '$lib/utils/supabase/admin'; - -export const GET: RequestHandler = async ({ url, locals: { safeGetSession } }) => { - const stripeCustomerId = url.searchParams.get('stripeCustomerId'); - const { session } = await safeGetSession(); - - if (!session || !stripeCustomerId) { - return json({ error: 'Unauthorized' }, { status: 401 }); - } - - await upsertCustomerToSupabase(session?.user.id, stripeCustomerId); - - return json({ message: 'Customer created' }); -}; diff --git a/src/routes/checkout/success/+page.server.ts b/src/routes/checkout/success/+page.server.ts index 8d1ee5e..b29cba2 100644 --- a/src/routes/checkout/success/+page.server.ts +++ b/src/routes/checkout/success/+page.server.ts @@ -1,8 +1,10 @@ import { redirect } from '@sveltejs/kit'; import Stripe from 'stripe'; -import { getProductById } from '$lib/utils/supabase/admin'; +import { getProductById, upsertCustomerToSupabase } from '$lib/utils/supabase/admin'; + +export const load = async ({ fetch, locals: { safeGetSession } }) => { + const { session } = await safeGetSession(); -export const load = async ({ fetch }) => { const response = await fetch('/api/checkout/status'); if (!response.ok) { console.error('Failed to fetch checkout status'); @@ -24,13 +26,11 @@ export const load = async ({ fetch }) => { lineItem.productDescription = product.description ?? ''; } - // Save the stripe customer id in the customers table - const stripeCustomerId = checkoutSession.customer as string; - const createCustomerResponse = await fetch( - '/api/checkout/create-customer?stripeCustomerId=' + stripeCustomerId - ); - if (!createCustomerResponse.ok) { - console.error('Failed to create customer'); + if (session?.user.id) { + const stripeCustomerId = checkoutSession.customer as string; + await upsertCustomerToSupabase(session.user.id, stripeCustomerId); + } else { + console.error('User ID is undefined'); } return { From f28f250fab1e506ce4149805bd50295b13f6e1bc Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:17:05 +0000 Subject: [PATCH 02/47] fix(#51): Ensure the input for quantity is reactive --- src/lib/components/checkout/BasketItem.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/checkout/BasketItem.svelte b/src/lib/components/checkout/BasketItem.svelte index 3924f37..1a50ef5 100644 --- a/src/lib/components/checkout/BasketItem.svelte +++ b/src/lib/components/checkout/BasketItem.svelte @@ -11,10 +11,11 @@ export let price = 0; export let quantity = 0; + $: dispatch('changeQuantity', { itemId, quantity }); + function reduceQuantityByOne() { if (quantity > 1) { quantity--; - dispatch('changeQuantity', { itemId, quantity }); } else { dispatch('removeItem', { itemId }); } @@ -22,7 +23,6 @@ function increaseQuantityByOne() { quantity++; - dispatch('changeQuantity', { itemId, quantity }); } From 8bc8399b5f6edf928f0db3d4887a75695072399b Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:17:25 +0000 Subject: [PATCH 03/47] chore(*): Remove redundant page --- src/routes/auth/update-password/+page.svelte | 81 -------------------- 1 file changed, 81 deletions(-) delete mode 100644 src/routes/auth/update-password/+page.svelte diff --git a/src/routes/auth/update-password/+page.svelte b/src/routes/auth/update-password/+page.svelte deleted file mode 100644 index aabfc69..0000000 --- a/src/routes/auth/update-password/+page.svelte +++ /dev/null @@ -1,81 +0,0 @@ - - - - Update Password - - -
-

Update Password

-
- - - -
-
From e4ef3760646f13e3ff76fd603a0680ea80a135ec Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:30:06 +0000 Subject: [PATCH 04/47] feat(#53): Add cookie and privacy policies --- src/routes/(policies)/cookies/+page.svelte | 85 ++++++++++++++ src/routes/(policies)/privacy/+page.svelte | 125 +++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 src/routes/(policies)/cookies/+page.svelte create mode 100644 src/routes/(policies)/privacy/+page.svelte diff --git a/src/routes/(policies)/cookies/+page.svelte b/src/routes/(policies)/cookies/+page.svelte new file mode 100644 index 0000000..511b4e0 --- /dev/null +++ b/src/routes/(policies)/cookies/+page.svelte @@ -0,0 +1,85 @@ +
+

Cookie Policy

+

Effective Date: [Insert Date]

+ +
+

1. Introduction

+

+ This Cookie Policy explains how [Your Company/Website Name] ("we", "us", or "our") uses + cookies and similar technologies to collect and store information when you visit our website. +

+
+ +
+

2. What Are Cookies?

+

+ Cookies are small text files stored on your device (computer, tablet, smartphone) by your web + browser when you visit a website. They help us remember your preferences and improve your + experience on our website. +

+
+ +
+

3. Types of Cookies We Use

+ +
+ +
+

4. How We Use Cookies

+

+ We use cookies to improve the functionality and performance of our website, analyse visitor + usage, and provide personalised content and advertisements. This helps us improve our website + and better understand our audience. +

+
+ +
+

5. Managing Your Cookie Preferences

+

+ You have the right to accept or reject cookies. You can manage your cookie preferences by + adjusting your browser settings. However, disabling certain cookies may affect your experience + on our website. For specific instructions on managing cookies, please consult your browser's + help documentation. +

+
+ +
+

6. Changes to Our Cookie Policy

+

+ We may update this Cookie Policy from time to time. Significant changes will be posted on this + page. We encourage you to review this policy periodically to stay informed about our use of + cookies. +

+
+ +
+

Contact Us

+

+ For questions about our Cookie Policy, please contact us at [Your Contact Email] + or visit our Contact Page. +

+
+
diff --git a/src/routes/(policies)/privacy/+page.svelte b/src/routes/(policies)/privacy/+page.svelte new file mode 100644 index 0000000..6901837 --- /dev/null +++ b/src/routes/(policies)/privacy/+page.svelte @@ -0,0 +1,125 @@ +
+

Privacy Policy

+

Effective Date: [Insert Date]

+ +
+

1. Introduction

+

+ This Privacy Policy explains how we at [Your Company/Website Name] collect, use, and protect + your personal information when you visit our website. We are committed to safeguarding your + data and complying with relevant data protection laws, including the General Data Protection + Regulation (GDPR). +

+
+ +
+

2. Information We Collect

+

+ We collect personal and non-personal information to provide our services and enhance your + experience. +

+ +
+ +
+

3. How We Use Your Information

+ +
+ +
+

4. Sharing Information with Third Parties

+

+ We work with trusted third-party providers to offer secure services, such as payment + processing and authentication. +

+
+

Stripe

+

+ For payment processing, we use Stripe. Please refer to their + Privacy Policy for details. +

+
+
+

Supabase

+

+ For database and authentication, we use Supabase. See their + Privacy Policy for more information. +

+
+
+ +
+

5. Cookies and Tracking

+

+ We use cookies to provide essential site functionality, analytics, and improved user + experience. By continuing to use our site, you consent to our use of cookies. You can manage + your cookie preferences in your browser settings. For more details, please refer to our Cookie Policy. +

+
+ +
+

6. Your Data Rights

+

+ Under GDPR, you have the right to access, correct, delete, and restrict the processing of your + personal data. To make a request, please contact us at [Your Contact Email]. +

+
+ +
+

7. Data Security

+

+ We take reasonable steps to protect your information through encryption and secure access + protocols. However, no online service can be 100% secure. +

+
+ +
+

8. Policy Updates

+

+ We may update this policy periodically. Significant changes will be posted on this page, so + please review it regularly. +

+
+ +
+

Contact Us

+

+ If you have questions about this Privacy Policy, please contact us at [Your Contact Email] + or visit our Contact Page. +

+
+
From 90875517da555a256681d717942a880080fad4ed Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:17:10 +0000 Subject: [PATCH 05/47] docs(#55): Add clearer migration commands --- docs/database-setup.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/database-setup.md b/docs/database-setup.md index 00f6553..227279a 100644 --- a/docs/database-setup.md +++ b/docs/database-setup.md @@ -79,7 +79,13 @@ There are two options to create a migration file: ## Applying migrations -Reset database in cloud +To do so without resetting the database, you can use the following command: + +`npx supabase migration up` + +And for the database in the cloud: + +`npx supabase db push` > Prerequisite login using `npx supabase login` From 33409bc3753aac68dbc80a38354a9f6045a90847 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:20:23 +0000 Subject: [PATCH 06/47] feat(#55): Enable deleting account and anonymising purchases and subscriptions (no email sending) --- src/lib/types/supabase.d.ts | 26 +++++++++ src/lib/utils/supabase/admin.ts | 53 ++++++++++++++++++- src/routes/account/+page.server.ts | 24 +++++++-- src/routes/account/+page.svelte | 16 ++++++ src/routes/api/delete-account/+server.ts | 18 +++++++ .../20241029193900_account_deletion.sql | 46 ++++++++++++++++ 6 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 src/routes/api/delete-account/+server.ts create mode 100644 supabase/migrations/20241029193900_account_deletion.sql diff --git a/src/lib/types/supabase.d.ts b/src/lib/types/supabase.d.ts index d774c18..199b7cd 100644 --- a/src/lib/types/supabase.d.ts +++ b/src/lib/types/supabase.d.ts @@ -34,6 +34,32 @@ export type Database = { } public: { Tables: { + account_deletion_requests: { + Row: { + requested_at: string + token: string + user_id: string + } + Insert: { + requested_at?: string + token: string + user_id: string + } + Update: { + requested_at?: string + token?: string + user_id?: string + } + Relationships: [ + { + foreignKeyName: "account_deletion_requests_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "users" + referencedColumns: ["id"] + }, + ] + } credit_transactions: { Row: { created_at: string diff --git a/src/lib/utils/supabase/admin.ts b/src/lib/utils/supabase/admin.ts index 4052695..8e324fd 100644 --- a/src/lib/utils/supabase/admin.ts +++ b/src/lib/utils/supabase/admin.ts @@ -480,6 +480,55 @@ const getProductById = async (productId: string) => { return product as Product; }; +const requestAccountDeletion = async (userId: string, userEmail: string, baseUrl: string) => { + const token = crypto.randomUUID(); + + const { error: insertError } = await supabaseAdmin.from('account_deletion_requests').upsert({ + user_id: userId, + token, + requested_at: new Date().toISOString() + }); + + if (insertError) throw new Error(`Account deletion request failed: ${insertError.message}`); + + const deletionLink = `${baseUrl}/api/delete-account?token=${token}`; + + // Send deletion email using Brevo + // try { + // await brevoClient.sendTransactionalEmail({ + // to: [{ email: userEmail }], + // subject: 'Confirm Account Deletion', + // htmlContent: `

Click here to confirm your account deletion.

` + // }); + // console.log('Deletion email sent successfully.'); + // } catch (emailError) { + // console.error('Failed to send deletion email:', emailError); + // throw new Error('Failed to send confirmation email.'); + // } + console.log('Deletion email would be sent successfully.', deletionLink); +}; + +const deleteAccount = async (token: string) => { + // Verify the token + const { data: deletionRequest, error } = await supabaseAdmin + .from('account_deletion_requests') + .select('user_id') + .eq('token', token) + .single(); + + if (error || !deletionRequest) { + return new Response('Invalid or expired token', { status: 400 }); + } + + // N.B. The SQL here will either cascade delete or nullify the foreign key constraints depending on the table, to enable anonymisation + const { error: deletionError } = await supabaseAdmin.auth.admin.deleteUser( + deletionRequest.user_id + ); + if (deletionError) { + throw new Error(`Failed to delete user: ${deletionError.message}`); + } +}; + export { upsertProductRecord, upsertPriceRecord, @@ -495,5 +544,7 @@ export { getActiveProductsWithPrices, getProductById, upsertCustomerToSupabase, - getStripeCustomerId + getStripeCustomerId, + requestAccountDeletion, + deleteAccount }; diff --git a/src/routes/account/+page.server.ts b/src/routes/account/+page.server.ts index d839775..9e420a9 100644 --- a/src/routes/account/+page.server.ts +++ b/src/routes/account/+page.server.ts @@ -1,5 +1,6 @@ import { fail, redirect } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; +import { requestAccountDeletion } from '$lib/utils/supabase/admin'; export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => { const { session } = await safeGetSession(); @@ -18,6 +19,16 @@ export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession }; export const actions: Actions = { + delete: async ({ url, locals: { safeGetSession } }) => { + const { session } = await safeGetSession(); + const baseUrl = url.origin; + if (session) { + await requestAccountDeletion(session.user.id, session.user.email!, baseUrl); + return {}; + } else { + redirect(303, '/'); + } + }, update: async ({ request, locals: { supabase, safeGetSession } }) => { const formData = await request.formData(); const name = formData.get('name') as string; @@ -27,20 +38,23 @@ export const actions: Actions = { if (!session) { return fail(401, { name }); } - const { error } = await supabase.from('users').update({ - name: name, - }).eq('id', session?.user.id); + const { error } = await supabase + .from('users') + .update({ + name: name + }) + .eq('id', session?.user.id); console.error(error); if (error) { return fail(500, { - name, + name }); } return { - name, + name }; }, signout: async ({ locals: { supabase, safeGetSession } }) => { diff --git a/src/routes/account/+page.svelte b/src/routes/account/+page.svelte index d133724..51ba98e 100644 --- a/src/routes/account/+page.svelte +++ b/src/routes/account/+page.svelte @@ -26,6 +26,16 @@ update(); }; }; + + const handleAccountDeletion: SubmitFunction = () => { + loading = true; + if (session.user && session.user.email) { + alert('Check your email to confirm account deletion.'); + } + return async () => { + loading = false; + }; + };
@@ -56,4 +66,10 @@
+ +
+
+ +
+
diff --git a/src/routes/api/delete-account/+server.ts b/src/routes/api/delete-account/+server.ts new file mode 100644 index 0000000..0bfac7b --- /dev/null +++ b/src/routes/api/delete-account/+server.ts @@ -0,0 +1,18 @@ +import type { RequestHandler } from '@sveltejs/kit'; +import { deleteAccount } from '$lib/utils/supabase/admin'; + +export const GET: RequestHandler = async ({ url }) => { + const token = url.searchParams.get('token'); + if (!token) { + return new Response('Invalid token', { status: 400 }); + } + + try { + await deleteAccount(token); + } catch (error) { + console.error(error); + return new Response('Failed to delete account', { status: 500 }); + } + + return new Response('Account deleted successfully', { status: 200 }); +}; diff --git a/supabase/migrations/20241029193900_account_deletion.sql b/supabase/migrations/20241029193900_account_deletion.sql new file mode 100644 index 0000000..bd2290d --- /dev/null +++ b/supabase/migrations/20241029193900_account_deletion.sql @@ -0,0 +1,46 @@ +create table account_deletion_requests ( + user_id uuid references auth.users not null primary key, + token text not null, + requested_at timestamp with time zone default now() not null +); + +alter table account_deletion_requests enable row level security; +create policy "Can view own deletion request data" on account_deletion_requests + for select using (auth.uid() = user_id); +create policy "Can insert own deletion request data" on account_deletion_requests + for insert with check (auth.uid() = user_id); + +ALTER TABLE customers +DROP CONSTRAINT IF EXISTS customers_id_fkey; +ALTER TABLE customers +ADD CONSTRAINT customers_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE subscriptions +DROP CONSTRAINT IF EXISTS subscriptions_user_id_fkey; +ALTER TABLE subscriptions +ADD CONSTRAINT subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +ALTER TABLE purchases +DROP CONSTRAINT IF EXISTS purchases_user_id_fkey; +ALTER TABLE purchases +ADD CONSTRAINT purchases_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +ALTER TABLE user_credits +DROP CONSTRAINT IF EXISTS user_credits_user_id_fkey; +ALTER TABLE user_credits +ADD CONSTRAINT user_credits_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE credit_transactions +DROP CONSTRAINT IF EXISTS credit_transactions_user_id_fkey; +ALTER TABLE credit_transactions +ADD CONSTRAINT credit_transactions_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL; + +ALTER TABLE account_deletion_requests +DROP CONSTRAINT IF EXISTS account_deletion_requests_user_id_fkey; +ALTER TABLE account_deletion_requests +ADD CONSTRAINT account_deletion_requests_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + +ALTER TABLE users +DROP CONSTRAINT IF EXISTS users_id_fkey; +ALTER TABLE users +ADD CONSTRAINT users_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id) ON DELETE CASCADE; From c4865337ee9f4dcd8070b1a66ec3a59e700c1f20 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:20:34 +0000 Subject: [PATCH 07/47] chore(*): Remove redundant example file --- src/routes/api/somebackendfunction/+server.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/routes/api/somebackendfunction/+server.ts diff --git a/src/routes/api/somebackendfunction/+server.ts b/src/routes/api/somebackendfunction/+server.ts deleted file mode 100644 index 5779c2f..0000000 --- a/src/routes/api/somebackendfunction/+server.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { json } from '@sveltejs/kit'; - -export const GET = async () => { - return json(await getSomeData()); -}; - -async function getSomeData() { - // Do your async fetching here -} From 464f3650858d2d8329d3c0f0f19a458be052755e Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 29 Oct 2024 22:10:21 +0000 Subject: [PATCH 08/47] feat(#55): Enable sending account deletion emails through brevo npm package --- .env.example | 5 +- package.json | 1 + pnpm-lock.yaml | 309 ++++++++++++++++++++++++++++++++ src/lib/utils/brevo/email.ts | 31 ++++ src/lib/utils/supabase/admin.ts | 24 +-- 5 files changed, 357 insertions(+), 13 deletions(-) create mode 100644 src/lib/utils/brevo/email.ts diff --git a/.env.example b/.env.example index 6d0104e..b0b9619 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,7 @@ PUBLIC_SUPABASE_ANON_KEY="" SUPABASE_SERVICE_ROLE_KEY="" STRIPE_SECRET_KEY="" STRIPE_ENDPOINT_SECRET="" -VITE_PRODUCT_ID_EXAMPLEPRODUCT="" \ No newline at end of file +VITE_PRODUCT_ID_EXAMPLEPRODUCT="" +VITE_BREVO_API_KEY="" +VITE_BREVO_SENDER_EMAIL="noreply@example.com" +VITE_BREVO_SENDER_NAME="No Reply" \ No newline at end of file diff --git a/package.json b/package.json index 6dd387c..fcbe125 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "type": "module", "dependencies": { + "@getbrevo/brevo": "^2.2.0", "@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.44.2", "stripe": "^15.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 437f249..eeb6b37 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@getbrevo/brevo': + specifier: ^2.2.0 + version: 2.2.0 '@supabase/ssr': specifier: ^0.4.0 version: 0.4.1(@supabase/supabase-js@2.45.4) @@ -865,6 +868,9 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@getbrevo/brevo@2.2.0': + resolution: {integrity: sha512-mNCkXtgqn6jqLglAx1JzZcTj53kBZ5dK9Yd6zVuEyXAvhz68f5Ps6dSJar9HvkHH0Lfr3NrqW76xurjcoxqhIg==} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1339,9 +1345,19 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -1357,6 +1373,12 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + + aws4@1.13.2: + resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -1400,6 +1422,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + bin-links@4.0.4: resolution: {integrity: sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1411,6 +1436,9 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1459,6 +1487,9 @@ packages: caniuse-lite@1.0.30001660: resolution: {integrity: sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==} + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -1512,6 +1543,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -1547,6 +1582,9 @@ packages: core-js-compat@3.38.1: resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==} + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1575,6 +1613,10 @@ packages: resolution: {integrity: sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==} engines: {node: '>=16.9.0'} + dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -1634,6 +1676,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -1669,6 +1715,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} @@ -1805,6 +1854,13 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1871,6 +1927,13 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + + form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -1918,6 +1981,9 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} + getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -1973,6 +2039,15 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + har-schema@2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + + har-validator@5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -2003,6 +2078,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + http-signature@1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + https-proxy-agent@7.0.5: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} @@ -2152,6 +2231,9 @@ packages: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -2161,6 +2243,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -2180,6 +2265,9 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true @@ -2204,6 +2292,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -2216,6 +2307,10 @@ packages: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} + jsprim@1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2296,6 +2391,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -2397,6 +2500,9 @@ packages: resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + oauth-sign@0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2468,6 +2574,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} @@ -2606,6 +2715,9 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -2624,6 +2736,10 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} + qs@6.5.3: + resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2677,6 +2793,11 @@ packages: resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} hasBin: true + request@2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -2693,6 +2814,9 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rewire@7.0.0: + resolution: {integrity: sha512-DyyNyzwMtGYgu0Zl/ya0PR/oaunM+VuCuBxCuhYJHHaV0V+YvYa3bBGxb5OZ71vndgmp1pYY8F4YOwQo1siRGw==} + rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -2735,6 +2859,9 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sander@0.5.1: resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} @@ -2827,6 +2954,11 @@ packages: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead + sshpk@1.18.0: + resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} + engines: {node: '>=0.10.0'} + hasBin: true + streamx@2.20.1: resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==} @@ -3042,6 +3174,10 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3066,6 +3202,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -3148,6 +3287,15 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + + verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + vite-plugin-pwa@0.20.5: resolution: {integrity: sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==} engines: {node: '>=16.0.0'} @@ -4206,6 +4354,14 @@ snapshots: '@eslint/js@8.57.1': {} + '@getbrevo/brevo@2.2.0': + dependencies: + bluebird: 3.7.2 + request: 2.88.2 + rewire: 7.0.0 + transitivePeerDependencies: + - supports-color + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -4739,8 +4895,16 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@1.0.0: {} + async@3.2.6: {} + asynckit@0.4.0: {} + at-least-node@1.0.0: {} autoprefixer@10.4.20(postcss@8.4.47): @@ -4757,6 +4921,10 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 + aws-sign2@0.7.0: {} + + aws4@1.13.2: {} + axobject-query@4.1.0: {} b4a@1.6.6: {} @@ -4813,6 +4981,10 @@ snapshots: base64-js@1.5.1: {} + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + bin-links@4.0.4: dependencies: cmd-shim: 6.0.3 @@ -4828,6 +5000,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + bluebird@3.7.2: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -4875,6 +5049,8 @@ snapshots: caniuse-lite@1.0.30001660: {} + caseless@0.12.0: {} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -4938,6 +5114,10 @@ snapshots: colorette@2.0.20: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@12.1.0: {} commander@2.20.3: {} @@ -4960,6 +5140,8 @@ snapshots: dependencies: browserslist: 4.23.3 + core-util-is@1.0.2: {} + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -4991,6 +5173,10 @@ snapshots: transitivePeerDependencies: - postcss + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + data-uri-to-buffer@4.0.1: {} data-view-buffer@1.0.1: @@ -5050,6 +5236,8 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + detect-indent@6.1.0: {} detect-libc@2.0.3: {} @@ -5076,6 +5264,11 @@ snapshots: eastasianwidth@0.2.0: {} + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + ejs@3.1.10: dependencies: jake: 10.9.2 @@ -5305,6 +5498,10 @@ snapshots: expand-template@2.0.3: {} + extend@3.0.2: {} + + extsprintf@1.3.0: {} + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -5372,6 +5569,14 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + forever-agent@0.6.1: {} + + form-data@2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -5421,6 +5626,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + github-from-package@0.0.0: {} glob-parent@5.1.2: @@ -5489,6 +5698,13 @@ snapshots: graphemer@1.4.0: {} + har-schema@2.0.0: {} + + har-validator@5.1.5: + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -5511,6 +5727,12 @@ snapshots: dependencies: function-bind: 1.1.2 + http-signature@1.2.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.18.0 + https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 @@ -5643,6 +5865,8 @@ snapshots: dependencies: which-typed-array: 1.1.15 + is-typedarray@1.0.0: {} + is-weakref@1.0.2: dependencies: call-bind: 1.0.7 @@ -5651,6 +5875,8 @@ snapshots: isexe@2.0.0: {} + isstream@0.1.2: {} + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -5672,6 +5898,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@0.1.1: {} + jsesc@0.5.0: {} jsesc@2.5.2: {} @@ -5686,6 +5914,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-safe@5.0.1: {} + json5@2.2.3: {} jsonfile@6.1.0: @@ -5696,6 +5926,13 @@ snapshots: jsonpointer@5.0.1: {} + jsprim@1.4.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -5760,6 +5997,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mimic-response@3.1.0: {} min-indent@1.0.1: {} @@ -5835,6 +6078,8 @@ snapshots: npm-normalize-package-bin@3.0.1: {} + oauth-sign@0.9.0: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -5901,6 +6146,8 @@ snapshots: path-type@4.0.0: {} + performance-now@2.1.0: {} + periscopic@3.1.0: dependencies: '@types/estree': 1.0.5 @@ -6012,6 +6259,8 @@ snapshots: pretty-bytes@6.1.1: {} + psl@1.9.0: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -6035,6 +6284,8 @@ snapshots: dependencies: side-channel: 1.0.6 + qs@6.5.3: {} + queue-microtask@1.2.3: {} queue-tick@1.0.1: {} @@ -6098,6 +6349,29 @@ snapshots: dependencies: jsesc: 0.5.0 + request@2.88.2: + dependencies: + aws-sign2: 0.7.0 + aws4: 1.13.2 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.3 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + require-from-string@2.0.2: {} resolve-from@4.0.0: {} @@ -6110,6 +6384,12 @@ snapshots: reusify@1.0.4: {} + rewire@7.0.0: + dependencies: + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + rimraf@2.7.1: dependencies: glob: 7.2.3 @@ -6171,6 +6451,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 + safer-buffer@2.1.2: {} + sander@0.5.1: dependencies: es6-promise: 3.3.1 @@ -6280,6 +6562,18 @@ snapshots: sourcemap-codec@1.4.8: {} + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + streamx@2.20.1: dependencies: fast-fifo: 1.3.2 @@ -6576,6 +6870,11 @@ snapshots: totalist@3.0.1: {} + tough-cookie@2.5.0: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + tr46@0.0.3: {} tr46@1.0.1: @@ -6596,6 +6895,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + tweetnacl@0.14.5: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -6684,6 +6985,14 @@ snapshots: util-deprecate@1.0.2: {} + uuid@3.4.0: {} + + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + vite-plugin-pwa@0.20.5(@vite-pwa/assets-generator@0.2.6)(vite@5.4.6(@types/node@22.5.5)(terser@5.33.0))(workbox-build@7.1.1)(workbox-window@7.1.0): dependencies: debug: 4.3.7 diff --git a/src/lib/utils/brevo/email.ts b/src/lib/utils/brevo/email.ts new file mode 100644 index 0000000..7980c7c --- /dev/null +++ b/src/lib/utils/brevo/email.ts @@ -0,0 +1,31 @@ +import brevo from '@getbrevo/brevo'; +import { + VITE_BREVO_API_KEY, + VITE_BREVO_SENDER_EMAIL, + VITE_BREVO_SENDER_NAME +} from '$env/static/private'; + +const apiInstance = new brevo.TransactionalEmailsApi(); +const apiKey = apiInstance.authentications['apiKey']; +apiKey.apiKey = VITE_BREVO_API_KEY; + +const sendEmail = async (subject: string, htmlContentString: string, toAddress: string) => { + const sendSmtpEmail = new brevo.SendSmtpEmail(); + + sendSmtpEmail.subject = subject; + sendSmtpEmail.htmlContent = htmlContentString; + sendSmtpEmail.sender = { name: VITE_BREVO_SENDER_NAME, email: VITE_BREVO_SENDER_EMAIL }; + sendSmtpEmail.to = [{ email: toAddress, name: toAddress }]; + sendSmtpEmail.replyTo = { email: VITE_BREVO_SENDER_EMAIL, name: VITE_BREVO_SENDER_NAME }; + + apiInstance.sendTransacEmail(sendSmtpEmail).then( + function (data) { + console.log('API called successfully. Returned data: ' + JSON.stringify(data)); + }, + function (error) { + console.error(error); + } + ); +}; + +export { sendEmail }; diff --git a/src/lib/utils/supabase/admin.ts b/src/lib/utils/supabase/admin.ts index 8e324fd..e6ac95a 100644 --- a/src/lib/utils/supabase/admin.ts +++ b/src/lib/utils/supabase/admin.ts @@ -4,6 +4,7 @@ import type { Database, Tables, TablesInsert } from '$lib/types/supabase'; import { PUBLIC_SUPABASE_URL } from '$env/static/public'; import { SUPABASE_SERVICE_ROLE_KEY } from '$env/static/private'; import { stripe as stripeClient } from '$lib/utils/stripe'; +import { sendEmail } from '../brevo/email'; const toDateTime = (secs: number) => { const t = new Date(+0); // Unix epoch start. @@ -494,18 +495,17 @@ const requestAccountDeletion = async (userId: string, userEmail: string, baseUrl const deletionLink = `${baseUrl}/api/delete-account?token=${token}`; // Send deletion email using Brevo - // try { - // await brevoClient.sendTransactionalEmail({ - // to: [{ email: userEmail }], - // subject: 'Confirm Account Deletion', - // htmlContent: `

Click here to confirm your account deletion.

` - // }); - // console.log('Deletion email sent successfully.'); - // } catch (emailError) { - // console.error('Failed to send deletion email:', emailError); - // throw new Error('Failed to send confirmation email.'); - // } - console.log('Deletion email would be sent successfully.', deletionLink); + try { + await sendEmail( + 'Confirm Account Deletion', + `

Click here to confirm your account deletion.

`, + userEmail + ); + console.log('Deletion email sent successfully.'); + } catch (emailError) { + console.error('Failed to send deletion email:', emailError); + throw new Error('Failed to send confirmation email.'); + } }; const deleteAccount = async (token: string) => { From cb1321825d1eb4e1a538031dee923d03ba3dc693 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:32:45 +0000 Subject: [PATCH 09/47] feat(#53): Add policy links to footer --- src/routes/+layout.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 9e50d24..7dfb5cd 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -195,8 +195,12 @@ d="M22.672 15.226l-2.432.811.841 2.515c.33 1.019-.209 2.127-1.23 2.456-1.15.325-2.148-.321-2.463-1.226l-.84-2.518-5.013 1.677.84 2.517c.391 1.203-.434 2.542-1.831 2.542-.88 0-1.601-.564-1.86-1.314l-.842-2.516-2.431.809c-1.135.328-2.145-.317-2.463-1.229-.329-1.018.211-2.127 1.231-2.456l2.432-.809-1.621-4.823-2.432.808c-1.355.384-2.558-.59-2.558-1.839 0-.817.509-1.582 1.327-1.846l2.433-.809-.842-2.515c-.33-1.02.211-2.129 1.232-2.458 1.02-.329 2.13.209 2.461 1.229l.842 2.515 5.011-1.677-.839-2.517c-.403-1.238.484-2.553 1.843-2.553.819 0 1.585.509 1.85 1.326l.841 2.517 2.431-.81c1.02-.33 2.131.211 2.461 1.229.332 1.018-.21 2.126-1.23 2.456l-2.433.809 1.622 4.823 2.433-.809c1.242-.401 2.557.484 2.557 1.838 0 .819-.51 1.583-1.328 1.847m-8.992-6.428l-5.01 1.675 1.619 4.828 5.011-1.674-1.62-4.829z" > -

Copyright © 2024 - All right reserved

+

Copyright © 2024 - All rights reserved

+
+ Privacy Policy + Cookie Policy +