@intranefr/superbackend 1.6.7 → 1.7.8

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.
Files changed (119) hide show
  1. package/.beads/.br_history/issues.20260314_212352_900045509.jsonl +0 -0
  2. package/.beads/.br_history/issues.20260314_212352_900045509.jsonl.meta.json +1 -0
  3. package/.beads/.br_history/issues.20260314_212353_087140743.jsonl +1 -0
  4. package/.beads/.br_history/issues.20260314_212353_087140743.jsonl.meta.json +1 -0
  5. package/.beads/.br_history/issues.20260314_212353_285881504.jsonl +2 -0
  6. package/.beads/.br_history/issues.20260314_212353_285881504.jsonl.meta.json +1 -0
  7. package/.beads/.br_history/issues.20260314_212353_473915419.jsonl +3 -0
  8. package/.beads/.br_history/issues.20260314_212353_473915419.jsonl.meta.json +1 -0
  9. package/.beads/.br_history/issues.20260314_212353_659476307.jsonl +4 -0
  10. package/.beads/.br_history/issues.20260314_212353_659476307.jsonl.meta.json +1 -0
  11. package/.beads/.br_history/issues.20260314_212353_869998925.jsonl +5 -0
  12. package/.beads/.br_history/issues.20260314_212353_869998925.jsonl.meta.json +1 -0
  13. package/.beads/.br_history/issues.20260314_212354_054785029.jsonl +6 -0
  14. package/.beads/.br_history/issues.20260314_212354_054785029.jsonl.meta.json +1 -0
  15. package/.beads/.br_history/issues.20260314_213336_175893691.jsonl +7 -0
  16. package/.beads/.br_history/issues.20260314_213336_175893691.jsonl.meta.json +1 -0
  17. package/.beads/.br_history/issues.20260314_213336_338509797.jsonl +7 -0
  18. package/.beads/.br_history/issues.20260314_213336_338509797.jsonl.meta.json +1 -0
  19. package/.beads/.br_history/issues.20260314_213336_515443192.jsonl +7 -0
  20. package/.beads/.br_history/issues.20260314_213336_515443192.jsonl.meta.json +1 -0
  21. package/.beads/.br_history/issues.20260314_213336_676417592.jsonl +7 -0
  22. package/.beads/.br_history/issues.20260314_213336_676417592.jsonl.meta.json +1 -0
  23. package/.beads/.br_history/issues.20260314_213336_839182422.jsonl +7 -0
  24. package/.beads/.br_history/issues.20260314_213336_839182422.jsonl.meta.json +1 -0
  25. package/.beads/.br_history/issues.20260314_213337_004349113.jsonl +7 -0
  26. package/.beads/.br_history/issues.20260314_213337_004349113.jsonl.meta.json +1 -0
  27. package/.beads/.br_history/issues.20260314_213337_179824080.jsonl +7 -0
  28. package/.beads/.br_history/issues.20260314_213337_179824080.jsonl.meta.json +1 -0
  29. package/.beads/.br_history/issues.20260314_213701_705075332.jsonl +7 -0
  30. package/.beads/.br_history/issues.20260314_213701_705075332.jsonl.meta.json +1 -0
  31. package/.beads/.br_history/issues.20260314_213706_783128702.jsonl +8 -0
  32. package/.beads/.br_history/issues.20260314_213706_783128702.jsonl.meta.json +1 -0
  33. package/.beads/config.yaml +4 -0
  34. package/.beads/issues.jsonl +8 -0
  35. package/.beads/metadata.json +4 -0
  36. package/.env.example +8 -0
  37. package/autochangelog/.env.example +36 -0
  38. package/autochangelog/README.md +412 -0
  39. package/autochangelog/config/database.js +27 -0
  40. package/autochangelog/package.json +47 -0
  41. package/autochangelog/public/landing.html +581 -0
  42. package/autochangelog/server.js +104 -0
  43. package/autochangelog/src/app.js +181 -0
  44. package/autochangelog/src/config/database.js +26 -0
  45. package/autochangelog/src/controllers/auth.js +488 -0
  46. package/autochangelog/src/controllers/changelog.js +682 -0
  47. package/autochangelog/src/controllers/project.js +580 -0
  48. package/autochangelog/src/controllers/repository.js +780 -0
  49. package/autochangelog/src/middleware/auth.js +386 -0
  50. package/autochangelog/src/models/Changelog.js +443 -0
  51. package/autochangelog/src/models/Project.js +226 -0
  52. package/autochangelog/src/models/Repository.js +366 -0
  53. package/autochangelog/src/models/User.js +223 -0
  54. package/autochangelog/src/routes/auth.routes.js +32 -0
  55. package/autochangelog/src/routes/changelog.routes.js +42 -0
  56. package/autochangelog/src/routes/github-auth.routes.js +102 -0
  57. package/autochangelog/src/routes/project.routes.js +50 -0
  58. package/autochangelog/src/routes/repository.routes.js +54 -0
  59. package/autochangelog/src/services/changelog.js +722 -0
  60. package/autochangelog/src/services/github.js +243 -0
  61. package/autochangelog/utils/logger.js +77 -0
  62. package/autochangelog/views/404.ejs +18 -0
  63. package/autochangelog/views/dashboard.ejs +596 -0
  64. package/autochangelog/views/index.ejs +231 -0
  65. package/autochangelog/views/layouts/main.ejs +44 -0
  66. package/autochangelog/views/login.ejs +104 -0
  67. package/autochangelog/views/partials/footer.ejs +20 -0
  68. package/autochangelog/views/partials/navbar.ejs +51 -0
  69. package/autochangelog/views/register.ejs +109 -0
  70. package/autochangelog-cli/README.md +266 -0
  71. package/autochangelog-cli/bin/autochangelog +120 -0
  72. package/autochangelog-cli/package.json +46 -0
  73. package/autochangelog-cli/src/cli/commands/auth.js +291 -0
  74. package/autochangelog-cli/src/cli/commands/changelog.js +619 -0
  75. package/autochangelog-cli/src/cli/commands/project.js +427 -0
  76. package/autochangelog-cli/src/cli/commands/repo.js +557 -0
  77. package/autochangelog-cli/src/cli/commands/stats.js +706 -0
  78. package/autochangelog-cli/src/cli/utils/config.js +277 -0
  79. package/autochangelog-cli/src/cli/utils/errors.js +307 -0
  80. package/autochangelog-cli/src/cli/utils/logger.js +75 -0
  81. package/autochangelog-cli/src/cli/utils/output.js +357 -0
  82. package/package.json +9 -3
  83. package/plugins/supercli/README.md +108 -0
  84. package/plugins/supercli/plugin.json +123 -0
  85. package/server.js +1 -1
  86. package/src/cli/api.js +380 -0
  87. package/src/cli/direct/agent-utils.js +61 -0
  88. package/src/cli/direct/cli-utils.js +112 -0
  89. package/src/cli/direct/data-seeding.js +307 -0
  90. package/src/cli/direct/db-admin.js +84 -0
  91. package/src/cli/direct/db-advanced.js +372 -0
  92. package/src/cli/direct/db-utils.js +558 -0
  93. package/src/cli/direct/help.js +195 -0
  94. package/src/cli/direct/migration.js +107 -0
  95. package/src/cli/direct/rbac-advanced.js +132 -0
  96. package/src/cli/direct/resources-additional.js +400 -0
  97. package/src/cli/direct/resources-cms-advanced.js +173 -0
  98. package/src/cli/direct/resources-cms.js +247 -0
  99. package/src/cli/direct/resources-core.js +253 -0
  100. package/src/cli/direct/resources-execution.js +367 -0
  101. package/src/cli/direct/resources-health.js +152 -0
  102. package/src/cli/direct/resources-integrations.js +182 -0
  103. package/src/cli/direct/resources-logs.js +204 -0
  104. package/src/cli/direct/resources-org-rbac.js +187 -0
  105. package/src/cli/direct/resources-system.js +236 -0
  106. package/src/cli/direct.js +556 -0
  107. package/src/controllers/admin.controller.js +4 -0
  108. package/src/controllers/auth.controller.js +148 -1
  109. package/src/controllers/waitingList.controller.js +130 -1
  110. package/src/models/RbacRole.js +1 -1
  111. package/src/models/User.js +39 -5
  112. package/src/routes/auth.routes.js +6 -0
  113. package/src/routes/waitingList.routes.js +12 -2
  114. package/src/routes/waitingListAdmin.routes.js +3 -0
  115. package/src/services/email.service.js +1 -0
  116. package/src/services/github.service.js +255 -0
  117. package/src/services/rateLimiter.service.js +29 -1
  118. package/src/services/waitingListJson.service.js +32 -3
  119. package/views/admin-waiting-list.ejs +386 -3
@@ -0,0 +1,255 @@
1
+ const axios = require('axios');
2
+ const crypto = require('crypto');
3
+ const globalSettingsService = require('./globalSettings.service');
4
+
5
+ class GitHubService {
6
+ constructor() {
7
+ this.clientId = null;
8
+ this.clientSecret = null;
9
+ this.callbackUrl = null;
10
+ this.baseURL = 'https://github.com';
11
+ this.apiBaseURL = 'https://api.github.com';
12
+
13
+ // OAuth scopes requested
14
+ this.scopes = ['read:user', 'user:email'];
15
+ }
16
+
17
+ /**
18
+ * Get GitHub OAuth credentials from global settings or environment variables
19
+ * Global settings take precedence over env vars
20
+ */
21
+ async getCredentials() {
22
+ // Try global settings first (they override env vars)
23
+ const clientId = await globalSettingsService.getSettingValue('github.oauth.clientId');
24
+ const clientSecret = await globalSettingsService.getSettingValue('github.oauth.clientSecret');
25
+ const callbackUrl = await globalSettingsService.getSettingValue('github.oauth.callbackUrl');
26
+
27
+ return {
28
+ clientId: clientId || process.env.GITHUB_CLIENT_ID,
29
+ clientSecret: clientSecret || process.env.GITHUB_CLIENT_SECRET,
30
+ callbackUrl: callbackUrl || process.env.GITHUB_CALLBACK_URL || 'http://localhost:3000/api/auth/github/callback'
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Generate OAuth authorization URL
36
+ * @param {string} state - CSRF protection state parameter
37
+ * @returns {string} GitHub OAuth URL
38
+ */
39
+ async getAuthURL(state) {
40
+ const credentials = await this.getCredentials();
41
+
42
+ if (!credentials.clientId) {
43
+ throw new Error('GITHUB_CLIENT_ID not configured');
44
+ }
45
+
46
+ const params = new URLSearchParams({
47
+ client_id: credentials.clientId,
48
+ redirect_uri: credentials.callbackUrl,
49
+ scope: this.scopes.join(' '),
50
+ state: state
51
+ });
52
+
53
+ return `${this.baseURL}/login/oauth/authorize?${params.toString()}`;
54
+ }
55
+
56
+ /**
57
+ * Exchange authorization code for access token
58
+ * @param {string} code - Authorization code from GitHub callback
59
+ * @param {string} state - State parameter to verify
60
+ * @returns {Promise<object>} Token response
61
+ */
62
+ async getAccessToken(code, state) {
63
+ const credentials = await this.getCredentials();
64
+
65
+ if (!credentials.clientId || !credentials.clientSecret) {
66
+ throw new Error('GitHub OAuth credentials not configured');
67
+ }
68
+
69
+ try {
70
+ const response = await axios.post(
71
+ `${this.baseURL}/login/oauth/access_token`,
72
+ {
73
+ client_id: credentials.clientId,
74
+ client_secret: credentials.clientSecret,
75
+ code: code,
76
+ redirect_uri: credentials.callbackUrl,
77
+ state: state
78
+ },
79
+ {
80
+ headers: {
81
+ 'Accept': 'application/json',
82
+ 'Content-Type': 'application/json'
83
+ }
84
+ }
85
+ );
86
+
87
+ if (response.data.error) {
88
+ throw new Error(response.data.error_description || response.data.error);
89
+ }
90
+
91
+ return {
92
+ accessToken: response.data.access_token,
93
+ refreshToken: response.data.refresh_token,
94
+ scope: response.data.scope,
95
+ tokenType: response.data.token_type
96
+ };
97
+ } catch (error) {
98
+ if (error.response) {
99
+ throw new Error(`GitHub token exchange failed: ${error.response.data.error_description || error.response.statusText}`);
100
+ }
101
+ throw new Error(`GitHub token exchange failed: ${error.message}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Get user profile from GitHub API
107
+ * @param {string} accessToken - GitHub access token
108
+ * @returns {Promise<object>} GitHub user profile
109
+ */
110
+ async getUserProfile(accessToken) {
111
+ try {
112
+ const response = await axios.get(`${this.apiBaseURL}/user`, {
113
+ headers: {
114
+ 'Authorization': `token ${accessToken}`,
115
+ 'Accept': 'application/vnd.github.v3+json'
116
+ }
117
+ });
118
+
119
+ return {
120
+ id: String(response.data.id),
121
+ login: response.data.login,
122
+ name: response.data.name || response.data.login,
123
+ email: response.data.email,
124
+ avatarUrl: response.data.avatar_url,
125
+ htmlUrl: response.data.html_url,
126
+ company: response.data.company,
127
+ location: response.data.location,
128
+ bio: response.data.bio
129
+ };
130
+ } catch (error) {
131
+ if (error.response) {
132
+ throw new Error(`GitHub API error: ${error.response.data.message || error.response.statusText}`);
133
+ }
134
+ throw new Error(`Failed to fetch GitHub profile: ${error.message}`);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Get user emails from GitHub API
140
+ * @param {string} accessToken - GitHub access token
141
+ * @returns {Promise<Array>} List of user emails
142
+ */
143
+ async getUserEmails(accessToken) {
144
+ try {
145
+ const response = await axios.get(`${this.apiBaseURL}/user/emails`, {
146
+ headers: {
147
+ 'Authorization': `token ${accessToken}`,
148
+ 'Accept': 'application/vnd.github.v3+json'
149
+ }
150
+ });
151
+
152
+ // Return primary verified email or first verified email
153
+ const emails = response.data || [];
154
+ const primaryEmail = emails.find(e => e.primary && e.verified);
155
+ const verifiedEmail = emails.find(e => e.verified);
156
+
157
+ return {
158
+ primary: primaryEmail?.email || verifiedEmail?.email || emails[0]?.email,
159
+ all: emails
160
+ };
161
+ } catch (error) {
162
+ if (error.response) {
163
+ throw new Error(`GitHub API error: ${error.response.data.message || error.response.statusText}`);
164
+ }
165
+ throw new Error(`Failed to fetch GitHub emails: ${error.message}`);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Refresh access token (if refresh token is available)
171
+ * @param {string} refreshToken - GitHub refresh token
172
+ * @returns {Promise<object>} New token response
173
+ */
174
+ async refreshAccessToken(refreshToken) {
175
+ const credentials = await this.getCredentials();
176
+
177
+ if (!credentials.clientId || !credentials.clientSecret) {
178
+ throw new Error('GitHub OAuth credentials not configured');
179
+ }
180
+
181
+ try {
182
+ const response = await axios.post(
183
+ `${this.baseURL}/login/oauth/access_token`,
184
+ {
185
+ client_id: credentials.clientId,
186
+ client_secret: credentials.clientSecret,
187
+ grant_type: 'refresh_token',
188
+ refresh_token: refreshToken
189
+ },
190
+ {
191
+ headers: {
192
+ 'Accept': 'application/json',
193
+ 'Content-Type': 'application/json'
194
+ }
195
+ }
196
+ );
197
+
198
+ if (response.data.error) {
199
+ throw new Error(response.data.error_description || response.data.error);
200
+ }
201
+
202
+ return {
203
+ accessToken: response.data.access_token,
204
+ refreshToken: response.data.refresh_token || refreshToken,
205
+ scope: response.data.scope,
206
+ tokenType: response.data.token_type
207
+ };
208
+ } catch (error) {
209
+ if (error.response) {
210
+ throw new Error(`GitHub token refresh failed: ${error.response.data.error_description || error.response.statusText}`);
211
+ }
212
+ throw new Error(`GitHub token refresh failed: ${error.message}`);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Generate a cryptographically secure state parameter
218
+ * @returns {string} State parameter for CSRF protection
219
+ */
220
+ generateState() {
221
+ return crypto.randomBytes(32).toString('hex');
222
+ }
223
+
224
+ /**
225
+ * Verify state parameter matches
226
+ * @param {string} provided - State from callback
227
+ * @param {string} expected - Expected state
228
+ * @returns {boolean} True if valid
229
+ */
230
+ verifyState(provided, expected) {
231
+ if (!provided || !expected) return false;
232
+ return crypto.timingSafeEqual(
233
+ Buffer.from(provided),
234
+ Buffer.from(expected)
235
+ );
236
+ }
237
+
238
+ /**
239
+ * Get full user info including email
240
+ * @param {string} accessToken - GitHub access token
241
+ * @returns {Promise<object>} Complete user profile
242
+ */
243
+ async getFullUserInfo(accessToken) {
244
+ const profile = await this.getUserProfile(accessToken);
245
+ const emails = await this.getUserEmails(accessToken);
246
+
247
+ return {
248
+ ...profile,
249
+ email: profile.email || emails.primary,
250
+ emailVerified: !!emails.primary
251
+ };
252
+ }
253
+ }
254
+
255
+ module.exports = new GitHubService();
@@ -128,6 +128,31 @@ function registerLimiter(limiterId, { label, integration, inferredMountPath } =
128
128
  return next;
129
129
  }
130
130
 
131
+ function getLimiterDefaultConfig(limiterId) {
132
+ // Default configuration for specific limiters
133
+ const defaults = {
134
+ // Waiting list endpoints - strict rate limiting to prevent spam
135
+ waitingListSubscribeLimiter: {
136
+ enabled: true,
137
+ mode: 'enforce',
138
+ algorithm: 'fixedWindow',
139
+ limit: { max: 1, windowMs: 60000 }, // 1 request per minute
140
+ identity: { type: 'ip' },
141
+ metrics: { enabled: true, bucketMs: 60000, retentionDays: 14 }
142
+ },
143
+ waitingListStatsLimiter: {
144
+ enabled: true,
145
+ mode: 'reportOnly', // Start with monitoring
146
+ algorithm: 'fixedWindow',
147
+ limit: { max: 60, windowMs: 60000 }, // 60 requests per minute
148
+ identity: { type: 'ip' },
149
+ metrics: { enabled: true, bucketMs: 60000, retentionDays: 14 }
150
+ }
151
+ };
152
+
153
+ return defaults[limiterId] || null;
154
+ }
155
+
131
156
  async function ensureLimiterOverrideExists(limiterId) {
132
157
  const id = normalizeLimiterId(limiterId);
133
158
 
@@ -156,10 +181,13 @@ async function ensureLimiterOverrideExists(limiterId) {
156
181
  return;
157
182
  }
158
183
 
184
+ // Get default config for this limiter, or use minimal enabled: false
185
+ const defaultConfig = getLimiterDefaultConfig(id) || { enabled: false };
186
+
159
187
  const updated = {
160
188
  version: Number(data?.version || 1) || 1,
161
189
  defaults: data?.defaults || {},
162
- limiters: { ...(data?.limiters || {}), [id]: { enabled: false } },
190
+ limiters: { ...(data?.limiters || {}), [id]: defaultConfig },
163
191
  };
164
192
 
165
193
  doc.jsonRaw = JSON.stringify(updated, null, 2);
@@ -290,9 +290,9 @@ async function getWaitingListStats(options = {}) {
290
290
  * Get paginated waiting list entries for admin
291
291
  */
292
292
  async function getWaitingListEntriesAdmin(filters = {}) {
293
- const { status, type, email, limit = 50, offset = 0 } = filters;
293
+ const { status, type, email, limit = 50, offset = 0, bypassCache = false } = filters;
294
294
 
295
- const { entries } = await getWaitingListEntries();
295
+ const { entries } = await getWaitingListEntries({ bypassCache });
296
296
 
297
297
  // Apply filters
298
298
  let filteredEntries = entries;
@@ -333,6 +333,32 @@ async function getWaitingListEntriesAdmin(filters = {}) {
333
333
  };
334
334
  }
335
335
 
336
+ /**
337
+ * Get available types with counts
338
+ */
339
+ async function getAvailableTypes() {
340
+ const { entries } = await getWaitingListEntries();
341
+
342
+ // Count entries by type
343
+ const typeCounts = entries.reduce((acc, entry) => {
344
+ const type = String(entry.type || 'unknown').trim();
345
+ if (type) {
346
+ acc[type] = (acc[type] || 0) + 1;
347
+ }
348
+ return acc;
349
+ }, {});
350
+
351
+ // Convert to sorted array
352
+ const types = Object.entries(typeCounts)
353
+ .map(([type, count]) => ({ type, count }))
354
+ .sort((a, b) => b.count - a.count);
355
+
356
+ return {
357
+ types,
358
+ total: entries.length
359
+ };
360
+ }
361
+
336
362
  /**
337
363
  * Clear all waiting list related caches
338
364
  */
@@ -352,6 +378,8 @@ function getWaitingListCacheInfo() {
352
378
 
353
379
  /**
354
380
  * Initialize waiting list data structure if it doesn't exist
381
+ * Note: cacheTtlSeconds MUST be 0 for persistence use cases (waiting list, rate limiters)
382
+ * to ensure real-time data consistency. Caching would cause stale reads.
355
383
  */
356
384
  async function initializeWaitingListData() {
357
385
  try {
@@ -368,7 +396,7 @@ async function initializeWaitingListData() {
368
396
  lastUpdated: new Date().toISOString()
369
397
  }),
370
398
  publicEnabled: false,
371
- cacheTtlSeconds: 300
399
+ cacheTtlSeconds: 0 // No caching - required for real-time persistence
372
400
  });
373
401
  } else {
374
402
  throw error;
@@ -384,6 +412,7 @@ module.exports = {
384
412
  removeWaitingListEntry,
385
413
  getWaitingListStats,
386
414
  getWaitingListEntriesAdmin,
415
+ getAvailableTypes,
387
416
 
388
417
  // Cache management
389
418
  clearWaitingListCache,