Skip to content

Commit

Permalink
Feat/admin (#139)
Browse files Browse the repository at this point in the history
* feat: textarea 추가

Signed-off-by: Jeong-Rae <kkwjdfo@gmail.com>

* feat: stage, question 컨테이너 추가 삭제 구현

Signed-off-by: Jeong-Rae <kkwjdfo@gmail.com>

* feat: 미들웨어 코드 수정

Signed-off-by: Jeong-Rae <kkwjdfo@gmail.com>

* feat: 관리자 페이지 방어기능 추가

Signed-off-by: Jeong-Rae <kkwjdfo@gmail.com>

---------

Signed-off-by: Jeong-Rae <kkwjdfo@gmail.com>
  • Loading branch information
Jeong-Rae committed May 4, 2024
1 parent fc36cc9 commit 66ae329
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/components/uploadStage/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const Button: React.FC<ButtonProps> = ({id, labelText, onClick, className = "",
<button
id={id}
onClick={onClick}
className={`block min-h-4 p-2 text-sm font-medium text-white bg-blue-500 rounded-lg border border-blue-500 hover:bg-blue-600 focus:outline-2 focus:outline-blue-600 ${className}`}
className={`block w-full min-h-4 p-2 text-sm text-white bg-blue-500 rounded-lg border border-blue-500 hover:bg-blue-600 focus:outline-2 focus:outline-blue-600 ${className}`}
disabled={disabled}
>
{labelText}
Expand Down
2 changes: 1 addition & 1 deletion src/components/uploadStage/Textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const Textarea: React.FC<TextAreaProps> = ({labelText, message, onChange, placeh
}) => {
return (
<div className="w-4/5 m-4 space-y-2 flex flex-1 flex-col">
<label htmlFor="message" className="block ml-1 text-sm font-medium text-gray-900">
<label htmlFor="message" className="block ml-1 text-sm font-bold text-gray-900">
{labelText}
</label>
<textarea
Expand Down
112 changes: 76 additions & 36 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import {
} from "next/dist/compiled/@edge-runtime/cookies";
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import {notFound} from "next/navigation";

import {isAdmin} from "@/utils/protection/protectAdminAccess";

const url = process.env.NEXT_PUBLIC_BASE_API;
const protectedPaths = ["/problem", "/challengelist"];
const playerAccessiblePaths = ["/problem", "/challengelist"];
const adminAccessiblePaths = ["/admin"];

const ReAccessToken = async (refreshToken: string) => {
try {
Expand All @@ -28,41 +32,24 @@ const ReAccessToken = async (refreshToken: string) => {
}
}


export async function middleware(request: NextRequest) {
const { nextUrl, cookies } = request;
const { pathname, host } = nextUrl;
const accessToken = cookies.get("POL_ACCESS_TOKEN");
const refreshToken = cookies.get("POL_REFRESH_TOKEN");
if (accessToken === undefined && refreshToken !== undefined) {
if (!accessToken) {
const token = await ReAccessToken(refreshToken.value)
if (token) {
const now = new Date();
const time = now.getTime();
const response = NextResponse.redirect(request.url);
response.cookies.set({
name: "POL_ACCESS_TOKEN",
value: token,
domain: "",
expires: time + 1000 * 60 * 60
});
applySetCookie(request, response);
return response;
}
}
}
if (protectedPaths.some(path => pathname.startsWith(path)) && accessToken === undefined) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', pathname);
return NextResponse.redirect(loginUrl);
async function refreshAccessToken(request: NextRequest, refreshToken: string): Promise<NextResponse | null> {
const token = await ReAccessToken(refreshToken);
if (token) {
const now = new Date();
const time = now.getTime();
const response = NextResponse.redirect(request.url);
response.cookies.set({
name: "POL_ACCESS_TOKEN",
value: token,
domain: "",
expires: time + 1000 * 60 * 60
});
applySetCookie(request, response);
return response;
}
return null;
}

export const config = {
matcher: '/:path*',
};

function applySetCookie(req: NextRequest, res: NextResponse): void {
const resCookies = new ResponseCookies(res.headers);
const newReqHeaders = new Headers(req.headers);
Expand All @@ -74,10 +61,63 @@ function applySetCookie(req: NextRequest, res: NextResponse): void {
request: { headers: newReqHeaders },
}).headers.forEach((value, key) => {
if (
key === "x-middleware-override-headers" ||
key.startsWith("x-middleware-request-")
key === "x-middleware-override-headers" ||
key.startsWith("x-middleware-request-")
) {
res.headers.set(key, value);
}
});
}
}

const isPlayerAccessiblePath = (pathname: string): boolean => {
return playerAccessiblePaths.some(path => pathname.startsWith(path));
}

const redirectToLogin = (request: NextRequest, pathname: string): NextResponse => {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', pathname);
return NextResponse.redirect(loginUrl);
}

const isAdminAccessiblePath = (pathname: string): boolean => {
return adminAccessiblePaths.some(path => pathname.startsWith(path));
}

const redirectToNotFound = (request: NextRequest): NextResponse => {
const url = new URL('/not-found', request.url);
return NextResponse.rewrite(url);
}

export async function middleware(request: NextRequest) {
const { nextUrl, cookies } = request;
const { pathname, host } = nextUrl;
const accessToken = cookies.get("POL_ACCESS_TOKEN");
const refreshToken = cookies.get("POL_REFRESH_TOKEN");

if (!accessToken && refreshToken) {
const response = await refreshAccessToken(request, refreshToken.value);
if (response) {
return response;
}
}

if (isPlayerAccessiblePath(pathname) && !accessToken) {
return redirectToLogin(request, pathname);
}

if (isAdminAccessiblePath(pathname)) {
if (!accessToken) {
return redirectToLogin(request, pathname);
}
const isAdminPlayer = await isAdmin(accessToken.value);
if (!isAdminPlayer) {
return redirectToNotFound(request);
}
}

return NextResponse.next();
}

export const config = {
matcher: '/:path*',
};
5 changes: 5 additions & 0 deletions src/types/basicResponseDto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface BasicResponseDto {
"response": string
"error": string,
"detail": string
}
23 changes: 23 additions & 0 deletions src/utils/protection/protectAdminAccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {BasicResponseDto} from "@/types/basicResponseDto";

const url = process.env.NEXT_PUBLIC_BASE_API;
export const isAdmin = async (accessToken: string) => {
try {
const response = await fetch(`${url}/api/players/me/admin`, {
credentials: "include",
headers: {
Authorization: `Bearer ${accessToken}`,
},
});

if (!response.ok) {
return false;
}

const body: BasicResponseDto = await response.json();

return body.response === "true";
} catch (error) {
console.log(error);
}
}

0 comments on commit 66ae329

Please sign in to comment.