Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/auth/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Request, Response } from "express";
import AuthService from "../services/auth.service";
import AuthService, { assertAllowedRedirectUri } from "../services/auth.service";
import { AppError } from "../../errors/AppError";
import { CompleteSignupDto } from "../dtos/complete-signup.dto";
import { validate } from "class-validator";
Expand Down Expand Up @@ -318,7 +318,7 @@ class AuthController {

async kakaoToken(req: Request, res: Response) {
try {
const { code } = req.body;
const { code, redirect_uri } = req.body;

if (!code) {
return res.status(400).json({
Expand All @@ -328,7 +328,8 @@ class AuthController {
});
}

const result = await AuthService.exchangeKakaoToken(code);
const validatedRedirectUri = assertAllowedRedirectUri(redirect_uri);
const result = await AuthService.exchangeKakaoToken(code, validatedRedirectUri);

res.status(200).json({
message: "카카였 둜그인이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.",
Expand All @@ -354,7 +355,7 @@ class AuthController {

async googleToken(req: Request, res: Response) {
try {
const { code } = req.body;
const { code, redirect_uri } = req.body;

if (!code) {
return res.status(400).json({
Expand All @@ -364,7 +365,8 @@ class AuthController {
});
}

const result = await AuthService.exchangeGoogleToken(code);
const validatedRedirectUri = assertAllowedRedirectUri(redirect_uri);
const result = await AuthService.exchangeGoogleToken(code, validatedRedirectUri);

res.status(200).json({
message: "ꡬ글 둜그인이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.",
Expand All @@ -390,7 +392,7 @@ class AuthController {

async naverToken(req: Request, res: Response) {
try {
const { code } = req.body;
const { code, redirect_uri } = req.body;

if (!code) {
return res.status(400).json({
Expand All @@ -400,7 +402,8 @@ class AuthController {
});
}

const result = await AuthService.exchangeNaverToken(code);
const validatedRedirectUri = assertAllowedRedirectUri(redirect_uri);
const result = await AuthService.exchangeNaverToken(code, validatedRedirectUri);

res.status(200).json({
message: "넀이버 둜그인이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.",
Expand Down
15 changes: 15 additions & 0 deletions src/auth/routes/auth.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,16 @@ router.post("/complete-signup", AuthController.completeSignup);
* type: object
* required:
* - code
* - redirect_uri
* properties:
* code:
* type: string
* description: 카카였 OAuth μΈμ¦μ½”λ“œ
* example: "abc123def456ghi789"
* redirect_uri:
* type: string
* description: OAuth 인증 μ‹œμž‘ μ‹œ μ‚¬μš©ν•œ redirect_uri (μ„œλ²„ ν™”μ΄νŠΈλ¦¬μŠ€νŠΈμ— λ“±λ‘λ˜μ–΄ μžˆμ–΄μ•Ό 함)
* example: "https://promptplace-develop.vercel.app/auth/callback"
* responses:
* 200:
* description: 카카였 둜그인 성곡
Expand Down Expand Up @@ -425,11 +430,16 @@ router.post("/kakao/token", AuthController.kakaoToken);
* type: object
* required:
* - code
* - redirect_uri
* properties:
* code:
* type: string
* description: ꡬ글 OAuth μΈμ¦μ½”λ“œ
* example: "4/0AfJohXn..."
* redirect_uri:
* type: string
* description: OAuth 인증 μ‹œμž‘ μ‹œ μ‚¬μš©ν•œ redirect_uri (μ„œλ²„ ν™”μ΄νŠΈλ¦¬μŠ€νŠΈμ— λ“±λ‘λ˜μ–΄ μžˆμ–΄μ•Ό 함)
* example: "https://promptplace-develop.vercel.app/auth/callback"
* responses:
* 200:
* description: ꡬ글 둜그인 성곡
Expand Down Expand Up @@ -560,11 +570,16 @@ router.post("/google/token", AuthController.googleToken);
* type: object
* required:
* - code
* - redirect_uri
* properties:
* code:
* type: string
* description: 넀이버 OAuth μΈμ¦μ½”λ“œ
* example: "abc123def456ghi789"
* redirect_uri:
* type: string
* description: OAuth 인증 μ‹œμž‘ μ‹œ μ‚¬μš©ν•œ redirect_uri (μ„œλ²„ ν™”μ΄νŠΈλ¦¬μŠ€νŠΈμ— λ“±λ‘λ˜μ–΄ μžˆμ–΄μ•Ό 함)
* example: "https://promptplace-develop.vercel.app/auth/callback"
* responses:
* 200:
* description: 넀이버 둜그인 성곡
Expand Down
60 changes: 30 additions & 30 deletions src/auth/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@ interface Tokens {
refreshToken: string;
}

const getCallbackUrl = (provider: 'KAKAO' | 'GOOGLE' | 'NAVER') => {
const isDev = process.env.NODE_ENV !== 'production';

switch (provider) {
case 'KAKAO':
return isDev
? process.env.KAKAO_CALLBACK_URL_DEV
: process.env.KAKAO_CALLBACK_URL;
case 'GOOGLE':
return isDev
? process.env.GOOGLE_CALLBACK_URL_DEV
: process.env.GOOGLE_CALLBACK_URL;
case 'NAVER':
return isDev
? process.env.NAVER_CALLBACK_URL_DEV
: process.env.NAVER_CALLBACK_URL;
default:
throw new Error('Unknown provider');
const parseAllowedRedirectUris = (): Set<string> => {
const raw = process.env.OAUTH_ALLOWED_REDIRECT_URIS ?? "";
return new Set(
raw
.split(",")
.map((uri) => uri.trim())
.filter(Boolean)
);
};

export const assertAllowedRedirectUri = (redirectUri: string | undefined): string => {
if (!redirectUri) {
throw new AppError("redirect_uriκ°€ ν•„μš”ν•©λ‹ˆλ‹€.", 400, "BadRequest");
}
const allowed = parseAllowedRedirectUris();
if (!allowed.has(redirectUri)) {
throw new AppError("ν—ˆμš©λ˜μ§€ μ•Šμ€ redirect_uriμž…λ‹ˆλ‹€.", 400, "BadRequest");
}
return redirectUri;
};

class AuthService {
Expand Down Expand Up @@ -109,10 +109,10 @@ class AuthService {
};
}

async exchangeKakaoToken(code: string) {
async exchangeKakaoToken(code: string, redirectUri: string) {
try {
// κΈ°μ‘΄ Passport 카카였 μ „λž΅μ„ ν™œμš©ν•˜μ—¬ μ‚¬μš©μž 정보 처리
const user = await this.handleKakaoUserFromCode(code);
const user = await this.handleKakaoUserFromCode(code, redirectUri);

const { accessToken, refreshToken } = await this.generateTokens(user);

Expand All @@ -137,10 +137,10 @@ class AuthService {
}
}

async exchangeGoogleToken(code: string) {
async exchangeGoogleToken(code: string, redirectUri: string) {
try {
// κΈ°μ‘΄ Passport ꡬ글 μ „λž΅μ„ ν™œμš©ν•˜μ—¬ μ‚¬μš©μž 정보 처리
let user = await this.handleGoogleUserFromCode(code);
let user = await this.handleGoogleUserFromCode(code, redirectUri);

if (!isActive(user.status)) {
const inactive = user.inactive_date
Expand Down Expand Up @@ -191,10 +191,10 @@ class AuthService {
}
}

async exchangeNaverToken(code: string) {
async exchangeNaverToken(code: string, redirectUri: string) {
try {
// κΈ°μ‘΄ Passport 넀이버 μ „λž΅μ„ ν™œμš©ν•˜μ—¬ μ‚¬μš©μž 정보 처리
let user = await this.handleNaverUserFromCode(code);
let user = await this.handleNaverUserFromCode(code, redirectUri);

if (!isActive(user.status)) {
const inactive = user.inactive_date
Expand Down Expand Up @@ -247,7 +247,7 @@ class AuthService {
}

// 카카였 μΈμ¦μ½”λ“œλ‘œ μ‚¬μš©μž 처리 (κΈ°μ‘΄ 둜직 μž¬μ‚¬μš©)
private async handleKakaoUserFromCode(code: string): Promise<any> {
private async handleKakaoUserFromCode(code: string, redirectUri: string): Promise<any> {
try {
// μΉ΄μΉ΄μ˜€μ—μ„œ μ•‘μ„ΈμŠ€ 토큰 λ°›κΈ°
const tokenResponse = await fetch("https://kauth.kakao.com/oauth/token", {
Expand All @@ -258,7 +258,7 @@ class AuthService {
client_id: process.env.KAKAO_CLIENT_ID!,
client_secret: process.env.KAKAO_CLIENT_SECRET!,
code: code,
redirect_uri: getCallbackUrl('KAKAO')!,
redirect_uri: redirectUri,
}),
});

Expand Down Expand Up @@ -312,7 +312,7 @@ class AuthService {
}

// ꡬ글 μΈμ¦μ½”λ“œλ‘œ μ‚¬μš©μž 처리 (κΈ°μ‘΄ 둜직 μž¬μ‚¬μš©)
private async handleGoogleUserFromCode(code: string): Promise<any> {
private async handleGoogleUserFromCode(code: string, redirectUri: string): Promise<any> {
try {
// κ΅¬κΈ€μ—μ„œ μ•‘μ„ΈμŠ€ 토큰 λ°›κΈ°
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
Expand All @@ -323,7 +323,7 @@ class AuthService {
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
code: code,
redirect_uri: getCallbackUrl('GOOGLE')!,
redirect_uri: redirectUri,
}),
});

Expand Down Expand Up @@ -380,7 +380,7 @@ class AuthService {
}

// 넀이버 μΈμ¦μ½”λ“œλ‘œ μ‚¬μš©μž 처리 (κΈ°μ‘΄ 둜직 μž¬μ‚¬μš©)
private async handleNaverUserFromCode(code: string): Promise<any> {
private async handleNaverUserFromCode(code: string, redirectUri: string): Promise<any> {
try {
// λ„€μ΄λ²„μ—μ„œ μ•‘μ„ΈμŠ€ 토큰 λ°›κΈ°
const tokenResponse = await fetch(
Expand All @@ -393,7 +393,7 @@ class AuthService {
client_id: process.env.NAVER_CLIENT_ID!,
client_secret: process.env.NAVER_CLIENT_SECRET!,
code: code,
redirect_uri: getCallbackUrl('NAVER')!,
redirect_uri: redirectUri,
}),
}
);
Expand Down
24 changes: 21 additions & 3 deletions swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,19 @@
"schema": {
"type": "object",
"required": [
"code"
"code",
"redirect_uri"
],
"properties": {
"code": {
"type": "string",
"description": "카카였 OAuth μΈμ¦μ½”λ“œ",
"example": "abc123def456ghi789"
},
"redirect_uri": {
"type": "string",
"description": "OAuth 인증 μ‹œμž‘ μ‹œ μ‚¬μš©ν•œ redirect_uri (μ„œλ²„ ν™”μ΄νŠΈλ¦¬μŠ€νŠΈμ— λ“±λ‘λ˜μ–΄ μžˆμ–΄μ•Ό 함)",
"example": "https://promptplace-develop.vercel.app/auth/callback"
}
}
}
Expand Down Expand Up @@ -572,13 +578,19 @@
"schema": {
"type": "object",
"required": [
"code"
"code",
"redirect_uri"
],
"properties": {
"code": {
"type": "string",
"description": "ꡬ글 OAuth μΈμ¦μ½”λ“œ",
"example": "4/0AfJohXn..."
},
"redirect_uri": {
"type": "string",
"description": "OAuth 인증 μ‹œμž‘ μ‹œ μ‚¬μš©ν•œ redirect_uri (μ„œλ²„ ν™”μ΄νŠΈλ¦¬μŠ€νŠΈμ— λ“±λ‘λ˜μ–΄ μžˆμ–΄μ•Ό 함)",
"example": "https://promptplace-develop.vercel.app/auth/callback"
}
}
}
Expand Down Expand Up @@ -760,13 +772,19 @@
"schema": {
"type": "object",
"required": [
"code"
"code",
"redirect_uri"
],
"properties": {
"code": {
"type": "string",
"description": "넀이버 OAuth μΈμ¦μ½”λ“œ",
"example": "abc123def456ghi789"
},
"redirect_uri": {
"type": "string",
"description": "OAuth 인증 μ‹œμž‘ μ‹œ μ‚¬μš©ν•œ redirect_uri (μ„œλ²„ ν™”μ΄νŠΈλ¦¬μŠ€νŠΈμ— λ“±λ‘λ˜μ–΄ μžˆμ–΄μ•Ό 함)",
"example": "https://promptplace-develop.vercel.app/auth/callback"
}
}
}
Expand Down
Loading