fix: link social, admin delete account,update

This commit is contained in:
Théo LAGACHE
2026-02-25 09:58:15 +01:00
parent b9ef58044e
commit 31285dd0d6
11 changed files with 928 additions and 857 deletions
+38 -28
View File
@@ -1,32 +1,42 @@
import React from "react";
import {redirect} from "next/navigation";
import {currentUser} from "@/lib/auth/current-user";
import {AuthLogoSection} from "@/components/wrappers/auth/auth-logo-section";
import {env} from "@/env.mjs";
import {Heart} from "lucide-react";
import React, { Suspense } from "react";
import { redirect } from "next/navigation";
import { currentUser } from "@/lib/auth/current-user";
import { AuthLogoSection } from "@/components/wrappers/auth/auth-logo-section";
import { env } from "@/env.mjs";
import { Heart } from "lucide-react";
import { AuthLayout } from "@/components/wrappers/auth/auth-layout";
export default async function Layout({children}: { children: React.ReactNode }) {
export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
const user = await currentUser();
const user = await currentUser();
if (user && !user.banned && user.role !== "pending") {
redirect("/dashboard/home");
}
if (user && !user.banned && user.role !== "pending") {
redirect("/dashboard/home");
}
return (
<div className="flex min-h-screen flex-col justify-between py-10 sm:px-6 lg:px-8 ">
<div className="flex flex-col items-center justify-center flex-1">
<div className="mx-auto w-full max-w-md">
<AuthLogoSection/>
<div className="mt-4">{children}</div>
</div>
</div>
<footer className="mt-8 text-center text-xs text-muted-foreground flex flex-col gap-1">
<p className="flex items-center justify-center gap-1">
Made with <Heart className="size-3 fill-red-500 text-red-500" /> by <span className="font-medium text-foreground">Portabase</span>
</p>
<p>v{env.NEXT_PUBLIC_PROJECT_VERSION}</p>
</footer>
return (
<div className="flex min-h-screen flex-col justify-between py-10 sm:px-6 lg:px-8 ">
<div className="flex flex-col items-center justify-center flex-1">
<div className="mx-auto w-full max-w-md">
<AuthLogoSection />
<div className="mt-4">
<Suspense>
<AuthLayout>{children}</AuthLayout>
</Suspense>
</div>
</div>
)
}
</div>
<footer className="mt-8 text-center text-xs text-muted-foreground flex flex-col gap-1">
<p className="flex items-center justify-center gap-1">
Made with{" "}
<Heart className="size-3 fill-red-500 text-red-500 animate-pulse" />{" "}
by <span className="font-medium text-foreground">Portabase</span>
</p>
<p>v{env.NEXT_PUBLIC_PROJECT_VERSION}</p>
</footer>
</div>
);
}
+12 -12
View File
@@ -14,8 +14,8 @@
"auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts"
},
"dependencies": {
"@better-auth/passkey": "^1.4.18",
"@better-auth/sso": "^1.4.18",
"@better-auth/passkey": "^1.4.19",
"@better-auth/sso": "^1.4.19",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
@@ -50,7 +50,7 @@
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-table": "^8.21.3",
"@types/express": "^5.0.6",
"@types/nodemailer": "^6.4.22",
"@types/nodemailer": "^6.4.23",
"@types/ws": "^8.18.1",
"@zenstackhq/runtime": "2.14.2",
"argon2": "^0.43.1",
@@ -70,7 +70,7 @@
"input-otp": "^1.4.2",
"lucide-react": "^0.553.0",
"minio": "^8.0.6",
"motion": "^12.34.0",
"motion": "^12.34.3",
"next": "16.1.5",
"next-safe-action": "^7.10.8",
"next-themes": "^0.4.6",
@@ -85,7 +85,7 @@
"react-dom": "^19.2.4",
"react-dropzone": "^14.4.1",
"react-email": "^4.3.2",
"react-hook-form": "^7.71.1",
"react-hook-form": "^7.71.2",
"react-qr-code": "^2.0.18",
"react-resizable-panels": "^3.0.6",
"react-twc": "^1.5.1",
@@ -95,8 +95,8 @@
"socket.io": "^4.8.3",
"socket.io-client": "^4.8.3",
"sonner": "^2.0.7",
"swiper": "^12.1.0",
"tailwind-merge": "^3.4.0",
"swiper": "^12.1.2",
"tailwind-merge": "^3.5.0",
"uuid": "^11.1.0",
"vaul": "^1.1.2",
"ws": "^8.19.0",
@@ -106,7 +106,7 @@
"@iconify/react": "^6.0.2",
"@react-email/preview-server": "4.3.2",
"@react-email/render": "^2.0.4",
"@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/postcss": "^4.2.1",
"@types/eslint-plugin-tailwindcss": "^3.17.0",
"@types/node": "^22.19.11",
"@types/node-forge": "^1.3.14",
@@ -115,15 +115,15 @@
"@types/react-dom": "^19.2.3",
"@zenstackhq/openapi": "^2.22.1",
"@zenstackhq/tanstack-query": "^2.22.2",
"baseline-browser-mapping": "^2.9.19",
"baseline-browser-mapping": "^2.10.0",
"drizzle-kit": "^0.31.9",
"esbuild": "^0.27.3",
"eslint": "^9.39.2",
"eslint": "^9.39.3",
"eslint-config-next": "^16.1.6",
"eslint-plugin-tailwindcss": "^3.18.2",
"framer-motion": "^12.34.0",
"framer-motion": "^12.34.3",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"tailwindcss": "^4.2.1",
"tsx": "^4.21.0",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
+535 -523
View File
File diff suppressed because it is too large Load Diff
+67 -61
View File
@@ -1,75 +1,81 @@
import {NextRequest, NextResponse} from "next/server";
import {loggingMiddleware} from "@/middleware/loggingMiddleware";
import {errorHandler} from "@/middleware/errorHandler";
import {auth} from "@/lib/auth/auth";
import {headers} from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import { loggingMiddleware } from "@/middleware/loggingMiddleware";
import { errorHandler } from "@/middleware/errorHandler";
import { auth } from "@/lib/auth/auth";
import { headers } from "next/headers";
export async function proxy(request: NextRequest) {
const url = request.nextUrl.clone();
const redirectUrl = encodeURIComponent(request.nextUrl.pathname)
const url = request.nextUrl.clone();
const redirectUrl = encodeURIComponent(request.nextUrl.pathname);
if (url.pathname.startsWith("/dashboard")) {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
return NextResponse.redirect(new URL(`/login?redirect=${redirectUrl}`, request.url));
}
if (session.user.banned) {
await auth.api.signOut({headers: await headers()});
return NextResponse.redirect(new URL("/login?error=banned", request.url));
}
if (session.user.role === "pending") {
await auth.api.signOut({headers: await headers()});
return NextResponse.redirect(new URL(`/login?error=pending?redirect=${redirectUrl}`, request.url));
}
if (url.pathname === "/dashboard") {
return NextResponse.redirect(new URL(`/dashboard/home`, request.url));
}
return NextResponse.next();
if (url.pathname.startsWith("/dashboard")) {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
return NextResponse.redirect(
new URL(`/login?redirect=${redirectUrl}`, request.url),
);
}
if (session.user.banned) {
await auth.api.signOut({ headers: await headers() });
return NextResponse.redirect(new URL("/login?error=banned", request.url));
}
if (session.user.role === "pending") {
await auth.api.signOut({ headers: await headers() });
return NextResponse.redirect(
new URL(`/login?error=pending?redirect=${redirectUrl}`, request.url),
);
}
if (url.pathname === "/dashboard") {
return NextResponse.redirect(new URL(`/dashboard/home`, request.url));
}
return NextResponse.next();
}
if (url.pathname.startsWith("/api/auth")) {
return NextResponse.next();
}
if (url.pathname.startsWith("/api/auth")) {
return NextResponse.next();
}
if (url.pathname.startsWith("/api")) {
const routeExists = checkRouteExists(url.pathname);
if (!routeExists) {
return new NextResponse(JSON.stringify({message: "This API route does not exist.", status: 404}), {
status: 404,
headers: {"Content-Type": "application/json"},
});
}
}
try {
loggingMiddleware(request);
} catch (err) {
errorHandler(err);
if (url.pathname.startsWith("/api")) {
const routeExists = checkRouteExists(url.pathname);
if (!routeExists) {
return new NextResponse(
JSON.stringify({
message: "This API route does not exist.",
status: 404,
}),
{
status: 404,
headers: { "Content-Type": "application/json" },
},
);
}
}
try {
loggingMiddleware(request);
} catch (err) {
errorHandler(err);
}
}
function checkRouteExists(pathname: string) {
const routePatterns = [
/^\/api\/agent\/[^/]+\/status\/?$/,
/^\/api\/agent\/[^/]+\/backup\/?$/,
/^\/api\/agent\/[^/]+\/backup\/upload\/init\/?$/,
/^\/api\/agent\/[^/]+\/backup\/upload\/status\/?$/,
/^\/api\/agent\/[^/]+\/restore\/?$/,
/^\/api\/files\/images\/[^/]+\/?$/,
/^\/api\/files\/backups\/?$/,
/^\/api\/tus\/hooks\/?$/,
/^\/api\/events\/?$/,
/^\/api\/config\/?$/,
/^\/api\/google\/drive\/callback\/?$/,
];
return routePatterns.some((pattern) => pattern.test(pathname));
const routePatterns = [
/^\/api\/agent\/[^/]+\/status\/?$/,
/^\/api\/agent\/[^/]+\/backup\/?$/,
/^\/api\/agent\/[^/]+\/backup\/upload\/init\/?$/,
/^\/api\/agent\/[^/]+\/backup\/upload\/status\/?$/,
/^\/api\/agent\/[^/]+\/restore\/?$/,
/^\/api\/files\/images\/[^/]+\/?$/,
/^\/api\/files\/backups\/?$/,
/^\/api\/tus\/hooks\/?$/,
/^\/api\/events\/?$/,
/^\/api\/config\/?$/,
/^\/api\/google\/drive\/callback\/?$/,
];
return routePatterns.some((pattern) => pattern.test(pathname));
}
export const config = {
matcher: [
"/api/:path*",
"/dashboard/:path*",
"/dashboard",
]
matcher: ["/api/:path*", "/dashboard/:path*", "/dashboard"],
};
@@ -0,0 +1,37 @@
"use client";
import React, { useEffect } from "react";
import { useSearchParams } from "next/navigation";
import { toast } from "sonner";
export const AuthLayout = ({ children }: { children: React.ReactNode }) => {
const url = useSearchParams();
useEffect(() => {
const error = url.get("error");
if (!error) return;
const errorMessages: Record<string, string> = {
pending: "Your account is not active.",
invalid_or_expired_token: "Password reset invalid token.",
};
const match = Object.entries(errorMessages).find(([key]) =>
error.includes(key),
);
if (!match) return;
toast.error(match[1]);
const params = new URLSearchParams(url.toString());
params.delete("error");
const newUrl =
window.location.pathname + (params.toString() ? `?${params.toString()}` : "");
window.history.replaceState({}, document.title, newUrl);
}, [url]);
return <>{children}</>;
};
@@ -1,123 +1,133 @@
"use client";
import {useEffect, useState} from "react";
import {useMutation} from "@tanstack/react-query";
import {toast} from "sonner";
import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
import {Input} from "@/components/ui/input";
import {Form} from "@/components/ui/form";
import {LoginSchema, LoginType} from "@/components/wrappers/auth/login/login-form/login-form.schema";
import {signIn} from "@/lib/auth/auth-client";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import { useMutation } from "@tanstack/react-query";
import Link from "next/link";
import {useRouter} from "next/navigation";
import {PasswordInput} from "@/components/ui/password-input";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { toast } from "sonner";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
useZodForm,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PasswordInput } from "@/components/ui/password-input";
import {
LoginSchema,
type LoginType,
} from "@/components/wrappers/auth/login/login-form/login-form.schema";
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
import { signIn } from "@/lib/auth/auth-client";
export type loginFormProps = {
defaultValues?: LoginType;
isPasskeyEnabled?: boolean;
defaultValues?: LoginType;
isPasskeyEnabled?: boolean;
};
export const LoginForm = (props: loginFormProps) => {
const {isPasskeyEnabled = false} = props;
const { isPasskeyEnabled = false } = props;
const url = useSearchParams();
const [urlParams, setUrlParams] = useState<URLSearchParams>();
const form = useZodForm({
schema: LoginSchema,
});
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
setUrlParams(urlParams);
const error = urlParams.get("error");
if (error?.includes("pending")) {
toast.error("Your account is not active.");
urlParams.delete("error");
window.history.replaceState({}, document.title, window.location.pathname + "?" + urlParams.toString());
}
if (error?.includes("invalid_or_expired_token")) {
toast.error("Password reset invalid token.");
urlParams.delete("error");
window.history.replaceState({}, document.title, window.location.pathname + "?" + urlParams.toString());
}
}, []);
const router = useRouter();
const form = useZodForm({
schema: LoginSchema,
});
const router = useRouter();
const mutation = useMutation({
mutationFn: async (values: LoginType) => {
await signIn.email(
{
password: values.password,
email: values.email,
callbackURL: urlParams?.get("redirect") ?? "/dashboard",
},
{
onSuccess: (context) => {
if (context.data.twoFactorRedirect) {
//@ts-ignore
router.push("/guard?redirect=" + encodeURIComponent(context.data.callbackURL || "/dashboard"));
}
toast.success("Login success");
},
onError: (error) => {
toast.error(error.error.message);
},
}
);
const mutation = useMutation({
mutationFn: async (values: LoginType) => {
await signIn.email(
{
password: values.password,
email: values.email,
callbackURL: url?.get("redirect") ?? "/dashboard",
},
});
{
onSuccess: (context) => {
if (context.data.twoFactorRedirect) {
router.push(
"/guard?redirect=" +
encodeURIComponent(context.data.callbackURL || "/dashboard"),
);
}
return (
<Form
form={form}
className="flex flex-col gap-4 mb-1"
onSubmit={async (values) => {
await mutation.mutateAsync(values);
}}
>
<FormField
control={form.control}
name="email"
defaultValue=""
render={({field}) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input autoComplete="email" autoFocus placeholder="example@portabase.io" {...field} />
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
defaultValue=""
render={({field}) => (
<FormItem>
<div className="flex items-center justify-between">
<FormLabel>Password</FormLabel>
<div className="text-center text-sm">
<Link href={"/forgot-password"} className="hover:underline ml-1">
Forgot your password ?
</Link>
</div>
</div>
<FormControl>
<PasswordInput autoComplete={isPasskeyEnabled ? "current-password webauthn" : "current-password"}
placeholder={"Enter your password"} {...field} />
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
<ButtonWithLoading className="mt-2 h-11" isPending={mutation.isPending}>
Login
</ButtonWithLoading>
</Form>
);
toast.success("Login success");
},
onError: (error) => {
toast.error(error.error.message);
},
},
);
},
});
return (
<Form
form={form}
className="flex flex-col gap-4 mb-1"
onSubmit={async (values) => {
await mutation.mutateAsync(values);
}}
>
<FormField
control={form.control}
name="email"
defaultValue=""
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input
autoComplete="email"
autoFocus
placeholder="example@portabase.io"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
defaultValue=""
render={({ field }) => (
<FormItem>
<div className="flex items-center justify-between">
<FormLabel>Password</FormLabel>
<div className="text-center text-sm">
<Link
href={"/forgot-password"}
className="hover:underline ml-1"
>
Forgot your password ?
</Link>
</div>
</div>
<FormControl>
<PasswordInput
autoComplete={
isPasskeyEnabled
? "current-password webauthn"
: "current-password"
}
placeholder={"Enter your password"}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<ButtonWithLoading className="mt-2 h-11" isPending={mutation.isPending}>
Login
</ButtonWithLoading>
</Form>
);
};
@@ -1,75 +1,86 @@
"use client";
import {useMutation} from "@tanstack/react-query";
import {User} from "@/db/schema/02_user";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {useRouter} from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import { User } from "@/db/schema/02_user";
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
import { useRouter } from "next/navigation";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {authClient} from "@/lib/auth/auth-client";
import {toast} from "sonner";
import {setSuperAdminOwnerOfOrganizationsOwnedByUser} from "@/components/wrappers/dashboard/admin/users/user.action";
import { authClient } from "@/lib/auth/auth-client";
import { toast } from "sonner";
import { setSuperAdminOwnerOfOrganizationsOwnedByUser } from "@/components/wrappers/dashboard/admin/users/user.action";
type AdminDeleteUserModalProps = {
open: boolean;
user: User;
onOpenChange: (open: boolean) => void;
open: boolean;
user: User;
onOpenChange: (open: boolean) => void;
};
export const AdminDeleteUserModal = ({user, open, onOpenChange}: AdminDeleteUserModalProps) => {
const router = useRouter();
export const AdminDeleteUserModal = ({
user,
open,
onOpenChange,
}: AdminDeleteUserModalProps) => {
const router = useRouter();
const mutation = useMutation({
mutationFn: async () => {
const res = await setSuperAdminOwnerOfOrganizationsOwnedByUser({userId: user.id});
const result = res?.data;
if (result?.success) {
await authClient.admin.removeUser(
{
userId: user.id,
},
{
onSuccess: async (response) => {
toast.success(`User ${user.name} successfully deleted`);
onOpenChange(false);
router.refresh();
},
onError: async (error) => {
toast.error("An error has occurred while deleting user");
onOpenChange(false);
},
}
);
} else {
toast.error("An error has occurred while deleting user");
}
},
onError: (error) => {
toast.error(error.message);
},
});
const mutation = useMutation({
mutationFn: async () => {
const res = await setSuperAdminOwnerOfOrganizationsOwnedByUser({
userId: user.id,
});
const result = res?.data;
if (result?.success) {
await authClient.admin.removeUser(
{
userId: user.id,
},
{
onSuccess: async () => {
toast.success(`User ${user.name} successfully deleted`);
onOpenChange(false);
router.refresh();
},
onError: async (error) => {
toast.error("An error has occurred while deleting user");
onOpenChange(false);
},
},
);
} else {
toast.error("An error has occurred while deleting user");
}
},
onError: (error) => {
toast.error(error.message);
},
});
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure you want to delete {user.name} ?</AlertDialogTitle>
<AlertDialogDescription>This action is irreversible: it will lead to the deletion of the user's
data.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<ButtonWithLoading
onClick={async () => await mutation.mutateAsync()}>Confirm</ButtonWithLoading>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want to delete {user.name} ?
</AlertDialogTitle>
<AlertDialogDescription>
This action is irreversible: it will lead to the deletion of the
user's data.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<ButtonWithLoading onClick={async () => await mutation.mutateAsync()}>
Confirm
</ButtonWithLoading>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
@@ -63,7 +63,7 @@ export function ProfileProviders({
callbackURL: "/dashboard",
});
} else {
result = await authClient.signIn.social({
result = await authClient.linkSocial({
provider: provider.id as any,
callbackURL: "/dashboard",
});
+1 -13
View File
@@ -134,7 +134,7 @@ export const auth = betterAuth({
trustedProviders: [
"credential",
...getOAuthProviders().map((p) => p.id),
...oidcProviders.map((p) => p.id),
...oidcProviders.map((p) => p.id)
],
allowDifferentEmails: true,
},
@@ -338,18 +338,6 @@ export const auth = betterAuth({
}
},
},
delete: {
before: async (account) => {
const provider = SUPPORTED_PROVIDERS.find(
(p) => p.id === account.providerId,
);
if (provider && provider.allowUnlinking === false) {
throw new APIError("FORBIDDEN", {
message: "Unlinking is disabled for this provider.",
});
}
},
},
},
user: {
update: {
+2 -1
View File
@@ -85,7 +85,8 @@ export function getOAuthProviders(): OAuthProvider[] {
const client = process.env[`AUTH_SOCIAL_${prefix}_CLIENT`];
if (!client) return;
const providerId = prefix.toLowerCase();
const providerId =
prefix.toLowerCase() === "x" ? "twitter" : prefix.toLowerCase();
const envTitle = process.env[`AUTH_SOCIAL_${prefix}_TITLE`];
const envDesc = process.env[`AUTH_SOCIAL_${prefix}_DESC`];
+42 -46
View File
@@ -1,76 +1,72 @@
import {createAccessControl} from "better-auth/plugins/access";
import {defaultStatements, adminAc} from "better-auth/plugins/admin/access";
import { createAccessControl } from "better-auth/plugins/access";
import { defaultStatements, adminAc } from "better-auth/plugins/admin/access";
import {
defaultStatements as orgDefaultStatements,
adminAc as orgAdminAc,
ownerAc as orgOwnerAc,
memberAc as orgMemberAc
defaultStatements as orgDefaultStatements,
adminAc as orgAdminAc,
ownerAc as orgOwnerAc,
memberAc as orgMemberAc,
} from "better-auth/plugins/organization/access";
const statement = {
...defaultStatements,
...orgDefaultStatements,
project: ["create", "list", "update", "delete"],
database: ["create", "list", "update", "delete", "backup"],
agent: ["create", "list", "update", "delete"],
...defaultStatements,
...orgDefaultStatements,
project: ["create", "list", "update", "delete"],
database: ["create", "list", "update", "delete", "backup"],
agent: ["create", "list", "update", "delete"],
} as const;
const ac = createAccessControl(statement);
const superadmin = ac.newRole({
project: ["create", "list", "update", "delete"],
database: ["create", "list", "update", "delete"],
agent: ["create", "list", "update", "delete"],
...adminAc.statements,
...orgMemberAc.statements,
...orgAdminAc.statements,
...orgOwnerAc.statements,
project: ["create", "list", "update", "delete"],
database: ["create", "list", "update", "delete"],
agent: ["create", "list", "update", "delete"],
...adminAc.statements,
...orgMemberAc.statements,
...orgAdminAc.statements,
...orgOwnerAc.statements,
});
const admin = ac.newRole({
project: ["create", "list", "update", "delete"],
database: ["create", "list", "update", "delete"],
agent: ["create", "list", "update", "delete"],
...adminAc.statements,
...orgMemberAc.statements,
...orgAdminAc.statements,
project: ["create", "list", "update", "delete"],
database: ["create", "list", "update", "delete"],
agent: ["create", "list", "update", "delete"],
...adminAc.statements,
...orgMemberAc.statements,
...orgAdminAc.statements,
});
const user = ac.newRole({
project: [],
database: [],
agent: [],
project: [],
database: [],
agent: [],
});
const pending = ac.newRole({
project: [],
database: [],
agent: [],
project: [],
database: [],
agent: [],
});
//org
const orgMember = ac.newRole({
project: ["list"],
database: ["list"],
agent: ["list"],
...orgMemberAc.statements,
project: ["list"],
database: ["list"],
agent: ["list"],
...orgMemberAc.statements,
});
const orgAdmin = ac.newRole({
project: ["create", "update"],
...orgMemberAc.statements,
...orgAdminAc.statements,
project: ["create", "update"],
...orgMemberAc.statements,
...orgAdminAc.statements,
});
const orgOwner = ac.newRole({
project: ["create", "update", "delete"],
...orgMemberAc.statements,
...orgAdminAc.statements,
...orgOwnerAc.statements,
project: ["create", "update", "delete"],
...orgMemberAc.statements,
...orgAdminAc.statements,
...orgOwnerAc.statements,
});
export {ac, admin, superadmin, user, pending, orgAdmin, orgMember, orgOwner};
export { ac, admin, superadmin, user, pending, orgAdmin, orgMember, orgOwner };