@manojkmfsi/monodog 1.1.30 → 1.1.31

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/.env.example CHANGED
@@ -1,19 +1,5 @@
1
1
  # GitHub OAuth Configuration
2
2
  GITHUB_CLIENT_ID=your_client_id_here
3
3
  GITHUB_CLIENT_SECRET=your_client_secret_here
4
- OAUTH_REDIRECT_URI=http://localhost:3000/auth/callback
4
+ OAUTH_REDIRECT_URI=http://localhost:3010/auth/callback
5
5
 
6
- # Database Configuration
7
- # DATABASE_URL=postgresql://user:password@localhost:5432/monodog
8
-
9
- # # Server Configuration
10
- # SERVER_HOST=localhost
11
- # SERVER_PORT=5000
12
- # DASHBOARD_HOST=localhost
13
- # DASHBOARD_PORT=3000
14
-
15
- # # Logging Level (debug, info, warn, error)
16
- # LOG_LEVEL=info
17
-
18
- # # Environment
19
- # NODE_ENV=development
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @manojkmfsi/monoapp
2
2
 
3
+ ## 1.1.31
4
+
5
+ ### Patch Changes
6
+
7
+ - [`0541cc8`](https://github.com/manojkmfsi/monodog/commit/0541cc88c639265386c29a59b09e6b63cdee6064) - vnbbnbn,n,mnm,m
8
+
9
+ - [`785a90a`](https://github.com/manojkmfsi/monodog/commit/785a90a9c15354559f13a88bf1f9486c9794fc18) - ffhjhkklnbncxdflk,
10
+
11
+ - [`3a32768`](https://github.com/manojkmfsi/monodog/commit/3a32768c4e1be683847d9308632c1d4eb85e43fd) - ccfckjjlkklkm,m.,m.,
12
+
13
+ - [`efee08b`](https://github.com/manojkmfsi/monodog/commit/efee08b5d8cc52f35b7a21dae69ca7dde5b99427) - fgkjlkn,m cgfcbv
14
+
3
15
  ## 1.1.30
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /**
3
+ * Authentication Controller
4
+ * Thin controller that handles HTTP concerns and delegates business logic to auth-service
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.refresh = exports.logout = exports.validate = exports.me = exports.callback = exports.login = void 0;
8
+ const auth_service_1 = require("../services/auth-service");
9
+ const logger_1 = require("../middleware/logger");
10
+ /**
11
+ * Start OAuth login flow
12
+ * GET /auth/login
13
+ */
14
+ const login = (req, res) => {
15
+ try {
16
+ const redirectUrl = req.query.redirect || '/';
17
+ const result = (0, auth_service_1.initiateLogin)(redirectUrl);
18
+ res.json({
19
+ success: true,
20
+ authUrl: result.authUrl,
21
+ message: 'Redirect to this URL to authenticate with GitHub',
22
+ });
23
+ }
24
+ catch (error) {
25
+ logger_1.AppLogger.error(`Login initiation failed: ${error}`);
26
+ res.status(500).json({
27
+ success: false,
28
+ error: 'Login failed',
29
+ message: error instanceof Error ? error.message : 'Failed to initiate GitHub OAuth flow',
30
+ });
31
+ }
32
+ };
33
+ exports.login = login;
34
+ /**
35
+ * OAuth callback handler
36
+ * GET /auth/callback?code=...&state=...
37
+ */
38
+ const callback = async (req, res) => {
39
+ try {
40
+ const { code, state, error, error_description } = req.query;
41
+ // Handle OAuth errors from GitHub
42
+ if (error) {
43
+ logger_1.AppLogger.warn(`OAuth error: ${error} - ${error_description}`);
44
+ res.status(400).json({
45
+ success: false,
46
+ error: error,
47
+ message: error_description,
48
+ });
49
+ return;
50
+ }
51
+ if (!code || !state) {
52
+ logger_1.AppLogger.warn('OAuth callback missing code or state');
53
+ res.status(400).json({
54
+ success: false,
55
+ error: 'Missing parameters',
56
+ message: 'OAuth code and state are required',
57
+ });
58
+ return;
59
+ }
60
+ const result = await (0, auth_service_1.handleOAuthCallback)(code, state);
61
+ res.json({
62
+ success: true,
63
+ message: 'Authentication successful',
64
+ ...result,
65
+ });
66
+ }
67
+ catch (error) {
68
+ logger_1.AppLogger.error(`OAuth callback failed: ${error}`);
69
+ const message = error instanceof Error ? error.message : 'Failed to complete GitHub OAuth flow';
70
+ if (message.includes('CSRF')) {
71
+ res.status(400).json({
72
+ success: false,
73
+ error: 'Invalid state',
74
+ message,
75
+ });
76
+ }
77
+ else {
78
+ res.status(500).json({
79
+ success: false,
80
+ error: 'Authentication failed',
81
+ message,
82
+ });
83
+ }
84
+ }
85
+ };
86
+ exports.callback = callback;
87
+ /**
88
+ * Get current user session
89
+ * GET /auth/me
90
+ */
91
+ const me = (req, res) => {
92
+ try {
93
+ const session = (0, auth_service_1.getCurrentSession)(req);
94
+ res.json({
95
+ success: true,
96
+ ...session,
97
+ });
98
+ }
99
+ catch (error) {
100
+ logger_1.AppLogger.error(`Failed to get user session: ${error}`);
101
+ res.status(401).json({
102
+ success: false,
103
+ error: 'Unauthorized',
104
+ message: error instanceof Error ? error.message : 'Failed to retrieve user information',
105
+ });
106
+ }
107
+ };
108
+ exports.me = me;
109
+ /**
110
+ * Validate session
111
+ * POST /auth/validate
112
+ */
113
+ const validate = async (req, res) => {
114
+ try {
115
+ const result = await (0, auth_service_1.validateCurrentSession)(req);
116
+ res.json({
117
+ success: true,
118
+ ...result,
119
+ message: 'Session is valid',
120
+ });
121
+ }
122
+ catch (error) {
123
+ logger_1.AppLogger.error(`Session validation failed: ${error}`);
124
+ if (error instanceof Error && error.message.includes('no longer valid')) {
125
+ res.status(401).json({
126
+ success: false,
127
+ valid: false,
128
+ error: 'Unauthorized',
129
+ message: error.message,
130
+ });
131
+ }
132
+ else {
133
+ res.status(500).json({
134
+ success: false,
135
+ valid: false,
136
+ error: 'Validation failed',
137
+ message: error instanceof Error ? error.message : 'Failed to validate session',
138
+ });
139
+ }
140
+ }
141
+ };
142
+ exports.validate = validate;
143
+ /**
144
+ * Logout
145
+ * POST /auth/logout
146
+ */
147
+ const logout = (req, res) => {
148
+ try {
149
+ (0, auth_service_1.logoutUser)(req);
150
+ res.json({
151
+ success: true,
152
+ message: 'Logout successful',
153
+ });
154
+ }
155
+ catch (error) {
156
+ logger_1.AppLogger.error(`Logout failed: ${error}`);
157
+ res.status(500).json({
158
+ success: false,
159
+ error: 'Logout failed',
160
+ message: error instanceof Error ? error.message : 'Failed to logout',
161
+ });
162
+ }
163
+ };
164
+ exports.logout = logout;
165
+ /**
166
+ * Refresh session (token)
167
+ * POST /auth/refresh
168
+ */
169
+ const refresh = async (req, res) => {
170
+ try {
171
+ const result = await (0, auth_service_1.refreshUserSession)(req);
172
+ res.json({
173
+ success: true,
174
+ message: 'Session refreshed successfully',
175
+ ...result,
176
+ });
177
+ }
178
+ catch (error) {
179
+ logger_1.AppLogger.error(`Session refresh failed: ${error}`);
180
+ if (error instanceof Error && error.message.includes('no longer valid')) {
181
+ res.status(401).json({
182
+ success: false,
183
+ error: 'Unauthorized',
184
+ message: error.message,
185
+ });
186
+ }
187
+ else {
188
+ res.status(500).json({
189
+ success: false,
190
+ error: 'Refresh failed',
191
+ message: error instanceof Error ? error.message : 'Failed to refresh session',
192
+ });
193
+ }
194
+ }
195
+ };
196
+ exports.refresh = refresh;
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ /**
3
+ * Permission Controller
4
+ * Thin controller that handles HTTP concerns and delegates business logic to permission-service
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.invalidateCache = exports.checkActionPermission = exports.getRepositoryPermission = void 0;
8
+ const auth_middleware_1 = require("../middleware/auth-middleware");
9
+ const permission_service_1 = require("../services/permission-service");
10
+ const logger_1 = require("../middleware/logger");
11
+ /**
12
+ * Get user's permission for a specific repository
13
+ * GET /permissions/:owner/:repo
14
+ */
15
+ const getRepositoryPermission = async (req, res) => {
16
+ try {
17
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
18
+ if (!session) {
19
+ res.status(401).json({
20
+ success: false,
21
+ error: 'Unauthorized',
22
+ message: 'No active session',
23
+ });
24
+ return;
25
+ }
26
+ const { owner, repo } = req.params;
27
+ const forceRefresh = req.query.refresh === 'true';
28
+ if (!owner || !repo) {
29
+ res.status(400).json({
30
+ success: false,
31
+ error: 'Bad request',
32
+ message: 'Owner and repo parameters are required',
33
+ });
34
+ return;
35
+ }
36
+ logger_1.AppLogger.debug(`Checking permission for ${session.user.login} in ${owner}/${repo}`);
37
+ const permissionCheck = await (0, permission_service_1.checkRepositoryPermission)(session.accessToken, session.user.id, session.user.login, owner, repo, forceRefresh);
38
+ res.json({
39
+ success: true,
40
+ owner,
41
+ repo,
42
+ user: session.user.login,
43
+ ...permissionCheck,
44
+ });
45
+ }
46
+ catch (error) {
47
+ logger_1.AppLogger.error(`Failed to check permission: ${error}`);
48
+ res.status(500).json({
49
+ success: false,
50
+ error: 'Internal server error',
51
+ message: 'Failed to check repository permission',
52
+ });
53
+ }
54
+ };
55
+ exports.getRepositoryPermission = getRepositoryPermission;
56
+ /**
57
+ * Check if user can perform a specific action
58
+ * POST /permissions/:owner/:repo/can-action
59
+ */
60
+ const checkActionPermission = async (req, res) => {
61
+ try {
62
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
63
+ if (!session) {
64
+ res.status(401).json({
65
+ success: false,
66
+ error: 'Unauthorized',
67
+ message: 'No active session',
68
+ });
69
+ return;
70
+ }
71
+ const { owner, repo } = req.params;
72
+ const { action } = req.body;
73
+ if (!owner || !repo) {
74
+ res.status(400).json({
75
+ success: false,
76
+ error: 'Bad request',
77
+ message: 'Owner and repo parameters are required',
78
+ });
79
+ return;
80
+ }
81
+ if (!action || !['read', 'write', 'maintain', 'admin'].includes(action)) {
82
+ res.status(400).json({
83
+ success: false,
84
+ error: 'Bad request',
85
+ message: 'Valid action is required (read, write, maintain, or admin)',
86
+ });
87
+ return;
88
+ }
89
+ logger_1.AppLogger.debug(`Checking if ${session.user.login} can perform '${action}' in ${owner}/${repo}`);
90
+ const actionCheck = await (0, permission_service_1.checkUserAction)(session.accessToken, session.user.id, session.user.login, owner, repo, action);
91
+ res.json({
92
+ success: true,
93
+ owner,
94
+ repo,
95
+ user: session.user.login,
96
+ ...actionCheck,
97
+ });
98
+ }
99
+ catch (error) {
100
+ logger_1.AppLogger.error(`Failed to check action permission: ${error}`);
101
+ res.status(500).json({
102
+ success: false,
103
+ error: 'Internal server error',
104
+ message: 'Failed to check action permission',
105
+ });
106
+ }
107
+ };
108
+ exports.checkActionPermission = checkActionPermission;
109
+ /**
110
+ * Invalidate permission cache for a repository
111
+ * POST /permissions/:owner/:repo/invalidate
112
+ */
113
+ const invalidateCache = (req, res) => {
114
+ try {
115
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
116
+ if (!session) {
117
+ res.status(401).json({
118
+ success: false,
119
+ error: 'Unauthorized',
120
+ message: 'No active session',
121
+ });
122
+ return;
123
+ }
124
+ const { owner, repo } = req.params;
125
+ if (!owner || !repo) {
126
+ res.status(400).json({
127
+ success: false,
128
+ error: 'Bad request',
129
+ message: 'Owner and repo parameters are required',
130
+ });
131
+ return;
132
+ }
133
+ logger_1.AppLogger.debug(`Invalidating permission cache for ${session.user.login} in ${owner}/${repo}`);
134
+ (0, permission_service_1.invalidatePermissionCache)(session.user.id, owner, repo);
135
+ res.json({
136
+ success: true,
137
+ message: 'Permission cache invalidated',
138
+ owner,
139
+ repo,
140
+ user: session.user.login,
141
+ });
142
+ }
143
+ catch (error) {
144
+ logger_1.AppLogger.error(`Failed to invalidate cache: ${error}`);
145
+ res.status(500).json({
146
+ success: false,
147
+ error: 'Internal server error',
148
+ message: 'Failed to invalidate permission cache',
149
+ });
150
+ }
151
+ };
152
+ exports.invalidateCache = invalidateCache;
@@ -5,368 +5,37 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const express_1 = require("express");
8
- const github_oauth_service_1 = require("../services/github-oauth-service");
8
+ const auth_controller_1 = require("../controllers/auth-controller");
9
9
  const auth_middleware_1 = require("../middleware/auth-middleware");
10
- const permission_service_1 = require("../services/permission-service");
11
- const logger_1 = require("../middleware/logger");
12
- const utilities_1 = require("../utils/utilities");
13
10
  const router = (0, express_1.Router)();
14
- // OAuth configuration (should come from environment variables)
15
- // const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || '';
16
- // const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET || '';
17
- // const OAUTH_REDIRECT_URI = process.env.OAUTH_REDIRECT_URI || 'http://localhost:3000/auth/callback';
18
- // State store for CSRF protection
19
- const stateStore = new Map();
20
- const STATE_EXPIRY = 10 * 60 * 1000; // 10 minutes
21
- /**
22
- * Generate random state for CSRF protection
23
- */
24
- function generateState() {
25
- return Math.random().toString(36).substring(2, 15) +
26
- Math.random().toString(36).substring(2, 15);
27
- }
28
- /**
29
- * Validate OAuth state
30
- */
31
- function validateState(state) {
32
- const entry = stateStore.get(state);
33
- if (!entry) {
34
- return false;
35
- }
36
- // Check if state has expired
37
- if (Date.now() - entry.createdAt > STATE_EXPIRY) {
38
- stateStore.delete(state);
39
- return false;
40
- }
41
- return true;
42
- }
43
- /**
44
- * Get redirect URL from state
45
- */
46
- function getRedirectUrl(state) {
47
- const entry = stateStore.get(state);
48
- return entry?.redirectUrl;
49
- }
50
- /**
51
- * Clear state after use
52
- */
53
- function clearState(state) {
54
- stateStore.delete(state);
55
- }
56
11
  /**
57
12
  * Start OAuth login flow
58
13
  * GET /auth/login
59
14
  */
60
- router.get('/login', (req, res) => {
61
- try {
62
- if (!process.env.GITHUB_CLIENT_ID) {
63
- logger_1.AppLogger.error('GitHub client ID not configured');
64
- res.status(500).json({
65
- error: 'OAuth not configured',
66
- message: 'GitHub OAuth is not properly configured',
67
- });
68
- return;
69
- }
70
- const state = generateState();
71
- const redirectUrl = req.query.redirect || '/';
72
- // Store state for validation
73
- stateStore.set(state, {
74
- createdAt: Date.now(),
75
- redirectUrl,
76
- });
77
- const authUrl = (0, github_oauth_service_1.generateAuthorizationUrl)(process.env.GITHUB_CLIENT_ID, process.env.OAUTH_REDIRECT_URI, state);
78
- res.json({
79
- success: true,
80
- authUrl,
81
- message: 'Redirect to this URL to authenticate with GitHub',
82
- });
83
- }
84
- catch (error) {
85
- logger_1.AppLogger.error(`Login initiation failed: ${error}`);
86
- res.status(500).json({
87
- error: 'Login failed',
88
- message: 'Failed to initiate GitHub OAuth flow',
89
- });
90
- }
91
- });
15
+ router.get('/login', auth_controller_1.login);
92
16
  /**
93
17
  * OAuth callback handler
94
18
  * GET /auth/callback?code=...&state=...
95
19
  */
96
- router.get('/callback', async (req, res) => {
97
- try {
98
- const { code, state, error, error_description } = req.query;
99
- // Handle OAuth errors
100
- if (error) {
101
- logger_1.AppLogger.warn(`OAuth error: ${error} - ${error_description}`);
102
- res.status(400).json({
103
- success: false,
104
- error: error,
105
- message: error_description,
106
- });
107
- return;
108
- }
109
- // Validate code and state
110
- if (!code || !state) {
111
- logger_1.AppLogger.warn('OAuth callback missing code or state');
112
- res.status(400).json({
113
- success: false,
114
- error: 'Missing parameters',
115
- message: 'OAuth code and state are required',
116
- });
117
- return;
118
- }
119
- // Validate state for CSRF protection
120
- if (!validateState(state)) {
121
- logger_1.AppLogger.warn(`Invalid or expired state in OAuth callback: ${state}`);
122
- res.status(400).json({
123
- success: false,
124
- error: 'Invalid state',
125
- message: 'CSRF validation failed',
126
- });
127
- return;
128
- }
129
- if (!process.env.GITHUB_CLIENT_SECRET) {
130
- logger_1.AppLogger.error('GitHub client secret not configured');
131
- res.status(500).json({
132
- error: 'OAuth not configured',
133
- message: 'GitHub OAuth is not properly configured',
134
- });
135
- return;
136
- }
137
- // Exchange code for access token
138
- logger_1.AppLogger.debug('Exchanging OAuth code for access token');
139
- const tokenResponse = await (0, github_oauth_service_1.exchangeCodeForToken)(code, process.env.GITHUB_CLIENT_ID, process.env.GITHUB_CLIENT_SECRET, process.env.OAUTH_REDIRECT_URI);
140
- // Get user information
141
- logger_1.AppLogger.debug('Retrieving authenticated user information');
142
- const user = await (0, github_oauth_service_1.getAuthenticatedUser)(tokenResponse.access_token);
143
- // Fetch user's repository permission
144
- let permission = null;
145
- try {
146
- logger_1.AppLogger.debug(`Fetching repository permission for user ${user.login}`);
147
- // Extract repository info from git remote
148
- const repoInfo = await (0, utilities_1.getRepositoryInfoFromGit)();
149
- if (!repoInfo) {
150
- logger_1.AppLogger.warn('Could not extract repository info from git remote - permission fetch skipped');
151
- }
152
- else {
153
- const { owner, repo } = repoInfo;
154
- permission = await (0, permission_service_1.getUserRepositoryPermission)(tokenResponse.access_token, user.id, user.login, owner, repo);
155
- logger_1.AppLogger.info(`User ${user.login} has ${permission.permission} permission on ${owner}/${repo}`);
156
- }
157
- }
158
- catch (permError) {
159
- logger_1.AppLogger.error(`Failed to fetch repository permission: ${permError}`);
160
- // Continue without permission - will be checked on protected routes
161
- permission = null;
162
- }
163
- // Create session with permission
164
- const session = {
165
- accessToken: tokenResponse.access_token,
166
- expiresIn: 3600, // 1 hour default
167
- expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
168
- user,
169
- scopes: tokenResponse.scope.split(','),
170
- permission, // Include fetched permission in session
171
- };
172
- // Store session and get token
173
- const sessionToken = (0, auth_middleware_1.storeSession)(session);
174
- // Get redirect URL
175
- const redirectUrl = getRedirectUrl(state) || '/';
176
- // Clear state
177
- clearState(state);
178
- logger_1.AppLogger.info(`User authenticated: ${user.login} with permission: ${permission?.permission || 'unknown'}`);
179
- res.json({
180
- success: true,
181
- message: 'Authentication successful',
182
- sessionToken,
183
- redirectUrl,
184
- user: {
185
- id: user.id,
186
- login: user.login,
187
- name: user.name,
188
- avatar_url: user.avatar_url,
189
- },
190
- permission: permission ? {
191
- level: permission.permission,
192
- role: permission.role,
193
- owner: permission.owner,
194
- repo: permission.repo,
195
- } : null,
196
- });
197
- }
198
- catch (error) {
199
- logger_1.AppLogger.error(`OAuth callback failed: ${error}`);
200
- res.status(500).json({
201
- success: false,
202
- error: 'Authentication failed',
203
- message: 'Failed to complete GitHub OAuth flow',
204
- });
205
- }
206
- });
20
+ router.get('/callback', auth_controller_1.callback);
207
21
  /**
208
22
  * Get current user session
209
23
  * GET /auth/me
210
24
  */
211
- router.get('/me', auth_middleware_1.authenticationMiddleware, (req, res) => {
212
- try {
213
- const session = (0, auth_middleware_1.getSessionFromRequest)(req);
214
- if (!session) {
215
- res.status(401).json({
216
- error: 'Unauthorized',
217
- message: 'No active session',
218
- });
219
- return;
220
- }
221
- res.json({
222
- success: true,
223
- user: {
224
- id: session.user.id,
225
- login: session.user.login,
226
- name: session.user.name,
227
- email: session.user.email,
228
- avatar_url: session.user.avatar_url,
229
- public_repos: session.user.public_repos,
230
- followers: session.user.followers,
231
- following: session.user.following,
232
- },
233
- scopes: session.scopes,
234
- expiresAt: session.expiresAt,
235
- permission: session.permission || null,
236
- });
237
- }
238
- catch (error) {
239
- logger_1.AppLogger.error(`Failed to get user session: ${error}`);
240
- res.status(500).json({
241
- error: 'Internal server error',
242
- message: 'Failed to retrieve user information',
243
- });
244
- }
245
- });
25
+ router.get('/me', auth_middleware_1.authenticationMiddleware, auth_controller_1.me);
246
26
  /**
247
27
  * Validate session
248
28
  * POST /auth/validate
249
29
  */
250
- router.post('/validate', auth_middleware_1.authenticationMiddleware, async (req, res) => {
251
- try {
252
- const session = (0, auth_middleware_1.getSessionFromRequest)(req);
253
- if (!session) {
254
- res.status(401).json({
255
- success: false,
256
- valid: false,
257
- message: 'No active session',
258
- });
259
- return;
260
- }
261
- // Validate token with GitHub
262
- const isValid = await (0, github_oauth_service_1.validateToken)(session.accessToken);
263
- if (!isValid) {
264
- // Token is no longer valid, invalidate session
265
- const token = req.headers.authorization?.replace('Bearer ', '') ||
266
- req.cookies?.['auth-token'];
267
- if (token) {
268
- (0, auth_middleware_1.invalidateSession)(token);
269
- }
270
- res.status(401).json({
271
- success: false,
272
- valid: false,
273
- message: 'Session token is no longer valid',
274
- });
275
- return;
276
- }
277
- res.json({
278
- success: true,
279
- valid: true,
280
- message: 'Session is valid',
281
- expiresAt: session.expiresAt,
282
- });
283
- }
284
- catch (error) {
285
- logger_1.AppLogger.error(`Session validation failed: ${error}`);
286
- res.status(500).json({
287
- success: false,
288
- valid: false,
289
- error: 'Validation failed',
290
- message: 'Failed to validate session',
291
- });
292
- }
293
- });
30
+ router.post('/validate', auth_middleware_1.authenticationMiddleware, auth_controller_1.validate);
294
31
  /**
295
32
  * Logout
296
33
  * POST /auth/logout
297
34
  */
298
- router.post('/logout', auth_middleware_1.authenticationMiddleware, (req, res) => {
299
- try {
300
- const token = req.headers.authorization?.replace('Bearer ', '') ||
301
- req.cookies?.['auth-token'];
302
- if (token) {
303
- (0, auth_middleware_1.invalidateSession)(token);
304
- }
305
- res.json({
306
- success: true,
307
- message: 'Logout successful',
308
- });
309
- }
310
- catch (error) {
311
- logger_1.AppLogger.error(`Logout failed: ${error}`);
312
- res.status(500).json({
313
- success: false,
314
- error: 'Logout failed',
315
- message: 'Failed to logout',
316
- });
317
- }
318
- });
35
+ router.post('/logout', auth_middleware_1.authenticationMiddleware, auth_controller_1.logout);
319
36
  /**
320
37
  * Refresh session (token)
321
38
  * POST /auth/refresh
322
39
  */
323
- router.post('/refresh', auth_middleware_1.authenticationMiddleware, async (req, res) => {
324
- try {
325
- const session = (0, auth_middleware_1.getSessionFromRequest)(req);
326
- if (!session) {
327
- res.status(401).json({
328
- success: false,
329
- error: 'Unauthorized',
330
- message: 'No active session',
331
- });
332
- return;
333
- }
334
- // Validate token is still valid
335
- const isValid = await (0, github_oauth_service_1.validateToken)(session.accessToken);
336
- if (!isValid) {
337
- res.status(401).json({
338
- success: false,
339
- error: 'Unauthorized',
340
- message: 'Token is no longer valid with GitHub',
341
- });
342
- return;
343
- }
344
- // Create new session with updated expiry
345
- const newSession = {
346
- ...session,
347
- expiresAt: Date.now() + 24 * 60 * 60 * 1000, // Extend 24 hours
348
- };
349
- const newToken = (0, auth_middleware_1.storeSession)(newSession);
350
- // Invalidate old token
351
- const oldToken = req.headers.authorization?.replace('Bearer ', '') ||
352
- req.cookies?.['auth-token'];
353
- if (oldToken) {
354
- (0, auth_middleware_1.invalidateSession)(oldToken);
355
- }
356
- res.json({
357
- success: true,
358
- message: 'Session refreshed successfully',
359
- sessionToken: newToken,
360
- expiresAt: newSession.expiresAt,
361
- });
362
- }
363
- catch (error) {
364
- logger_1.AppLogger.error(`Session refresh failed: ${error}`);
365
- res.status(500).json({
366
- success: false,
367
- error: 'Refresh failed',
368
- message: 'Failed to refresh session',
369
- });
370
- }
371
- });
40
+ router.post('/refresh', auth_middleware_1.authenticationMiddleware, auth_controller_1.refresh);
372
41
  exports.default = router;
@@ -6,156 +6,21 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const express_1 = require("express");
8
8
  const auth_middleware_1 = require("../middleware/auth-middleware");
9
- const permission_service_1 = require("../services/permission-service");
10
- const logger_1 = require("../middleware/logger");
9
+ const permission_controller_1 = require("../controllers/permission-controller");
11
10
  const router = (0, express_1.Router)();
12
11
  /**
13
12
  * Get user's permission for a specific repository
14
13
  * GET /permissions/:owner/:repo
15
14
  */
16
- router.get('/:owner/:repo', auth_middleware_1.authenticationMiddleware, async (req, res) => {
17
- try {
18
- const session = (0, auth_middleware_1.getSessionFromRequest)(req);
19
- if (!session) {
20
- res.status(401).json({
21
- error: 'Unauthorized',
22
- message: 'No active session',
23
- });
24
- return;
25
- }
26
- const { owner, repo } = req.params;
27
- const forceRefresh = req.query.refresh === 'true';
28
- if (!owner || !repo) {
29
- res.status(400).json({
30
- error: 'Bad request',
31
- message: 'Owner and repo parameters are required',
32
- });
33
- return;
34
- }
35
- logger_1.AppLogger.debug(`Checking permission for ${session.user.login} in ${owner}/${repo}`);
36
- // Get permission from cache or GitHub API
37
- const cachedPermission = await (0, permission_service_1.getUserRepositoryPermission)(session.accessToken, session.user.id, session.user.login, owner, repo, forceRefresh);
38
- const response = {
39
- permission: cachedPermission.permission,
40
- role: cachedPermission.role,
41
- canAdmin: cachedPermission.permission === 'admin',
42
- canMaintain: (0, permission_service_1.canPerformAction)(cachedPermission.permission, 'maintain'),
43
- canWrite: (0, permission_service_1.canPerformAction)(cachedPermission.permission, 'write'),
44
- canRead: (0, permission_service_1.canPerformAction)(cachedPermission.permission, 'read'),
45
- denied: cachedPermission.permission === 'none',
46
- };
47
- res.json({
48
- success: true,
49
- owner,
50
- repo,
51
- user: session.user.login,
52
- ...response,
53
- });
54
- }
55
- catch (error) {
56
- logger_1.AppLogger.error(`Failed to check permission: ${error}`);
57
- res.status(500).json({
58
- success: false,
59
- error: 'Internal server error',
60
- message: 'Failed to check repository permission',
61
- });
62
- }
63
- });
15
+ router.get('/:owner/:repo', auth_middleware_1.authenticationMiddleware, permission_controller_1.getRepositoryPermission);
64
16
  /**
65
17
  * Check if user can perform a specific action
66
18
  * POST /permissions/:owner/:repo/can-action
67
19
  */
68
- router.post('/:owner/:repo/can-action', auth_middleware_1.authenticationMiddleware, async (req, res) => {
69
- try {
70
- const session = (0, auth_middleware_1.getSessionFromRequest)(req);
71
- if (!session) {
72
- res.status(401).json({
73
- error: 'Unauthorized',
74
- message: 'No active session',
75
- });
76
- return;
77
- }
78
- const { owner, repo } = req.params;
79
- const { action } = req.body;
80
- if (!owner || !repo) {
81
- res.status(400).json({
82
- error: 'Bad request',
83
- message: 'Owner and repo parameters are required',
84
- });
85
- return;
86
- }
87
- if (!action || !['read', 'write', 'maintain', 'admin'].includes(action)) {
88
- res.status(400).json({
89
- error: 'Bad request',
90
- message: 'Valid action is required (read, write, maintain, or admin)',
91
- });
92
- return;
93
- }
94
- logger_1.AppLogger.debug(`Checking if ${session.user.login} can perform '${action}' in ${owner}/${repo}`);
95
- // Get permission
96
- const cachedPermission = await (0, permission_service_1.getUserRepositoryPermission)(session.accessToken, session.user.id, session.user.login, owner, repo);
97
- // Check if user can perform action
98
- const can = (0, permission_service_1.canPerformAction)(cachedPermission.permission, action);
99
- res.json({
100
- success: true,
101
- owner,
102
- repo,
103
- user: session.user.login,
104
- action,
105
- can,
106
- permission: cachedPermission.permission,
107
- role: cachedPermission.role,
108
- });
109
- }
110
- catch (error) {
111
- logger_1.AppLogger.error(`Failed to check action permission: ${error}`);
112
- res.status(500).json({
113
- success: false,
114
- error: 'Internal server error',
115
- message: 'Failed to check action permission',
116
- });
117
- }
118
- });
20
+ router.post('/:owner/:repo/can-action', auth_middleware_1.authenticationMiddleware, permission_controller_1.checkActionPermission);
119
21
  /**
120
22
  * Invalidate permission cache for a repository
121
23
  * POST /permissions/:owner/:repo/invalidate
122
24
  */
123
- router.post('/:owner/:repo/invalidate', auth_middleware_1.authenticationMiddleware, (req, res) => {
124
- try {
125
- const session = (0, auth_middleware_1.getSessionFromRequest)(req);
126
- if (!session) {
127
- res.status(401).json({
128
- error: 'Unauthorized',
129
- message: 'No active session',
130
- });
131
- return;
132
- }
133
- const { owner, repo } = req.params;
134
- if (!owner || !repo) {
135
- res.status(400).json({
136
- error: 'Bad request',
137
- message: 'Owner and repo parameters are required',
138
- });
139
- return;
140
- }
141
- logger_1.AppLogger.debug(`Invalidating permission cache for ${session.user.login} in ${owner}/${repo}`);
142
- // Invalidate cache
143
- (0, permission_service_1.invalidatePermissionCache)(session.user.id, owner, repo);
144
- res.json({
145
- success: true,
146
- message: 'Permission cache invalidated',
147
- owner,
148
- repo,
149
- user: session.user.login,
150
- });
151
- }
152
- catch (error) {
153
- logger_1.AppLogger.error(`Failed to invalidate cache: ${error}`);
154
- res.status(500).json({
155
- success: false,
156
- error: 'Internal server error',
157
- message: 'Failed to invalidate permission cache',
158
- });
159
- }
160
- });
25
+ router.post('/:owner/:repo/invalidate', auth_middleware_1.authenticationMiddleware, permission_controller_1.invalidateCache);
161
26
  exports.default = router;
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ /**
3
+ * Authentication Service
4
+ * Handles all authentication business logic including OAuth flow, session management
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.initiateLogin = initiateLogin;
8
+ exports.handleOAuthCallback = handleOAuthCallback;
9
+ exports.getCurrentSession = getCurrentSession;
10
+ exports.validateCurrentSession = validateCurrentSession;
11
+ exports.logoutUser = logoutUser;
12
+ exports.refreshUserSession = refreshUserSession;
13
+ const github_oauth_service_1 = require("./github-oauth-service");
14
+ const auth_middleware_1 = require("../middleware/auth-middleware");
15
+ const permission_service_1 = require("./permission-service");
16
+ const logger_1 = require("../middleware/logger");
17
+ const utilities_1 = require("../utils/utilities");
18
+ // State store for CSRF protection
19
+ const stateStore = new Map();
20
+ const STATE_EXPIRY = 10 * 60 * 1000; // 10 minutes
21
+ /**
22
+ * Generate random state for CSRF protection
23
+ */
24
+ function generateState() {
25
+ return Math.random().toString(36).substring(2, 15) +
26
+ Math.random().toString(36).substring(2, 15);
27
+ }
28
+ /**
29
+ * Validate OAuth state
30
+ */
31
+ function validateState(state) {
32
+ const entry = stateStore.get(state);
33
+ if (!entry) {
34
+ return false;
35
+ }
36
+ // Check if state has expired
37
+ if (Date.now() - entry.createdAt > STATE_EXPIRY) {
38
+ stateStore.delete(state);
39
+ return false;
40
+ }
41
+ return true;
42
+ }
43
+ /**
44
+ * Get redirect URL from state
45
+ */
46
+ function getRedirectUrl(state) {
47
+ const entry = stateStore.get(state);
48
+ return entry?.redirectUrl;
49
+ }
50
+ /**
51
+ * Clear state after use
52
+ */
53
+ function clearState(state) {
54
+ stateStore.delete(state);
55
+ }
56
+ /**
57
+ * Initiate login by generating authorization URL
58
+ * @param redirectUrl - Where to redirect after login
59
+ * @returns Login URL response with state
60
+ */
61
+ function initiateLogin(redirectUrl = '/') {
62
+ if (!process.env.GITHUB_CLIENT_ID) {
63
+ throw new Error('GitHub client ID not configured');
64
+ }
65
+ const state = generateState();
66
+ // Store state for validation
67
+ stateStore.set(state, {
68
+ createdAt: Date.now(),
69
+ redirectUrl,
70
+ });
71
+ const authUrl = (0, github_oauth_service_1.generateAuthorizationUrl)(process.env.GITHUB_CLIENT_ID, process.env.OAUTH_REDIRECT_URI, state);
72
+ return {
73
+ authUrl,
74
+ state,
75
+ };
76
+ }
77
+ /**
78
+ * Handle OAuth callback and create session
79
+ * @param code - OAuth authorization code
80
+ * @param state - CSRF protection state
81
+ * @returns OAuth callback response with session token
82
+ */
83
+ async function handleOAuthCallback(code, state) {
84
+ // Validate code and state
85
+ if (!code || !state) {
86
+ throw new Error('OAuth code and state are required');
87
+ }
88
+ // Validate state for CSRF protection
89
+ if (!validateState(state)) {
90
+ throw new Error('Invalid or expired state - CSRF validation failed');
91
+ }
92
+ if (!process.env.GITHUB_CLIENT_SECRET) {
93
+ throw new Error('GitHub client secret not configured');
94
+ }
95
+ // Exchange code for access token
96
+ logger_1.AppLogger.debug('Exchanging OAuth code for access token');
97
+ const tokenResponse = await (0, github_oauth_service_1.exchangeCodeForToken)(code, process.env.GITHUB_CLIENT_ID, process.env.GITHUB_CLIENT_SECRET, process.env.OAUTH_REDIRECT_URI);
98
+ // Get user information
99
+ logger_1.AppLogger.debug('Retrieving authenticated user information');
100
+ const user = await (0, github_oauth_service_1.getAuthenticatedUser)(tokenResponse.access_token);
101
+ // Fetch user's repository permission
102
+ let permission = null;
103
+ try {
104
+ logger_1.AppLogger.debug(`Fetching repository permission for user ${user.login}`);
105
+ // Extract repository info from git remote
106
+ const repoInfo = await (0, utilities_1.getRepositoryInfoFromGit)();
107
+ if (!repoInfo) {
108
+ logger_1.AppLogger.warn('Could not extract repository info from git remote - permission fetch skipped');
109
+ }
110
+ else {
111
+ const { owner, repo } = repoInfo;
112
+ permission = await (0, permission_service_1.getUserRepositoryPermission)(tokenResponse.access_token, user.id, user.login, owner, repo);
113
+ logger_1.AppLogger.info(`User ${user.login} has ${permission.permission} permission on ${owner}/${repo}`);
114
+ }
115
+ }
116
+ catch (permError) {
117
+ logger_1.AppLogger.error(`Failed to fetch repository permission: ${permError}`);
118
+ // Continue without permission - will be checked on protected routes
119
+ permission = null;
120
+ }
121
+ // Create session with permission
122
+ const session = {
123
+ accessToken: tokenResponse.access_token,
124
+ expiresIn: 3600, // 1 hour default
125
+ expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
126
+ user,
127
+ scopes: tokenResponse.scope.split(','),
128
+ permission, // Include fetched permission in session
129
+ };
130
+ // Store session and get token
131
+ const sessionToken = (0, auth_middleware_1.storeSession)(session);
132
+ // Get redirect URL
133
+ const redirectUrl = getRedirectUrl(state) || '/';
134
+ // Clear state
135
+ clearState(state);
136
+ logger_1.AppLogger.info(`User authenticated: ${user.login} with permission: ${permission?.permission || 'unknown'}`);
137
+ return {
138
+ sessionToken,
139
+ redirectUrl,
140
+ user: {
141
+ id: user.id,
142
+ login: user.login,
143
+ name: user.name,
144
+ avatar_url: user.avatar_url,
145
+ },
146
+ permission: permission ? {
147
+ level: permission.permission,
148
+ role: permission.role,
149
+ owner: permission.owner,
150
+ repo: permission.repo,
151
+ } : null,
152
+ };
153
+ }
154
+ /**
155
+ * Get current user session from request
156
+ * @param req - Express request
157
+ * @returns Session response
158
+ */
159
+ function getCurrentSession(req) {
160
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
161
+ if (!session) {
162
+ throw new Error('No active session');
163
+ }
164
+ return {
165
+ user: session.user,
166
+ scopes: session.scopes,
167
+ expiresAt: session.expiresAt,
168
+ permission: session.permission || null,
169
+ };
170
+ }
171
+ /**
172
+ * Validate current session with GitHub
173
+ * @param req - Express request
174
+ * @returns Validation response
175
+ */
176
+ async function validateCurrentSession(req) {
177
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
178
+ if (!session) {
179
+ throw new Error('No active session');
180
+ }
181
+ // Validate token with GitHub
182
+ const isValid = await (0, github_oauth_service_1.validateToken)(session.accessToken);
183
+ if (!isValid) {
184
+ // Token is no longer valid, invalidate session
185
+ const token = req.headers.authorization?.replace('Bearer ', '') ||
186
+ req.cookies?.['auth-token'];
187
+ if (token) {
188
+ (0, auth_middleware_1.invalidateSession)(token);
189
+ }
190
+ throw new Error('Session token is no longer valid');
191
+ }
192
+ return {
193
+ valid: true,
194
+ expiresAt: session.expiresAt,
195
+ };
196
+ }
197
+ /**
198
+ * Logout user by invalidating session
199
+ * @param req - Express request
200
+ */
201
+ function logoutUser(req) {
202
+ const token = req.headers.authorization?.replace('Bearer ', '') ||
203
+ req.cookies?.['auth-token'];
204
+ if (token) {
205
+ (0, auth_middleware_1.invalidateSession)(token);
206
+ }
207
+ }
208
+ /**
209
+ * Refresh session token
210
+ * @param req - Express request
211
+ * @returns New session token and expiry
212
+ */
213
+ async function refreshUserSession(req) {
214
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
215
+ if (!session) {
216
+ throw new Error('No active session');
217
+ }
218
+ // Validate token is still valid
219
+ const isValid = await (0, github_oauth_service_1.validateToken)(session.accessToken);
220
+ if (!isValid) {
221
+ throw new Error('Token is no longer valid with GitHub');
222
+ }
223
+ // Create new session with updated expiry
224
+ const newSession = {
225
+ ...session,
226
+ expiresAt: Date.now() + 24 * 60 * 60 * 1000, // Extend 24 hours
227
+ };
228
+ const newToken = (0, auth_middleware_1.storeSession)(newSession);
229
+ // Invalidate old token
230
+ const oldToken = req.headers.authorization?.replace('Bearer ', '') ||
231
+ req.cookies?.['auth-token'];
232
+ if (oldToken) {
233
+ (0, auth_middleware_1.invalidateSession)(oldToken);
234
+ }
235
+ return {
236
+ sessionToken: newToken,
237
+ expiresAt: newSession.expiresAt,
238
+ };
239
+ }
@@ -11,6 +11,8 @@ exports.invalidateUserCache = invalidateUserCache;
11
11
  exports.clearAllCache = clearAllCache;
12
12
  exports.getCacheStats = getCacheStats;
13
13
  exports.canPerformAction = canPerformAction;
14
+ exports.checkRepositoryPermission = checkRepositoryPermission;
15
+ exports.checkUserAction = checkUserAction;
14
16
  const github_oauth_service_1 = require("./github-oauth-service");
15
17
  const logger_1 = require("../middleware/logger");
16
18
  // Cache storage: key = `${userId}:${owner}/${repo}`
@@ -172,3 +174,31 @@ function canPerformAction(permission, requiredAction) {
172
174
  const allowedPermissions = actionPermissionMap[requiredAction] || [];
173
175
  return allowedPermissions.includes(permission);
174
176
  }
177
+ /**
178
+ * Get user's permission for a specific repository with response formatting
179
+ */
180
+ async function checkRepositoryPermission(accessToken, userId, username, owner, repo, forceRefresh = false) {
181
+ const cachedPermission = await getUserRepositoryPermission(accessToken, userId, username, owner, repo, forceRefresh);
182
+ return {
183
+ permission: cachedPermission.permission,
184
+ role: cachedPermission.role,
185
+ canAdmin: cachedPermission.permission === 'admin',
186
+ canMaintain: canPerformAction(cachedPermission.permission, 'maintain'),
187
+ canWrite: canPerformAction(cachedPermission.permission, 'write'),
188
+ canRead: canPerformAction(cachedPermission.permission, 'read'),
189
+ denied: cachedPermission.permission === 'none',
190
+ };
191
+ }
192
+ /**
193
+ * Check if user can perform a specific action with response formatting
194
+ */
195
+ async function checkUserAction(accessToken, userId, username, owner, repo, action) {
196
+ const cachedPermission = await getUserRepositoryPermission(accessToken, userId, username, owner, repo);
197
+ const can = canPerformAction(cachedPermission.permission, action);
198
+ return {
199
+ action,
200
+ can,
201
+ permission: cachedPermission.permission,
202
+ role: cachedPermission.role,
203
+ };
204
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manojkmfsi/monodog",
3
- "version": "1.1.30",
3
+ "version": "1.1.31",
4
4
  "description": "App for monodog monorepo",
5
5
  "license": "MIT",
6
6
  "author": "Mindfiredigital",