Hello I'm new to GraphQl and to Apollo Server.
I would like to implement authentication on my project.
But
For some reason, I can't seem to set context on my resolvers in apollo server.
Here's my index
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const userId = jwtDecode(req.headers.authorization)
return userId.sub
}
})
And my query
Query: {
users: async (parent, args, context) => {
try {
console.log(context)
return await getUsers(context)
} catch (err) {
console.log(err)
throw new Error(err.message)
}
}
When I try to output the context the result is always like this...
{ injector:
Injector {
options:
{ name: 'index.ts_8346047369535445_SESSION',
injectorScope: 'SESSION',
hooks: [Array],
children: [] },
_classMap: Map {},
_factoryMap: Map {},
_applicationScopeInstanceMap:
Map {
Symbol(ModuleConfig.index.ts_8346047369535445) => undefined,
[Function] => undefined },
_sessionScopeInstanceMap: Map { [Function: ModuleSessionInfo] => [ModuleSessionInfo] },
_applicationScopeServiceIdentifiers:
[ Symbol(ModuleConfig.index.ts_8346047369535445), [Function] ],
_requestScopeServiceIdentifiers: [],
_sessionScopeServiceIdentifiers: [ [Function: ModuleSessionInfo] ],
_hookServiceIdentifiersMap: Map {},
_name: 'index.ts_8346047369535445_SESSION',
_injectorScope: 'SESSION',
_defaultProviderScope: 'SESSION',
........
What's returned inside the context function should always be an object. So you would do something like
context: ({ req }) => {
const { sub } = jwtDecode(req.headers.authorization)
return {
sub,
}
}
and then access the value inside the resolver by calling context.sub.
However, if you're using GraphQL Modules to create your schema, you should follow the library's documentation for configuring your context on a per-module basis.
Related
I'm trying to create an authentication strategy using context within express-graphql, however, when I access context within isAuthenticated it returns [Function: context]. What am I not understanding?
app.use(
"/graphql",
graphqlHTTP(async (req: any) => ({
schema: schema,
graphiql: true,
context: (req: any) => {
const user = users.find((user) => user.username === "test user");
if (!user) {
return {
message: "Incorrect username or password.",
};
}
return {
user: "test user",
active: "Yes",
};
},
}))
);
const isAuthenticated =
() =>
(next: any) =>
async (root: any, args: any, context: any, info: any) => {
console.log("context", context);
if (!context.currentUser) {
throw new Error("You are not authorized");
}
return next(root, args, context, info);
};
Here's an advice I got from Samer Buna in his book titled "GraphQL in Action":
Don't do authentication within GraphQL resolving logic. It's better to
have a different layer handling it either before or after the GraphQL
service layer.
Can you try something like this
server.use("/", (req, res) => {
//currentUser logic here
graphqlHTTP({
schema: schema,
context: { currentUser },
})(req, res);
});
I'm working at a React app and I need to loop inside an array containing objects with this structure:
const servers = [
{
name: "Server A",
url: "https://server-one.com/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC",
subVersions: [
{
name: "Subversion A1",
ip: "https://10.4.20/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC"
},
{
name: "Subversion A2",
ip: "https://10.4.20/v1/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC"
}
]
},
// ... more servers obj with the same structure
]
and I need to fetch information about the server versions via api calls to the url (or ip) and return an array of objects that looks like this:
[
{
name: "Server A",
version: "1.0.1",
subVersions: [
{
name: "Subversion A1",
version: "1.0.0"
},
{
name: "Subversion A2",
version: "1.0.0"
},
]
}
]
I'm doing is the following: the fetch() method will call fetchVersion() (which returns the main server version), and then it maps inside all the subVersions to fetch them too.
I'm struggling to get the result.data of the subVersions fetch out of that nested map you can see below.
I've tried to:
return the data at every iteration
pushing the data inside an array and try to return it at the end of the iterations
returning the array of data or returning a new Promise that resolves the array of data
But nothing. I can see the right data at the most nested map, but outside I either get a
Promise { <pending> } array or an empty one.
Sorry if the code looks messy, I hope it makes sense.
const fetchVersion = server =>
axios
.get(server.url, {
headers: {
Authorization: `Bearer ${server.accessToken}`,
"Content-Type": "application/json"
},
timeout: 30000
})
.then(result => new Promise(resolve => resolve(result.data)));
const fetchSubVersion = subVersion =>
axios
.get(subVersion.ip, {
headers: {
Authorization: `Bearer ${subVersion.accessToken}`,
"Content-Type": "application/json"
},
timeout: 30000
})
.then(result => new Promise(resolve => resolve(result.data)));
Class Servers {
constructor(servers = []) {
this.servers = servers ;
}
fetch() {
// ==== this map below is the problematic part =====
const subVersions = this.servers.map(server => {
var subVersArr = server.subVersions.map(
async server.subVersions.map(subVersion =>
await fetchSubVersions(subVersion)
.catch(() => ({ data: {} }))
.then(result => new Promise(resolve => resolve(result.data)));
});
})
);
return Promise.all(subVersArr)
.catch(() => ({ data: {} }))
.then(data => {
console.log("data", data); // <- I see the data here correctly
return data;
});
};
// ======= till here ============
const fetches = this.servers.map(server =>
fetchVersion(server)
.catch(() => ({ data: {} }))
.then(result => {
console.log("subVersions", subVersions(server)); <- but not here
return {
name: server.name,
versions: result.data,
subVersions: subVersions(server), // should contain the result of the
problematic map above
}
}))
);
return Promise.all(fetches);
}
Thanks for the help!!
How are you calling your fetch function? You should await it wherever you are calling it.
Like this:
async dummyFunction() {
await fetch();
}
I finally figured it out. What I didn't have clear is that then() returns a promise itself, so all I had to just do subVersion: await subVersions(server) and the data is there.
I'm trying to test NestJS's built in HttpService (which is based on Axios). I'm having trouble testing error/exception states though. In my test suite I have:
let client: SomeClearingFirmClient;
const mockConfigService = {
get: jest.fn((type) => {
switch(type) {
case 'someApiBaseUrl': {
return 'http://example.com'
}
case 'someAddAccountEndpoint': {
return '/ClientAccounts/Add';
}
case 'someApiKey': {
return 'some-api-key';
}
default:
return 'test';
}
}),
};
const successfulAdd: AxiosResponse = {
data: {
batchNo: '39cba402-bfa9-424c-b265-1c98204df7ea',
warning: '',
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
};
const failAddAuth: AxiosError = {
code: '401',
config: {},
name: '',
message: 'Not Authorized',
}
const mockHttpService = {
post: jest.fn(),
get: jest.fn(),
}
it('Handles a failure', async () => {
expect.assertions(1);
mockHttpService.post = jest.fn(() => of(failAddAuth));
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: ConfigService,
useValue: mockConfigService,
},
{
provide: HttpService,
useValue: mockHttpService,
},
SomeClearingFirmClient,
],
}).compile();
client = module.get<SomeClearingFirmClient>(SomeClearingFirmClient);
const payload = new SomeClearingPayload();
try {
await client.addAccount(payload);
} catch(e) {
console.log('e', e);
}
});
And my implementation is:
async addAccount(payload: any): Promise<SomeAddResponse> {
const addAccountEndpoint = this.configService.get('api.someAddAccountEndpoint');
const url = `${this.baseUrl}${addAccountEndpoint}?apiKey=${this.apiKey}`;
const config = {
headers: {
'Content-Type': 'application/json',
}
};
const response = this.httpService.post(url, payload, config)
.pipe(
map(res => {
return res.data;
}),
catchError(e => {
throw new HttpException(e.response.data, e.response.status);
}),
).toPromise().catch(e => {
throw new HttpException(e.message, e.code);
});
return response;
}
Regardless of whether I use Observables or Promises, I can't get anything to catch. 4xx level errors sail on through as a success. I feel like I remember Axios adding some sort of config option to reject/send an Observable error to subscribers on failures... but I could be imagining that. Am I doing something wrong in my test harness? The other StackOverflow posts I've seen seem to say that piping through catchError should do the trick, but my errors are going through the map operator.
Your mockHttpService seems to return no error, but a value:
mockHttpService.post = jest.fn(() => of(failAddAuth));
What of(failAddAuth) does is to emit a value(failAddAuth) and then complete.
That's why the catchError from this.httpService.post(url, payload, config) will never be reached, because no errors occur.
In order to make sure that catchError is hit, the observable returned by post() must emit an error notification.
You could try this:
// Something to comply with `HttpException`'s arguments
const err = { response: 'resp', status: '4xx' };
mockHttpService.post = jest.fn(() => throwError(err));
throwError(err) is the same as new Observable(s => s.error(err))(Source code).
In Apollo Server it looks like you have to an AsyncIterator for subscription resolvers:
const rootResolver = {
Query: () => { ... },
Mutation: () => { ... },
Subscription: {
commentAdded: {
subscribe: () => pubsub.asyncIterator('commentAdded')
}
},
};
Source
I'm wondering if there is any way to use an RXJS Observable instead or somehow convert it to an AsyncIterator?
I am OK with javascript but I am very new to GraphQL. I currently have this GraphQL structure and it is working. I found examples online for how to get different types organized into SRP files. I am however unable to find how to do this with the resolve: as it requires a function.
GraphQL:
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
ownerData: {
type: OwnerType,
description: 'Get all owners',
args:{key: {type: GraphQLString} },
resolve: (obj, args) => {
const url = 'http://localhost:5001/api/.../' + args.key
return fetch(url)
.then(response => {
return response.json()
})
.then(json => {
return transform(json)
})
.catch(err => {
console.trace(err)
})
}
},
carData: {
type: carType,
description: 'Get owned vehicles',
args:{key: {type: GraphQLString} },
resolve: (obj, args) => {
const url = 'http://localhost:6001/api/.../' + args.key
return fetch(url)
.then(response => {
return response.json()
})
.then(json => {
return transform(json)
})
.catch(err => {
console.trace(err)
})
}
},
}
})
I can move the service calls into separate files but not sure how to structure the resolve as it needs a function.
Would it be something like this:
const VehicleService = require('./ExternalServices/Vehicles');
.....snip...
resolve: (obj, args) => { VehicleService.GetVehicles() }
Generally speaking, I've found the best way to keep my code organized is to put all of the business logic elsewhere, initialized into the context object. If you're using graphql-js directly (it'll be set up differently if you're using something like apollo-server, but the context is still the right place for this):
graphql.js excerpt
(dataloaders is the SRP logic here)
const { graphql } = require('graphql');
const dataloaders = require('./dataloaders');
const typeDefs = require('./type-definitions')
const schema = require('./schema')
exports.query = (whatever, args) => {
const context = {};
context.requestId = 'uuid-something';
context.loaders = dataloaders.initialize({ context });
return graphql(schema, query, null, context)
}
schema.js excerpt
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
ownerData: {
type: OwnerType,
description: 'Get all owners',
args:{key: {type: GraphQLString} },
resolve: (obj, args, context) => {
const key = args.key;
return context.loaders.owner({ key });
}
}
}
});
dataloaders.js excerpt
exports.initialize = ({ context }) => {
return {
owner({ key }) {
const url = 'http://localhost:6001/api/.../' + key
return fetch(url, { headers: { requestId: context.requestId }})
.then(response => {
return response.json()
})
.then(json => {
return transform(json)
})
.catch(err => {
console.trace(err)
});
}
}
};
In addition to better code organization, doing it this way allows for easier testing, since your resolvers don't need any external dependencies. You can inject your dependencies this way by preloading the context with whatever you want for testing, and you can handle business logic where business logic belongs.
Initializing your business logic with the context of the request also allows you to adjust functionality based off the request: requestId (as I've shown), access control, etc.
The syntax () => {} is simply a function definition. The resolve field expects a function definition so it can run it when the field must be resolved.
You can move the resolving function to a different file like so:
car-data-resolve.js:
const resolveCarData = (obj, args) => {
const url = 'http://localhost:6001/api/.../' + args.key
return fetch(url)
.then(response => {
return response.json()
})
.then(json => {
return transform(json)
})
.catch(err => {
console.trace(err)
})
}
export default resolveCarData;
And then use it in your schema definition:
import resolveCarData from './car-data-resolve';
const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
/* Other fields */
carData: {
type: carType,
description: 'Get owned vehicles',
args: { key: { type: GraphQLString } },
resolve: resolveCarData
},
},
});