@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahhaohho/auth-middleware",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Shared authentication and authorization middleware for ahhaohho microservices",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -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
  };
@@ -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
- // 하위 호환성: req.userId, req.userRole 설정
94
- req.userId = null;
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
- req.userId = null;
154
- req.userRole = 'api_client';
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) {