auth.ts
import { expressjwt } from "express-jwt";
import reviewRepository from "../repositories/reviewRepository";
import {
AuthenticationError,
ForbiddenError,
NotfoundError,
ValidationError,
} from "../types/errors";
import { NextFunction, Request, Response } from "express";
function throwUnauthorizedError() {
// 인증되지 않은 경우 401 에러를 발생시키는 함수
const error = new AuthenticationError("Unauthorized");
throw error;
}
async function verifySessionLogin(
req: Request,
res: Response,
next: NextFunction,
) {
try {
if (!req.isAuthenticated()) {
throwUnauthorizedError();
}
next();
} catch (error) {
next(error);
}
}
const verifyAccessToken = expressjwt({
secret: process.env.JWT_SECRET!,
algorithms: ["HS256"],
});
const verifyRefreshToken = expressjwt({
secret: process.env.JWT_SECRET!,
algorithms: ["HS256"],
getToken: (req) => req.cookies.refreshToken,
});
async function verifyReviewAuth(
req: Request<{ id: string }>,
res: Response,
next: NextFunction,
) {
const { id: reviewId } = req.params;
try {
const review = await reviewRepository.getById(+reviewId);
if (!review) {
const error = new NotFoundError("Review not found");
throw error;
}
// expressjJwt와 passport-jwt 둘 다 지원
const userId = req.user?.id || req.auth?.userId;
if (!userId) {
const error new AuthenticationError("User not authenticated");
throw error;
}
if (review.authorId !== userId) {
const error = new ForbiddenError("Forbidden");
throw error;
}
// 인증 성공 시 다음 미들웨어로 이동
next();
} catch (error) {
// 에러 발생 시 에러 핸들러로 전파
return next(error);
}
}
function validateEmailAndPassword(
req: Request<{}, {}, { email: string; password: string }>,
res: Response,
next: NextFunction,
) {
const { email, password } = req.body;
if (!email || !password) {
const error = new ValidationError("email, password가 모두 필요합니다.");
throw error;
}
next();
}
export default {
verifySessionLogin,
verifyAccessToken,
verifyReviewAuth,
verifyRefreshToken,
validateEmailAndPassword,
};
errorHandler.ts
import { ErrorRequestHandler } from "express";
const errorHandler: ErrorRequestHandler = (error, req, res, next) => {
if (error.name === "UnauthorizedError") {
res.status(401).send("invalid token...");
}
const status = error.code ?? 500;
console.error(error);
res.status(status).json({
path: req.path,
method: req.method,
message: error.message ?? "Internal Server Error",
data: error.data ?? undefined,
date: new Date();
});
};
export default errorHandler;
middlewares/passport/jwtStrategy.ts
import {
Strategy as JwtStrategy,
ExtractJwt,
VerifyCallback,
} from "passport-jwt";
import userService from "../../services/userService";
import { Request } from "express";
import { DoneCallback } from "passport";
const accessTokenOptions = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET!,
};
const cookieExtractor = function (req: Request) {
var token = null;
if (req && req.cookies) {
token = req.cookies["refreshToken"];
}
return token;
};
const refreshTokenOptions = {
jwtFromRequest: cookieExtractor,
secretOrKey: process.env.JWT_SECRET!,
};
type JwtPayload = {
userId: string;
};
async function jwtVerify(payload: JwtPayload, done: DoneCallback) {
try {
const user = await userService.getUserById(+payload.userId);
if (!user) {
return done(null, false);
}
return done(null, user);
} catch (error) {
return done(error);
}
}
const accessTokenStrategy = new JwtStrategy(accessTokenOptions, jwtVerify);
const refreshTokenStrategy = new JwtStrategy(refreshTokenOptions, jwtVerify);
export default {
accessTokenStrategy,
refreshTokenStrategy,
};
middlewares/passport/localStrategy.ts
import { Strategy as LocalStrategy } from "passport-local";
import userService from "../../services/userService";
const localStrategy = new LocalStrategy(
{
usernameField: "email",
},
async (email, password, done) => {
try {
const user = await userService.getUser(email, password);
if (!user) {
return done(null, false);
}
return done(null, user);
} catch (error) {
return done(error);
}
},
};
export default localStrategy;