cozy-sharing 33.3.0 → 33.3.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [33.3.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.3.0...cozy-sharing@33.3.1) (2026-06-10)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-sharing:** Expire share links at the end of the selected day ([db81ed9](https://github.com/cozy/cozy-libs/commit/db81ed936a15850f278404511c1b6faec0fc7a54))
11
+
6
12
  # [33.3.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.2.2...cozy-sharing@33.3.0) (2026-06-10)
7
13
 
8
14
  ### Features
@@ -1,5 +1,6 @@
1
1
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
+ import { endOfDay } from 'date-fns';
3
4
  import { generateWebLink } from 'cozy-client';
4
5
  import minilog from 'cozy-minilog';
5
6
  import { getAppSlugFromDocumentType } from '../../helpers/link';
@@ -7,6 +8,21 @@ import { getShortcode } from '../../helpers/shortcodes';
7
8
  var log = minilog('ShareRestrictionModal/helpers');
8
9
  export var WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH'];
9
10
  export var READ_ONLY_PERMS = ['GET'];
11
+ /**
12
+ * A share link expires at the END of the selected day, so picking today keeps
13
+ * the link reachable for the rest of today and expires it the next day.
14
+ * Without this, the picked day defaults to its midnight (start of day), which
15
+ * is already in the past and makes the link unreachable as soon as it is set.
16
+ * @param {Date|string|null|undefined} date - The picked expiration day
17
+ * @returns {Date|null|undefined} - The same value normalized to end of day
18
+ */
19
+
20
+ export var toExpirationDate = function toExpirationDate(date) {
21
+ if (!date) return date;
22
+ var parsed = date instanceof Date ? date : new Date(date);
23
+ if (parsed.toString() === 'Invalid Date') return date;
24
+ return endOfDay(parsed);
25
+ };
10
26
  /**
11
27
  * Create a sharing link for a file with specified options
12
28
  * @param {object} options
@@ -250,7 +266,8 @@ export var makeTTL = function makeTTL(selectedDate) {
250
266
 
251
267
  if (selectedDateConverted instanceof Date) {
252
268
  var now = new Date();
253
- var ttl = selectedDateConverted > now ? "".concat(Math.round((selectedDateConverted - now) / 1000), "s") : undefined;
269
+ var expiration = toExpirationDate(selectedDateConverted);
270
+ var ttl = expiration > now ? "".concat(Math.round((expiration - now) / 1000), "s") : undefined;
254
271
  return ttl;
255
272
  }
256
273
 
@@ -326,13 +343,14 @@ export var createPermissions = /*#__PURE__*/function () {
326
343
 
327
344
  export var updatePermissions = /*#__PURE__*/function () {
328
345
  var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(_ref8) {
329
- var file, t, dateToggle, selectedDate, passwordToggle, password, editingRights, documentType, updateDocumentPermissions, showAlert, expiresAt, ensurePassword, verbs;
346
+ var file, t, dateToggle, selectedDate, passwordToggle, password, editingRights, documentType, updateDocumentPermissions, showAlert, _toExpirationDate, expiresAt, ensurePassword, verbs;
347
+
330
348
  return _regeneratorRuntime.wrap(function _callee4$(_context4) {
331
349
  while (1) switch (_context4.prev = _context4.next) {
332
350
  case 0:
333
351
  file = _ref8.file, t = _ref8.t, dateToggle = _ref8.dateToggle, selectedDate = _ref8.selectedDate, passwordToggle = _ref8.passwordToggle, password = _ref8.password, editingRights = _ref8.editingRights, documentType = _ref8.documentType, updateDocumentPermissions = _ref8.updateDocumentPermissions, showAlert = _ref8.showAlert;
334
352
  _context4.prev = 1;
335
- expiresAt = dateToggle ? (selectedDate === null || selectedDate === void 0 ? void 0 : selectedDate.toISOString()) || undefined : '';
353
+ expiresAt = dateToggle ? ((_toExpirationDate = toExpirationDate(selectedDate)) === null || _toExpirationDate === void 0 ? void 0 : _toExpirationDate.toISOString()) || undefined : '';
336
354
  ensurePassword = passwordToggle ? password || undefined : '';
337
355
  verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS;
338
356
  return _context4.abrupt("return", updateDocumentPermissions(file, {
@@ -1,7 +1,23 @@
1
1
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
- import { makeTTL, revokePermissions, updatePermissions } from './helpers';
3
+ import { endOfDay } from 'date-fns';
4
+ import { makeTTL, revokePermissions, toExpirationDate, updatePermissions } from './helpers';
4
5
  describe('ShareRestrictionModal/helpers', function () {
6
+ describe('toExpirationDate', function () {
7
+ it('returns falsy values unchanged', function () {
8
+ expect(toExpirationDate(null)).toBeNull();
9
+ expect(toExpirationDate(undefined)).toBeUndefined();
10
+ expect(toExpirationDate('')).toBe('');
11
+ });
12
+ it('normalizes a Date to the end of its day', function () {
13
+ var date = new Date(2100, 0, 1, 9, 30);
14
+ expect(toExpirationDate(date)).toEqual(endOfDay(date));
15
+ });
16
+ it('accepts an ISO string', function () {
17
+ var iso = '2100-01-01T09:30:00.000Z';
18
+ expect(toExpirationDate(iso)).toEqual(endOfDay(new Date(iso)));
19
+ });
20
+ });
5
21
  describe('makeTTL', function () {
6
22
  it('sould return undefined', function () {
7
23
  expect(makeTTL()).toBeUndefined();
@@ -15,6 +31,18 @@ describe('ShareRestrictionModal/helpers', function () {
15
31
  expect(makeTTL(new Date('2100-01-01T00:00:00.000Z'))).toBeDefined();
16
32
  expect(makeTTL('2100-01-01T00:00:00.000Z')).toBeDefined();
17
33
  });
34
+ it('keeps a link picked for today valid until the end of the day', function () {
35
+ // Regression: picking "today" used to resolve to midnight (already past),
36
+ // which dropped the TTL and the link never expired or was unreachable.
37
+ jest.useFakeTimers();
38
+ jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'));
39
+
40
+ try {
41
+ expect(makeTTL(new Date())).toBeDefined();
42
+ } finally {
43
+ jest.useRealTimers();
44
+ }
45
+ });
18
46
  });
19
47
  describe('updatePermissions', function () {
20
48
  var documentType = 'Files';
@@ -163,7 +191,7 @@ describe('ShareRestrictionModal/helpers', function () {
163
191
  }, _callee4);
164
192
  })));
165
193
  it('should call updateDocumentPermissions with expected date & password if their switches are true', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
166
- var updateDocumentPermissions, file;
194
+ var updateDocumentPermissions, file, selectedDate;
167
195
  return _regeneratorRuntime.wrap(function _callee5$(_context5) {
168
196
  while (1) switch (_context5.prev = _context5.next) {
169
197
  case 0:
@@ -171,12 +199,13 @@ describe('ShareRestrictionModal/helpers', function () {
171
199
  file = {
172
200
  _id: '123'
173
201
  };
174
- _context5.next = 4;
202
+ selectedDate = new Date('2100-01-01T00:00:00.000Z');
203
+ _context5.next = 5;
175
204
  return updatePermissions({
176
205
  file: file,
177
206
  t: jest.fn(),
178
207
  dateToggle: true,
179
- selectedDate: new Date('2100-01-01T00:00:00.000Z'),
208
+ selectedDate: selectedDate,
180
209
  passwordToggle: true,
181
210
  password: '1234',
182
211
  editingRights: 'readOnly',
@@ -185,32 +214,77 @@ describe('ShareRestrictionModal/helpers', function () {
185
214
  showAlert: jest.fn()
186
215
  });
187
216
 
188
- case 4:
217
+ case 5:
189
218
  expect(updateDocumentPermissions).toHaveBeenCalledWith(file, {
190
- expiresAt: '2100-01-01T00:00:00.000Z',
219
+ expiresAt: endOfDay(selectedDate).toISOString(),
191
220
  password: '1234',
192
221
  verbs: ['GET']
193
222
  });
194
223
 
195
- case 5:
224
+ case 6:
196
225
  case "end":
197
226
  return _context5.stop();
198
227
  }
199
228
  }, _callee5);
200
229
  })));
230
+ it('sets expiration to the end of the selected day so picking today stays valid', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
231
+ var updateDocumentPermissions, file, today, expiresAt;
232
+ return _regeneratorRuntime.wrap(function _callee6$(_context6) {
233
+ while (1) switch (_context6.prev = _context6.next) {
234
+ case 0:
235
+ // Freeze time so the "expires in the future" assertion can't flake near
236
+ // midnight or under slow CI.
237
+ jest.useFakeTimers();
238
+ jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'));
239
+ _context6.prev = 2;
240
+ updateDocumentPermissions = jest.fn();
241
+ file = {
242
+ _id: '123'
243
+ };
244
+ today = new Date();
245
+ _context6.next = 8;
246
+ return updatePermissions({
247
+ file: file,
248
+ t: jest.fn(),
249
+ dateToggle: true,
250
+ selectedDate: today,
251
+ passwordToggle: false,
252
+ password: '',
253
+ editingRights: 'readOnly',
254
+ documentType: documentType,
255
+ updateDocumentPermissions: updateDocumentPermissions,
256
+ showAlert: jest.fn()
257
+ });
258
+
259
+ case 8:
260
+ expiresAt = updateDocumentPermissions.mock.calls[0][1].expiresAt;
261
+ expect(expiresAt).toBe(endOfDay(today).toISOString());
262
+ expect(new Date(expiresAt).getTime()).toBeGreaterThan(Date.now());
263
+
264
+ case 11:
265
+ _context6.prev = 11;
266
+ jest.useRealTimers();
267
+ return _context6.finish(11);
268
+
269
+ case 14:
270
+ case "end":
271
+ return _context6.stop();
272
+ }
273
+ }, _callee6, null, [[2,, 11, 14]]);
274
+ })));
201
275
  });
202
276
  describe('revokePermissions', function () {
203
277
  var documentType = 'Files';
204
- it('should call revokeSharingLink', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
278
+ it('should call revokeSharingLink', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
205
279
  var revokeSharingLink, file;
206
- return _regeneratorRuntime.wrap(function _callee6$(_context6) {
207
- while (1) switch (_context6.prev = _context6.next) {
280
+ return _regeneratorRuntime.wrap(function _callee7$(_context7) {
281
+ while (1) switch (_context7.prev = _context7.next) {
208
282
  case 0:
209
283
  revokeSharingLink = jest.fn();
210
284
  file = {
211
285
  _id: '123'
212
286
  };
213
- _context6.next = 4;
287
+ _context7.next = 4;
214
288
  return revokePermissions({
215
289
  file: file,
216
290
  t: jest.fn(),
@@ -224,9 +298,9 @@ describe('ShareRestrictionModal/helpers', function () {
224
298
 
225
299
  case 5:
226
300
  case "end":
227
- return _context6.stop();
301
+ return _context7.stop();
228
302
  }
229
- }, _callee6);
303
+ }, _callee7);
230
304
  })));
231
305
  });
232
306
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.3.0",
3
+ "version": "33.3.1",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -83,5 +83,5 @@
83
83
  "sideEffects": [
84
84
  "*.css"
85
85
  ],
86
- "gitHead": "4fe0199d7042ff150315bd4a097256b2364f200c"
86
+ "gitHead": "80c72e3af992596597b9319ee436ec8d238c9967"
87
87
  }
@@ -1,3 +1,5 @@
1
+ import { endOfDay } from 'date-fns'
2
+
1
3
  import { generateWebLink } from 'cozy-client'
2
4
  import minilog from 'cozy-minilog'
3
5
 
@@ -9,6 +11,21 @@ const log = minilog('ShareRestrictionModal/helpers')
9
11
  export const WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH']
10
12
  export const READ_ONLY_PERMS = ['GET']
11
13
 
14
+ /**
15
+ * A share link expires at the END of the selected day, so picking today keeps
16
+ * the link reachable for the rest of today and expires it the next day.
17
+ * Without this, the picked day defaults to its midnight (start of day), which
18
+ * is already in the past and makes the link unreachable as soon as it is set.
19
+ * @param {Date|string|null|undefined} date - The picked expiration day
20
+ * @returns {Date|null|undefined} - The same value normalized to end of day
21
+ */
22
+ export const toExpirationDate = date => {
23
+ if (!date) return date
24
+ const parsed = date instanceof Date ? date : new Date(date)
25
+ if (parsed.toString() === 'Invalid Date') return date
26
+ return endOfDay(parsed)
27
+ }
28
+
12
29
  /**
13
30
  * Create a sharing link for a file with specified options
14
31
  * @param {object} options
@@ -161,9 +178,10 @@ export const makeTTL = selectedDate => {
161
178
  }
162
179
  if (selectedDateConverted instanceof Date) {
163
180
  const now = new Date()
181
+ const expiration = toExpirationDate(selectedDateConverted)
164
182
  const ttl =
165
- selectedDateConverted > now
166
- ? `${Math.round((selectedDateConverted - now) / 1000)}s`
183
+ expiration > now
184
+ ? `${Math.round((expiration - now) / 1000)}s`
167
185
  : undefined
168
186
  return ttl
169
187
  }
@@ -239,7 +257,9 @@ export const updatePermissions = async ({
239
257
  showAlert
240
258
  }) => {
241
259
  try {
242
- const expiresAt = dateToggle ? selectedDate?.toISOString() || undefined : ''
260
+ const expiresAt = dateToggle
261
+ ? toExpirationDate(selectedDate)?.toISOString() || undefined
262
+ : ''
243
263
  const ensurePassword = passwordToggle ? password || undefined : ''
244
264
  const verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS
245
265
  return updateDocumentPermissions(file, {
@@ -1,6 +1,29 @@
1
- import { makeTTL, revokePermissions, updatePermissions } from './helpers'
1
+ import { endOfDay } from 'date-fns'
2
+
3
+ import {
4
+ makeTTL,
5
+ revokePermissions,
6
+ toExpirationDate,
7
+ updatePermissions
8
+ } from './helpers'
2
9
 
3
10
  describe('ShareRestrictionModal/helpers', () => {
11
+ describe('toExpirationDate', () => {
12
+ it('returns falsy values unchanged', () => {
13
+ expect(toExpirationDate(null)).toBeNull()
14
+ expect(toExpirationDate(undefined)).toBeUndefined()
15
+ expect(toExpirationDate('')).toBe('')
16
+ })
17
+ it('normalizes a Date to the end of its day', () => {
18
+ const date = new Date(2100, 0, 1, 9, 30)
19
+ expect(toExpirationDate(date)).toEqual(endOfDay(date))
20
+ })
21
+ it('accepts an ISO string', () => {
22
+ const iso = '2100-01-01T09:30:00.000Z'
23
+ expect(toExpirationDate(iso)).toEqual(endOfDay(new Date(iso)))
24
+ })
25
+ })
26
+
4
27
  describe('makeTTL', () => {
5
28
  it('sould return undefined', () => {
6
29
  expect(makeTTL()).toBeUndefined()
@@ -14,6 +37,17 @@ describe('ShareRestrictionModal/helpers', () => {
14
37
  expect(makeTTL(new Date('2100-01-01T00:00:00.000Z'))).toBeDefined()
15
38
  expect(makeTTL('2100-01-01T00:00:00.000Z')).toBeDefined()
16
39
  })
40
+ it('keeps a link picked for today valid until the end of the day', () => {
41
+ // Regression: picking "today" used to resolve to midnight (already past),
42
+ // which dropped the TTL and the link never expired or was unreachable.
43
+ jest.useFakeTimers()
44
+ jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'))
45
+ try {
46
+ expect(makeTTL(new Date())).toBeDefined()
47
+ } finally {
48
+ jest.useRealTimers()
49
+ }
50
+ })
17
51
  })
18
52
 
19
53
  describe('updatePermissions', () => {
@@ -118,11 +152,12 @@ describe('ShareRestrictionModal/helpers', () => {
118
152
  const updateDocumentPermissions = jest.fn()
119
153
  const file = { _id: '123' }
120
154
 
155
+ const selectedDate = new Date('2100-01-01T00:00:00.000Z')
121
156
  await updatePermissions({
122
157
  file,
123
158
  t: jest.fn(),
124
159
  dateToggle: true,
125
- selectedDate: new Date('2100-01-01T00:00:00.000Z'),
160
+ selectedDate,
126
161
  passwordToggle: true,
127
162
  password: '1234',
128
163
  editingRights: 'readOnly',
@@ -132,11 +167,42 @@ describe('ShareRestrictionModal/helpers', () => {
132
167
  })
133
168
 
134
169
  expect(updateDocumentPermissions).toHaveBeenCalledWith(file, {
135
- expiresAt: '2100-01-01T00:00:00.000Z',
170
+ expiresAt: endOfDay(selectedDate).toISOString(),
136
171
  password: '1234',
137
172
  verbs: ['GET']
138
173
  })
139
174
  })
175
+
176
+ it('sets expiration to the end of the selected day so picking today stays valid', async () => {
177
+ // Freeze time so the "expires in the future" assertion can't flake near
178
+ // midnight or under slow CI.
179
+ jest.useFakeTimers()
180
+ jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'))
181
+ try {
182
+ const updateDocumentPermissions = jest.fn()
183
+ const file = { _id: '123' }
184
+ const today = new Date()
185
+
186
+ await updatePermissions({
187
+ file,
188
+ t: jest.fn(),
189
+ dateToggle: true,
190
+ selectedDate: today,
191
+ passwordToggle: false,
192
+ password: '',
193
+ editingRights: 'readOnly',
194
+ documentType,
195
+ updateDocumentPermissions,
196
+ showAlert: jest.fn()
197
+ })
198
+
199
+ const { expiresAt } = updateDocumentPermissions.mock.calls[0][1]
200
+ expect(expiresAt).toBe(endOfDay(today).toISOString())
201
+ expect(new Date(expiresAt).getTime()).toBeGreaterThan(Date.now())
202
+ } finally {
203
+ jest.useRealTimers()
204
+ }
205
+ })
140
206
  })
141
207
 
142
208
  describe('revokePermissions', () => {