@manojkmfsi/monodog 1.1.18 → 1.1.19

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 ADDED
@@ -0,0 +1,19 @@
1
+ # GitHub OAuth Configuration
2
+ GITHUB_CLIENT_ID=your_client_id_here
3
+ GITHUB_CLIENT_SECRET=your_client_secret_here
4
+ OAUTH_REDIRECT_URI=http://localhost:3000/auth/callback
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,11 @@
1
1
  # @manojkmfsi/monoapp
2
2
 
3
+ ## 1.1.19
4
+
5
+ ### Patch Changes
6
+
7
+ - [#146](https://github.com/manojkmfsi/monodog/pull/146) [`5419367`](https://github.com/manojkmfsi/monodog/commit/54193676da5c07498a6dfbbc2bb62a53072648ca) Thanks [@manojkmfsi](https://github.com/manojkmfsi)! - km..,/.,/.
8
+
3
9
  ## 1.1.18
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ /**
3
+ * Authentication Middleware
4
+ * Handles session validation and permission checks
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.generateSessionToken = generateSessionToken;
8
+ exports.storeSession = storeSession;
9
+ exports.getSession = getSession;
10
+ exports.invalidateSession = invalidateSession;
11
+ exports.authenticationMiddleware = authenticationMiddleware;
12
+ exports.repositoryPermissionMiddleware = repositoryPermissionMiddleware;
13
+ exports.getSessionFromRequest = getSessionFromRequest;
14
+ exports.isAuthenticated = isAuthenticated;
15
+ exports.clearExpiredSessions = clearExpiredSessions;
16
+ exports.getSessionStats = getSessionStats;
17
+ exports.initializeAuthentication = initializeAuthentication;
18
+ const logger_1 = require("./logger");
19
+ // Store sessions in memory (should be replaced with proper session store in production)
20
+ const sessionStore = new Map();
21
+ const sessionTimeout = 24 * 60 * 60 * 1000; // 24 hours
22
+ /**
23
+ * Generate session token
24
+ */
25
+ function generateSessionToken(length = 32) {
26
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
27
+ let token = '';
28
+ for (let i = 0; i < length; i++) {
29
+ token += chars.charAt(Math.floor(Math.random() * chars.length));
30
+ }
31
+ return token;
32
+ }
33
+ /**
34
+ * Store session
35
+ */
36
+ function storeSession(session) {
37
+ const token = generateSessionToken();
38
+ sessionStore.set(token, session);
39
+ // Set expiration timeout
40
+ setTimeout(() => {
41
+ sessionStore.delete(token);
42
+ logger_1.AppLogger.debug(`Session expired: ${session.user.login}`);
43
+ }, sessionTimeout);
44
+ logger_1.AppLogger.debug(`Session stored for user: ${session.user.login}`);
45
+ return token;
46
+ }
47
+ /**
48
+ * Get session by token
49
+ */
50
+ function getSession(token) {
51
+ const session = sessionStore.get(token);
52
+ if (!session) {
53
+ return null;
54
+ }
55
+ // Check if session has expired based on expiresAt
56
+ if (Date.now() > session.expiresAt) {
57
+ sessionStore.delete(token);
58
+ logger_1.AppLogger.warn(`Session token expired: ${token.substring(0, 10)}...`);
59
+ return null;
60
+ }
61
+ return session;
62
+ }
63
+ /**
64
+ * Invalidate session
65
+ */
66
+ function invalidateSession(token) {
67
+ sessionStore.delete(token);
68
+ logger_1.AppLogger.debug(`Session invalidated: ${token.substring(0, 10)}...`);
69
+ }
70
+ /**
71
+ * Middleware to verify authentication
72
+ */
73
+ function authenticationMiddleware(req, res, next) {
74
+ const token = req.headers.authorization?.replace('Bearer ', '') ||
75
+ req.cookies?.['auth-token'];
76
+ if (!token) {
77
+ logger_1.AppLogger.warn(`Unauthorized request to ${req.path}: no auth token`);
78
+ res.status(401).json({
79
+ error: 'Unauthorized',
80
+ message: 'Authentication token required',
81
+ });
82
+ return;
83
+ }
84
+ const session = getSession(token);
85
+ if (!session) {
86
+ logger_1.AppLogger.warn(`Unauthorized request to ${req.path}: invalid session`);
87
+ res.status(401).json({
88
+ error: 'Unauthorized',
89
+ message: 'Invalid or expired session',
90
+ });
91
+ return;
92
+ }
93
+ // Attach session to request
94
+ req.session = session;
95
+ logger_1.AppLogger.debug(`Authenticated request from user: ${session.user.login}`);
96
+ next();
97
+ }
98
+ /**
99
+ * Middleware to verify repository permission
100
+ */
101
+ function repositoryPermissionMiddleware(requiredPermission) {
102
+ return (req, res, next) => {
103
+ const authReq = req;
104
+ const session = authReq.session;
105
+ if (!session) {
106
+ res.status(401).json({
107
+ error: 'Unauthorized',
108
+ message: 'Session not found',
109
+ });
110
+ return;
111
+ }
112
+ const permission = authReq.permission;
113
+ if (!permission) {
114
+ res.status(403).json({
115
+ error: 'Forbidden',
116
+ message: 'Permission not resolved for repository',
117
+ });
118
+ return;
119
+ }
120
+ const permissionHierarchy = {
121
+ admin: 4,
122
+ maintain: 3,
123
+ write: 2,
124
+ read: 1,
125
+ none: 0,
126
+ };
127
+ const userLevel = permissionHierarchy[permission.permission] || 0;
128
+ const requiredLevel = permissionHierarchy[requiredPermission] || 0;
129
+ if (userLevel < requiredLevel) {
130
+ logger_1.AppLogger.warn(`User ${session.user.login} lacks permission for action requiring ${requiredPermission}`);
131
+ res.status(403).json({
132
+ error: 'Forbidden',
133
+ message: `This action requires ${requiredPermission} permission`,
134
+ });
135
+ return;
136
+ }
137
+ next();
138
+ };
139
+ }
140
+ /**
141
+ * Get session from request
142
+ */
143
+ function getSessionFromRequest(req) {
144
+ return req.session || null;
145
+ }
146
+ /**
147
+ * Check if user is authenticated
148
+ */
149
+ function isAuthenticated(req) {
150
+ return !!getSessionFromRequest(req);
151
+ }
152
+ /**
153
+ * Clear expired sessions (should be called periodically)
154
+ */
155
+ function clearExpiredSessions() {
156
+ const now = Date.now();
157
+ let clearedCount = 0;
158
+ for (const [token, session] of sessionStore.entries()) {
159
+ if (now > session.expiresAt) {
160
+ sessionStore.delete(token);
161
+ clearedCount++;
162
+ }
163
+ }
164
+ if (clearedCount > 0) {
165
+ logger_1.AppLogger.debug(`Cleared ${clearedCount} expired sessions`);
166
+ }
167
+ }
168
+ /**
169
+ * Get session statistics
170
+ */
171
+ function getSessionStats() {
172
+ return {
173
+ activeSessions: sessionStore.size,
174
+ maxSessions: 10000,
175
+ };
176
+ }
177
+ /**
178
+ * Initialize authentication (call on server startup)
179
+ */
180
+ function initializeAuthentication() {
181
+ // Periodically clear expired sessions
182
+ setInterval(() => {
183
+ clearExpiredSessions();
184
+ }, 60 * 1000); // Every minute
185
+ logger_1.AppLogger.info('Authentication system initialized');
186
+ }
@@ -18,7 +18,11 @@ const package_routes_1 = __importDefault(require("../routes/package-routes"));
18
18
  const commit_routes_1 = __importDefault(require("../routes/commit-routes"));
19
19
  const health_routes_1 = __importDefault(require("../routes/health-routes"));
20
20
  const config_routes_1 = __importDefault(require("../routes/config-routes"));
21
+ const auth_routes_1 = __importDefault(require("../routes/auth-routes"));
22
+ const permission_routes_1 = __importDefault(require("../routes/permission-routes"));
21
23
  const constants_1 = require("../constants");
24
+ const auth_middleware_1 = require("./auth-middleware");
25
+ const permission_service_1 = require("../services/permission-service");
22
26
  /**
23
27
  * Validate port number
24
28
  */
@@ -49,7 +53,13 @@ function createApp(rootPath) {
49
53
  app.use(logger_1.httpLogger);
50
54
  // Setup Swagger documentation
51
55
  (0, swagger_middleware_1.setupSwaggerDocs)(app);
56
+ // Initialize authentication system
57
+ (0, auth_middleware_1.initializeAuthentication)();
58
+ // Start permission cache cleanup
59
+ (0, permission_service_1.startCacheCleanup)();
52
60
  // Routes
61
+ app.use('/api/auth', auth_routes_1.default);
62
+ app.use('/api/permissions', permission_routes_1.default);
53
63
  app.use('/api/packages', package_routes_1.default);
54
64
  app.use('/api/commits/', commit_routes_1.default);
55
65
  app.use('/api/health/', health_routes_1.default);
@@ -75,13 +85,28 @@ function startServer(rootPath) {
75
85
  console.log((0, constants_1.SUCCESS_SERVER_START)(host, validatedPort));
76
86
  logger_1.AppLogger.info('API endpoints available:', {
77
87
  endpoints: [
88
+ // Auth endpoints
89
+ 'GET /api/auth/login',
90
+ 'GET /api/auth/callback',
91
+ 'GET /api/auth/me',
92
+ 'POST /api/auth/validate',
93
+ 'POST /api/auth/logout',
94
+ 'POST /api/auth/refresh',
95
+ // Permission endpoints
96
+ 'GET /api/permissions/:owner/:repo',
97
+ 'POST /api/permissions/:owner/:repo/can-action',
98
+ 'POST /api/permissions/:owner/:repo/invalidate',
99
+ // Package endpoints
78
100
  'POST /api/packages/refresh',
79
101
  'GET /api/packages',
80
102
  'GET /api/packages/:name',
81
103
  'PUT /api/packages/update-config',
104
+ // Commit endpoints
82
105
  'GET /api/commits/:packagePath',
106
+ // Health endpoints
83
107
  'GET /api/health/packages',
84
108
  'POST /api/health/refresh',
109
+ // Config endpoints
85
110
  'PUT /api/config/files/:id',
86
111
  'GET /api/config/files',
87
112
  ],
@@ -0,0 +1,342 @@
1
+ "use strict";
2
+ /**
3
+ * Authentication Routes
4
+ * Handles GitHub OAuth and session management
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const express_1 = require("express");
8
+ const github_oauth_service_1 = require("../services/github-oauth-service");
9
+ const auth_middleware_1 = require("../middleware/auth-middleware");
10
+ const logger_1 = require("../middleware/logger");
11
+ const router = (0, express_1.Router)();
12
+ // OAuth configuration (should come from environment variables)
13
+ // const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || '';
14
+ // const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET || '';
15
+ // const OAUTH_REDIRECT_URI = process.env.OAUTH_REDIRECT_URI || 'http://localhost:3000/auth/callback';
16
+ // State store for CSRF protection
17
+ const stateStore = new Map();
18
+ const STATE_EXPIRY = 10 * 60 * 1000; // 10 minutes
19
+ /**
20
+ * Generate random state for CSRF protection
21
+ */
22
+ function generateState() {
23
+ return Math.random().toString(36).substring(2, 15) +
24
+ Math.random().toString(36).substring(2, 15);
25
+ }
26
+ /**
27
+ * Validate OAuth state
28
+ */
29
+ function validateState(state) {
30
+ const entry = stateStore.get(state);
31
+ if (!entry) {
32
+ return false;
33
+ }
34
+ // Check if state has expired
35
+ if (Date.now() - entry.createdAt > STATE_EXPIRY) {
36
+ stateStore.delete(state);
37
+ return false;
38
+ }
39
+ return true;
40
+ }
41
+ /**
42
+ * Get redirect URL from state
43
+ */
44
+ function getRedirectUrl(state) {
45
+ const entry = stateStore.get(state);
46
+ return entry?.redirectUrl;
47
+ }
48
+ /**
49
+ * Clear state after use
50
+ */
51
+ function clearState(state) {
52
+ stateStore.delete(state);
53
+ }
54
+ /**
55
+ * Start OAuth login flow
56
+ * GET /auth/login
57
+ */
58
+ router.get('/login', (req, res) => {
59
+ try {
60
+ if (!process.env.GITHUB_CLIENT_ID) {
61
+ logger_1.AppLogger.error('GitHub client ID not configured');
62
+ res.status(500).json({
63
+ error: 'OAuth not configured',
64
+ message: 'GitHub OAuth is not properly configured',
65
+ });
66
+ return;
67
+ }
68
+ const state = generateState();
69
+ const redirectUrl = req.query.redirect || '/';
70
+ // Store state for validation
71
+ stateStore.set(state, {
72
+ createdAt: Date.now(),
73
+ redirectUrl,
74
+ });
75
+ const authUrl = (0, github_oauth_service_1.generateAuthorizationUrl)(process.env.GITHUB_CLIENT_ID, process.env.OAUTH_REDIRECT_URI, state);
76
+ res.json({
77
+ success: true,
78
+ authUrl,
79
+ message: 'Redirect to this URL to authenticate with GitHub',
80
+ });
81
+ }
82
+ catch (error) {
83
+ logger_1.AppLogger.error(`Login initiation failed: ${error}`);
84
+ res.status(500).json({
85
+ error: 'Login failed',
86
+ message: 'Failed to initiate GitHub OAuth flow',
87
+ });
88
+ }
89
+ });
90
+ /**
91
+ * OAuth callback handler
92
+ * GET /auth/callback?code=...&state=...
93
+ */
94
+ router.get('/callback', async (req, res) => {
95
+ try {
96
+ const { code, state, error, error_description } = req.query;
97
+ // Handle OAuth errors
98
+ if (error) {
99
+ logger_1.AppLogger.warn(`OAuth error: ${error} - ${error_description}`);
100
+ res.status(400).json({
101
+ success: false,
102
+ error: error,
103
+ message: error_description,
104
+ });
105
+ return;
106
+ }
107
+ // Validate code and state
108
+ if (!code || !state) {
109
+ logger_1.AppLogger.warn('OAuth callback missing code or state');
110
+ res.status(400).json({
111
+ success: false,
112
+ error: 'Missing parameters',
113
+ message: 'OAuth code and state are required',
114
+ });
115
+ return;
116
+ }
117
+ // Validate state for CSRF protection
118
+ if (!validateState(state)) {
119
+ logger_1.AppLogger.warn(`Invalid or expired state in OAuth callback: ${state}`);
120
+ res.status(400).json({
121
+ success: false,
122
+ error: 'Invalid state',
123
+ message: 'CSRF validation failed',
124
+ });
125
+ return;
126
+ }
127
+ if (!process.env.GITHUB_CLIENT_SECRET) {
128
+ logger_1.AppLogger.error('GitHub client secret not configured');
129
+ res.status(500).json({
130
+ error: 'OAuth not configured',
131
+ message: 'GitHub OAuth is not properly configured',
132
+ });
133
+ return;
134
+ }
135
+ // Exchange code for access token
136
+ logger_1.AppLogger.debug('Exchanging OAuth code for access token');
137
+ 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);
138
+ // Get user information
139
+ logger_1.AppLogger.debug('Retrieving authenticated user information');
140
+ const user = await (0, github_oauth_service_1.getAuthenticatedUser)(tokenResponse.access_token);
141
+ // Create session
142
+ const session = {
143
+ accessToken: tokenResponse.access_token,
144
+ expiresIn: 3600, // 1 hour default
145
+ expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
146
+ user,
147
+ scopes: tokenResponse.scope.split(','),
148
+ };
149
+ // Store session and get token
150
+ const sessionToken = (0, auth_middleware_1.storeSession)(session);
151
+ // Get redirect URL
152
+ const redirectUrl = getRedirectUrl(state) || '/';
153
+ // Clear state
154
+ clearState(state);
155
+ logger_1.AppLogger.info(`User authenticated: ${user.login}`);
156
+ res.json({
157
+ success: true,
158
+ message: 'Authentication successful',
159
+ sessionToken,
160
+ redirectUrl,
161
+ user: {
162
+ id: user.id,
163
+ login: user.login,
164
+ name: user.name,
165
+ avatar_url: user.avatar_url,
166
+ },
167
+ });
168
+ }
169
+ catch (error) {
170
+ logger_1.AppLogger.error(`OAuth callback failed: ${error}`);
171
+ res.status(500).json({
172
+ success: false,
173
+ error: 'Authentication failed',
174
+ message: 'Failed to complete GitHub OAuth flow',
175
+ });
176
+ }
177
+ });
178
+ /**
179
+ * Get current user session
180
+ * GET /auth/me
181
+ */
182
+ router.get('/me', auth_middleware_1.authenticationMiddleware, (req, res) => {
183
+ try {
184
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
185
+ if (!session) {
186
+ res.status(401).json({
187
+ error: 'Unauthorized',
188
+ message: 'No active session',
189
+ });
190
+ return;
191
+ }
192
+ res.json({
193
+ success: true,
194
+ user: {
195
+ id: session.user.id,
196
+ login: session.user.login,
197
+ name: session.user.name,
198
+ email: session.user.email,
199
+ avatar_url: session.user.avatar_url,
200
+ public_repos: session.user.public_repos,
201
+ followers: session.user.followers,
202
+ following: session.user.following,
203
+ },
204
+ scopes: session.scopes,
205
+ expiresAt: session.expiresAt,
206
+ });
207
+ }
208
+ catch (error) {
209
+ logger_1.AppLogger.error(`Failed to get user session: ${error}`);
210
+ res.status(500).json({
211
+ error: 'Internal server error',
212
+ message: 'Failed to retrieve user information',
213
+ });
214
+ }
215
+ });
216
+ /**
217
+ * Validate session
218
+ * POST /auth/validate
219
+ */
220
+ router.post('/validate', auth_middleware_1.authenticationMiddleware, async (req, res) => {
221
+ try {
222
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
223
+ if (!session) {
224
+ res.status(401).json({
225
+ success: false,
226
+ valid: false,
227
+ message: 'No active session',
228
+ });
229
+ return;
230
+ }
231
+ // Validate token with GitHub
232
+ const isValid = await (0, github_oauth_service_1.validateToken)(session.accessToken);
233
+ if (!isValid) {
234
+ // Token is no longer valid, invalidate session
235
+ const token = req.headers.authorization?.replace('Bearer ', '') ||
236
+ req.cookies?.['auth-token'];
237
+ if (token) {
238
+ (0, auth_middleware_1.invalidateSession)(token);
239
+ }
240
+ res.status(401).json({
241
+ success: false,
242
+ valid: false,
243
+ message: 'Session token is no longer valid',
244
+ });
245
+ return;
246
+ }
247
+ res.json({
248
+ success: true,
249
+ valid: true,
250
+ message: 'Session is valid',
251
+ expiresAt: session.expiresAt,
252
+ });
253
+ }
254
+ catch (error) {
255
+ logger_1.AppLogger.error(`Session validation failed: ${error}`);
256
+ res.status(500).json({
257
+ success: false,
258
+ valid: false,
259
+ error: 'Validation failed',
260
+ message: 'Failed to validate session',
261
+ });
262
+ }
263
+ });
264
+ /**
265
+ * Logout
266
+ * POST /auth/logout
267
+ */
268
+ router.post('/logout', auth_middleware_1.authenticationMiddleware, (req, res) => {
269
+ try {
270
+ const token = req.headers.authorization?.replace('Bearer ', '') ||
271
+ req.cookies?.['auth-token'];
272
+ if (token) {
273
+ (0, auth_middleware_1.invalidateSession)(token);
274
+ }
275
+ res.json({
276
+ success: true,
277
+ message: 'Logout successful',
278
+ });
279
+ }
280
+ catch (error) {
281
+ logger_1.AppLogger.error(`Logout failed: ${error}`);
282
+ res.status(500).json({
283
+ success: false,
284
+ error: 'Logout failed',
285
+ message: 'Failed to logout',
286
+ });
287
+ }
288
+ });
289
+ /**
290
+ * Refresh session (token)
291
+ * POST /auth/refresh
292
+ */
293
+ router.post('/refresh', auth_middleware_1.authenticationMiddleware, async (req, res) => {
294
+ try {
295
+ const session = (0, auth_middleware_1.getSessionFromRequest)(req);
296
+ if (!session) {
297
+ res.status(401).json({
298
+ success: false,
299
+ error: 'Unauthorized',
300
+ message: 'No active session',
301
+ });
302
+ return;
303
+ }
304
+ // Validate token is still valid
305
+ const isValid = await (0, github_oauth_service_1.validateToken)(session.accessToken);
306
+ if (!isValid) {
307
+ res.status(401).json({
308
+ success: false,
309
+ error: 'Unauthorized',
310
+ message: 'Token is no longer valid with GitHub',
311
+ });
312
+ return;
313
+ }
314
+ // Create new session with updated expiry
315
+ const newSession = {
316
+ ...session,
317
+ expiresAt: Date.now() + 24 * 60 * 60 * 1000, // Extend 24 hours
318
+ };
319
+ const newToken = (0, auth_middleware_1.storeSession)(newSession);
320
+ // Invalidate old token
321
+ const oldToken = req.headers.authorization?.replace('Bearer ', '') ||
322
+ req.cookies?.['auth-token'];
323
+ if (oldToken) {
324
+ (0, auth_middleware_1.invalidateSession)(oldToken);
325
+ }
326
+ res.json({
327
+ success: true,
328
+ message: 'Session refreshed successfully',
329
+ sessionToken: newToken,
330
+ expiresAt: newSession.expiresAt,
331
+ });
332
+ }
333
+ catch (error) {
334
+ logger_1.AppLogger.error(`Session refresh failed: ${error}`);
335
+ res.status(500).json({
336
+ success: false,
337
+ error: 'Refresh failed',
338
+ message: 'Failed to refresh session',
339
+ });
340
+ }
341
+ });
342
+ exports.default = router;