feat: enhance team account creation with policy checks and UI updates (#436)
* feat: enhance team account creation with policy checks and UI updates
This commit is contained in:
committed by
GitHub
parent
ab57b24518
commit
5237d34e6f
@@ -31,6 +31,7 @@
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@supabase/supabase-js": "catalog:",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "catalog:",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"./hooks/*": "./src/hooks/*.ts",
|
||||
"./webhooks": "./src/server/services/webhooks/index.ts",
|
||||
"./policies": "./src/server/policies/index.ts",
|
||||
"./policies/orchestrator": "./src/server/policies/orchestrator.ts",
|
||||
"./services/account-invitations.service": "./src/server/services/account-invitations.service.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -61,7 +61,7 @@ export function CreateTeamAccountDialog(
|
||||
}
|
||||
|
||||
function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
||||
const [error, setError] = useState<boolean>();
|
||||
const [error, setError] = useState<{ message?: string } | undefined>();
|
||||
const [pending, startTransition] = useTransition();
|
||||
|
||||
const form = useForm({
|
||||
@@ -78,14 +78,14 @@ function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
||||
onSubmit={form.handleSubmit((data) => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const { error } = await createTeamAccountAction(data);
|
||||
const result = await createTeamAccountAction(data);
|
||||
|
||||
if (error) {
|
||||
setError(true);
|
||||
if (result.error) {
|
||||
setError({ message: result.message });
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isRedirectError(error)) {
|
||||
setError(true);
|
||||
} catch (e) {
|
||||
if (!isRedirectError(e)) {
|
||||
setError({});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -93,7 +93,7 @@ function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
||||
>
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<If condition={error}>
|
||||
<CreateOrganizationErrorAlert />
|
||||
<CreateOrganizationErrorAlert message={error?.message} />
|
||||
</If>
|
||||
|
||||
<FormField
|
||||
@@ -150,7 +150,7 @@ function CreateOrganizationAccountForm(props: { onClose: () => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
function CreateOrganizationErrorAlert() {
|
||||
function CreateOrganizationErrorAlert(props: { message?: string }) {
|
||||
return (
|
||||
<Alert variant={'destructive'}>
|
||||
<AlertTitle>
|
||||
@@ -158,7 +158,11 @@ function CreateOrganizationErrorAlert() {
|
||||
</AlertTitle>
|
||||
|
||||
<AlertDescription>
|
||||
<Trans i18nKey={'teams:createTeamErrorMessage'} />
|
||||
{props.message ? (
|
||||
<Trans i18nKey={props.message} defaults={props.message} />
|
||||
) : (
|
||||
<Trans i18nKey={'teams:createTeamErrorMessage'} />
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getLogger } from '@kit/shared/logger';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
import { CreateTeamSchema } from '../../schema/create-team.schema';
|
||||
import { createAccountCreationPolicyEvaluator } from '../policies';
|
||||
import { createCreateTeamAccountService } from '../services/create-team-account.service';
|
||||
|
||||
export const createTeamAccountAction = enhanceAction(
|
||||
@@ -23,19 +24,40 @@ export const createTeamAccountAction = enhanceAction(
|
||||
|
||||
logger.info(ctx, `Creating team account...`);
|
||||
|
||||
const { data, error } = await service.createNewOrganizationAccount({
|
||||
// Check policies before creating
|
||||
const evaluator = createAccountCreationPolicyEvaluator();
|
||||
|
||||
if (await evaluator.hasPoliciesForStage('submission')) {
|
||||
const policyContext = {
|
||||
timestamp: new Date().toISOString(),
|
||||
userId: user.id,
|
||||
accountName: name,
|
||||
};
|
||||
|
||||
const result = await evaluator.canCreateAccount(
|
||||
policyContext,
|
||||
'submission',
|
||||
);
|
||||
|
||||
if (!result.allowed) {
|
||||
logger.warn(
|
||||
{ ...ctx, reasons: result.reasons },
|
||||
`Policy denied team account creation`,
|
||||
);
|
||||
|
||||
return {
|
||||
error: true,
|
||||
message: result.reasons[0] ?? 'Policy denied account creation',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Service throws on error, so no need to check for error
|
||||
const { data } = await service.createNewOrganizationAccount({
|
||||
name,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
logger.error({ ...ctx, error }, `Failed to create team account`);
|
||||
|
||||
return {
|
||||
error: true,
|
||||
};
|
||||
}
|
||||
|
||||
logger.info(ctx, `Team account created`);
|
||||
|
||||
const accountHomePath = '/home/' + data.slug;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'server-only';
|
||||
|
||||
import { createPolicyRegistry } from '@kit/policies';
|
||||
|
||||
/**
|
||||
* Feature-specific registry for create account policies.
|
||||
*/
|
||||
export const createAccountPolicyRegistry = createPolicyRegistry();
|
||||
|
||||
// Register policies here
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'server-only';
|
||||
|
||||
import type { EvaluationResult } from '@kit/policies';
|
||||
import { createPoliciesEvaluator } from '@kit/policies';
|
||||
|
||||
import { createAccountPolicyRegistry } from './create-account-policies';
|
||||
import type { FeaturePolicyCreateAccountContext } from './feature-policy-create-account-context';
|
||||
|
||||
export interface CreateAccountPolicyEvaluator {
|
||||
hasPoliciesForStage(stage: 'preliminary' | 'submission'): Promise<boolean>;
|
||||
canCreateAccount(
|
||||
context: FeaturePolicyCreateAccountContext,
|
||||
stage: 'preliminary' | 'submission',
|
||||
): Promise<EvaluationResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a create account policy evaluator
|
||||
*/
|
||||
export function createAccountCreationPolicyEvaluator(): CreateAccountPolicyEvaluator {
|
||||
const evaluator =
|
||||
createPoliciesEvaluator<FeaturePolicyCreateAccountContext>();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Checks if there are any create account policies for the given stage
|
||||
* @param stage - The stage to check if there are any policies for
|
||||
*/
|
||||
async hasPoliciesForStage(stage: 'preliminary' | 'submission') {
|
||||
return evaluator.hasPoliciesForStage(createAccountPolicyRegistry, stage);
|
||||
},
|
||||
|
||||
/**
|
||||
* Evaluates the create account policies for the given context and stage
|
||||
* @param context - The context for the create account policy
|
||||
* @param stage - The stage to evaluate the policies for
|
||||
*/
|
||||
async canCreateAccount(
|
||||
context: FeaturePolicyCreateAccountContext,
|
||||
stage: 'preliminary' | 'submission',
|
||||
) {
|
||||
return evaluator.evaluate(
|
||||
createAccountPolicyRegistry,
|
||||
context,
|
||||
'ALL',
|
||||
stage,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { PolicyContext } from '@kit/policies';
|
||||
|
||||
/**
|
||||
* Minimal context for create account policies.
|
||||
* Policies can fetch additional data internally using getSupabaseServerClient().
|
||||
*/
|
||||
export interface FeaturePolicyCreateAccountContext extends PolicyContext {
|
||||
/** The ID of the user creating the account */
|
||||
userId: string;
|
||||
|
||||
/** The name of the account being created (empty string for preliminary checks) */
|
||||
accountName: string;
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
// Invitation policies
|
||||
export { createInvitationsPolicyEvaluator } from './invitation-policies';
|
||||
|
||||
// Context building
|
||||
export { createInvitationContextBuilder } from './invitation-context-builder';
|
||||
|
||||
// Type exports
|
||||
export type { FeaturePolicyInvitationContext } from './feature-policy-invitation-context';
|
||||
|
||||
// Create account policies
|
||||
export { createAccountCreationPolicyEvaluator } from './create-account-policy-evaluator';
|
||||
export type { CreateAccountPolicyEvaluator } from './create-account-policy-evaluator';
|
||||
export { createAccountPolicyRegistry } from './create-account-policies';
|
||||
export type { FeaturePolicyCreateAccountContext } from './feature-policy-create-account-context';
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@kit/supabase": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@supabase/supabase-js": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"next": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user