Refactor API permissions and throttling: Updated default permission classes to enforce authentication and tenant access. Introduced new permission for system accounts and developers. Enhanced throttling rates for various operations to reduce false 429 errors. Improved API key loading logic to prioritize account-specific settings, with fallbacks to system accounts and Django settings. Updated integration views and sidebar to reflect new permission structure.
This commit is contained in:
25
frontend/src/components/auth/AdminGuard.tsx
Normal file
25
frontend/src/components/auth/AdminGuard.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { useAuthStore } from "../../store/authStore";
|
||||
|
||||
interface AdminGuardProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* AdminGuard - restricts access to system account (aws-admin/default) or developer
|
||||
*/
|
||||
export default function AdminGuard({ children }: AdminGuardProps) {
|
||||
const { user } = useAuthStore();
|
||||
const role = user?.role;
|
||||
const accountSlug = user?.account?.slug;
|
||||
const isSystemAccount = accountSlug === 'aws-admin' || accountSlug === 'default-account' || accountSlug === 'default';
|
||||
const allowed = role === 'developer' || isSystemAccount;
|
||||
|
||||
if (!allowed) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from "../../icons";
|
||||
import Label from "../form/Label";
|
||||
@@ -6,6 +6,15 @@ import Input from "../form/input/InputField";
|
||||
import Checkbox from "../form/input/Checkbox";
|
||||
import { useAuthStore } from "../../store/authStore";
|
||||
|
||||
type Plan = {
|
||||
id: number;
|
||||
name: string;
|
||||
price?: number;
|
||||
billing_cycle?: string;
|
||||
is_active?: boolean;
|
||||
included_credits?: number;
|
||||
};
|
||||
|
||||
export default function SignUpForm() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
@@ -15,11 +24,45 @@ export default function SignUpForm() {
|
||||
email: "",
|
||||
password: "",
|
||||
username: "",
|
||||
accountName: "",
|
||||
});
|
||||
const [plans, setPlans] = useState<Plan[]>([]);
|
||||
const [selectedPlanId, setSelectedPlanId] = useState<number | null>(null);
|
||||
const [plansLoading, setPlansLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const { register, loading } = useAuthStore();
|
||||
|
||||
const apiBaseUrl = useMemo(
|
||||
() => import.meta.env.VITE_BACKEND_URL || "https://api.igny8.com/api",
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const loadPlans = async () => {
|
||||
setPlansLoading(true);
|
||||
try {
|
||||
const res = await fetch(`${apiBaseUrl}/v1/auth/plans/`, {
|
||||
method: "GET",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const data = await res.json();
|
||||
const list: Plan[] = data?.results || data || [];
|
||||
const activePlans = list.filter((p) => p.is_active !== false);
|
||||
setPlans(activePlans);
|
||||
if (activePlans.length > 0) {
|
||||
setSelectedPlanId(activePlans[0].id);
|
||||
}
|
||||
} catch (e) {
|
||||
// keep empty list; surface error on submit if no plan
|
||||
console.error("Failed to load plans", e);
|
||||
} finally {
|
||||
setPlansLoading(false);
|
||||
}
|
||||
};
|
||||
loadPlans();
|
||||
}, [apiBaseUrl]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
@@ -39,6 +82,11 @@ export default function SignUpForm() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedPlanId) {
|
||||
setError("Please select a plan to continue");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate username from email if not provided
|
||||
const username = formData.username || formData.email.split("@")[0];
|
||||
@@ -49,6 +97,8 @@ export default function SignUpForm() {
|
||||
username: username,
|
||||
first_name: formData.firstName,
|
||||
last_name: formData.lastName,
|
||||
account_name: formData.accountName,
|
||||
plan_id: selectedPlanId,
|
||||
});
|
||||
|
||||
// Redirect to plan selection after successful registration
|
||||
@@ -191,6 +241,43 @@ export default function SignUpForm() {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/* <!-- Account Name --> */}
|
||||
<div>
|
||||
<Label>Account Name (optional)</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="accountName"
|
||||
name="accountName"
|
||||
value={formData.accountName}
|
||||
onChange={handleChange}
|
||||
placeholder="Workspace / Company name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* <!-- Plan Selection --> */}
|
||||
<div>
|
||||
<Label>
|
||||
Select Plan<span className="text-error-500">*</span>
|
||||
</Label>
|
||||
<select
|
||||
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-sm"
|
||||
value={selectedPlanId ?? ""}
|
||||
onChange={(e) => setSelectedPlanId(Number(e.target.value))}
|
||||
disabled={plansLoading || plans.length === 0}
|
||||
>
|
||||
{plansLoading && <option>Loading plans...</option>}
|
||||
{!plansLoading && plans.length === 0 && (
|
||||
<option value="">No plans available</option>
|
||||
)}
|
||||
{plans.map((plan) => (
|
||||
<option key={plan.id} value={plan.id}>
|
||||
{plan.name}
|
||||
{plan.price ? ` - $${plan.price}/${plan.billing_cycle || "month"}` : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* <!-- Password --> */}
|
||||
<div>
|
||||
<Label>
|
||||
|
||||
Reference in New Issue
Block a user