nodebb-plugin-calendar-onekite 11.1.52 → 11.1.54

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/lib/api.js CHANGED
@@ -203,6 +203,12 @@ api.getEvents = async function (req, res) {
203
203
  ev.extendedProps.canModerate = canMod;
204
204
  ev.extendedProps.total = r.total || 0;
205
205
  ev.extendedProps.createdAt = r.createdAt || null;
206
+ // Expose payment URL only to the requester (or moderators) when awaiting payment
207
+ if (r.status === 'awaiting_payment' && r.paymentUrl && (/^https?:\/\//i).test(String(r.paymentUrl))) {
208
+ if ((req.uid && String(req.uid) === String(r.uid)) || canMod) {
209
+ ev.extendedProps.paymentUrl = String(r.paymentUrl);
210
+ }
211
+ }
206
212
  out.push(ev);
207
213
  }
208
214
  }
package/lib/helloasso.js CHANGED
@@ -14,31 +14,35 @@ function requestJson(method, url, headers = {}, bodyObj = null) {
14
14
  port: u.port || 443,
15
15
  path: u.pathname + u.search,
16
16
  headers: {
17
- 'Accept': 'application/json',
17
+ Accept: 'application/json',
18
+ 'Content-Type': 'application/json',
19
+ ...(body ? { 'Content-Length': Buffer.byteLength(body) } : {}),
18
20
  ...headers,
19
- ...(body ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } : {}),
20
21
  },
21
22
  },
22
- (res) => {
23
+ (resp) => {
23
24
  let data = '';
24
- res.setEncoding('utf8');
25
- res.on('data', (chunk) => (data += chunk));
26
- res.on('end', () => {
27
- const status = res.statusCode || 0;
28
- if (!data) {
29
- return resolve({ status, json: null });
30
- }
25
+ resp.setEncoding('utf8');
26
+ resp.on('data', (chunk) => { data += chunk; });
27
+ resp.on('end', () => {
28
+ const status = resp.statusCode || 0;
29
+
30
+ // Try JSON first, but never throw from here (avoid crashing NodeBB).
31
31
  try {
32
- const json = JSON.parse(data);
33
- resolve({ status, json });
32
+ const json = data ? JSON.parse(data) : null;
33
+ return resolve({ status, json });
34
34
  } catch (e) {
35
- reject(new Error(`HelloAsso: Invalid JSON (status ${status})`));
35
+ // Non-JSON response (HTML/proxy error/etc.)
36
+ const snippet = String(data || '').slice(0, 500);
37
+ return resolve({ status, json: null, raw: snippet });
36
38
  }
37
39
  });
38
40
  }
39
41
  );
40
42
 
41
- req.on('error', reject);
43
+ req.on('error', (err) => {
44
+ resolve({ status: 0, json: null, error: err && err.message ? err.message : String(err) });
45
+ });
42
46
  if (body) req.write(body);
43
47
  req.end();
44
48
  });
@@ -246,4 +250,4 @@ module.exports = {
246
250
  extractCatalogItems,
247
251
  listCatalogItems,
248
252
  createCheckoutIntent,
249
- };
253
+ };
@@ -145,7 +145,7 @@ function buildBodyCandidates(req) {
145
145
 
146
146
  async function verifySignature(req) {
147
147
  const settings = await meta.settings.get(SETTINGS_KEY);
148
- const secret = settings && settings.helloassoClientSecret ? String(settings.helloassoClientSecret) : '';
148
+ const secret = settings && (settings.helloassoSignatureKey || settings.helloassoClientSecret) ? String(settings.helloassoSignatureKey || settings.helloassoClientSecret) : '';
149
149
  const provided = req.headers['x-ha-signature'] || req.headers['X-HA-Signature'];
150
150
  if (!secret || !provided) return false;
151
151
 
@@ -254,13 +254,26 @@ function getReservationIdFromPayload(payload) {
254
254
  */
255
255
  async function handler(req, res, next) {
256
256
  try {
257
+ if (req.method === 'GET') {
258
+ return res.json({ ok: true });
259
+ }
257
260
  if (req.method !== 'POST') {
258
261
  return res.status(405).json({ ok: false, error: 'method-not-allowed' });
259
262
  }
260
263
 
261
- const sigOk = await verifySignature(req);
262
- if (!sigOk) {
263
- return res.status(401).json({ ok: false, error: 'invalid-signature' });
264
+ // Verify signature if a signatureKey is configured; otherwise accept but log a warning.
265
+ const settings = await meta.settings.get(SETTINGS_KEY);
266
+ const hasSigKey = settings && settings.helloassoSignatureKey;
267
+ if (hasSigKey) {
268
+ const sigOk = await verifySignature(req);
269
+ if (!sigOk) {
270
+ // eslint-disable-next-line no-console
271
+ console.warn('[calendar-onekite] HelloAsso webhook invalid signature', { ip: req.ip, ua: req.headers['user-agent'] });
272
+ return res.status(401).json({ ok: false, error: 'invalid-signature' });
273
+ }
274
+ } else {
275
+ // eslint-disable-next-line no-console
276
+ console.warn('[calendar-onekite] HelloAsso webhook signatureKey not set; skipping signature verification');
264
277
  }
265
278
 
266
279
  // At this point, the payload is trusted.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "11.1.52",
3
+ "version": "11.1.54",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -287,7 +287,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
287
287
  const rid = p.rid || ev.id;
288
288
  const status = p.status || '';
289
289
  const itemsHtml = (() => {
290
- const names = Array.isArray(p.itemNames) ? p.itemNames : (p.itemName ? [p.itemName] : []);
290
+ const names = Array.isArray(p.itemNames) ? p.itemNames : (typeof p.itemNames === 'string' && p.itemNames.trim() ? p.itemNames.split(',').map(s=>s.trim()).filter(Boolean) : (p.itemName ? [p.itemName] : []));
291
291
  if (names.length) {
292
292
  return `<ul style="margin:0 0 0 1.1rem; padding:0;">${names.map(n => `<li>${String(n).replace(/</g,'&lt;').replace(/>/g,'&gt;')}</li>`).join('')}</ul>`;
293
293
  }
@@ -361,7 +361,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
361
361
  callback: async () => {
362
362
  const itemNames = Array.isArray(p.itemNames) && p.itemNames.length
363
363
  ? p.itemNames
364
- : (p.itemName ? [p.itemName] : []);
364
+ : (typeof p.itemNames === 'string' && p.itemNames.trim() ? p.itemNames.split(',').map(s=>s.trim()).filter(Boolean) : (p.itemName ? [p.itemName] : []));
365
365
  const itemsListHtml = itemNames.length
366
366
  ? `<div class="mb-2"><strong>Matériel</strong><ul style="margin:0.25rem 0 0 1.1rem; padding:0;">${itemNames.map(n => `<li>${String(n).replace(/</g, '&lt;').replace(/>/g, '&gt;')}</li>`).join('')}</ul></div>`
367
367
  : '';
@@ -61,6 +61,12 @@
61
61
  <input class="form-control" name="helloassoClientSecret" type="password">
62
62
  </div>
63
63
 
64
+ <div class="mb-3">
65
+ <label class="form-label">Webhook signatureKey (x-ha-signature)</label>
66
+ <input class="form-control" name="helloassoSignatureKey" type="password">
67
+ <div class="form-text">Clé de signature HelloAsso utilisée pour vérifier le header <code>x-ha-signature</code> des webhooks. Recommandé.</div>
68
+ </div>
69
+
64
70
  <div class="mb-3">
65
71
  <label class="form-label">Organization Slug</label>
66
72
  <input class="form-control" name="helloassoOrganizationSlug">