@ahhaohho/auth-middleware 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/constants/permissions.js +317 -0
- package/src/index.js +19 -1
- package/src/middleware/apiKey.js +38 -10
- package/src/middleware/authorization.js +3 -27
package/package.json
CHANGED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 역할 및 권한 상수 정의
|
|
3
|
+
*
|
|
4
|
+
* 권한 명명 규칙: {서비스}:{리소스}:{액션}[:범위]
|
|
5
|
+
* - 서비스: challenge, world, gallery, community, membership, auth, ai, search, notification
|
|
6
|
+
* - 리소스: content, progress, project, user, profile 등
|
|
7
|
+
* - 액션: read, write, delete, upload, sync 등
|
|
8
|
+
* - 범위: own (선택적, 본인 리소스만)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 역할 정의
|
|
13
|
+
*/
|
|
14
|
+
const ROLES = {
|
|
15
|
+
ADMIN: 'admin',
|
|
16
|
+
SERVICE: 'service',
|
|
17
|
+
CREATOR: 'creator',
|
|
18
|
+
FLC_MEMBER: 'flc-member',
|
|
19
|
+
USER: 'user'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 접근 범위 정의
|
|
24
|
+
*/
|
|
25
|
+
const ACCESS_SCOPES = {
|
|
26
|
+
CONTENT: 'Content',
|
|
27
|
+
COMMUNITY: 'Community',
|
|
28
|
+
REWARD: 'Reward',
|
|
29
|
+
PROFILE: 'Profile',
|
|
30
|
+
ADMIN_PANEL: 'AdminPanel',
|
|
31
|
+
INTERNAL: 'Internal'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 권한 정의 - 서비스별로 그룹화
|
|
36
|
+
*/
|
|
37
|
+
const PERMISSIONS = {
|
|
38
|
+
// Challenge 서비스
|
|
39
|
+
CHALLENGE: {
|
|
40
|
+
CONTENT_READ: 'challenge:content:read',
|
|
41
|
+
CONTENT_WRITE: 'challenge:content:write',
|
|
42
|
+
CONTENT_WRITE_OWN: 'challenge:content:write:own',
|
|
43
|
+
CONTENT_DELETE: 'challenge:content:delete',
|
|
44
|
+
CONTENT_DELETE_OWN: 'challenge:content:delete:own',
|
|
45
|
+
CONTENT_GROUP_READ: 'challenge:content-group:read',
|
|
46
|
+
CONTENT_GROUP_WRITE: 'challenge:content-group:write',
|
|
47
|
+
CONTENT_GROUP_DELETE: 'challenge:content-group:delete',
|
|
48
|
+
PROGRESS_READ: 'challenge:progress:read',
|
|
49
|
+
PROGRESS_READ_OWN: 'challenge:progress:read:own',
|
|
50
|
+
PROGRESS_WRITE: 'challenge:progress:write',
|
|
51
|
+
PROGRESS_WRITE_OWN: 'challenge:progress:write:own'
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// World 서비스
|
|
55
|
+
WORLD: {
|
|
56
|
+
CONTENT_READ: 'world:content:read',
|
|
57
|
+
CONTENT_WRITE: 'world:content:write',
|
|
58
|
+
CONTENT_WRITE_OWN: 'world:content:write:own',
|
|
59
|
+
CONTENT_DELETE: 'world:content:delete',
|
|
60
|
+
CONTENT_DELETE_OWN: 'world:content:delete:own',
|
|
61
|
+
CONTENT_GROUP_READ: 'world:content-group:read',
|
|
62
|
+
CONTENT_GROUP_OWN: 'world:content-group:own',
|
|
63
|
+
CONTENT_GROUP_WRITE: 'world:content-group:write',
|
|
64
|
+
CONTENT_GROUP_WRITE_OWN: 'world:content-group:write:own',
|
|
65
|
+
CONTENT_GROUP_DELETE: 'world:content-group:delete',
|
|
66
|
+
CONTENT_GROUP_DELETE_OWN: 'world:content-group:delete:own',
|
|
67
|
+
PROGRESS_READ: 'world:progress:read',
|
|
68
|
+
PROGRESS_READ_OWN: 'world:progress:read:own',
|
|
69
|
+
PROGRESS_WRITE: 'world:progress:write',
|
|
70
|
+
PROGRESS_WRITE_OWN: 'world:progress:write:own'
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Gallery 서비스
|
|
74
|
+
GALLERY: {
|
|
75
|
+
PROJECT_READ: 'gallery:project:read',
|
|
76
|
+
PROJECT_WRITE: 'gallery:project:write',
|
|
77
|
+
PROJECT_WRITE_OWN: 'gallery:project:write:own',
|
|
78
|
+
PROJECT_DELETE: 'gallery:project:delete',
|
|
79
|
+
PROJECT_DELETE_OWN: 'gallery:project:delete:own',
|
|
80
|
+
MEDIA_UPLOAD: 'gallery:media:upload',
|
|
81
|
+
REPORT_READ: 'gallery:report:read',
|
|
82
|
+
REPORT_READ_OWN: 'gallery:report:read:own',
|
|
83
|
+
REPORT_WRITE: 'gallery:report:write',
|
|
84
|
+
REPORT_WRITE_OWN: 'gallery:report:write:own'
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// Community 서비스
|
|
88
|
+
COMMUNITY: {
|
|
89
|
+
READ: 'community:read',
|
|
90
|
+
READ_OWN: 'community:read:own',
|
|
91
|
+
WRITE: 'community:write',
|
|
92
|
+
WRITE_OWN: 'community:write:own',
|
|
93
|
+
DELETE: 'community:delete',
|
|
94
|
+
DELETE_OWN: 'community:delete:own',
|
|
95
|
+
MESSAGE_WRITE: 'community:message:write',
|
|
96
|
+
MESSAGE_WRITE_OWN: 'community:message:write:own',
|
|
97
|
+
NOTICE_READ: 'community:notice:read',
|
|
98
|
+
NOTICE_READ_OWN: 'community:notice:read:own',
|
|
99
|
+
NOTICE_WRITE: 'community:notice:write',
|
|
100
|
+
NOTICE_WRITE_OWN: 'community:notice:write:own',
|
|
101
|
+
NOTICE_DELETE: 'community:notice:delete',
|
|
102
|
+
NOTICE_DELETE_OWN: 'community:notice:delete:own',
|
|
103
|
+
CURATION_READ: 'community:curation:read',
|
|
104
|
+
CURATION_READ_OWN: 'community:curation:read:own',
|
|
105
|
+
CURATION_WRITE: 'community:curation:write',
|
|
106
|
+
CURATION_WRITE_OWN: 'community:curation:write:own',
|
|
107
|
+
CURATION_DELETE: 'community:curation:delete',
|
|
108
|
+
CURATION_DELETE_OWN: 'community:curation:delete:own',
|
|
109
|
+
MEMBER_READ: 'community:member:read',
|
|
110
|
+
MEMBER_READ_OWN: 'community:member:read:own',
|
|
111
|
+
MEMBER_WRITE: 'community:member:write',
|
|
112
|
+
MEMBER_WRITE_OWN: 'community:member:write:own',
|
|
113
|
+
MEMBER_DELETE: 'community:member:delete',
|
|
114
|
+
MEMBER_DELETE_COMMUNITY_ADMIN: 'community:member:delete:community-admin',
|
|
115
|
+
MEMBER_JOIN: 'community:member:join',
|
|
116
|
+
MEMBER_LEAVE: 'community:member:leave',
|
|
117
|
+
MEMBER_LEAVE_OWN: 'community:member:leave:own',
|
|
118
|
+
GALLERY_READ: 'community:gallery:read',
|
|
119
|
+
GALLERY_READ_OWN: 'community:gallery:read:own',
|
|
120
|
+
GALLERY_WRITE: 'community:gallery:write',
|
|
121
|
+
GALLERY_WRITE_OWN: 'community:gallery:write:own',
|
|
122
|
+
GALLERY_DELETE: 'community:gallery:delete',
|
|
123
|
+
GALLERY_DELETE_OWN: 'community:gallery:delete:own',
|
|
124
|
+
GALLERY_COMMENT_READ: 'community:gallery.comment:read',
|
|
125
|
+
GALLERY_COMMENT_READ_OWN: 'community:gallery.comment:read:own',
|
|
126
|
+
GALLERY_COMMENT_WRITE: 'community:gallery.comment:write',
|
|
127
|
+
GALLERY_COMMENT_WRITE_OWN: 'community:gallery.comment:write:own',
|
|
128
|
+
GALLERY_COMMENT_DELETE: 'community:gallery.comment:delete',
|
|
129
|
+
GALLERY_COMMENT_DELETE_OWN: 'community:gallery.comment:delete:own',
|
|
130
|
+
GALLERY_COMMENT_HIDE: 'community:gallery.comment:hide',
|
|
131
|
+
GALLERY_COMMENT_HIDE_OWN: 'community:gallery.comment:hide:own',
|
|
132
|
+
GALLERY_COMMENT_REPORT: 'community:gallery.comment:report',
|
|
133
|
+
GALLERY_COMMENT_REPORT_OWN: 'community:gallery.comment:report:own',
|
|
134
|
+
INVITE_READ: 'community:invite:read',
|
|
135
|
+
INVITE_READ_OWN: 'community:invite:read:own',
|
|
136
|
+
INVITE_WRITE: 'community:invite:write',
|
|
137
|
+
INVITE_WRITE_OWN: 'community:invite:write:own',
|
|
138
|
+
INVITE_DELETE: 'community:invite:delete',
|
|
139
|
+
INVITE_DELETE_OWN: 'community:invite:delete:own',
|
|
140
|
+
USER_BLOCK_READ: 'community:user-block:read',
|
|
141
|
+
USER_BLOCK_READ_OWN: 'community:user-block:read:own',
|
|
142
|
+
USER_BLOCK_WRITE: 'community:user-block:write',
|
|
143
|
+
USER_BLOCK_WRITE_OWN: 'community:user-block:write:own',
|
|
144
|
+
USER_BLOCK_DELETE: 'community:user-block:delete',
|
|
145
|
+
USER_BLOCK_DELETE_OWN: 'community:user-block:delete:own'
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Auth 서비스
|
|
149
|
+
AUTH: {
|
|
150
|
+
OTP_WRITE: 'auth:otp:write',
|
|
151
|
+
LOGIN_WRITE: 'auth:login:write',
|
|
152
|
+
LOGOUT_WRITE: 'auth:logout:write'
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Membership 서비스
|
|
156
|
+
MEMBERSHIP: {
|
|
157
|
+
USER_READ: 'membership:user:read',
|
|
158
|
+
USER_READ_OWN: 'membership:user:read:own',
|
|
159
|
+
USER_WRITE: 'membership:user:write',
|
|
160
|
+
USER_WRITE_OWN: 'membership:user:write:own',
|
|
161
|
+
USER_DELETE: 'membership:user:delete',
|
|
162
|
+
USER_DELETE_OWN: 'membership:user:delete:own',
|
|
163
|
+
PROFILE_READ: 'membership:profile:read',
|
|
164
|
+
PROFILE_READ_OWN: 'membership:profile:read:own',
|
|
165
|
+
PROFILE_WRITE: 'membership:profile:write',
|
|
166
|
+
PROFILE_WRITE_OWN: 'membership:profile:write:own',
|
|
167
|
+
PROFILE_DELETE: 'membership:profile:delete',
|
|
168
|
+
PROFILE_DELETE_OWN: 'membership:profile:delete:own',
|
|
169
|
+
INVENTORY_READ: 'membership:inventory:read',
|
|
170
|
+
INVENTORY_READ_OWN: 'membership:inventory:read:own',
|
|
171
|
+
INVENTORY_WRITE: 'membership:inventory:write',
|
|
172
|
+
INVENTORY_WRITE_OWN: 'membership:inventory:write:own',
|
|
173
|
+
INVENTORY_DELETE: 'membership:inventory:delete',
|
|
174
|
+
INVENTORY_DELETE_OWN: 'membership:inventory:delete:own',
|
|
175
|
+
STORAGE_READ: 'membership:storage:read',
|
|
176
|
+
STORAGE_READ_OWN: 'membership:storage:read:own',
|
|
177
|
+
STORAGE_WRITE: 'membership:storage:write',
|
|
178
|
+
STORAGE_WRITE_OWN: 'membership:storage:write:own',
|
|
179
|
+
STORAGE_DELETE: 'membership:storage:delete',
|
|
180
|
+
STORAGE_DELETE_OWN: 'membership:storage:delete:own',
|
|
181
|
+
STORAGE_BOOKMARK_READ: 'membership:storage.bookmark:read',
|
|
182
|
+
STORAGE_BOOKMARK_READ_OWN: 'membership:storage.bookmark:read:own',
|
|
183
|
+
STORAGE_BOOKMARK_WRITE: 'membership:storage.bookmark:write',
|
|
184
|
+
STORAGE_BOOKMARK_WRITE_OWN: 'membership:storage.bookmark:write:own',
|
|
185
|
+
STORAGE_BOOKMARK_DELETE: 'membership:storage.bookmark:delete',
|
|
186
|
+
STORAGE_BOOKMARK_DELETE_OWN: 'membership:storage.bookmark:delete:own',
|
|
187
|
+
STORAGE_POST_READ: 'membership:storage.post:read',
|
|
188
|
+
STORAGE_POST_READ_OWN: 'membership:storage.post:read:own',
|
|
189
|
+
STORAGE_POST_WRITE: 'membership:storage.post:write',
|
|
190
|
+
STORAGE_POST_WRITE_OWN: 'membership:storage.post:write:own',
|
|
191
|
+
STORAGE_POST_DELETE: 'membership:storage.post:delete',
|
|
192
|
+
STORAGE_POST_DELETE_OWN: 'membership:storage.post:delete:own',
|
|
193
|
+
STORAGE_HISTORY_READ: 'membership:storage.history:read',
|
|
194
|
+
STORAGE_HISTORY_READ_OWN: 'membership:storage.history:read:own',
|
|
195
|
+
STORAGE_HISTORY_WRITE: 'membership:storage.history:write',
|
|
196
|
+
STORAGE_HISTORY_WRITE_OWN: 'membership:storage.history:write:own',
|
|
197
|
+
STORAGE_HISTORY_DELETE: 'membership:storage.history:delete',
|
|
198
|
+
STORAGE_HISTORY_DELETE_OWN: 'membership:storage.history:delete:own',
|
|
199
|
+
REPORT_READ: 'membership:report:read',
|
|
200
|
+
REPORT_READ_OWN: 'membership:report:read:own',
|
|
201
|
+
REPORT_WRITE: 'membership:report:write',
|
|
202
|
+
REPORT_WRITE_OWN: 'membership:report:write:own',
|
|
203
|
+
REPORT_MANAGE: 'membership:report:manage',
|
|
204
|
+
POLICY_READ: 'membership:policy:read',
|
|
205
|
+
POLICY_MANAGE: 'membership:policy:manage',
|
|
206
|
+
NOTICE_READ: 'membership:notice:read',
|
|
207
|
+
NOTICE_MANAGE: 'membership:notice:manage',
|
|
208
|
+
INQUIRY_WRITE: 'membership:inquiry:write',
|
|
209
|
+
INQUIRY_WRITE_OWN: 'membership:inquiry:write:own',
|
|
210
|
+
FLC_READ: 'membership:flc:read',
|
|
211
|
+
FLC_READ_OWN: 'membership:flc:read:own',
|
|
212
|
+
FLC_SYNC: 'membership:flc:sync',
|
|
213
|
+
WITHDRAWAL_READ: 'membership:withdrawal:read',
|
|
214
|
+
WITHDRAWAL_READ_OWN: 'membership:withdrawal:read:own',
|
|
215
|
+
WITHDRAWAL_WRITE: 'membership:withdrawal:write',
|
|
216
|
+
WITHDRAWAL_WRITE_OWN: 'membership:withdrawal:write:own',
|
|
217
|
+
WITHDRAWAL_DELETE: 'membership:withdrawal:delete',
|
|
218
|
+
WITHDRAWAL_DELETE_OWN: 'membership:withdrawal:delete:own',
|
|
219
|
+
SIGNUP_WRITE: 'membership:signup:write',
|
|
220
|
+
INTERNAL_READ: 'membership:internal:read'
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// AI 서비스
|
|
224
|
+
AI: {
|
|
225
|
+
FEEDBACK_WRITE: 'ai:feedback:write',
|
|
226
|
+
FEEDBACK_WRITE_OWN: 'ai:feedback:write:own'
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// Search 서비스
|
|
230
|
+
SEARCH: {
|
|
231
|
+
CHALLENGE_READ: 'search:challenge:read',
|
|
232
|
+
HISTORY_READ: 'search:history:read',
|
|
233
|
+
HISTORY_READ_OWN: 'search:history:read:own',
|
|
234
|
+
HISTORY_DELETE: 'search:history:delete',
|
|
235
|
+
HISTORY_DELETE_OWN: 'search:history:delete:own',
|
|
236
|
+
CATEGORY_READ: 'search:category:read',
|
|
237
|
+
SUGGESTION_READ: 'search:suggestion:read',
|
|
238
|
+
TOPLINK_READ: 'search:toplink:read'
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
// Notification 서비스
|
|
242
|
+
NOTIFICATION: {
|
|
243
|
+
READ: 'notification:read',
|
|
244
|
+
READ_OWN: 'notification:read:own',
|
|
245
|
+
WRITE: 'notification:write',
|
|
246
|
+
UPDATE: 'notification:update',
|
|
247
|
+
UPDATE_OWN: 'notification:update:own',
|
|
248
|
+
DELETE: 'notification:delete',
|
|
249
|
+
DELETE_OWN: 'notification:delete:own',
|
|
250
|
+
DEVICE_READ: 'notification:device:read',
|
|
251
|
+
DEVICE_READ_OWN: 'notification:device:read:own',
|
|
252
|
+
DEVICE_WRITE: 'notification:device:write',
|
|
253
|
+
DEVICE_WRITE_OWN: 'notification:device:write:own',
|
|
254
|
+
DEVICE_UPDATE: 'notification:device:update',
|
|
255
|
+
DEVICE_UPDATE_OWN: 'notification:device:update:own',
|
|
256
|
+
DEVICE_DELETE: 'notification:device:delete',
|
|
257
|
+
DEVICE_DELETE_OWN: 'notification:device:delete:own'
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 와일드카드 권한
|
|
263
|
+
*/
|
|
264
|
+
const WILDCARD = '*';
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 권한이 요구된 권한과 일치하는지 확인
|
|
268
|
+
* @param {string[]} userPermissions - 사용자가 가진 권한 목록
|
|
269
|
+
* @param {string} requiredPermission - 필요한 권한
|
|
270
|
+
* @returns {boolean}
|
|
271
|
+
*/
|
|
272
|
+
function hasPermission(userPermissions, requiredPermission) {
|
|
273
|
+
if (!userPermissions || !Array.isArray(userPermissions)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 와일드카드 체크
|
|
278
|
+
if (userPermissions.includes(WILDCARD)) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 정확히 일치
|
|
283
|
+
if (userPermissions.includes(requiredPermission)) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 와일드카드 패턴 매칭 (예: 'challenge:*' → 'challenge:content:write' 허용)
|
|
288
|
+
const hasWildcardMatch = userPermissions.some(perm => {
|
|
289
|
+
if (perm.endsWith(':*')) {
|
|
290
|
+
const prefix = perm.slice(0, -1);
|
|
291
|
+
return requiredPermission.startsWith(prefix);
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return hasWildcardMatch;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* :own 권한 체크 (본인 리소스 권한이 있는지 확인)
|
|
301
|
+
* @param {string[]} userPermissions - 사용자가 가진 권한 목록
|
|
302
|
+
* @param {string} basePermission - 기본 권한 (예: 'challenge:content:write')
|
|
303
|
+
* @returns {boolean}
|
|
304
|
+
*/
|
|
305
|
+
function hasOwnPermission(userPermissions, basePermission) {
|
|
306
|
+
const ownPermission = `${basePermission}:own`;
|
|
307
|
+
return hasPermission(userPermissions, basePermission) || hasPermission(userPermissions, ownPermission);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = {
|
|
311
|
+
ROLES,
|
|
312
|
+
ACCESS_SCOPES,
|
|
313
|
+
PERMISSIONS,
|
|
314
|
+
WILDCARD,
|
|
315
|
+
hasPermission,
|
|
316
|
+
hasOwnPermission
|
|
317
|
+
};
|
package/src/index.js
CHANGED
|
@@ -31,6 +31,16 @@ const { getJwtKeys, invalidateCache } = require('./utils/secretManager');
|
|
|
31
31
|
const { verifyApiKey, getApiKeyTTL } = require('./utils/apiKeyValidator');
|
|
32
32
|
const redisManager = require('./config/redis');
|
|
33
33
|
|
|
34
|
+
// 상수
|
|
35
|
+
const {
|
|
36
|
+
ROLES,
|
|
37
|
+
ACCESS_SCOPES,
|
|
38
|
+
PERMISSIONS,
|
|
39
|
+
WILDCARD,
|
|
40
|
+
hasPermission,
|
|
41
|
+
hasOwnPermission
|
|
42
|
+
} = require('./constants/permissions');
|
|
43
|
+
|
|
34
44
|
module.exports = {
|
|
35
45
|
// ===== JWT 인증 미들웨어 =====
|
|
36
46
|
authenticateJWT, // JWT Access Token 필수 인증
|
|
@@ -75,5 +85,13 @@ module.exports = {
|
|
|
75
85
|
},
|
|
76
86
|
|
|
77
87
|
// Redis 관리자
|
|
78
|
-
redisManager
|
|
88
|
+
redisManager,
|
|
89
|
+
|
|
90
|
+
// ===== 상수 =====
|
|
91
|
+
ROLES, // 역할 상수: admin, service, creator, flc-member, user
|
|
92
|
+
ACCESS_SCOPES, // 접근 범위 상수: Content, Community, etc.
|
|
93
|
+
PERMISSIONS, // 권한 상수: PERMISSIONS.CHALLENGE.CONTENT_READ 등
|
|
94
|
+
WILDCARD, // 와일드카드 권한: '*'
|
|
95
|
+
hasPermission, // 권한 체크 유틸: hasPermission(permissions, required)
|
|
96
|
+
hasOwnPermission // 본인 권한 체크 유틸: hasOwnPermission(permissions, base)
|
|
79
97
|
};
|
package/src/middleware/apiKey.js
CHANGED
|
@@ -3,6 +3,9 @@ const { verifyApiKey } = require('../utils/apiKeyValidator');
|
|
|
3
3
|
/**
|
|
4
4
|
* API 키 인증 미들웨어
|
|
5
5
|
* Redis에서 로컬 검증 수행
|
|
6
|
+
*
|
|
7
|
+
* API 키로 인증된 요청은 'service' 역할로 처리되며,
|
|
8
|
+
* 해당 역할에 정의된 권한만 사용 가능합니다.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
/**
|
|
@@ -34,10 +37,17 @@ async function authenticateApiKey(req, res, next) {
|
|
|
34
37
|
});
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
// 인증 성공:
|
|
40
|
+
// 인증 성공: service 역할로 설정
|
|
38
41
|
req.apiKeyVerified = true;
|
|
39
|
-
req.skipAuthorization = true; // 하위 호환성
|
|
40
42
|
req.deviceId = deviceId;
|
|
43
|
+
req.userRole = 'service';
|
|
44
|
+
|
|
45
|
+
// user 객체 설정 (권한 체크 미들웨어와 호환)
|
|
46
|
+
req.user = {
|
|
47
|
+
userId: `service:${deviceId}`,
|
|
48
|
+
userRole: 'service',
|
|
49
|
+
isServiceAccount: true
|
|
50
|
+
};
|
|
41
51
|
|
|
42
52
|
console.log('[@ahhaohho/auth-middleware] API key verified for device:', deviceId);
|
|
43
53
|
next();
|
|
@@ -62,8 +72,15 @@ async function optionalApiKey(req, res, next) {
|
|
|
62
72
|
|
|
63
73
|
if (result.valid) {
|
|
64
74
|
req.apiKeyVerified = true;
|
|
65
|
-
req.skipAuthorization = true;
|
|
66
75
|
req.deviceId = deviceId;
|
|
76
|
+
req.userRole = 'service';
|
|
77
|
+
|
|
78
|
+
req.user = {
|
|
79
|
+
userId: `service:${deviceId}`,
|
|
80
|
+
userRole: 'service',
|
|
81
|
+
isServiceAccount: true
|
|
82
|
+
};
|
|
83
|
+
|
|
67
84
|
console.log('[@ahhaohho/auth-middleware] Optional API key verified for device:', deviceId);
|
|
68
85
|
}
|
|
69
86
|
|
|
@@ -87,12 +104,17 @@ async function combinedAuth(req, res, next) {
|
|
|
87
104
|
|
|
88
105
|
if (result.valid) {
|
|
89
106
|
req.apiKeyVerified = true;
|
|
90
|
-
req.skipAuthorization = true;
|
|
91
107
|
req.deviceId = deviceId;
|
|
108
|
+
req.userRole = 'service';
|
|
109
|
+
|
|
110
|
+
req.user = {
|
|
111
|
+
userId: `service:${deviceId}`,
|
|
112
|
+
userRole: 'service',
|
|
113
|
+
isServiceAccount: true
|
|
114
|
+
};
|
|
92
115
|
|
|
93
|
-
// 하위
|
|
94
|
-
req.userId =
|
|
95
|
-
req.userRole = 'api_client';
|
|
116
|
+
// 하위 호환성
|
|
117
|
+
req.userId = req.user.userId;
|
|
96
118
|
|
|
97
119
|
console.log('[@ahhaohho/auth-middleware] Combined auth: API key verified');
|
|
98
120
|
return next();
|
|
@@ -147,11 +169,17 @@ async function combinedAuthHybrid(req, res, next) {
|
|
|
147
169
|
|
|
148
170
|
if (result.valid) {
|
|
149
171
|
req.apiKeyVerified = true;
|
|
150
|
-
req.skipAuthorization = true;
|
|
151
172
|
req.deviceId = deviceId;
|
|
173
|
+
req.userRole = 'service';
|
|
174
|
+
|
|
175
|
+
req.user = {
|
|
176
|
+
userId: `service:${deviceId}`,
|
|
177
|
+
userRole: 'service',
|
|
178
|
+
isServiceAccount: true
|
|
179
|
+
};
|
|
152
180
|
|
|
153
|
-
|
|
154
|
-
req.
|
|
181
|
+
// 하위 호환성
|
|
182
|
+
req.userId = req.user.userId;
|
|
155
183
|
|
|
156
184
|
console.log('[@ahhaohho/auth-middleware] Combined hybrid auth: API key verified');
|
|
157
185
|
return next();
|
|
@@ -62,11 +62,6 @@ function clearPermissionCache() {
|
|
|
62
62
|
*/
|
|
63
63
|
function requireRoles(allowedRoles) {
|
|
64
64
|
return async (req, res, next) => {
|
|
65
|
-
// API 키 인증인 경우 스킵
|
|
66
|
-
if (req.skipAuthorization || req.apiKeyVerified) {
|
|
67
|
-
return next();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
65
|
// 사용자 역할 확인 (req.user 또는 req.userRole)
|
|
71
66
|
const userRole = req.user?.userRole || req.userRole;
|
|
72
67
|
|
|
@@ -102,11 +97,6 @@ function requireRoles(allowedRoles) {
|
|
|
102
97
|
*/
|
|
103
98
|
function requirePermission(requiredPermission) {
|
|
104
99
|
return async (req, res, next) => {
|
|
105
|
-
// API 키 인증인 경우 스킵
|
|
106
|
-
if (req.skipAuthorization || req.apiKeyVerified) {
|
|
107
|
-
return next();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
100
|
const userRole = req.user?.userRole || req.userRole;
|
|
111
101
|
|
|
112
102
|
if (!userRole) {
|
|
@@ -182,11 +172,6 @@ function requirePermission(requiredPermission) {
|
|
|
182
172
|
*/
|
|
183
173
|
function requireAccess(requiredAccess) {
|
|
184
174
|
return async (req, res, next) => {
|
|
185
|
-
// API 키 인증인 경우 스킵
|
|
186
|
-
if (req.skipAuthorization || req.apiKeyVerified) {
|
|
187
|
-
return next();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
175
|
const userRole = req.user?.userRole || req.userRole;
|
|
191
176
|
|
|
192
177
|
if (!userRole) {
|
|
@@ -223,6 +208,7 @@ function requireAccess(requiredAccess) {
|
|
|
223
208
|
* 리소스 소유자 검증 미들웨어
|
|
224
209
|
* 사용자가 자신의 리소스에만 접근할 수 있도록 제한
|
|
225
210
|
* admin 역할은 모든 리소스 접근 가능
|
|
211
|
+
* service 역할(API Key)은 모든 리소스 접근 가능
|
|
226
212
|
*
|
|
227
213
|
* @param {string} paramName - 라우트 파라미터 이름 (기본값: 'userId')
|
|
228
214
|
* @returns {Function} Express 미들웨어
|
|
@@ -233,17 +219,12 @@ function requireAccess(requiredAccess) {
|
|
|
233
219
|
*/
|
|
234
220
|
function requireOwnership(paramName = 'userId') {
|
|
235
221
|
return (req, res, next) => {
|
|
236
|
-
// API 키 인증인 경우 스킵
|
|
237
|
-
if (req.skipAuthorization || req.apiKeyVerified) {
|
|
238
|
-
return next();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
222
|
const userId = req.user?.userId || req.userId;
|
|
242
223
|
const resourceOwnerId = req.params[paramName];
|
|
243
224
|
|
|
244
|
-
// admin
|
|
225
|
+
// admin, service 역할은 모든 리소스 접근 가능
|
|
245
226
|
const userRole = req.user?.userRole || req.userRole;
|
|
246
|
-
if (userRole === 'admin') {
|
|
227
|
+
if (userRole === 'admin' || userRole === 'service') {
|
|
247
228
|
return next();
|
|
248
229
|
}
|
|
249
230
|
|
|
@@ -285,11 +266,6 @@ function requireOwnership(paramName = 'userId') {
|
|
|
285
266
|
*/
|
|
286
267
|
function requireAny(options = {}) {
|
|
287
268
|
return async (req, res, next) => {
|
|
288
|
-
// API 키 인증인 경우 스킵
|
|
289
|
-
if (req.skipAuthorization || req.apiKeyVerified) {
|
|
290
|
-
return next();
|
|
291
|
-
}
|
|
292
|
-
|
|
293
269
|
const userRole = req.user?.userRole || req.userRole;
|
|
294
270
|
|
|
295
271
|
if (!userRole) {
|