디렉토리 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
src/
├── types/
│ └── model.d.ts
├── passports/
│ ├── index.ts
│ └── strategy/
│ ├── local.strategy.ts
├── models/
│ └── user.model.ts
├── utils/
│ └── crypto.until.ts
├── tsconfig.json
└── app.ts
index.js
index.js
파일을 index.ts
파일로 변환해보겠습니다.
확장자만 변경
1
2
3
4
5
6
7
import passport from "passport";
import localStrategy from "./strategy/local.strategy.js";
// 로컬 로그인
passport.use("local", localStrategy);
export default passport;
local.strategy.ts
local.strategy.js
파일을 local.strategy.ts
파일로 변환해보겠습니다.
passport-local 라이브러리는 타입 정의 파일을 제공합니다.
설치
1
npm i -D @types/passport-local
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// local.strategy.ts
import { User } from "types/model/user";
import { Strategy as LocalStrategy } from "passport-local";
import { userPasswordVerify } from "utils/crypto.util.js";
import userStorage from "models/user.model.js";
const localStrategy = new LocalStrategy(
{
usernameField: "id",
passwordField: "password",
session: false,
},
async (id, password, done) => {
try {
let user: User | null = await userStorage.getUserById(id);
if (!user) {
return done(null, false, {
message: "가입되지 않은 회원입니다.",
});
}
let checkedPassword = userPasswordVerify(password, user.password);
if (checkedPassword) {
return done(null, user.id);
}
done(null, false, {
message: "비밀번호가 틀렸습니다",
});
} catch (err) {
done(err);
}
}
);
export default localStrategy;
model.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
// User 타입 정의
export interface User {
user_id: number;
id: string;
password: string;
}
// UserInfo 타입 정의
export interface UserInfo {
user_id: number;
nickname: string; // 사용자 이름
}
crypto.util.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import crypto from "crypto";
// 암호화된 비밀번호 생성 함수
const createPasswordAndSalt = (userInputPassword: string): string => {
try {
const salt = crypto.randomBytes(32).toString("base64");
// 매개변수 : 입력값, salt, 반복 횟수, Key의 길이, 해시알고리즘
const key = crypto
.pbkdf2Sync(userInputPassword, salt, 200, 8, "sha512")
.toString("base64");
return `${key}$${salt}`;
// 암호화된 비밀번호와 임의로 생성된 salt를 '$'로 구분
} catch (err) {
throw err;
}
};
// 비밀번호 검증 함수
// 사용자의 입력 값과 데이터베이스에 저장된 암호화된 값을 비교해 검증합니다.
const userPasswordVerify = (
userInputPassword: string,
storedPasswordAndSalt: string
): boolean => {
try {
const [encrypted, salt] = storedPasswordAndSalt.split("$");
const userInputEncrypted = crypto
.pbkdf2Sync(userInputPassword, salt, 200, 8, "sha512")
.toString("base64");
if (userInputEncrypted === encrypted) {
return true;
} else {
return false;
}
} catch (err) {
throw err;
}
};
export { createPasswordAndSalt, userPasswordVerify };
user.model.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import pool from "config/dbPool.js";
import { RowDataPacket } from "mysql2";
import { User, UserInfo } from "types/model";
class userStorage {
static async getUserById(id: string): Promise<User | null> {
const conn = await pool.getConnection();
try {
const [rows] = await conn.query<RowDataPacket[]>(
"SELECT * FROM user WHERE id = ?",
[id]
);
console.log(rows);
if (rows.length === 0) return null;
return rows[0] as User;
} catch (err) {
throw err;
} finally {
conn.release();
}
}
static async getUserInfo(userId: number): Promise<UserInfo | null> {
const conn = await pool.getConnection();
try {
const [rows] = await conn.query<RowDataPacket[]>(
"SELECT * FROM userInfo WHERE user_id = ?",
[userId]
);
if (rows.length === 0) return null;
return rows[0] as UserInfo;
} catch (err) {
throw err;
} finally {
conn.release();
}
}
static async createUser(userInfo: {
id: string;
password: string;
nickname: string;
}): Promise<void> {
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
const [rows] = await conn.query<RowDataPacket[]>(
"INSERT INTO user (id, password) VALUES (?, ?)",
[userInfo.id, userInfo.password]
);
await conn.query(
"INSERT INTO userInfo (user_id, nickname) VALUES (?, ?)",
[rows[0].insertId, userInfo.nickname]
);
await conn.commit();
return;
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release();
}
}
}
export default userStorage;
파일 하나씩 ts 파일로 변환하면서 모듈에서 타입 정의 파일을 제공하지 않는다면 따로 정의합니다.