Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(permissionColl & models/sharing): Improve sharing link feature #1579

Merged
merged 4 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/api/cozy-client/modules/models.sharing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

## Functions

### getSharingLink
### makeSharingLink

▸ **getSharingLink**(`client`, `filesIds`, `options?`): `Promise`<`string`>
▸ **makeSharingLink**(`client`, `filesIds`, `options?`): `Promise`<`string`>

Generate Sharing link for one or many files

Expand All @@ -21,6 +21,7 @@ Generate Sharing link for one or many files
| `options` | `Object` | Options |
| `options.password` | `string` | - |
| `options.ttl` | `string` | - |
| `options.verbs` | `string`\[] | - |

*Returns*

Expand All @@ -30,4 +31,4 @@ Shared link

*Defined in*

[packages/cozy-client/src/models/sharing.js:15](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/sharing.js#L15)
[packages/cozy-client/src/models/sharing.js:16](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/models/sharing.js#L16)
25 changes: 17 additions & 8 deletions docs/api/cozy-stack-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -1667,7 +1667,7 @@ Implements `DocumentCollection` API along with specific methods for `io.cozy.per

* [PermissionCollection](#PermissionCollection)
* [.create(permission)](#PermissionCollection+create)
* [.add(document, permission)](#PermissionCollection+add) ⇒ <code>Promise</code>
* [.add(document, permission, options)](#PermissionCollection+add) ⇒ <code>Promise</code>
* ~~[.findApps()](#PermissionCollection+findApps)~~
* [.createSharingLink(document, options)](#PermissionCollection+createSharingLink)
* [.fetchPermissionsByLink(permissions)](#PermissionCollection+fetchPermissionsByLink)
Expand All @@ -1692,7 +1692,7 @@ It can also associates one or more codes to it, via the codes parameter

<a name="PermissionCollection+add"></a>

### permissionCollection.add(document, permission) ⇒ <code>Promise</code>
### permissionCollection.add(document, permission, options) ⇒ <code>Promise</code>
Adds a permission to the given document. Document type must be
`io.cozy.apps`, `io.cozy.konnectors` or `io.cozy.permissions`

Expand All @@ -1702,18 +1702,23 @@ Adds a permission to the given document. Document type must be
| --- | --- | --- |
| document | <code>object</code> | Document which receives the permission |
| permission | <code>object</code> | Describes the permission |
| options | <code>object</code> | options |
| [options.expiresAt] | <code>string</code> | Date at which the permission will expire. Set to "" to remove it. |
| [options.password] | <code>string</code> | To generate a password-protected link. Set to "" to remove it. |

**Example**
```
const permissions = await client
.collection('io.cozy.permissions')
.add(konnector, {
const permissions = await client.collection('io.cozy.permissions').add(
konnector,
{
folder: {
type: 'io.cozy.files',
verbs: ['GET', 'PUT'],
values: [`io.cozy.files.bc57b60eb2954537b0dcdc6ebd8e9d23`]
}
})
},
{ expiresAt: '2100-01-01T00:00:00Z', password: 'password' }
)
```
<a name="PermissionCollection+findApps"></a>

Expand All @@ -1732,7 +1737,11 @@ Create a share link
| --- | --- | --- |
| document | <code>Object</code> | cozy document |
| options | <code>object</code> | options |
| options.verbs | <code>Array.&lt;string&gt;</code> | explicit permissions to use |
| [options.ttl] | <code>string</code> | Time to live (bigduration format, e.g. "4Y3M2D1h30m15s") |
| [options.password] | <code>string</code> | To generate a password-protected link |
| [options.verbs] | <code>Array.&lt;string&gt;</code> | explicit permissions to use |
| [options.codes] | <code>string</code> | A comma separed list of values (defaulted to code) |
| [options.tiny] | <code>boolean</code> | If set to true then the generated shortcode will be 6 digits Cozy-Stack has a few conditions to be able to use this tiny shortcode ATM you have to specifiy a ttl < 1h, but it can change. see https://docs.cozy.io/en/cozy-stack/permissions/#post-permissions for exact informations |

<a name="PermissionCollection+fetchPermissionsByLink"></a>

Expand Down Expand Up @@ -2305,7 +2314,7 @@ Build a permission set
| document | <code>Object</code> | cozy document |
| publicLink | <code>boolean</code> | are the permissions for a public link ? |
| options | <code>object</code> | options |
| options.verbs | <code>Array.&lt;string&gt;</code> | explicit permissions to use |
| [options.verbs] | <code>Array.&lt;string&gt;</code> | explicit permissions to use |

<a name="normalizeSettings"></a>

Expand Down
7 changes: 4 additions & 3 deletions packages/cozy-client/src/models/sharing.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import { generateWebLink } from '../helpers'
* @param {object} options - Options
* @param {string} [options.ttl] - Time to live (bigduration format, e.g. "4Y3M2D1h30m15s")
* @param {string} [options.password] - To generate a password-protected link
* @param {string[]} [options.verbs] - Array of verbs to allow on the shared link. Default is ['GET']
* @returns {Promise<string>} Shared link
*/
export const getSharingLink = async (
export const makeSharingLink = async (
client,
filesIds,
{ ttl, password } = {}
{ ttl, password, verbs = ['GET'] } = {}
) => {
const PERMS = {
_type: DOCTYPE_PERMISSIONS,
permissions: {
files: { type: DOCTYPE_FILES, values: filesIds, verbs: ['GET'] }
files: { type: DOCTYPE_FILES, values: filesIds, verbs }
},
...(ttl && { ttl }),
...(password && { password })
Expand Down
37 changes: 28 additions & 9 deletions packages/cozy-client/src/models/sharing.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getSharingLink } from './sharing'
import { makeSharingLink } from './sharing'

describe('getSharingLink', () => {
describe('makeSharingLink', () => {
const mockSharecode = { attributes: { shortcodes: { code: 'shortcode' } } }
const mockClient = ({ isFlatDomain = false } = {}) => ({
save: jest.fn(() => ({ data: mockSharecode })),
Expand All @@ -10,15 +10,15 @@ describe('getSharingLink', () => {
const mockFiles = ['fileId01', 'fileId02']

it('should generate the right share link if "isFlatDomain" param is not defined', async () => {
const sharingLink = await getSharingLink(mockClient(), mockFiles)
const sharingLink = await makeSharingLink(mockClient(), mockFiles)

expect(sharingLink).toBe(
'http://drive.cozy.cloud/public?sharecode=shortcode#/'
)
})

it('should generate the right share link to a nested cozy', async () => {
const sharingLink = await getSharingLink(
const sharingLink = await makeSharingLink(
mockClient({ isFlatDomain: false }),
mockFiles
)
Expand All @@ -29,7 +29,7 @@ describe('getSharingLink', () => {
})

it('should generate the right share link to a flat cozy', async () => {
const sharingLink = await getSharingLink(
const sharingLink = await makeSharingLink(
mockClient({ isFlatDomain: true }),
mockFiles
)
Expand All @@ -40,14 +40,14 @@ describe('getSharingLink', () => {
})

it('should generate the right share link with an correct sharecode', async () => {
const sharingLink = await getSharingLink(mockClient(), mockFiles)
const sharingLink = await makeSharingLink(mockClient(), mockFiles)

expect(sharingLink).toContain('sharecode=shortcode')
})
it('should save called with the "ttl" param', async () => {
const mockSave = jest.fn().mockReturnValue({ data: mockSharecode })
const client = { ...mockClient(), save: mockSave }
await getSharingLink(client, mockFiles, {
await makeSharingLink(client, mockFiles, {
ttl: '1d'
})

Expand All @@ -67,7 +67,7 @@ describe('getSharingLink', () => {
it('should save called with the "password" param', async () => {
const mockSave = jest.fn().mockReturnValue({ data: mockSharecode })
const client = { ...mockClient(), save: mockSave }
await getSharingLink(client, mockFiles, {
await makeSharingLink(client, mockFiles, {
password: 'password'
})

Expand All @@ -84,10 +84,29 @@ describe('getSharingLink', () => {
})
})

it('should save called with the "verbs" param', async () => {
const mockSave = jest.fn().mockReturnValue({ data: mockSharecode })
const client = { ...mockClient(), save: mockSave }
await makeSharingLink(client, mockFiles, {
verbs: ['GET', 'POST']
})

expect(mockSave).toBeCalledWith({
_type: 'io.cozy.permissions',
permissions: {
files: {
type: 'io.cozy.files',
values: ['fileId01', 'fileId02'],
verbs: ['GET', 'POST']
}
}
})
})

it('should save called without the "ttl" or "password" params', async () => {
const mockSave = jest.fn().mockReturnValue({ data: mockSharecode })
const client = { ...mockClient(), save: mockSave }
await getSharingLink(client, mockFiles)
await makeSharingLink(client, mockFiles)

expect(mockSave).toBeCalledWith({
_type: 'io.cozy.permissions',
Expand Down
3 changes: 2 additions & 1 deletion packages/cozy-client/types/models/sharing.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export function getSharingLink(client: CozyClient, filesIds: string[], { ttl, password }?: {
export function makeSharingLink(client: CozyClient, filesIds: string[], { ttl, password, verbs }?: {
ttl: string;
password: string;
verbs: string[];
}): Promise<string>;
import CozyClient from "../CozyClient";
48 changes: 37 additions & 11 deletions packages/cozy-stack-client/src/PermissionCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,27 @@ class PermissionCollection extends DocumentCollection {
*
* @param {object} document - Document which receives the permission
* @param {object} permission - Describes the permission
* @param {object} options - options
* @param {string} [options.expiresAt] - Date at which the permission will expire. Set to "" to remove it.
* @param {string} [options.password] - To generate a password-protected link. Set to "" to remove it.
* @returns {Promise}
*
* @example
* ```
* const permissions = await client
* .collection('io.cozy.permissions')
* .add(konnector, {
* const permissions = await client.collection('io.cozy.permissions').add(
* konnector,
* {
* folder: {
* type: 'io.cozy.files',
* verbs: ['GET', 'PUT'],
* values: [`io.cozy.files.bc57b60eb2954537b0dcdc6ebd8e9d23`]
* }
* })
* },
* { expiresAt: '2100-01-01T00:00:00Z', password: 'password' }
* )
* ```
*/
async add(document, permission) {
async add(document, permission, options = {}) {
let endpoint
switch (document._type) {
case 'io.cozy.apps':
Expand All @@ -94,11 +99,19 @@ class PermissionCollection extends DocumentCollection {
)
}

const { expiresAt, password } = options
// We need to pass password and expires_at even if they are empty strings because the API expects them.
// If value is a empty string, the stack will remove the password or expires_at.
const hasPassword = password || password === ''
const hasExpiresAt = expiresAt || expiresAt === ''

const resp = await this.stackClient.fetchJSON('PATCH', endpoint, {
data: {
type: 'io.cozy.permissions',
attributes: {
permissions: permission
permissions: permission,
...(hasPassword && { password }),
...(hasExpiresAt && { expires_at: expiresAt })
}
}
})
Expand Down Expand Up @@ -143,13 +156,25 @@ class PermissionCollection extends DocumentCollection {
*
* @param {{_id, _type}} document - cozy document
* @param {object} options - options
* @param {string[]} options.verbs - explicit permissions to use
* @param {string} [options.ttl] - Time to live (bigduration format, e.g. "4Y3M2D1h30m15s")
* @param {string} [options.password] - To generate a password-protected link
* @param {string[]} [options.verbs] - explicit permissions to use
* @param {string} [options.codes] A comma separed list of values (defaulted to code)
* @param {boolean} [options.tiny] If set to true then the generated shortcode will be 6 digits
* Cozy-Stack has a few conditions to be able to use this tiny shortcode ATM you have to specifiy
* a ttl < 1h, but it can change.
* see https://docs.cozy.io/en/cozy-stack/permissions/#post-permissions for exact informations
*/
async createSharingLink(document, options = {}) {
const { verbs } = options
const { ttl, password, verbs, tiny, codes = 'code' } = options
const searchParams = new URLSearchParams()
searchParams.append('codes', codes)
if (ttl) searchParams.append('ttl', ttl)
if (tiny) searchParams.append('tiny', true)

const resp = await this.stackClient.fetchJSON(
'POST',
`/permissions?codes=email`,
`/permissions?${searchParams}`,
Merkur39 marked this conversation as resolved.
Show resolved Hide resolved
{
data: {
type: 'io.cozy.permissions',
Expand All @@ -158,7 +183,8 @@ class PermissionCollection extends DocumentCollection {
document,
true,
verbs ? { verbs } : {}
)
),
...(password && { password })
}
}
}
Expand Down Expand Up @@ -245,7 +271,7 @@ class PermissionCollection extends DocumentCollection {
* @param {{_id, _type}} document - cozy document
* @param {boolean} publicLink - are the permissions for a public link ?
* @param {object} options - options
* @param {string[]} options.verbs - explicit permissions to use
* @param {string[]} [options.verbs] - explicit permissions to use
* @returns {object} permissions object that can be sent through /permissions/*
*/
export const getPermissionsFor = (
Expand Down
Loading
Loading