Skip to content
This repository was archived by the owner on Oct 11, 2022. It is now read-only.

Commit 5a9f8b1

Browse files
authored
Merge pull request #4148 from withspectrum/image-signing
Image signing
2 parents 3850621 + d119043 commit 5a9f8b1

File tree

23 files changed

+230
-162
lines changed

23 files changed

+230
-162
lines changed

api/apollo-server.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ const server = new ProtectedApolloServer({
6161
req.login(data, err => (err ? rej(err) : res()))
6262
),
6363
user: currentUser,
64+
getImageSignatureExpiration: () => {
65+
/*
66+
Expire images sent to the client at midnight each day (UTC).
67+
Expiration needs to be consistent across all images in order
68+
to preserve client-side caching abilities and to prevent checksum
69+
mismatches during SSR
70+
*/
71+
const date = new Date();
72+
date.setHours(24);
73+
date.setMinutes(0);
74+
date.setSeconds(0);
75+
date.setMilliseconds(0);
76+
return date.getTime();
77+
},
6478
};
6579
},
6680
subscriptions: {

api/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type { Loader } from './loaders/types';
2626
export type GraphQLContext = {
2727
user: DBUser,
2828
updateCookieUserData: (data: DBUser) => Promise<void>,
29+
getImageSignatureExpiration: () => number,
2930
loaders: {
3031
[key: string]: Loader,
3132
},
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @flow
2+
import type { GraphQLContext } from '../../';
3+
import type { DBCommunity } from 'shared/types';
4+
import { signImageUrl } from 'shared/imgix';
5+
6+
export default ({ coverPhoto }: DBCommunity, _: any, ctx: GraphQLContext) => {
7+
return signImageUrl(coverPhoto, {
8+
w: 1280,
9+
h: 384,
10+
expires: ctx.getImageSignatureExpiration(),
11+
});
12+
};

api/queries/community/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import watercooler from './watercooler';
2424
import brandedLogin from './brandedLogin';
2525
import slackSettings from './slackSettings';
2626
import joinSettings from './joinSettings';
27+
import coverPhoto from './coverPhoto';
28+
import profilePhoto from './profilePhoto';
2729

2830
// no-op resolvers to transition while removing payments
2931
import type { DBCommunity } from 'shared/types';
@@ -67,6 +69,8 @@ module.exports = {
6769
brandedLogin,
6870
slackSettings,
6971
joinSettings,
72+
coverPhoto,
73+
profilePhoto,
7074

7175
invoices,
7276
recurringPayments,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @flow
2+
import type { GraphQLContext } from '../../';
3+
import type { DBUser } from 'shared/types';
4+
import { signImageUrl } from 'shared/imgix';
5+
6+
export default ({ profilePhoto }: DBUser, _: any, ctx: GraphQLContext) => {
7+
return signImageUrl(profilePhoto, {
8+
w: 256,
9+
h: 256,
10+
expires: ctx.getImageSignatureExpiration(),
11+
});
12+
};

api/queries/message/content.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// @flow
2+
import type { GraphQLContext } from '../../';
3+
import type { DBMessage } from 'shared/types';
4+
import body from './content/body';
5+
6+
export default (message: DBMessage, _: any, ctx: GraphQLContext) => ({
7+
body: body(message, ctx.getImageSignatureExpiration()),
8+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// @flow
2+
import type { DBMessage } from 'shared/types';
3+
import { signImageUrl } from 'shared/imgix';
4+
5+
export default (message: DBMessage, imageSignatureExpiration: number) => {
6+
const { content, messageType } = message;
7+
if (messageType !== 'media') return content.body;
8+
return signImageUrl(content.body, { expires: imageSignatureExpiration });
9+
};

api/queries/message/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import author from './author';
66
import thread from './thread';
77
import reactions from './reactions';
88
import parent from './parent';
9+
import content from './content';
910

1011
module.exports = {
1112
Query: {
@@ -18,5 +19,6 @@ module.exports = {
1819
thread,
1920
reactions,
2021
parent,
22+
content,
2123
},
2224
};

api/queries/thread/content.js

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
11
// @flow
22
import type { GraphQLContext } from '../../';
33
import type { DBThread } from 'shared/types';
4+
import body from './content/body';
45

5-
export default ({ content }: DBThread, _: any, ctx: GraphQLContext) => {
6-
const defaultDraftState = JSON.stringify({
7-
blocks: [
8-
{
9-
key: 'foo',
10-
text: '',
11-
type: 'unstyled',
12-
depth: 0,
13-
inlineStyleRanges: [],
14-
entityRanges: [],
15-
data: {},
16-
},
17-
],
18-
entityMap: {},
19-
});
20-
6+
export default (thread: DBThread, _: any, ctx: GraphQLContext) => {
217
return {
22-
title: content.title,
23-
body: content.body ? content.body : defaultDraftState,
8+
title: thread.content.title,
9+
body: body(thread, ctx.getImageSignatureExpiration()),
2410
};
2511
};

api/queries/thread/content/body.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// @flow
2+
import type { DBThread } from 'shared/types';
3+
import { signImageUrl } from 'shared/imgix';
4+
5+
export default (thread: DBThread, imageSignatureExpiration: number) => {
6+
const { content } = thread;
7+
8+
if (!content.body) {
9+
return JSON.stringify({
10+
blocks: [
11+
{
12+
key: 'foo',
13+
text: '',
14+
type: 'unstyled',
15+
depth: 0,
16+
inlineStyleRanges: [],
17+
entityRanges: [],
18+
data: {},
19+
},
20+
],
21+
entityMap: {},
22+
});
23+
}
24+
25+
// Replace the local image srcs with the remote image src
26+
const body = JSON.parse(content.body);
27+
28+
const imageKeys = Object.keys(body.entityMap).filter(
29+
key => body.entityMap[key].type.toLowerCase() === 'image'
30+
);
31+
32+
imageKeys.forEach((key, index) => {
33+
if (!body.entityMap[key[index]]) return;
34+
35+
const { src } = body.entityMap[imageKeys[index]].data;
36+
37+
// transform the body inline with signed image urls
38+
body.entityMap[imageKeys[index]].data.src = signImageUrl(src, {
39+
expires: imageSignatureExpiration,
40+
});
41+
});
42+
43+
return JSON.stringify(body);
44+
};

0 commit comments

Comments
 (0)