Compare commits

..

22 Commits

Author SHA1 Message Date
Mauricio Siu
e01d92d1d9 Merge pull request #161 from Dokploy/canary
v0.2.4
2024-06-23 19:40:45 -06:00
Mauricio Siu
8f0a4f0886 chore: bump version 2024-06-23 19:32:11 -06:00
Mauricio Siu
01794c7742 fix: correct url swagger api 2024-06-23 19:21:40 -06:00
Mauricio Siu
19bbe69ee3 Merge pull request #159 from Dokploy/refactor/update-trpc-openapi
Refactor/update trpc openapi
2024-06-23 13:17:23 -06:00
Mauricio Siu
92c4e769ab refactor: add tags to openAPI 2024-06-23 13:08:52 -06:00
Mauricio Siu
0801e91816 refactor: add dokploy trpc open api 2024-06-23 12:17:38 -06:00
Mauricio Siu
b360cc2af4 Merge pull request #158 from Dokploy/57-dokploy-api-or-cli
57 dokploy api or cli
2024-06-23 00:28:39 -06:00
Mauricio Siu
ba5d8feba2 remove file 2024-06-23 00:26:30 -06:00
Mauricio Siu
43be9d0171 refactor: remove console log 2024-06-22 23:49:04 -06:00
Mauricio Siu
a6bbf5d96b chore: remove unused dependencie 2024-06-22 22:59:06 -06:00
Mauricio Siu
7e6e43adfe remove 2024-06-22 22:57:52 -06:00
Mauricio Siu
e8a4611ab7 refactor: fix styles 2024-06-22 22:57:10 -06:00
Mauricio Siu
608db3d401 feat: add no expiration to token generated 2024-06-22 22:38:01 -06:00
Mauricio Siu
0add62f14d refactor: restrict swagger api by user access 2024-06-22 21:45:09 -06:00
Mauricio Siu
1754f63352 feat: add token access to user 2024-06-22 20:45:29 -06:00
Mauricio Siu
edcc7544fb feat: add access to API/CLI support 2024-06-22 20:23:06 -06:00
Mauricio Siu
1edf30546d refactor: remove swagger from server startup 2024-06-22 20:18:22 -06:00
Mauricio Siu
ad806437af feat: add openapi and swagger support 2024-06-22 20:17:55 -06:00
Mauricio Siu
f0eecf354b refactor: remove input 2024-06-22 18:27:16 -06:00
Mauricio Siu
c5ace67b3f Merge branch 'canary' into 57-dokploy-api-or-cli 2024-06-21 21:49:41 -06:00
Mauricio Siu
ae4226531e Merge branch 'canary' into 57-dokploy-api-or-cli 2024-06-12 00:23:53 -06:00
Mauricio Siu
b9bff95c3d feat: wip cli token authentication 2024-06-05 22:42:11 -06:00
42 changed files with 9895 additions and 101 deletions

View File

@@ -10,7 +10,7 @@ import { RedbuildApplication } from "../rebuild-application";
import { StartApplication } from "../start-application";
import { StopApplication } from "../stop-application";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
import { Terminal } from "lucide-react";
import { CheckCircle2, Terminal } from "lucide-react";
import { DeployApplication } from "./deploy-application";
import { ResetApplication } from "./reset-application";
interface Props {
@@ -55,8 +55,10 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.error("Error to update Auto Deploy");
});
}}
className="flex flex-row gap-2 items-center"
>
Autodeploy
{data?.autoDeploy && <CheckCircle2 className="size-4" />}
</Toggle>
<RedbuildApplication applicationId={applicationId} />
{data?.applicationStatus === "idle" ? (

View File

@@ -0,0 +1,73 @@
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { toast } from "sonner";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Label } from "@/components/ui/label";
import Link from "next/link";
import { ExternalLinkIcon } from "lucide-react";
export const GenerateToken = () => {
const { data, refetch } = api.auth.get.useQuery();
const { mutateAsync: generateToken, isLoading: isLoadingToken } =
api.auth.generateToken.useMutation();
return (
<Card className="bg-transparent">
<CardHeader className="flex flex-row gap-2 flex-wrap justify-between items-center">
<div>
<CardTitle className="text-xl">API/CLI</CardTitle>
<CardDescription>
Generate a token to access the API/CLI
</CardDescription>
</div>
<div className="flex flex-row gap-2 max-sm:flex-wrap items-end">
<span className="text-sm font-medium text-muted-foreground">
Swagger API:
</span>
<Link
href="/swagger"
target="_blank"
className="flex flex-row gap-2 items-center"
>
<span className="text-sm font-medium">View</span>
<ExternalLinkIcon className="size-4" />
</Link>
</div>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex flex-row gap-2 max-sm:flex-wrap justify-end items-end">
<div className="grid w-full gap-8">
<div className="flex flex-col gap-2">
<Label>Token</Label>
<ToggleVisibilityInput
placeholder="Token"
value={data?.token || ""}
disabled
/>
</div>
</div>
<Button
type="button"
isLoading={isLoadingToken}
onClick={async () => {
await generateToken().then(() => {
refetch();
toast.success("Token generated");
});
}}
>
Generate
</Button>
</div>
</CardContent>
</Card>
);
};

View File

@@ -51,6 +51,9 @@ const randomImages = [
export const ProfileForm = () => {
const { data, refetch } = api.auth.get.useQuery();
const { mutateAsync, isLoading } = api.auth.update.useMutation();
const { mutateAsync: generateToken, isLoading: isLoadingToken } =
api.auth.generateToken.useMutation();
const form = useForm<Profile>({
defaultValues: {
email: data?.email || "",

View File

@@ -32,7 +32,6 @@ export const ShowSettings = () => {
<ShowCertificates />
<WebDomain />
<WebServer />
<ShowUsers />
</>
)}

View File

@@ -38,6 +38,7 @@ const addPermissions = z.object({
canDeleteServices: z.boolean().optional().default(false),
canAccessToTraefikFiles: z.boolean().optional().default(false),
canAccessToDocker: z.boolean().optional().default(false),
canAccessToAPI: z.boolean().optional().default(false),
});
type AddPermissions = z.infer<typeof addPermissions>;
@@ -80,6 +81,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
canDeleteServices: data.canDeleteServices,
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
canAccessToDocker: data.canAccessToDocker,
canAccessToAPI: data.canAccessToAPI,
});
}
}, [form, form.formState.isSubmitSuccessful, form.reset, data]);
@@ -95,6 +97,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
accesedProjects: data.accesedProjects || [],
accesedServices: data.accesedServices || [],
canAccessToDocker: data.canAccessToDocker,
canAccessToAPI: data.canAccessToAPI,
})
.then(async () => {
toast.success("Permissions updated");
@@ -247,6 +250,26 @@ export const AddUserPermissions = ({ userId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="canAccessToAPI"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Access to API/CLI</FormLabel>
<FormDescription>
Allow the user to access to the API/CLI
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="accesedProjects"

View File

@@ -0,0 +1 @@
ALTER TABLE "auth" ADD COLUMN "token" text;

View File

@@ -0,0 +1 @@
ALTER TABLE "user" ADD COLUMN "canAccessToAPI" boolean DEFAULT false NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "user" ALTER COLUMN "token" DROP NOT NULL;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -106,6 +106,27 @@
"when": 1716715367982,
"tag": "0014_same_hammerhead",
"breakpoints": true
},
{
"idx": 15,
"version": "6",
"when": 1717564517104,
"tag": "0015_fearless_callisto",
"breakpoints": true
},
{
"idx": 16,
"version": "6",
"when": 1719109196484,
"tag": "0016_chunky_leopardon",
"breakpoints": true
},
{
"idx": 17,
"version": "6",
"when": 1719109531147,
"tag": "0017_yummy_norrin_radd",
"breakpoints": true
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.2.3",
"version": "v0.2.4",
"private": true,
"license": "AGPL-3.0-only",
"type": "module",
@@ -109,9 +109,11 @@
"slugify": "^1.6.6",
"sonner": "^1.4.0",
"superjson": "^2.2.1",
"swagger-ui-react": "^5.17.14",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
"tar-fs": "3.0.5",
"@dokploy/trpc-openapi": "0.0.4",
"use-resize-observer": "9.1.0",
"ws": "8.16.0",
"xterm-addon-fit": "^0.8.0",
@@ -129,6 +131,7 @@
"@types/qrcode": "^1.5.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/swagger-ui-react": "^4.18.3",
"@types/tar-fs": "2.0.4",
"@types/ws": "8.5.10",
"autoprefixer": "^10.4.14",

View File

@@ -5,14 +5,14 @@ import { createTRPCContext } from "@/server/api/trpc";
// export API handler
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
onError:
process.env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
);
}
: undefined,
router: appRouter,
createContext: createTRPCContext,
onError:
process.env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
);
}
: undefined,
});

View File

@@ -1,14 +1,26 @@
import { GenerateToken } from "@/components/dashboard/settings/profile/generate-token";
import { ProfileForm } from "@/components/dashboard/settings/profile/profile-form";
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { SettingsLayout } from "@/components/layouts/settings-layout";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
import type { GetServerSidePropsContext } from "next";
import React, { type ReactElement } from "react";
const Page = () => {
const { data } = api.auth.get.useQuery();
const { data: user } = api.user.byAuthId.useQuery(
{
authId: data?.id || "",
},
{
enabled: !!data?.id && data?.rol === "user",
},
);
return (
<div className="flex flex-col gap-4 w-full">
<ProfileForm />
{(user?.canAccessToAPI || data?.rol === "admin") && <GenerateToken />}
</div>
);
};

63
pages/swagger.tsx Normal file
View File

@@ -0,0 +1,63 @@
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { api } from "@/utils/api";
import { createServerSideHelpers } from "@trpc/react-query/server";
import type { GetServerSidePropsContext, NextPage } from "next";
import dynamic from "next/dynamic";
import "swagger-ui-react/swagger-ui.css";
import superjson from "superjson";
const SwaggerUI = dynamic(() => import("swagger-ui-react"), { ssr: false });
const Home: NextPage = () => {
const { data } = api.settings.getOpenApiDocument.useQuery();
return (
<div className="h-screen bg-white">
<SwaggerUI spec={data || {}} />
</div>
);
};
export default Home;
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req, res } = context;
const { user, session } = await validateRequest(context.req, context.res);
if (!user) {
return {
redirect: {
permanent: true,
destination: "/",
},
};
}
const helpers = createServerSideHelpers({
router: appRouter,
ctx: {
req: req as any,
res: res as any,
db: null as any,
session: session,
user: user,
},
transformer: superjson,
});
if (user.rol === "user") {
const result = await helpers.user.byAuthId.fetch({
authId: user.id,
});
if (!result.canAccessToAPI) {
return {
redirect: {
permanent: true,
destination: "/",
},
};
}
}
return {
props: {},
};
}

1597
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@ import { dockerRouter } from "./routers/docker";
import { composeRouter } from "./routers/compose";
import { registryRouter } from "./routers/registry";
import { clusterRouter } from "./routers/cluster";
/**
* This is the primary router for your server.
*
@@ -39,6 +40,7 @@ export const appRouter = createTRPCRouter({
redis: redisRouter,
mongo: mongoRouter,
mariadb: mariadbRouter,
compose: composeRouter,
user: userRouter,
domain: domainRouter,
destination: destinationRouter,
@@ -50,7 +52,6 @@ export const appRouter = createTRPCRouter({
security: securityRouter,
redirects: redirectsRouter,
port: portRouter,
compose: composeRouter,
registry: registryRouter,
cluster: clusterRouter,
});

View File

@@ -26,6 +26,7 @@ import {
updateAuthById,
verify2FA,
} from "../services/auth";
import { luciaToken } from "@/server/auth/token";
export const authRouter = createTRPCRouter({
createAdmin: publicProcedure
@@ -138,6 +139,23 @@ export const authRouter = createTRPCRouter({
return auth;
}),
generateToken: protectedProcedure.mutation(async ({ ctx, input }) => {
const auth = await findAuthById(ctx.user.authId);
if (auth.token) {
await luciaToken.invalidateSession(auth.token);
}
const session = await luciaToken.createSession(auth?.id || "", {
expiresIn: 60 * 60 * 24 * 30,
});
await updateAuthById(auth.id, {
token: session.id,
});
return auth;
}),
one: adminProcedure.input(apiFindOneAuth).query(async ({ input }) => {
const auth = await findAuthById(input.id);
return auth;
@@ -196,4 +214,7 @@ export const authRouter = createTRPCRouter({
});
return auth;
}),
verifyToken: protectedProcedure.mutation(async () => {
return true;
}),
});

View File

@@ -1,4 +1,8 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
cliProcedure,
createTRPCRouter,
protectedProcedure,
} from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiCreateProject,
@@ -54,6 +58,7 @@ export const projectRouter = createTRPCRouter({
});
}
}),
one: protectedProcedure
.input(apiFindOneProject)
.query(async ({ input, ctx }) => {

View File

@@ -41,6 +41,8 @@ import {
} from "../services/settings";
import { canAccessToTraefikFiles } from "../services/user";
import { recreateDirectory } from "@/server/utils/filesystem/directory";
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
import { appRouter } from "../root";
export const settingsRouter = createTRPCRouter({
reloadServer: adminProcedure.mutation(async () => {
@@ -242,5 +244,50 @@ export const settingsRouter = createTRPCRouter({
}
return readConfigInPath(input.path);
}),
getOpenApiDocument: protectedProcedure.query(
async ({ ctx }): Promise<unknown> => {
const protocol = ctx.req.headers["x-forwarded-proto"];
const url = `${protocol}://${ctx.req.headers.host}/api/trpc`;
const openApiDocument = generateOpenApiDocument(appRouter, {
title: "tRPC OpenAPI",
version: "1.0.0",
baseUrl: url,
docsUrl: `${url}/settings.getOpenApiDocument`,
tags: [
"admin",
"docker",
"compose",
"registry",
"cluster",
"user",
"domain",
"destination",
"backup",
"deployment",
"mounts",
"certificates",
"settings",
"security",
"redirects",
"port",
"project",
"application",
"mysql",
"postgres",
"redis",
"mongo",
"mariadb",
],
});
openApiDocument.info = {
title: "Dokploy API",
description: "Endpoints for dokploy",
version: getDokployVersion(),
};
return openApiDocument;
},
),
});
// apt-get install apache2-utils

View File

@@ -16,11 +16,15 @@ import { createTraefikConfig } from "@/server/utils/traefik/application";
import { docker } from "@/server/constants";
import { getAdvancedStats } from "@/server/monitoring/utilts";
import { validUniqueServerAppName } from "./project";
import { generatePassword } from "@/templates/utils";
import { generateAppName } from "@/server/db/schema/utils";
export type Application = typeof applications.$inferSelect;
export const createApplication = async (
input: typeof apiCreateApplication._type,
) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("app");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);

View File

@@ -14,10 +14,14 @@ import { COMPOSE_PATH } from "@/server/constants";
import { cloneGithubRepository } from "@/server/utils/providers/github";
import { cloneGitRepository } from "@/server/utils/providers/git";
import { validUniqueServerAppName } from "./project";
import { generateAppName } from "@/server/db/schema/utils";
import { generatePassword } from "@/templates/utils";
export type Compose = typeof compose.$inferSelect;
export const createCompose = async (input: typeof apiCreateCompose._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("compose");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);

View File

@@ -6,10 +6,14 @@ import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
import { generateAppName } from "@/server/db/schema/utils";
import { generatePassword } from "@/templates/utils";
export type Mariadb = typeof mariadb.$inferSelect;
export const createMariadb = async (input: typeof apiCreateMariaDB._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("mariadb");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);

View File

@@ -6,10 +6,14 @@ import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
import { generateAppName } from "@/server/db/schema/utils";
import { generatePassword } from "@/templates/utils";
export type Mongo = typeof mongo.$inferSelect;
export const createMongo = async (input: typeof apiCreateMongo._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("postgres");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);

View File

@@ -6,10 +6,15 @@ import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
import { generatePassword } from "@/templates/utils";
import { generateAppName } from "@/server/db/schema/utils";
export type MySql = typeof mysql.$inferSelect;
export const createMysql = async (input: typeof apiCreateMySql._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("mysql");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);

View File

@@ -6,10 +6,14 @@ import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
import { generatePassword } from "@/templates/utils";
import { generateAppName } from "@/server/db/schema/utils";
export type Postgres = typeof postgres.$inferSelect;
export const createPostgres = async (input: typeof apiCreatePostgres._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("postgres");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);

View File

@@ -6,11 +6,15 @@ import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
import { generateAppName } from "@/server/db/schema/utils";
import { generatePassword } from "@/templates/utils";
export type Redis = typeof redis.$inferSelect;
// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881
export const createRedis = async (input: typeof apiCreateRedis._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("redis");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);

View File

@@ -15,6 +15,8 @@ import superjson from "superjson";
import { ZodError } from "zod";
import { validateRequest } from "../auth/auth";
import type { Session, User } from "lucia";
import { validateBearerToken } from "../auth/token";
import type { OpenApiMeta } from "@dokploy/trpc-openapi";
/**
* 1. CONTEXT
@@ -59,9 +61,15 @@ const createInnerTRPCContext = (opts: CreateContextOptions) => {
*/
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;
// const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
const { session, user } = await validateRequest(req, res);
user;
let { session, user } = await validateBearerToken(req);
if (!session) {
const cookieResult = await validateRequest(req, res);
session = cookieResult.session;
user = cookieResult.user;
}
return createInnerTRPCContext({
req,
res,
@@ -88,19 +96,22 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
* errors on the backend.
*/
const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
const t = initTRPC
.meta<OpenApiMeta>()
.context<typeof createTRPCContext>()
.create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
/**
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
@@ -147,6 +158,20 @@ export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
});
});
export const cliProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session || !ctx.user || ctx.user.rol !== "admin") {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
// infers the `session` as non-nullable
session: ctx.session,
user: ctx.user,
// session: { ...ctx.session, user: ctx.user },
},
});
});
export const adminProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session || !ctx.user || ctx.user.rol !== "admin") {
throw new TRPCError({ code: "UNAUTHORIZED" });

View File

@@ -33,14 +33,17 @@ declare module "lucia" {
}
}
export type ReturnValidateToken = Promise<{
user: (User & { authId: string }) | null;
session: Session | null;
}>;
export async function validateRequest(
req: IncomingMessage,
res: ServerResponse,
): Promise<{
user: (User & { authId: string }) | null;
session: Session | null;
}> {
): ReturnValidateToken {
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
if (!sessionId) {
return {
user: null,

48
server/auth/token.ts Normal file
View File

@@ -0,0 +1,48 @@
import { Lucia } from "lucia/dist/core.js";
import type { IncomingMessage } from "node:http";
import { TimeSpan } from "lucia";
import { adapter, type ReturnValidateToken } from "./auth";
export const luciaToken = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: false,
},
},
sessionExpiresIn: new TimeSpan(365, "d"),
getUserAttributes: (attributes) => {
return {
email: attributes.email,
rol: attributes.rol,
secret: attributes.secret !== null,
};
},
});
export const validateBearerToken = async (
req: IncomingMessage,
): ReturnValidateToken => {
const authorizationHeader = req.headers.authorization;
const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
if (!sessionId) {
return {
user: null,
session: null,
};
}
const result = await luciaToken.validateSession(sessionId);
return {
session: result.session,
...((result.user && {
user: {
authId: result.user.id,
email: result.user.email,
rol: result.user.rol,
id: result.user.id,
secret: result.user.secret,
},
}) || {
user: null,
}),
};
};

View File

@@ -308,17 +308,12 @@ const createSchema = createInsertSchema(applications, {
networkSwarm: NetworkSwarmSchema.nullable(),
});
export const apiCreateApplication = createSchema
.pick({
name: true,
appName: true,
description: true,
projectId: true,
})
.transform((data) => ({
...data,
appName: `${data.appName}-${generatePassword(6)}` || generateAppName("app"),
}));
export const apiCreateApplication = createSchema.pick({
name: true,
appName: true,
description: true,
projectId: true,
});
export const apiFindOneApplication = createSchema
.pick({

View File

@@ -43,6 +43,7 @@ export const auth = pgTable("auth", {
rol: roles("rol").notNull(),
image: text("image").$defaultFn(() => generateRandomImage()),
secret: text("secret"),
token: text("token"),
is2FAEnabled: boolean("is2FAEnabled").notNull().default(false),
createdAt: text("createdAt")
.notNull()

View File

@@ -75,19 +75,13 @@ const createSchema = createInsertSchema(compose, {
composeType: z.enum(["docker-compose", "stack"]).optional(),
});
export const apiCreateCompose = createSchema
.pick({
name: true,
description: true,
projectId: true,
composeType: true,
appName: true,
})
.transform((data) => ({
...data,
appName:
`${data.appName}-${generatePassword(6)}` || generateAppName("compose"),
}));
export const apiCreateCompose = createSchema.pick({
name: true,
description: true,
projectId: true,
composeType: true,
appName: true,
});
export const apiCreateComposeByTemplate = createSchema
.pick({

View File

@@ -89,12 +89,7 @@ export const apiCreateMariaDB = createSchema
databaseUser: true,
databasePassword: true,
})
.required()
.transform((data) => ({
...data,
appName:
`${data.appName}-${generatePassword(6)}` || generateAppName("mariadb"),
}));
.required();
export const apiFindOneMariaDB = createSchema
.pick({

View File

@@ -81,12 +81,7 @@ export const apiCreateMongo = createSchema
databaseUser: true,
databasePassword: true,
})
.required()
.transform((data) => ({
...data,
appName:
`${data.appName}-${generatePassword(6)}` || generateAppName("postgres"),
}));
.required();
export const apiFindOneMongo = createSchema
.pick({

View File

@@ -87,12 +87,7 @@ export const apiCreateMySql = createSchema
databasePassword: true,
databaseRootPassword: true,
})
.required()
.transform((data) => ({
...data,
appName:
`${data.appName}-${generatePassword(6)}` || generateAppName("mysql"),
}));
.required();
export const apiFindOneMySql = createSchema
.pick({

View File

@@ -83,12 +83,7 @@ export const apiCreatePostgres = createSchema
projectId: true,
description: true,
})
.required()
.transform((data) => ({
...data,
appName:
`${data.appName}-${generatePassword(6)}` || generateAppName("postgres"),
}));
.required();
export const apiFindOnePostgres = createSchema
.pick({

View File

@@ -76,12 +76,7 @@ export const apiCreateRedis = createSchema
projectId: true,
description: true,
})
.required()
.transform((data) => ({
...data,
appName:
`${data.appName}-${generatePassword(6)}` || generateAppName("redis"),
}));
.required();
export const apiFindOneRedis = createSchema
.pick({

View File

@@ -1,13 +1,6 @@
import { auth } from "./auth";
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
// export const sessionTable = sqliteTable("session", {
// id: text("id").notNull().primaryKey(),
// userId: text("user_id")
// .notNull()
// .references(() => users.id),
// expiresAt: integer("expires_at").notNull(),
// });
export const sessionTable = pgTable("session", {
id: text("id").primaryKey(),
userId: text("user_id")

View File

@@ -32,6 +32,7 @@ export const users = pgTable("user", {
canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
canDeleteServices: boolean("canDeleteServices").notNull().default(false),
canAccessToDocker: boolean("canAccessToDocker").notNull().default(false),
canAccessToAPI: boolean("canAccessToAPI").notNull().default(false),
canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
.notNull()
.default(false),
@@ -105,6 +106,7 @@ export const apiAssignPermissions = createSchema
accesedServices: true,
canAccessToTraefikFiles: true,
canAccessToDocker: true,
canAccessToAPI: true,
})
.required();

View File

@@ -158,3 +158,14 @@
.animate-heartbeat {
animation: heartbeat 2.5s infinite;
}
@media (prefers-color-scheme: dark) {
.swagger-ui {
background-color: white;
}
.swagger-ui .info{
margin: 0px !important;
padding-top: 1rem !important;
}
}