I am trying to add some charts to my webapp, using the ChartDataProvider from superset-ui/core but get the follow response message:
"Request is incorrect: {'query_context': ['Unknown field.']}"
I think that 'query_context' json parent node must not be present.
Following is my code:
import { ChartDataProvider,
QueryFormData, SupersetClient, SuperChart } from '#superset-ui/core';
import TableChartPlugin from '#superset-ui/plugin-chart-table';
new TableChartPlugin().configure({ key: 'table' }).register();
const client = SupersetClient.configure({
credentials: 'include',
host: 'localhost:8088',
protocol: 'http:',
mode: 'cors',
});
client.init();
export const SampleChart = () => {
const formData: Partial = {
datasource: '17__table',
viz_type: 'table'
};
return (
{({ loading, error, payload }) => {
if (loading) return
Loading...
;
if (error) return
ERROR: {error.message}
;
if (payload) {
console.log('payload', payload);
return (
);
}
return null;
}}
);
}
Where am I wrong? Can it be a bug?
Related
Using SvelteKit 1.0.0-next.571
My application has a login route with:
+page.server.ts => redirects to / if locals.user is set
+page.svelte => show login page
signin/+server.ts => Login and get a jwt from graphql app running on the same machine.
+server.ts:
[..]
let gqlResponse = await response.json();
if ( gqlResponse.errors ) {
console.log("ERRORS FROM GRAPHQL MIDDLEWARE:", gqlResponse.errors);
return json( { error: gqlResponse.errors, isException: true } );
}
if (gqlResponse.data.login.user && !gqlResponse.data.login.error) {
opts.cookies.set('jwt', gqlResponse.data.login.token, {
path: '/',
maxAge: SESSION_MAX_AGE
});
opts.setHeaders( { 'Access-Control-Allow-Credentials': 'true' } )
opts.setHeaders({ 'Content-Type': 'application/json' })
}
return json( gqlResponse.data.login );
and the login handler in +page.svelte :
[..]
const fetchOptions = {
method: 'POST',
//mode: 'no-cors',
//redirect: 'follow' as RequestRedirect,
body: JSON.stringify(credentials),
credentials: 'include' as RequestCredentials
}
try {
const response = await fetch('/login/signin', fetchOptions);
const login = await response.json();
if (login.error) {
handleError(login);
return false;
}
} catch (e) {
return handleException(e);
}
goto('/', { replaceState: true, invalidateAll: true} );
This works fine in localhost, but connecting another device to the local network does not set any cookies making impossible to login:
Local: http://localhost:5173/
➜ Network: http://192.168.x.x:5173/
I also tried with different fetch options and cookie settings like:
opts.cookies.set('jwt', gqlResponse.data.login.token, {
path: '/',
httpOnly: true,
sameSite: 'strict',
// secure: true
maxAge: SESSION_MAX_AGE
});
but no luck, and now 'm stuck.
Basically this other post Express-session does not set cookie? where I'm following Ben Awad's Fullstack Tutorial. The cookie gets created but the server crashes and this is the error
node:internal/errors:464
ErrorCaptureStackTrace(err);
^
TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Array
at new NodeError (node:internal/errors:371:5)
at _write (node:internal/streams/writable:312:13)
at Socket.Writable.write (node:internal/streams/writable:334:10)
at RedisSocket.writeCommand (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/socket.js:57:130)
at Commander._RedisClient_tick (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:415:64)
at Commander._RedisClient_sendCommand (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:396:82)
at Commander.commandsExecutor (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/client/index.js:160:154)
at Commander.BaseClass.<computed> [as set] (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/#node-redis/client/dist/lib/commander.js:8:29)
at RedisStore.set (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/connect-redis/lib/connect-redis.js:65:21)
at Session.save (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/session/session.js:72:25)
at Session.save (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/index.js:406:15)
at ServerResponse.end (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express-session/index.js:335:21)
at ServerResponse.send (/home/kuratar/github/milestone-4-Kuratar/server/node_modules/express/lib/response.js:221:10)
at /home/kuratar/github/milestone-4-Kuratar/server/node_modules/apollo-server-express/dist/ApolloServer.js:89:25 {
code: 'ERR_INVALID_ARG_TYPE'
}
I noticed that this specific line of code in user.ts:
req.session.userId = user.id
when it's commented out, the error doesn't occur but the cookie is not set. There isn't a set-cookie option in the response-header.
My files are pretty much the same as this other person in the post I linked.
index.ts
import "reflect-metadata";
import { MikroORM } from "#mikro-orm/core";
import { __prod__ } from "./constants";
import microConfig from "./mikro-orm.config";
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core";
import { buildSchema } from "type-graphql";
import { HelloResolver } from "./resolvers/hello";
import { PostResolver } from "./resolvers/post";
import { UserResolver } from "./resolvers/user";
import * as redis from "redis";
import session from "express-session";
import connectRedis from "connect-redis";
import { MyContext } from "./types";
// start postgresql server on wsl - sudo service postgresql start
// stop - sudo service postgresql stop
// start redis server on wsl - redis-server
// sudo /etc/init.d/redis-server restart
// stop, start
// watch ts changes - npm run watch
// run server - npm run dev
const main = async () => {
const orm = await MikroORM.init(microConfig); // initialize database
await orm.getMigrator().up(); // run migrations before anything else
const app = express();
app.set("trust proxy", 1); // trust first proxy
// this comes before applyMiddleware since use session middleware inside apollo
const RedisStore = connectRedis(session);
const redisClient = redis.createClient(); // TODO: TypeError: Cannot read properties of undefined (reading 'createClient')
redisClient.on("error", (err) => console.log("Redis Client Error", err));
await redisClient.connect();
app.use(
session({
name: "qid",
// touch - make request to redis to reset the user's session
// if user does something, it means they are active and should reset the timer of automatically logging them out
// after 24 hours for example
// disableTouch: true - keep session forever, can change this later to timed sessions
store: new RedisStore({ client: redisClient, disableTouch: true }), // tell express session using redis
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: "lax", // csrf
secure: __prod__, // only works in https
},
saveUninitialized: false,
secret: "askljdhfjkalshdjlf", // want to keep this secret separately
resave: true,
rolling: true,
})
);
// app.use(function (req, res, next) {
// res.header(
// "Access-Control-Allow-Origin",
// "https://studio.apollographql.com"
// );
// res.header("Access-Control-Allow-Credentials", "true");
// next();
// });
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [HelloResolver, PostResolver, UserResolver],
validate: false,
}),
// object that is accessible by resolvers, basically pass the database itself
context: ({ req, res }): MyContext => ({ em: orm.em, req, res }),
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground({
settings: { "request.credentials": "include" },
}),
],
});
await apolloServer.start();
const corsOptions = {
origin: new RegExp("/*/"),
credentials: true,
};
apolloServer.applyMiddleware({ app, cors: corsOptions }); // create graphql endpoint on express
app.listen(4000, () => {
console.log("Server started on localhost:4000");
});
};
main().catch((error) => {
console.log("----------MAIN CATCHED ERROR----------");
console.error(error);
console.log("-----------------END------------------");
});
user.ts
import {
Resolver,
Arg,
Mutation,
InputType,
Field,
Ctx,
ObjectType,
} from "type-graphql";
import { User } from "../entities/User";
import { MyContext } from "../types";
import argon2 from "argon2";
// another way to implementing arguments for methods instead of #Arg()
#InputType()
class UsernamePasswordInput {
#Field()
username: string;
#Field()
password: string;
}
#ObjectType()
class FieldError {
#Field()
field: string;
#Field()
message: string;
}
#ObjectType()
class UserResponse {
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
#Field(() => User, { nullable: true })
user?: User;
}
#Resolver()
export class UserResolver {
#Mutation(() => UserResponse)
async register(
#Arg("options") options: UsernamePasswordInput,
#Ctx() { em }: MyContext
): Promise<UserResponse> {
if (options.username.length <= 2) {
return {
errors: [
{ field: "username", message: "length must be greater than 2" },
],
};
}
if (options.password.length <= 2) {
return {
errors: [
{ field: "password", message: "length must be greater than 2" },
],
};
}
// argon2 is a password hasher package
const hashedPassword = await argon2.hash(options.password);
const user = em.create(User, {
username: options.username,
password: hashedPassword,
});
try {
await em.persistAndFlush(user);
} catch (error) {
// duplicate username error
if (error.code === "23505") {
// || error.detail.includes("already exists")
return {
errors: [{ field: "username", message: "Username already taken" }],
};
}
}
// return user in an object since response is now a response object - UserResponse
return { user };
}
#Mutation(() => UserResponse)
async login(
#Arg("options") options: UsernamePasswordInput,
#Ctx() { em, req }: MyContext
): Promise<UserResponse> {
// argon2 is a password hasher package
const user = await em.findOne(User, {
username: options.username,
});
// can give same field error message like invalid login
if (!user) {
return {
errors: [{ field: "username", message: "That username doesn't exist" }],
};
}
const valid = await argon2.verify(user.password, options.password);
if (!valid) {
return {
errors: [{ field: "password", message: "Incorrect password" }],
};
}
// mutation {
// login(options: {username: "eric", password: "eric"}) {
// errors {
// field
// message
// }
// user {
// id
// username
// }
// }
// }
console.log(req.session)
console.log(user.id)
req.session.userId = user.id
console.log(req.session)
console.log(req.session.id)
// console.log(req.session.userId)
// return user in an object since response is now a response object - UserResponse
return { user };
}
}
types.ts
import { EntityManager, IDatabaseDriver, Connection } from "#mikro-orm/core";
import { Request, Response } from "express";
import { Session, SessionData } from "express-session";
// this is the type of orm.em from index.ts
// extracted to make code look cleaner in post.ts
export type MyContext = {
em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>;
req: Request & {
session: Session & Partial<SessionData> & { userId?: number };
};
res: Response;
};
I've had the same error. In my situation I was able to fix it by changing the redis client to ioredis(I was using redis).
To be more specific on Bernardo, Ben also changes it to ioredis in the github repo. So you need to install ioredis and add these lines
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
and delete/comment out the old redisClient lines of code.
So I've recently learned about JWT authentication using Django Rest Framework and now, I would like to use it. Setting things up with DRF was easy, but now I'm facing a problem : I have no idea how to consume the given tokens ( access and refresh ) with redux. I also have no idea how to retrieve a user based on the given tokens.
Here is what I have for the moment.
My actions :
import axios from 'axios';
import {
LOGIN_STARTED,
LOGIN_SUCCESS,
LOGIN_FAILURE,
} from './types.js';
const loginStarted = () => ({
type: LOGIN_STARTED,
})
const loginFailure = error => ({
type: LOGIN_FAILURE,
payload: {
error: error
}
})
const loginSuccess = (access_token, refresh_token) => ({
type: LOGIN_SUCCESS,
payload: {
access_token: access_token,
refresh_token : refresh_token
}
})
export const authLogin = (username, password) => dispatch => {
dispatch(loginStarted);
axios.post("http://127.0.0.1:8000/api/token/", {
username: username,
password: password
})
.then( res => {
console.log(res.data);
dispatch(loginSuccess(res.data))
})
.catch( err => {
console.log(err);
dispatch(loginFailure(err.data));
})
}
And my reducer looks like this :
import {
LOGIN_STARTED,
LOGIN_SUCCESS,
LOGIN_FAILURE,
} from '../actions/types.js';
const initialstate = {
access: undefined,
refresh: undefined,
error: {}
}
export default function(state=initialstate, action){
switch (action.type) {
case LOGIN_SUCCESS:
return {
...state,
access: action.type.access_token,
refresh: action.type.refresh_token,
}
case LOGIN_FAILURE:
return {
...state,
error: action.payload.error
}
default:
return state;
}
}
Thank you !
apollo-client: 2.6.3
apollo-link-http: 1.5.15
apollo-link-ws: 1.0.18
subscriptions-transport-ws: 0.9.16
So I have a front-end (node) server which connects to a back-end (graphql-yoga) server as follows:
const httpLink = createHttpLink({
uri: 'https://localhost:4444',
credentials: 'include',
});
const wsLink = process.browser ? new WebSocketLink({
uri: 'wss://eu1.prisma.sh/MyID/MyProject/dev',
options: {
reconnect: true,
timeout: 3000,
}
}) : null;
const authLink = setContext(() => {
return {
headers: {
...headers,
}
}
});
const link = process.browser ? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
) : httpLink;
const client = new ApolloClient({
link: authLink.concat(link),
...
});
Query and mutation resolvers are being triggered correctly, but the subscription resolver is not being triggered. I'm assuming that's because I'm not connecting directly to graphql-yoga.
But if I change the WebSocketLink uri to ws://localhost:4000 this causes an:
failed: Error during WebSocket handshake: Unexpected response code: 200
error message to be issued.
The graphql-yoga server itself makes a subscription connection to prisma as follows:
const options = {
subscriptions: 'https://eu1.prisma.sh/MyID/MyProject/dev',
};
// start it!!
server.start(options, ({ port }) =>
console.log(`Server is now running on port http://localhost:${port}`),
);
And my subscription resolver is as follows:
const Subscription = {
order: {
async subscribe(parent, args, ctx, info) {
return ctx.db.subscription.order(
{
where: {
mutation_in: ['CREATED', 'UPDATED'],
},
},
info
).catch(handleSubmitError);
},
}
};
If I run the following subscription in playground:
subscription {
order {
mutation
node {
id
total
createdAt
items {
id
title
price
description
mainDescription
quantity
image
}
}
}
}
and trigger an order mutation via my site, the correct
data resultset is shown:
But the subscription resolver is not being actioned from the subscription call:
const USER_ORDERS_SUBSCRIPTION = gql`
subscription {
order {
mutation
node {
id
total
createdAt
items {
id
title
price
description
mainDescription
quantity
image
}
}
}
}
`;
<Query query={USER_ORDERS_QUERY}>
{({ subscribeToMore, data: { orders }, loading, error }) => {
if (loading) return <p>loading...</p>;
if (error) return <Error erorr={error} />;
return (
<div>
<h2>You have {orders.length === 0 ? 'no' : orders.length} order{orders.length > 1 || orders.length === 0 ? 's' : ''}.</h2>
<OrderListItem
orders={orders}
urlReferer={urlReferer}
subscribeToNewOrders={() =>
subscribeToMore({
document: USER_ORDERS_SUBSCRIPTION,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newOrdertem = subscriptionData.data.order.node;
return Object.assign({}, prev, {
orders: [newOrdertem, ...prev.orders]
});
}
})
}
/>
</div>
);
}}
</Query>
How do I resolve this?
So it turns out the solution was to:
const options = {
subscriptions: '/subscriptions',
};
and then call the server as:
const wsLink = process.browser ? new WebSocketLink({
uri: 'ws://localhost:4444/subscriptions',
})
Apollo link offers an error handler onError
Issue:
Currently, we wish to refresh oauth tokens when they expires during an apollo call and we are unable to execute an async fetch request inside the onError properly.
Code:
initApolloClient.js
import { ApolloClient } from 'apollo-client';
import { onError } from 'apollo-link-error';
import { ApolloLink, fromPromise } from 'apollo-link';
//Define Http link
const httpLink = new createHttpLink({
uri: '/my-graphql-endpoint',
credentials: 'include'
});
//Add on error handler for apollo link
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
//User access token has expired
if(graphQLErrors[0].message==="Unauthorized") {
//We assume we have both tokens needed to run the async request
if(refreshToken && clientToken) {
//let's refresh token through async request
return fromPromise(
authAPI.requestRefreshToken(refreshToken,clientToken)
.then((refreshResponse) => {
let headers = {
//readd old headers
...operation.getContext().headers,
//switch out old access token for new one
authorization: `Bearer ${refreshResponse.access_token}`,
};
operation.setContext({
headers
});
//Retry last failed request
return forward(operation);
})
.catch(function (error) {
//No refresh or client token available, we force user to login
return error;
})
)
}
}
}
}
}
}),
What happens is:
Initial graphQL query runs and fails due to unauthorization
The onError function of ApolloLink is executed.
The promise to refresh the token is executed.
The onError function of ApolloLink is executed again??
The promise to refresh the token is completed.
The initial graphQL query result is returned and its data is undefined
Between step 5 and 6, apollo doesn't re-run the initial failed graphQL query and hence the result is undefined.
Errors from console:
Uncaught (in promise) Error: Network error: Error writing result to store for query:
query UserProfile($id: ID!) {
UserProfile(id: $id) {
id
email
first_name
last_name
}
__typename
}
}
The solution should allow us to:
Run an async request when an operation fails
Wait for the result of the request
Retry failed operation with data from the request's result
Operation should succeed to return its intended result
I'm refreshing the token this way (updated OP's):
import { ApolloClient } from 'apollo-client';
import { onError } from 'apollo-link-error';
import { ApolloLink, Observable } from 'apollo-link'; // add Observable
// Define Http link
const httpLink = new createHttpLink({
uri: '/my-graphql-endpoint',
credentials: 'include'
});
// Add on error handler for apollo link
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError, operation, forward }) => {
// User access token has expired
if (graphQLErrors && graphQLErrors[0].message === 'Unauthorized') {
// We assume we have both tokens needed to run the async request
if (refreshToken && clientToken) {
// Let's refresh token through async request
return new Observable(observer => {
authAPI.requestRefreshToken(refreshToken, clientToken)
.then(refreshResponse => {
operation.setContext(({ headers = {} }) => ({
headers: {
// Re-add old headers
...headers,
// Switch out old access token for new one
authorization: `Bearer ${refreshResponse.access_token}` || null,
}
}));
})
.then(() => {
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
};
// Retry last failed request
forward(operation).subscribe(subscriber);
})
.catch(error => {
// No refresh or client token available, we force user to login
observer.error(error);
});
});
}
}
})
])
});
Accepted answer is quite good but it wouldn't work with 2 or more concurrent requests. I've crafted the one below after testing different cases with my token renew workflow that fits my needs.
It's necessary to set errorLink before authLink in link pipeline.
client.ts
import { ApolloClient, from, HttpLink } from '#apollo/client'
import errorLink from './errorLink'
import authLink from './authLink'
import cache from './cache'
const httpLink = new HttpLink({
uri: process.env.REACT_APP_API_URL,
})
const apiClient = new ApolloClient({
link: from([errorLink, authLink, httpLink]),
cache,
credentials: 'include',
})
export default apiClient
Cache shared between 2 apollo client instances for setting user query when my renewal token is expired
cache.ts
import { InMemoryCache } from '#apollo/client'
const cache = new InMemoryCache()
export default cache
authLink.ts
import { ApolloLink } from '#apollo/client'
type Headers = {
authorization?: string
}
const authLink = new ApolloLink((operation, forward) => {
const accessToken = localStorage.getItem('accessToken')
operation.setContext(({ headers }: { headers: Headers }) => ({
headers: {
...headers,
authorization: accessToken,
},
}))
return forward(operation)
})
export default authLink
errorLink.ts
import { ApolloClient, createHttpLink, fromPromise } from '#apollo/client'
import { onError } from '#apollo/client/link/error'
import { GET_CURRENT_USER } from 'queries'
import { RENEW_TOKEN } from 'mutations'
import cache from './cache'
let isRefreshing = false
let pendingRequests: Function[] = []
const setIsRefreshing = (value: boolean) => {
isRefreshing = value
}
const addPendingRequest = (pendingRequest: Function) => {
pendingRequests.push(pendingRequest)
}
const renewTokenApiClient = new ApolloClient({
link: createHttpLink({ uri: process.env.REACT_APP_API_URL }),
cache,
credentials: 'include',
})
const resolvePendingRequests = () => {
pendingRequests.map((callback) => callback())
pendingRequests = []
}
const getNewToken = async () => {
const oldRenewalToken = localStorage.getItem('renewalToken')
const {
data: {
renewToken: {
session: { renewalToken, accessToken },
},
},
} = await renewTokenApiClient.mutate({
mutation: RENEW_TOKEN,
variables: { input: { renewalToken: oldRenewalToken } },
})!
localStorage.setItem('renewalToken', renewalToken)
localStorage.setItem('accessToken', accessToken)
}
const errorLink = onError(({ graphQLErrors, operation, forward }) => {
if (graphQLErrors) {
for (const err of graphQLErrors) {
switch (err?.message) {
case 'expired':
if (!isRefreshing) {
setIsRefreshing(true)
return fromPromise(
getNewToken().catch(() => {
resolvePendingRequests()
setIsRefreshing(false)
localStorage.clear()
// Cache shared with main client instance
renewTokenApiClient!.writeQuery({
query: GET_CURRENT_USER,
data: { currentUser: null },
})
return forward(operation)
}),
).flatMap(() => {
resolvePendingRequests()
setIsRefreshing(false)
return forward(operation)
})
} else {
return fromPromise(
new Promise((resolve) => {
addPendingRequest(() => resolve())
}),
).flatMap(() => {
return forward(operation)
})
}
}
}
}
})
export default errorLink
We just had the same issues and after a very complicated solution with lots of Observeables we got a simple solution using promises which will be wrapped as an Observable in the end.
let tokenRefreshPromise: Promise = Promise.resolve()
let isRefreshing: boolean
function createErrorLink (store): ApolloLink {
return onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
// this is a helper method where we are checking the error message
if (isExpiredLogin(graphQLErrors) && !isRefreshing) {
isRefreshing = true
tokenRefreshPromise = store.dispatch('authentication/refreshToken')
tokenRefreshPromise.then(() => isRefreshing = false)
}
return fromPromise(tokenRefreshPromise).flatMap(() => forward(operation))
}
if (networkError) {
handleNetworkError(displayErrorMessage)
}
})
}
All pending requests are waiting for the tokenRefreshPromise and will then be forwarded.