diff --git a/apps/web/app/(dashboard)/home/[account]/billing/layout.tsx b/apps/web/app/(dashboard)/home/[account]/billing/layout.tsx
index 36bd91250..e6657eb86 100644
--- a/apps/web/app/(dashboard)/home/[account]/billing/layout.tsx
+++ b/apps/web/app/(dashboard)/home/[account]/billing/layout.tsx
@@ -2,8 +2,8 @@ import { notFound } from 'next/navigation';
import featureFlagsConfig from '~/config/feature-flags.config';
-function OrganizationAccountBillingLayout(props: React.PropsWithChildren) {
- const isEnabled = featureFlagsConfig.enableOrganizationBilling;
+function TeamAccountBillingLayout(props: React.PropsWithChildren) {
+ const isEnabled = featureFlagsConfig.enableTeamAccountBilling;
if (!isEnabled) {
notFound();
@@ -12,4 +12,4 @@ function OrganizationAccountBillingLayout(props: React.PropsWithChildren) {
return <>{props.children}>;
}
-export default OrganizationAccountBillingLayout;
+export default TeamAccountBillingLayout;
diff --git a/apps/web/app/(dashboard)/home/[account]/billing/page.tsx b/apps/web/app/(dashboard)/home/[account]/billing/page.tsx
index 75a9ac0ae..bff7a8661 100644
--- a/apps/web/app/(dashboard)/home/[account]/billing/page.tsx
+++ b/apps/web/app/(dashboard)/home/[account]/billing/page.tsx
@@ -21,7 +21,7 @@ interface Params {
};
}
-async function OrganizationAccountBillingPage({ params }: Params) {
+async function TeamAccountBillingPage({ params }: Params) {
const workspace = await loadTeamWorkspace(params.account);
const accountId = workspace.account.id;
const [subscription, customerId] = await loadAccountData(accountId);
@@ -60,7 +60,7 @@ async function OrganizationAccountBillingPage({ params }: Params) {
);
}
-export default withI18n(OrganizationAccountBillingPage);
+export default withI18n(TeamAccountBillingPage);
async function loadAccountData(accountId: string) {
const client = getSupabaseServerComponentClient();
diff --git a/apps/web/app/(dashboard)/home/[account]/layout.tsx b/apps/web/app/(dashboard)/home/[account]/layout.tsx
index 16d0c6df1..5f88f62b8 100644
--- a/apps/web/app/(dashboard)/home/[account]/layout.tsx
+++ b/apps/web/app/(dashboard)/home/[account]/layout.tsx
@@ -13,7 +13,7 @@ interface Params {
account: string;
}
-function OrganizationWorkspaceLayout({
+function TeamWorkspaceLayout({
children,
params,
}: React.PropsWithChildren<{
@@ -48,7 +48,7 @@ function OrganizationWorkspaceLayout({
);
}
-export default withI18n(OrganizationWorkspaceLayout);
+export default withI18n(TeamWorkspaceLayout);
function getUIStateCookies() {
return {
diff --git a/apps/web/app/(dashboard)/home/[account]/members/page.tsx b/apps/web/app/(dashboard)/home/[account]/members/page.tsx
index 42d73b816..36977dd58 100644
--- a/apps/web/app/(dashboard)/home/[account]/members/page.tsx
+++ b/apps/web/app/(dashboard)/home/[account]/members/page.tsx
@@ -56,7 +56,7 @@ async function loadInvitations(account: string) {
return data ?? [];
}
-async function OrganizationAccountMembersPage({ params }: Params) {
+async function TeamAccountMembersPage({ params }: Params) {
const slug = params.account;
const [{ account, user }, members, invitations] = await Promise.all([
@@ -142,4 +142,4 @@ async function OrganizationAccountMembersPage({ params }: Params) {
);
}
-export default withI18n(OrganizationAccountMembersPage);
+export default withI18n(TeamAccountMembersPage);
diff --git a/apps/web/app/(dashboard)/home/[account]/page.tsx b/apps/web/app/(dashboard)/home/[account]/page.tsx
index 820e36cea..5e4329d46 100644
--- a/apps/web/app/(dashboard)/home/[account]/page.tsx
+++ b/apps/web/app/(dashboard)/home/[account]/page.tsx
@@ -35,7 +35,7 @@ export const metadata = {
title: 'Organization Account Home',
};
-function OrganizationAccountHomePage({
+function TeamAccountHomePage({
params,
}: {
params: {
@@ -62,4 +62,4 @@ function OrganizationAccountHomePage({
);
}
-export default withI18n(OrganizationAccountHomePage);
+export default withI18n(TeamAccountHomePage);
diff --git a/apps/web/config/organization-account-sidebar.config.tsx b/apps/web/config/organization-account-sidebar.config.tsx
index 60fa02dce..f8668ec79 100644
--- a/apps/web/config/organization-account-sidebar.config.tsx
+++ b/apps/web/config/organization-account-sidebar.config.tsx
@@ -28,7 +28,7 @@ const routes = (account: string) => [
path: createPath(pathsConfig.app.accountMembers, account),
Icon: ,
},
- featureFlagsConfig.enableOrganizationBilling
+ featureFlagsConfig.enableTeamAccountBilling
? {
label: 'common:billingTabLabel',
path: createPath(pathsConfig.app.accountBilling, account),
diff --git a/apps/web/public/locales/en/common.json b/apps/web/public/locales/en/common.json
index 92ef71bc2..4efdbf74e 100644
--- a/apps/web/public/locales/en/common.json
+++ b/apps/web/public/locales/en/common.json
@@ -2,6 +2,7 @@
"homeTabLabel": "Home",
"homeTabDescription": "Welcome to your home page",
"accountMembers": "Members",
+ "membersTabDescription": "Manage your organization's members",
"billingTabLabel": "Billing",
"billingTabDescription": "Manage your billing and subscription",
"yourAccountTabLabel": "Account Settings",
diff --git a/packages/features/accounts/src/server/services/delete-personal-account.service.ts b/packages/features/accounts/src/server/services/delete-personal-account.service.ts
index 9bf3f22c1..cb1b5f38b 100644
--- a/packages/features/accounts/src/server/services/delete-personal-account.service.ts
+++ b/packages/features/accounts/src/server/services/delete-personal-account.service.ts
@@ -35,25 +35,30 @@ export class DeletePersonalAccountService {
productName: string;
};
}) {
+ const userId = params.userId;
+
Logger.info(
- { userId: params.userId, name: this.namespace },
+ { name: this.namespace, userId },
'User requested deletion. Processing...',
);
// Cancel all user subscriptions
const billingService = new AccountBillingService(params.adminClient);
- await billingService.cancelAllAccountSubscriptions(params.userId);
+ await billingService.cancelAllAccountSubscriptions({
+ userId,
+ accountId: userId,
+ });
// execute the deletion of the user
try {
- await params.adminClient.auth.admin.deleteUser(params.userId);
+ await params.adminClient.auth.admin.deleteUser(userId);
} catch (error) {
Logger.error(
{
- userId: params.userId,
- error,
name: this.namespace,
+ userId,
+ error,
},
'Error deleting user',
);
@@ -66,8 +71,8 @@ export class DeletePersonalAccountService {
try {
Logger.info(
{
- userId: params.userId,
name: this.namespace,
+ userId,
},
`Sending account deletion email...`,
);
@@ -81,8 +86,8 @@ export class DeletePersonalAccountService {
} catch (error) {
Logger.error(
{
- userId: params.userId,
name: this.namespace,
+ userId,
error,
},
`Error sending account deletion email`,
diff --git a/packages/features/team-accounts/src/server/actions/delete-team-account-server-actions.ts b/packages/features/team-accounts/src/server/actions/delete-team-account-server-actions.ts
index 6bff8cbeb..0e8af1895 100644
--- a/packages/features/team-accounts/src/server/actions/delete-team-account-server-actions.ts
+++ b/packages/features/team-accounts/src/server/actions/delete-team-account-server-actions.ts
@@ -17,6 +17,11 @@ export async function deleteTeamAccountAction(formData: FormData) {
);
const client = getSupabaseServerActionClient();
+ const auth = await requireAuth(client);
+
+ if (auth.error) {
+ throw new Error('Authentication required');
+ }
// Check if the user has the necessary permissions to delete the team account
await assertUserPermissionsToDeleteTeamAccount(client, params.accountId);
@@ -29,7 +34,10 @@ export async function deleteTeamAccountAction(formData: FormData) {
getSupabaseServerActionClient({
admin: true,
}),
- params,
+ {
+ accountId: params.accountId,
+ userId: auth.data.user.id,
+ },
);
return redirect('/home');
diff --git a/packages/features/team-accounts/src/server/services/delete-team-account.service.ts b/packages/features/team-accounts/src/server/services/delete-team-account.service.ts
index 953ebe0db..d42f95e03 100644
--- a/packages/features/team-accounts/src/server/services/delete-team-account.service.ts
+++ b/packages/features/team-accounts/src/server/services/delete-team-account.service.ts
@@ -20,12 +20,16 @@ export class DeleteTeamAccountService {
*/
async deleteTeamAccount(
adminClient: SupabaseClient,
- params: { accountId: string },
+ params: {
+ accountId: string;
+ userId: string;
+ },
) {
Logger.info(
{
name: this.namespace,
accountId: params.accountId,
+ userId: params.userId,
},
`Requested team account deletion. Processing...`,
);
@@ -34,6 +38,7 @@ export class DeleteTeamAccountService {
{
name: this.namespace,
accountId: params.accountId,
+ userId: params.userId,
},
`Deleting all account subscriptions...`,
);
@@ -41,7 +46,7 @@ export class DeleteTeamAccountService {
// First - we want to cancel all Stripe active subscriptions
const billingService = new AccountBillingService(adminClient);
- await billingService.cancelAllAccountSubscriptions(params.accountId);
+ await billingService.cancelAllAccountSubscriptions(params);
// now we can use the admin client to delete the account.
const { error } = await adminClient
@@ -54,6 +59,7 @@ export class DeleteTeamAccountService {
{
name: this.namespace,
accountId: params.accountId,
+ userId: params.userId,
error,
},
'Failed to delete team account',
@@ -66,6 +72,7 @@ export class DeleteTeamAccountService {
{
name: this.namespace,
accountId: params.accountId,
+ userId: params.userId,
},
'Successfully deleted team account',
);
diff --git a/packages/ui/src/shadcn/badge.tsx b/packages/ui/src/shadcn/badge.tsx
index 12411df06..a2d542b29 100644
--- a/packages/ui/src/shadcn/badge.tsx
+++ b/packages/ui/src/shadcn/badge.tsx
@@ -17,10 +17,10 @@ const badgeVariants = cva(
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
outline: 'text-foreground',
success:
- 'border-transparent bg-green-50 text-green-500 dark:bg-green-500/20',
+ 'border-transparent bg-green-50 hover:bg-green-50 text-green-500 dark:bg-green-500/20 dark:hover:bg-green-500/20',
warning:
- 'border-transparent bg-orange-50 text-orange-500 dark:bg-transparent',
- info: 'border-transparent bg-blue-50 text-blue-500 dark:bg-transparent',
+ 'border-transparent bg-orange-50 hover:bg-orange-50 text-orange-500 dark:bg-orange-500/20 dark:hover:bg-orange-500/20',
+ info: 'border-transparent bg-blue-50 hover:bg-blue-50 text-blue-500 dark:bg-blue-500/20 dark:hover:bg-blue-500/20',
},
},
defaultVariants: {
diff --git a/supabase/migrations/20221215192558_schema.sql b/supabase/migrations/20221215192558_schema.sql
index 9f513c636..acd37e264 100644
--- a/supabase/migrations/20221215192558_schema.sql
+++ b/supabase/migrations/20221215192558_schema.sql
@@ -516,7 +516,7 @@ begin
$$ language plpgsql;
-grant execute on function kit.can_remove_account_member (uuid, uuid) to authenticated, postgres;
+grant execute on function kit.can_remove_account_member (uuid, uuid) to authenticated, service_role;
-- RLS
-- SELECT: Users can read their team members account memberships
@@ -649,7 +649,7 @@ begin
$$ language plpgsql;
-grant execute on function public.has_permission (uuid, uuid, public.app_permissions) to authenticated, postgres;
+grant execute on function public.has_permission (uuid, uuid, public.app_permissions) to authenticated, service_role;
-- Enable RLS on the role_permissions table
alter table public.role_permissions enable row level security;
@@ -1116,7 +1116,7 @@ end;
$$ language plpgsql;
grant
-execute on function public.create_account (text) to authenticated;
+execute on function public.create_account (text) to authenticated, service_role;
-- RLS
-- Authenticated users can create organization accounts
@@ -1194,7 +1194,7 @@ where
grant
select
on public.user_account_workspace to authenticated,
- postgres;
+ service_role;
create or replace view
public.user_accounts as
@@ -1214,7 +1214,7 @@ where
grant
select
on public.user_accounts to authenticated,
- postgres;
+ service_role;
create
or replace function public.organization_account_workspace (account_slug text) returns table (
@@ -1256,7 +1256,7 @@ $$ language plpgsql;
grant
execute on function public.organization_account_workspace (text) to authenticated,
-postgres;
+service_role;
CREATE
OR REPLACE FUNCTION public.get_account_members (account_slug text) RETURNS TABLE (
@@ -1283,7 +1283,7 @@ $$;
grant
execute on function public.get_account_members (text) to authenticated,
-postgres;
+service_role;
create or replace function public.get_account_invitations(account_slug text) returns table (
id integer,
@@ -1316,7 +1316,7 @@ begin
end;
$$ language plpgsql;
-grant execute on function public.get_account_invitations (text) to authenticated, postgres;
+grant execute on function public.get_account_invitations (text) to authenticated, service_role;
CREATE TYPE kit.invitation AS (
email text,
@@ -1359,7 +1359,7 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
-grant execute on function public.add_invitations_to_account (text, kit.invitation[]) to authenticated, postgres;
+grant execute on function public.add_invitations_to_account (text, kit.invitation[]) to authenticated, service_role;
-- Storage
-- Account Image
@@ -1368,25 +1368,27 @@ insert into
values
('account_image', 'account_image', true);
+create or replace function kit.get_storage_filename_as_uuid (name text) returns uuid as $$
+begin
+ return replace(
+ storage.filename (name),
+ concat('.', storage.extension (name)),
+ ''
+ )::uuid;
+end;
+$$ language plpgsql;
+
+grant execute on function kit.get_storage_filename_as_uuid (text) to authenticated, service_role;
+
-- RLS policies for storage
create policy account_image on storage.objects for all using (
bucket_id = 'account_image'
- and (
- replace(
- storage.filename (name),
- concat('.', storage.extension (name)),
- ''
- )::uuid
- ) = auth.uid ()
+ and kit.get_storage_filename_as_uuid(name) = auth.uid () or
+ public.has_role_on_account(kit.get_storage_filename_as_uuid(name))
)
with
check (
bucket_id = 'account_image'
- and (
- replace(
- storage.filename (name),
- concat('.', storage.extension (name)),
- ''
- )::uuid
- ) = auth.uid ()
+ and kit.get_storage_filename_as_uuid(name) = auth.uid () or
+ public.has_permission(auth.uid(), kit.get_storage_filename_as_uuid(name), 'settings.manage')
);
\ No newline at end of file