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:
IGNY8 VPS (Salman)
2025-12-07 17:23:42 +00:00
parent 3cbed65601
commit 65fea95d33
15 changed files with 374 additions and 71 deletions

View 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}</>;
}

View File

@@ -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>