diff --git a/app/stores/DocumentsStore.js b/app/stores/DocumentsStore.js index 8ffba9c5..7aafcc14 100644 --- a/app/stores/DocumentsStore.js +++ b/app/stores/DocumentsStore.js @@ -14,6 +14,12 @@ export const DEFAULT_PAGINATION_LIMIT = 25; type Options = { ui: UiStore, + errors: ErrorsStore, +}; + +type FetchOptions = { + prefetch?: boolean, + shareId?: string, }; class DocumentsStore extends BaseStore { @@ -166,15 +172,20 @@ class DocumentsStore extends BaseStore { @action prefetchDocument = async (id: string) => { - if (!this.getById(id)) this.fetch(id, true); + if (!this.getById(id)) { + this.fetch(id, { prefetch: true }); + } }; @action - fetch = async (id: string, prefetch?: boolean): Promise<*> => { - if (!prefetch) this.isFetching = true; + fetch = async (id: string, options?: FetchOptions = {}): Promise<*> => { + if (!options.prefetch) this.isFetching = true; try { - const res = await client.post('/documents.info', { id }); + const res = await client.post('/documents.info', { + id, + shareId: options.shareId, + }); invariant(res && res.data, 'Document not available'); const { data } = res; const document = new Document(data); @@ -186,7 +197,7 @@ class DocumentsStore extends BaseStore { return document; } catch (e) { - this.errors.add('Failed to load documents'); + this.errors.add('Failed to load document'); } finally { this.isFetching = false; } diff --git a/server/api/documents.js b/server/api/documents.js index f17e7bbb..25e1c876 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -4,7 +4,7 @@ import Sequelize from 'sequelize'; import auth from './middlewares/authentication'; import pagination from './middlewares/pagination'; import { presentDocument, presentRevision } from '../presenters'; -import { Document, Collection, Star, View, Revision } from '../models'; +import { Document, Collection, Share, Star, View, Revision } from '../models'; import { InvalidRequestError } from '../errors'; import events from '../events'; import policy from '../policies'; @@ -157,12 +157,26 @@ router.post('documents.drafts', auth(), pagination(), async ctx => { }; }); -router.post('documents.info', auth(), async ctx => { - const { id } = ctx.body; - ctx.assertPresent(id, 'id is required'); - const document = await Document.findById(id); +router.post('documents.info', auth({ required: false }), async ctx => { + const { id, shareId } = ctx.body; + ctx.assertPresent(id || shareId, 'id or shareId is required'); - authorize(ctx.state.user, 'read', document); + let document; + if (shareId) { + const share = await Share.findById(shareId, { + include: [ + { + model: Document, + required: true, + as: 'document', + }, + ], + }); + document = share.document; + } else { + document = await Document.findById(id); + authorize(ctx.state.user, 'read', document); + } ctx.body = { data: await presentDocument(ctx, document), diff --git a/server/api/documents.test.js b/server/api/documents.test.js index 20280ffb..ccc4f81a 100644 --- a/server/api/documents.test.js +++ b/server/api/documents.test.js @@ -3,7 +3,7 @@ import TestServer from 'fetch-test-server'; import app from '..'; import { Document, View, Star, Revision } from '../models'; import { flushdb, seed } from '../test/support'; -import { buildUser } from '../test/factories'; +import { buildShare, buildUser } from '../test/factories'; const server = new TestServer(app.callback()); @@ -35,6 +35,22 @@ describe('#documents.info', async () => { expect(res.status).toEqual(200); expect(body.data.id).toEqual(document.id); }); + + it('should return documents from shareId', async () => { + const { user, document } = await seed(); + const share = await buildShare({ + documentId: document.id, + teamId: document.teamId, + }); + + const res = await server.post('/api/documents.info', { + body: { token: user.getJwtToken(), shareId: share.id }, + }); + const body = await res.json(); + + expect(res.status).toEqual(200); + expect(body.data.id).toEqual(document.id); + }); }); describe('#documents.list', async () => { diff --git a/server/api/middlewares/authentication.js b/server/api/middlewares/authentication.js index 26e06a28..3cd33ab6 100644 --- a/server/api/middlewares/authentication.js +++ b/server/api/middlewares/authentication.js @@ -4,7 +4,7 @@ import { type Context } from 'koa'; import { User, ApiKey } from '../../models'; import { AuthenticationError, UserSuspendedError } from '../../errors'; -export default function auth() { +export default function auth(options?: { required?: boolean } = {}) { return async function authMiddleware( ctx: Context, next: () => Promise @@ -33,58 +33,61 @@ export default function auth() { token = ctx.request.query.token; } - if (!token) throw new AuthenticationError('Authentication required'); + if (!token && options.required !== false) { + throw new AuthenticationError('Authentication required'); + } let user; + if (token) { + if (String(token).match(/^[\w]{38}$/)) { + // API key + let apiKey; + try { + apiKey = await ApiKey.findOne({ + where: { + secret: token, + }, + }); + } catch (e) { + throw new AuthenticationError('Invalid API key'); + } - if (String(token).match(/^[\w]{38}$/)) { - // API key - let apiKey; - try { - apiKey = await ApiKey.findOne({ - where: { - secret: token, - }, - }); - } catch (e) { - throw new AuthenticationError('Invalid API key'); + if (!apiKey) throw new AuthenticationError('Invalid API key'); + + user = await User.findById(apiKey.userId); + if (!user) throw new AuthenticationError('Invalid API key'); + } else { + // JWT + // Get user without verifying payload signature + let payload; + try { + payload = JWT.decode(token); + } catch (e) { + throw new AuthenticationError('Unable to decode JWT token'); + } + + if (!payload) throw new AuthenticationError('Invalid token'); + + user = await User.findById(payload.id); + + try { + JWT.verify(token, user.jwtSecret); + } catch (e) { + throw new AuthenticationError('Invalid token'); + } } - if (!apiKey) throw new AuthenticationError('Invalid API key'); - - user = await User.findById(apiKey.userId); - if (!user) throw new AuthenticationError('Invalid API key'); - } else { - // JWT - // Get user without verifying payload signature - let payload; - try { - payload = JWT.decode(token); - } catch (e) { - throw new AuthenticationError('Unable to decode JWT token'); + if (user.isSuspended) { + const suspendingAdmin = await User.findById(user.suspendedById); + throw new UserSuspendedError({ adminEmail: suspendingAdmin.email }); } - if (!payload) throw new AuthenticationError('Invalid token'); - - user = await User.findById(payload.id); - - try { - JWT.verify(token, user.jwtSecret); - } catch (e) { - throw new AuthenticationError('Invalid token'); - } + ctx.state.token = token; + ctx.state.user = user; + // $FlowFixMe + ctx.cache[user.id] = user; } - if (user.isSuspended) { - const suspendingAdmin = await User.findById(user.suspendedById); - throw new UserSuspendedError({ adminEmail: suspendingAdmin.email }); - } - - ctx.state.token = token; - ctx.state.user = user; - // $FlowFixMe - ctx.cache[user.id] = user; - return next(); }; }