Retrieve documents using shareId

This commit is contained in:
Tom Moor 2018-05-13 13:26:06 -07:00
parent 22bc5a7373
commit 500d039856
4 changed files with 100 additions and 56 deletions

View File

@ -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;
}

View File

@ -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),

View File

@ -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 () => {

View File

@ -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<void>
@ -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();
};
}