Skip to content

Commit

Permalink
chore: Recreate sign out in AdminCP
Browse files Browse the repository at this point in the history
  • Loading branch information
aXenDeveloper committed Oct 28, 2024
1 parent 1273ef7 commit 394d820
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 117 deletions.
4 changes: 3 additions & 1 deletion packages/backend/src/core/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
SignAuthObj,
SignInAuthBody,
SignInAuthObj,
SignOutAuthBody,
SignUpAuthBody,
VerifyConfirmEmailAuthBody,
} from 'vitnode-shared/auth.dto';
Expand Down Expand Up @@ -73,8 +74,9 @@ export class AuthController {
async signOut(
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
@Body() body: SignOutAuthBody,
): Promise<void> {
await this.signOutService.signOut({ req, res });
await this.signOutService.signOut({ req, res, body });
}

@Post('sign_up')
Expand Down
58 changes: 43 additions & 15 deletions packages/backend/src/core/auth/services/sign_out.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { core_admin_sessions } from '@/database/schema/admins';
import { core_sessions } from '@/database/schema/sessions';
import { InternalDatabaseService } from '@/utils/database/internal_database.service';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { eq } from 'drizzle-orm';
import { Request, Response } from 'express';
import { SignOutAuthBody } from 'vitnode-shared/auth.dto';

@Injectable()
export class SignOutAuthService {
Expand All @@ -12,29 +14,55 @@ export class SignOutAuthService {
private readonly configService: ConfigService,
) {}

async signOut({ req, res }: { req: Request; res: Response }): Promise<void> {
async signOut({
req,
res,
body: { is_admin },
}: {
body: SignOutAuthBody;
req: Request;
res: Response;
}): Promise<void> {
const login_token = req.cookies[
this.configService.getOrThrow('cookies.login_token.name')
this.configService.getOrThrow(
`cookies.login_token.${is_admin ? 'admin.' : ''}name`,
)
] as string;

if (!login_token) return;

await this.databaseService.db
.delete(core_sessions)
.where(eq(core_sessions.login_token, login_token));
if (is_admin) {
await this.databaseService.db
.update(core_admin_sessions)
.set({
expires: new Date(),
})
.where(eq(core_admin_sessions.login_token, login_token));
} else {
await this.databaseService.db
.delete(core_sessions)
.where(eq(core_sessions.login_token, login_token));
}

res.clearCookie(this.configService.getOrThrow('cookies.login_token.name'), {
httpOnly: true,
secure: !!this.configService.getOrThrow('cookies.secure'),
domain: this.configService.getOrThrow('cookies.domain'),
path: '/',
sameSite: this.configService.getOrThrow('cookies.secure')
? 'none'
: 'lax',
});
res.clearCookie(
this.configService.getOrThrow(
`cookies.login_token.${is_admin ? 'admin.' : ''}name`,
),
{
httpOnly: true,
secure: !!this.configService.getOrThrow('cookies.secure'),
domain: this.configService.getOrThrow('cookies.domain'),
path: '/',
sameSite: this.configService.getOrThrow('cookies.secure')
? 'none'
: 'lax',
},
);

res.clearCookie(
this.configService.getOrThrow('cookies.login_token.user_id'),
this.configService.getOrThrow(
`cookies.login_token.${is_admin ? 'admin.admin_id' : 'user_id'}`,
),
{
httpOnly: true,
secure: !!this.configService.getOrThrow('cookies.secure'),
Expand Down
16 changes: 15 additions & 1 deletion packages/backend/src/helpers/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,12 @@ export class UserHelper {
return userReturnValues;
}

return {
const data: UserWithDangerousInfo = {
...userReturnValues,
email: user.email,
is_admin: await this.isAdmin({
user: userReturnValues,
}),
files_permissions: {
space_used: countStorageUsed,
allow_upload: user.group.files_allow_upload,
Expand All @@ -123,5 +126,16 @@ export class UserHelper {
: user.group.files_total_max_storage,
},
};

return data;
}

async isAdmin({ user }: { user: User }): Promise<boolean> {
return !!(await this.databaseService.db.query.core_admin_permissions.findFirst(
{
where: (table, { eq, or }) =>
or(eq(table.group_id, user.group.id), eq(table.user_id, user.id)),
},
));
}
}
18 changes: 9 additions & 9 deletions packages/frontend/src/hooks/sign/in/mutation-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ export const mutationApi = async (body: SignInAuthBody) => {
});

const cookie = await cookies();
if (!body.admin) {
const userIdFromCookie = cookie.get('vitnode-user-id')?.value;
if (userIdFromCookie) {
revalidateTags.session(+userIdFromCookie);
if (body.admin) {
const adminIdFromCookie = await getAdminIdCookie();
if (adminIdFromCookie) {
revalidateTags.sessionAdmin(+adminIdFromCookie);
}

await redirect('/');
await redirect('/admin/core/dashboard');

return;
}

const adminIdFromCookie = await getAdminIdCookie();
if (adminIdFromCookie) {
revalidateTags.sessionAdmin(+adminIdFromCookie);
const userIdFromCookie = cookie.get('vitnode-user-id')?.value;
if (userIdFromCookie) {
revalidateTags.session(+userIdFromCookie);
}

await redirect('/admin/core/dashboard');
await redirect('/');
};
19 changes: 16 additions & 3 deletions packages/frontend/src/hooks/sign/out/mutation-api.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
'use server';

import { fetcher } from '@/api/fetcher';
import { getUserIdCookie } from '@/api/get-user-id-cookie';
import { getAdminIdCookie, getUserIdCookie } from '@/api/get-user-id-cookie';
import { revalidateTags } from '@/api/revalidate-tags';
import { redirect } from '@/navigation';
import { SignOutAuthBody } from 'vitnode-shared/auth.dto';

export const mutationApi = async () => {
await fetcher({
export const mutationApi = async (body: SignOutAuthBody) => {
await fetcher<object, SignOutAuthBody>({
url: '/core/auth/sign_out',
method: 'DELETE',
body,
});

if (body.is_admin) {
const adminIdFromCookie = await getAdminIdCookie();
if (adminIdFromCookie) {
revalidateTags.sessionAdmin(+adminIdFromCookie);
}

await redirect('/admin/');

return;
}

const userIdFromCookie = await getUserIdCookie();
if (userIdFromCookie) {
revalidateTags.session(+userIdFromCookie);
Expand Down
5 changes: 3 additions & 2 deletions packages/frontend/src/hooks/sign/out/use-sign-out-api.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { useTranslations } from 'next-intl';
import { toast } from 'sonner';
import { SignOutAuthBody } from 'vitnode-shared/auth.dto';

import { mutationApi } from './mutation-api';

export const useSignOutApi = () => {
export const useSignOutApi = (body: SignOutAuthBody) => {
const t = useTranslations('core.global.errors');

const onSubmit = async () => {
try {
await mutationApi();
await mutationApi(body);
} catch (e) {
const { message } = e as Error;
if (message === 'NEXT_REDIRECT') return;
Expand Down
152 changes: 75 additions & 77 deletions packages/frontend/src/views/admin/layout/sidebar/item-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import {
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
Expand All @@ -17,96 +16,95 @@ import {
import { Link, usePathname } from '@/navigation';
import { ChevronRight } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { NavAuthAdminObj } from 'vitnode-shared/admin/auth.dto';
import React from 'react';
import { ParentNavAuthAdminObj } from 'vitnode-shared/admin/auth.dto';

import { TextAndIconsAsideAdmin } from './sidebar';

export const ItemNavSidebarAdmin = ({
plugin,
item,
textsAndIcons,
plugin_code,
}: {
plugin: NavAuthAdminObj;
item: ParentNavAuthAdminObj;
plugin_code: string;
textsAndIcons: TextAndIconsAsideAdmin[];
}) => {
const [open, setOpen] = React.useState(false);
const t = useTranslations();
const pathname = usePathname();
const textAndIcon = textsAndIcons.find(
el => el.id === item.code && el.plugin_code === plugin_code,
);
if (!textAndIcon) return null;
const href = `/admin/${plugin_code}/${item.code}`;

return (
<SidebarMenu>
{plugin.nav.map(item => {
const textAndIcon = textsAndIcons.find(
el => el.id === item.code && el.plugin_code === plugin.code,
);
if (!textAndIcon) return null;
const href = `/admin/${plugin.code}/${item.code}`;
const button = (
<SidebarMenuButton
asChild
isActive={pathname.startsWith(href)}
onClick={() => {
setOpen(true);
}}
>
<Link href={href}>
{textAndIcon.icon}
<span>{textAndIcon.text}</span>
</Link>
</SidebarMenuButton>
);

const button = (
<SidebarMenuButton asChild isActive={pathname.startsWith(href)}>
<Link href={href}>
{textAndIcon.icon}
<span>{textAndIcon.text}</span>
</Link>
</SidebarMenuButton>
);
if (!item.children?.length) {
return <SidebarMenuItem>{button}</SidebarMenuItem>;
}

if (!item.children?.length) {
return (
<SidebarMenuItem key={`${plugin.code}_${item.code}`}>
{button}
</SidebarMenuItem>
);
}
return (
<Collapsible
asChild
defaultOpen={pathname.startsWith(href)}
onOpenChange={setOpen}
open={open}
>
<SidebarMenuItem>
{button}
<>
<CollapsibleTrigger asChild>
<SidebarMenuAction className="data-[state=open]:rotate-90">
<ChevronRight />
<span className="sr-only">{t('core.global.sidebar.toggle')}</span>
</SidebarMenuAction>
</CollapsibleTrigger>

return (
<Collapsible
asChild
defaultOpen={pathname.startsWith(href)}
key={`${plugin.code}_${item.code}`}
>
<SidebarMenuItem>
{button}
<>
<CollapsibleTrigger asChild>
<SidebarMenuAction className="data-[state=open]:rotate-90">
<ChevronRight />
<span className="sr-only">
{t('core.global.sidebar.toggle')}
</span>
</SidebarMenuAction>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.children.map(child => {
const textAndIcon = textsAndIcons.find(
el =>
el.id === `${item.code}_${child.code}` &&
el.plugin_code === plugin.code,
);
if (!textAndIcon) return null;
const href = `/admin/${plugin.code}/${item.code}/${child.code}`;
<CollapsibleContent>
<SidebarMenuSub>
{item.children.map(child => {
const textAndIcon = textsAndIcons.find(
el =>
el.id === `${item.code}_${child.code}` &&
el.plugin_code === plugin_code,
);
if (!textAndIcon) return null;
const href = `/admin/${plugin_code}/${item.code}/${child.code}`;

return (
<SidebarMenuSubItem
key={`${plugin.code}_${item.code}_${child.code}`}
>
<SidebarMenuSubButton
asChild
isActive={pathname.startsWith(href)}
>
<Link href={href}>
<span>{textAndIcon.text}</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
);
})}
</SidebarMenuSub>
</CollapsibleContent>
</>
</SidebarMenuItem>
</Collapsible>
);
})}
</SidebarMenu>
return (
<SidebarMenuSubItem
key={`${plugin_code}_${item.code}_${child.code}`}
>
<SidebarMenuSubButton
asChild
isActive={pathname.startsWith(href)}
>
<Link href={href}>
<span>{textAndIcon.text}</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
);
})}
</SidebarMenuSub>
</CollapsibleContent>
</>
</SidebarMenuItem>
</Collapsible>
);
};
Loading

0 comments on commit 394d820

Please sign in to comment.