Retrieve documents using shareId
This commit is contained in:
@ -14,6 +14,12 @@ export const DEFAULT_PAGINATION_LIMIT = 25;
|
|||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
ui: UiStore,
|
ui: UiStore,
|
||||||
|
errors: ErrorsStore,
|
||||||
|
};
|
||||||
|
|
||||||
|
type FetchOptions = {
|
||||||
|
prefetch?: boolean,
|
||||||
|
shareId?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
class DocumentsStore extends BaseStore {
|
class DocumentsStore extends BaseStore {
|
||||||
@ -166,15 +172,20 @@ class DocumentsStore extends BaseStore {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
prefetchDocument = async (id: string) => {
|
prefetchDocument = async (id: string) => {
|
||||||
if (!this.getById(id)) this.fetch(id, true);
|
if (!this.getById(id)) {
|
||||||
|
this.fetch(id, { prefetch: true });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
fetch = async (id: string, prefetch?: boolean): Promise<*> => {
|
fetch = async (id: string, options?: FetchOptions = {}): Promise<*> => {
|
||||||
if (!prefetch) this.isFetching = true;
|
if (!options.prefetch) this.isFetching = true;
|
||||||
|
|
||||||
try {
|
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');
|
invariant(res && res.data, 'Document not available');
|
||||||
const { data } = res;
|
const { data } = res;
|
||||||
const document = new Document(data);
|
const document = new Document(data);
|
||||||
@ -186,7 +197,7 @@ class DocumentsStore extends BaseStore {
|
|||||||
|
|
||||||
return document;
|
return document;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errors.add('Failed to load documents');
|
this.errors.add('Failed to load document');
|
||||||
} finally {
|
} finally {
|
||||||
this.isFetching = false;
|
this.isFetching = false;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import Sequelize from 'sequelize';
|
|||||||
import auth from './middlewares/authentication';
|
import auth from './middlewares/authentication';
|
||||||
import pagination from './middlewares/pagination';
|
import pagination from './middlewares/pagination';
|
||||||
import { presentDocument, presentRevision } from '../presenters';
|
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 { InvalidRequestError } from '../errors';
|
||||||
import events from '../events';
|
import events from '../events';
|
||||||
import policy from '../policies';
|
import policy from '../policies';
|
||||||
@ -157,12 +157,26 @@ router.post('documents.drafts', auth(), pagination(), async ctx => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('documents.info', auth(), async ctx => {
|
router.post('documents.info', auth({ required: false }), async ctx => {
|
||||||
const { id } = ctx.body;
|
const { id, shareId } = ctx.body;
|
||||||
ctx.assertPresent(id, 'id is required');
|
ctx.assertPresent(id || shareId, 'id or shareId is required');
|
||||||
const document = await Document.findById(id);
|
|
||||||
|
|
||||||
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 = {
|
ctx.body = {
|
||||||
data: await presentDocument(ctx, document),
|
data: await presentDocument(ctx, document),
|
||||||
|
@ -3,7 +3,7 @@ import TestServer from 'fetch-test-server';
|
|||||||
import app from '..';
|
import app from '..';
|
||||||
import { Document, View, Star, Revision } from '../models';
|
import { Document, View, Star, Revision } from '../models';
|
||||||
import { flushdb, seed } from '../test/support';
|
import { flushdb, seed } from '../test/support';
|
||||||
import { buildUser } from '../test/factories';
|
import { buildShare, buildUser } from '../test/factories';
|
||||||
|
|
||||||
const server = new TestServer(app.callback());
|
const server = new TestServer(app.callback());
|
||||||
|
|
||||||
@ -35,6 +35,22 @@ describe('#documents.info', async () => {
|
|||||||
expect(res.status).toEqual(200);
|
expect(res.status).toEqual(200);
|
||||||
expect(body.data.id).toEqual(document.id);
|
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 () => {
|
describe('#documents.list', async () => {
|
||||||
|
@ -4,7 +4,7 @@ import { type Context } from 'koa';
|
|||||||
import { User, ApiKey } from '../../models';
|
import { User, ApiKey } from '../../models';
|
||||||
import { AuthenticationError, UserSuspendedError } from '../../errors';
|
import { AuthenticationError, UserSuspendedError } from '../../errors';
|
||||||
|
|
||||||
export default function auth() {
|
export default function auth(options?: { required?: boolean } = {}) {
|
||||||
return async function authMiddleware(
|
return async function authMiddleware(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
next: () => Promise<void>
|
next: () => Promise<void>
|
||||||
@ -33,58 +33,61 @@ export default function auth() {
|
|||||||
token = ctx.request.query.token;
|
token = ctx.request.query.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token) throw new AuthenticationError('Authentication required');
|
if (!token && options.required !== false) {
|
||||||
|
throw new AuthenticationError('Authentication required');
|
||||||
|
}
|
||||||
|
|
||||||
let user;
|
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}$/)) {
|
if (!apiKey) throw new AuthenticationError('Invalid API key');
|
||||||
// API key
|
|
||||||
let apiKey;
|
user = await User.findById(apiKey.userId);
|
||||||
try {
|
if (!user) throw new AuthenticationError('Invalid API key');
|
||||||
apiKey = await ApiKey.findOne({
|
} else {
|
||||||
where: {
|
// JWT
|
||||||
secret: token,
|
// Get user without verifying payload signature
|
||||||
},
|
let payload;
|
||||||
});
|
try {
|
||||||
} catch (e) {
|
payload = JWT.decode(token);
|
||||||
throw new AuthenticationError('Invalid API key');
|
} 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');
|
if (user.isSuspended) {
|
||||||
|
const suspendingAdmin = await User.findById(user.suspendedById);
|
||||||
user = await User.findById(apiKey.userId);
|
throw new UserSuspendedError({ adminEmail: suspendingAdmin.email });
|
||||||
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');
|
ctx.state.token = token;
|
||||||
|
ctx.state.user = user;
|
||||||
user = await User.findById(payload.id);
|
// $FlowFixMe
|
||||||
|
ctx.cache[user.id] = user;
|
||||||
try {
|
|
||||||
JWT.verify(token, user.jwtSecret);
|
|
||||||
} catch (e) {
|
|
||||||
throw new AuthenticationError('Invalid token');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
return next();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user