diff --git a/apps/web/supabase/tests/database/create-team-accounts.test.sql b/apps/web/supabase/tests/database/create-team-accounts.test.sql index 77236bebb..9f7d1cb12 100644 --- a/apps/web/supabase/tests/database/create-team-accounts.test.sql +++ b/apps/web/supabase/tests/database/create-team-accounts.test.sql @@ -3,76 +3,124 @@ begin; create extension "basejump-supabase_test_helpers" version '0.0.6'; select - no_plan(); + no_plan(); --- we insert a user into auth.users and return the id into user_id to use select - tests.create_supabase_user('test1', 'test1@test.com'); + tests.create_supabase_user('test1', 'test1@test.com'); select - tests.create_supabase_user('test2'); + tests.create_supabase_user('test2'); -- Create an team account select - tests.authenticate_as('test1'); + tests.authenticate_as('test1'); select - public.create_team_account('Test'); + public.create_team_account('Test'); select - row_eq($$ - select - primary_owner_user_id, is_personal_account, slug, name from - makerkit.get_account_by_slug('test') $$, row - (tests.get_supabase_uid('test1'), false, 'test'::text, - 'Test'::varchar), 'Users can create a team account'); + row_eq($$ + select + primary_owner_user_id, is_personal_account, slug, name + from makerkit.get_account_by_slug('test') $$, + row (tests.get_supabase_uid('test1'), false, + 'test'::text, 'Test'::varchar), + 'Users can create a team account'); -- Should be the primary owner of the team account by default select - row_eq($$ - select - account_role from public.accounts_memberships - where - account_id =( - select - id - from public.accounts - where - slug = 'test') - and user_id = tests.get_supabase_uid('test1') $$, row - ('owner'::varchar), 'The primary owner should have the owner role for the team account'); + row_eq($$ + select + account_role from public.accounts_memberships + where + account_id =( + select + id + from public.accounts + where + slug = 'test') + and user_id = tests.get_supabase_uid('test1') + $$, row ('owner'::varchar), + 'The primary owner should have the owner role for the team account'); -- Should be able to see the team account select - isnt_empty($$ - select - * from public.accounts - where - primary_owner_user_id = tests.get_supabase_uid('test1') $$, 'The primary owner should be able to see the team account'); + isnt_empty($$ + select + * from public.accounts + where + primary_owner_user_id = + tests.get_supabase_uid('test1') $$, + 'The primary owner should be able to see the team account'); -- Others should not be able to see the team account select - tests.authenticate_as('test2'); + tests.authenticate_as('test2'); select - is_empty($$ - select - * from public.accounts - where - primary_owner_user_id = tests.get_supabase_uid('test1') $$, 'Other users should not be able to see the team account'); + is_empty($$ + select + * from public.accounts + where + primary_owner_user_id = + tests.get_supabase_uid('test1') $$, + 'Other users should not be able to see the team account'); -- should not have any role for the team account select - is (public.has_role_on_account(( - select - id - from makerkit.get_account_by_slug('test'))), - false, - 'Foreign users should not have any role for the team account'); + is (public.has_role_on_account(( + select + id + from makerkit.get_account_by_slug('test'))), + false, + 'Foreign users should not have any role for the team account'); + +-- enforcing a single team account per owner using a trigger when +-- inserting a team +set local role postgres; + +create or replace function kit.single_account_per_owner() + returns trigger + as $$ +declare + total_accounts int; +begin + select + count(id) + from + public.accounts + where + primary_owner_user_id = auth.uid() into total_accounts; + + if total_accounts > 0 then + raise exception 'User can only own 1 account'; + end if; + + return NEW; + +end +$$ +language plpgsql +set search_path = ''; + +-- trigger to protect account fields +create trigger single_account_per_owner + before insert on public.accounts for each row + execute function kit.single_account_per_owner(); + +-- Create an team account +select + tests.authenticate_as('test1'); select - * + throws_ok( + $$ select + public.create_team_account('Test2') $$, 'User can only own 1 account'); + +select + * from - finish(); + finish(); rollback; diff --git a/turbo/generators/templates/env/generator.ts b/turbo/generators/templates/env/generator.ts index 7bbe57cae..c88130953 100644 --- a/turbo/generators/templates/env/generator.ts +++ b/turbo/generators/templates/env/generator.ts @@ -77,17 +77,16 @@ export function createEnvironmentVariablesGenerator( default: allVariables.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'en', }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_AUTH_PASSWORD', message: 'Do you want to use email/password authentication?', - default: allVariables.NEXT_PUBLIC_DEFAULT_LOCALE ?? 'true', + default: getBoolean(allVariables.NEXT_PUBLIC_AUTH_PASSWORD, true), }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_AUTH_MAGIC_LINK', - message: - 'Do you want to use magic link authentication? (leave empty for false)', - default: allVariables.NEXT_PUBLIC_AUTH_MAGIC_LINK ?? 'false', + message: 'Do you want to use magic link authentication?', + default: getBoolean(allVariables.NEXT_PUBLIC_AUTH_MAGIC_LINK, false), }, { type: 'input', @@ -96,63 +95,85 @@ export function createEnvironmentVariablesGenerator( default: allVariables.CONTACT_EMAIL, }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_ENABLE_THEME_TOGGLE', message: 'Do you want to enable the theme toggle?', - default: allVariables.NEXT_PUBLIC_ENABLE_THEME_TOGGLE ?? 'true', + default: getBoolean(allVariables.NEXT_PUBLIC_ENABLE_THEME_TOGGLE, true), }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION', message: 'Do you want to enable personal account deletion?', - default: - allVariables.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION ?? 'true', + default: getBoolean( + allVariables.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION, + true, + ), }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING', message: 'Do you want to enable personal account billing?', - default: - allVariables.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING ?? 'true', + default: getBoolean( + allVariables.NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING, + true, + ), }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS', message: 'Do you want to enable team accounts?', - default: allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS ?? 'true', + default: getBoolean( + allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS, + true, + ), }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNT_DELETION', message: 'Do you want to enable team account deletion?', - default: - allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNT_DELETION ?? 'true', + default: getBoolean( + allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNT_DELETION, + true, + ), }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING', message: 'Do you want to enable team account billing?', - default: - allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING ?? 'true', + default: getBoolean( + allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING, + true, + ), }, { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION', message: 'Do you want to enable team account creation?', - default: - allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION ?? 'true', + default: getBoolean( + allVariables.NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION, + true, + ), }, { - type: 'input', - name: 'values.NEXT_PUBLIC_REALTIME_NOTIFICATIONS', - message: 'Do you want to enable realtime notifications?', - default: allVariables.NEXT_PUBLIC_REALTIME_NOTIFICATIONS ?? 'true', - }, - { - type: 'input', + type: 'confirm', name: 'values.NEXT_PUBLIC_ENABLE_NOTIFICATIONS', - message: 'Do you want to enable email notifications?', - default: allVariables.NEXT_PUBLIC_ENABLE_NOTIFICATIONS ?? 'true', + message: + 'Do you want to enable notifications? If not - we will hide the notifications bell from the UI.', + default: getBoolean( + allVariables.NEXT_PUBLIC_ENABLE_NOTIFICATIONS, + true, + ), + }, + { + when: (answers) => answers.values.NEXT_PUBLIC_ENABLE_NOTIFICATIONS, + type: 'confirm', + name: 'values.NEXT_PUBLIC_REALTIME_NOTIFICATIONS', + message: + 'Do you want to enable realtime notifications? If yes, we will enable the realtime notifications from Supabase. If not - updated will be fetched lazily.', + default: getBoolean( + allVariables.NEXT_PUBLIC_REALTIME_NOTIFICATIONS, + false, + ), }, { type: 'input', @@ -279,11 +300,35 @@ export function createEnvironmentVariablesGenerator( }, { when: (answers) => answers.values.MAILER_PROVIDER === 'nodemailer', - type: 'input', + type: 'confirm', name: 'values.EMAIL_TLS', message: 'Do you want to enable TLS for your emails?', - default: 'true', + default: getBoolean(allVariables.EMAIL_TLS, true), + }, + { + type: 'confirm', + name: 'captcha', + message: + 'Do you want to enable Cloudflare Captcha protection for the Auth endpoints?', + }, + { + when: (answers) => answers.captcha, + type: 'input', + name: 'values.NEXT_PUBLIC_CAPTCHA_SITE_KEY', + message: + 'What is the Cloudflare Captcha site key? NB: this is the PUBLIC key!', + }, + { + when: (answers) => answers.captcha, + type: 'input', + name: 'values.CAPTCHA_SECRET_TOKEN', + message: + 'What is the Cloudflare Captcha secret key? NB: this is the PRIVATE key!', }, ], }); } + +function getBoolean(value: string | undefined, defaultValue: boolean) { + return value === 'true' ? true : defaultValue; +}