Skip to content

Commit 20ff5d8

Browse files
MwanPygmayMatthis-M-ReliefAppsAntoineRelief
authored
feat: add auth code APIs (#789)
--------- Co-authored-by: Matthis-M-ReliefApps <[email protected]> Co-authored-by: Antoine Hurard <[email protected]> Co-authored-by: Antoine Hurard <[email protected]>
1 parent efa4e1a commit 20ff5d8

File tree

14 files changed

+82
-36
lines changed

14 files changed

+82
-36
lines changed

src/const/enumTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const authType = {
3636
public: 'public',
3737
serviceToService: 'service-to-service',
3838
userToService: 'user-to-service',
39+
authorizationCode: 'authorization-code',
3940
};
4041

4142
/** AuthType type for queries/mutations argument */

src/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
},
146146
"fetch": {
147147
"errors": {
148+
"fetchRequestFailed": "Failed to fetch groups.",
148149
"groupsFromServiceDisabled": "Fetching groups from service not available."
149150
}
150151
}

src/i18n/test.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
},
146146
"fetch": {
147147
"errors": {
148+
"fetchRequestFailed": "******",
148149
"groupsFromServiceDisabled": "******"
149150
}
150151
}

src/routes/download/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { formatFilename } from '@utils/files/format.helper';
3131
import { sendEmail } from '@utils/email';
3232
import exportBatch from '@utils/files/exportBatch';
3333
import { accessibleBy } from '@casl/mongoose';
34+
import dataSources from '@server/apollo/dataSources';
3435

3536
/**
3637
* Exports files in csv or xlsx format, excepted if specified otherwise
@@ -208,6 +209,15 @@ router.get('/form/records/:id/history', async (req, res) => {
208209
{
209210
translate: req.t,
210211
ability,
212+
context: {
213+
// Need to use 'any' in order to use a class which is supposed to initialize with Apollo context
214+
dataSources: (
215+
await dataSources({
216+
// Passing upstream request so accesstoken can be used for authentication
217+
req: req,
218+
} as any)
219+
)(),
220+
},
211221
}
212222
).getHistory();
213223
const fields = filters.fields;

src/routes/gis/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,12 @@ router.get('/feature', async (req, res) => {
409409
referenceData.apiConfiguration,
410410
'name endpoint graphQLEndpoint'
411411
);
412-
const contextDataSources = (await dataSources())();
412+
const contextDataSources = (
413+
await dataSources({
414+
// Passing upstream request so accesstoken can be used for authentication
415+
req: req,
416+
} as any)
417+
)();
413418
const dataSource = contextDataSources[
414419
apiConfiguration.name
415420
] as CustomAPI;

src/routes/proxy/index.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import express from 'express';
1+
import express, { Request, Response } from 'express';
22
import { ApiConfiguration } from '@models';
33
import { getToken } from '@utils/proxy';
44
import { get, isEmpty } from 'lodash';
@@ -8,6 +8,7 @@ import config from 'config';
88
import * as CryptoJS from 'crypto-js';
99
import axios from 'axios';
1010
import { createClient, RedisClientType } from 'redis';
11+
import { authType } from '@const/enumTypes';
1112

1213
/** Express router */
1314
const router = express.Router();
@@ -24,7 +25,7 @@ const SETTING_PLACEHOLDER = '●●●●●●●●●●●●●';
2425
* @param path url path
2526
* @returns API request
2627
*/
27-
const proxyAPIRequest = async (req, res, api, path) => {
28+
const proxyAPIRequest = async (req: Request, res: Response, api, path) => {
2829
try {
2930
let client: RedisClientType;
3031
if (config.get('redis.url') && req.method === 'get') {
@@ -45,7 +46,7 @@ const proxyAPIRequest = async (req, res, api, path) => {
4546
logger.info(`REDIS: get key : ${url}`);
4647
res.status(200).send(JSON.parse(cacheData));
4748
} else {
48-
const token = await getToken(api);
49+
const token = await getToken(api, req.headers.accesstoken);
4950
await axios({
5051
url,
5152
method: req.method,
@@ -59,9 +60,14 @@ const proxyAPIRequest = async (req, res, api, path) => {
5960
}),
6061
})
6162
.then(async ({ data, status }) => {
63+
// We are only caching the results of requests that are not user-dependent.
64+
// Otherwise, unwanted users could access cached data of other users.
65+
// As an improvement, we could include a stringified unique property of the user to the cache-key to enable user-specific cache.
6266
if (
6367
client &&
64-
['service-to-service', 'public'].includes(api.authType) &&
68+
[authType.serviceToService, authType.public].includes(
69+
api.authType
70+
) &&
6571
status === 200
6672
) {
6773
await client
@@ -86,7 +92,7 @@ const proxyAPIRequest = async (req, res, api, path) => {
8692
}
8793
};
8894

89-
router.post('/ping/**', async (req, res) => {
95+
router.post('/ping/**', async (req: Request, res: Response) => {
9096
try {
9197
const body = req.body;
9298
if (body) {
@@ -148,7 +154,7 @@ router.post('/ping/**', async (req, res) => {
148154
/**
149155
* Forward requests to actual API using the API Configuration
150156
*/
151-
router.all('/:name/**', async (req, res) => {
157+
router.all('/:name/**', async (req: Request, res: Response) => {
152158
try {
153159
const api = await ApiConfiguration.findOne({
154160
$or: [{ name: req.params.name }, { id: req.params.name }],

src/schema/mutation/editApiConfiguration.mutation.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ export default {
115115
).toString(),
116116
});
117117
}
118-
119118
return await ApiConfiguration.findOneAndUpdate(filters, update, {
120119
new: true,
121120
});

src/schema/mutation/fetchGroups.mutation.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,14 @@ export default {
3232
context.i18next.t('common.errors.permissionNotGranted')
3333
);
3434
}
35-
36-
const groups = await fetchGroups();
35+
let groups: Group[] = null;
36+
try {
37+
groups = await fetchGroups();
38+
} catch {
39+
throw new GraphQLError(
40+
context.i18next.t('mutations.group.fetch.errors.fetchRequestFailed')
41+
);
42+
}
3743
const bulkOps: any[] = [];
3844
groups.forEach((group) => {
3945
const upsertGroup = {

src/server/apollo/context.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ interface UserWithAbility extends User {
2727
export default (server: ApolloServer<Context>) =>
2828
async ({ req }): Promise<Context> => {
2929
if (req) {
30+
// Attaching the request object to server since it needs to be used by datasources
31+
// eslint-disable-next-line @typescript-eslint/dot-notation
32+
server['req'] = req;
3033
return {
3134
// Makes the translation library accessible in the context object.
3235
// https://github.com/i18next/i18next-http-middleware

src/server/apollo/dataSources.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const LAST_UPDATE_CODE = '{{lastUpdate}}';
2121
/**
2222
* CustomAPI class to create a dataSource fetching from an APIConfiguration.
2323
* If nothing is passed in the constructor, it will only be a standard REST DataSource.
24+
* Data sources are invoked, for example, when using reference data in a context involving the display of data, such as in a grid, or when fetching or downloading historical record items.
2425
*/
2526
export class CustomAPI extends RESTDataSource {
2627
public apiConfiguration: ApiConfiguration;
@@ -38,7 +39,7 @@ export class CustomAPI extends RESTDataSource {
3839
* @param apiConfiguration optional argument used to initialize the calls using the passed ApiConfiguration
3940
*/
4041
constructor(
41-
server?: ApolloServer<Context>,
42+
public server: ApolloServer<Context>,
4243
apiConfiguration?: ApiConfiguration
4344
) {
4445
super(server ? { cache: server.cache } : undefined);
@@ -60,7 +61,8 @@ export class CustomAPI extends RESTDataSource {
6061
*/
6162
async willSendRequest(_: string, request: AugmentedRequest) {
6263
if (this.apiConfiguration) {
63-
const token: string = await getToken(this.apiConfiguration);
64+
const accessToken = (this.server as any).req.headers.accesstoken ?? '';
65+
const token: string = await getToken(this.apiConfiguration, accessToken);
6466
// eslint-disable-next-line @typescript-eslint/dot-notation
6567
request.headers['authorization'] = `Bearer ${token}`;
6668
}
@@ -180,7 +182,10 @@ export class CustomAPI extends RESTDataSource {
180182
if (!cacheTimestamp || cacheTimestamp < modifiedAt) {
181183
// Check if referenceData has changed. In this case, refresh choices instead of using cached ones.
182184
const body = { query: this.processQuery(referenceData) };
183-
const data = await this.post(url, { body });
185+
let data = await this.post(url, { body });
186+
if (typeof data === 'string') {
187+
data = JSON.parse(data);
188+
}
184189
items = referenceData.path
185190
? jsonpath.query(data, referenceData.path)
186191
: data;
@@ -191,7 +196,10 @@ export class CustomAPI extends RESTDataSource {
191196
const isCached = cache !== undefined;
192197
const valueField = referenceData.valueField || 'id';
193198
const body = { query: this.processQuery(referenceData) };
194-
const data = await this.post(url, { body });
199+
let data = await this.post(url, { body });
200+
if (typeof data === 'string') {
201+
data = JSON.parse(data);
202+
}
195203
items = referenceData.path
196204
? jsonpath.query(data, referenceData.path)
197205
: data;

0 commit comments

Comments
 (0)