GOAL: have my api gateway get the httponly cookies being returned from my rest endpoints and pass it along to frontend, also the front end should be able to pass the cookies through.
httpO=httponly
SPA(react) apiGateway(apolloQL) restEndpoint
httpO-cookies----> <-----(httpO)cookies-----> <-----(httpO)cookies
current the resolvers I have are able to see the "set-cookies" in the response from the endpoints but throughout the response lifecycle the header's are lost.
const apolloServer: ApolloServer = new ApolloServer({
context: ({ res }) => {
// console.log(res,"res");
return ({
res
});
},
formatError,
resolvers,
typeDefs,
formatResponse: (response: GraphQLResponse) => {
console.log(response.http?.headers, "header?");
return {
headers: {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Credentials': 'true',
},
data: response.data,
errors: response.errors,
};
}
});
ex. of resolver:
const signupUser = async (parent: any, args: any, context: any, info: any) => {
try {
const response = await UserService.createUser(args);
console.log(response , "response");
return response;
} catch (error) {
console.log(error
}
};
in this example lets assume the UserService.createUser return the entire response Object, not response.data.
const signupUser = async (parent: any, args: any, context: any, info: any) => {
try {
//call get the Express response from the context
const contextResponse = context.res
//or
const {res} = context
//then set the cookies to the response
const response = await UserService.createUser(args);
contextResponse.header('set-cookie', response?.headers['set-cookie']);
//return the original data
return response?.data;
} catch (error) {
console.log(error
}
};
Related
Cookies are not sent to the server via getServerSideProps, here is the code in the front-end:
export async function getServerSideProps() {
const res = await axios.get("http://localhost:5000/api/auth", {withCredentials: true});
const data = await res.data;
return { props: { data } }
}
On the server I have a strategy that checks the access JWT token.
export class JwtStrategy extends PassportStrategy(Strategy, "jwt") {
constructor() {
super({
ignoreExpiration: false,
secretOrKey: "secret",
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
console.log(request.cookies) // [Object: null prototype] {}
let data = request.cookies['access'];
return data;
}
]),
});
}
async validate(payload: any){
return payload;
}
}
That is, when I send a request via getServerSideProps cookies do not come to the server, although if I send, for example via useEffect, then cookies come normally.
That's because the request inside getServerSideProps doesn't run in the browser - where cookies are automatically sent on every request - but actually gets executed on the server, in a Node.js environment.
This means you need to explicitly pass the cookies to the axios request to send them through.
export async function getServerSideProps({ req }) {
const res = await axios.get("http://localhost:5000/api/auth", {
withCredentials: true,
headers: {
Cookie: req.headers.cookie
}
});
const data = await res.data;
return { props: { data } }
}
The same principle applies to requests made from API routes to external APIs, cookies need to be explicitly passed as well.
export default function handler(req, res) {
const res = await axios.get("http://localhost:5000/api/auth", {
withCredentials: true,
headers: {
Cookie: req.headers.cookie
}
});
const data = await res.data;
res.status(200).json(data)
}
My code is based on the repository - https://github.com/ttntm/recept0r-ts
Code from "\functions\read-all.js":
const faunadb = require('faunadb');
const fnHeaders = require('./_shared/headers.js');
exports.handler = (event, context) => {
const client = new faunadb.Client({
secret: process.env.FAUNA_SECRET,
domain: 'db.fauna.com',
scheme: 'https',
port: '443'
});
const q = faunadb.query;
const headers = { ...fnHeaders };
const origin = event.headers.Origin || event.headers.origin;
headers['Access-Control-Allow-Origin'] = origin ? origin : '*';
return client.query(q.Paginate(q.Match(q.Index('all_users'), false), { size: 500 }))
.then((response) => {
const listRefs = response.data;
const getListDataQuery = listRefs.map(ref => q.Get(ref)); // create new query out of list refs, then query the refs
return client.query(getListDataQuery).then((records) => {
return { statusCode: 200, headers: headers, body: JSON.stringify(records) }
})
})
.catch((error) => {
return { statusCode: 400, headers: headers, body: JSON.stringify(error) }
});
}
Code from "\src\store\modules\data.js":
async readAll({ commit, dispatch, rootGetters })
{
const fn = rootGetters['app/functions'];
const request = await fetch(fn.readAll, { method: 'GET' });
const response = await request.json();
if (response.length > 0) {
commit('SET_ALL_RECIPES', response);
commit('SET_LAST_UPDATED', new Date); }
else {
dispatch('app/sendToastMessage', { text: 'Error loading recipes. Please try again later.', type: 'error' }, { root: true });
return 'error';
}
}
Everything seems to be set. For example, this code works:
client.query(q.CreateCollection({ name: 'someCollection' }))
But can't read any data.
If launch application by "netlify dev" (localhost) - "read-all" returns empty array ("[]").
If launch application by "network" - "read-all" returns default "index.html".
I have no idea what's wrong. Maybe someone give advice...
I found a similar question - Local Netlify function server gives strange response instead of FaunaDB data
Some answer:
"In my experience, one of the most common reasons for this error is a routing problem, which is triggering a 404 response route serving HTML instead of your expected function handler."
This code works:
return client.query(q.Paginate(q.Documents(q.Collection('customers')), { size: 500 }))
.then((response) => {
const listRefs = response.data;
const getListDataQuery = listRefs.map(ref => q.Get(ref)); // create new query out of list refs, then query the refs
return client.query(getListDataQuery).then((records) => {
return { statusCode: 200, headers: headers, body: JSON.stringify(records) }
});
})
.catch((error) => {
return { statusCode: 400, headers: headers, body: JSON.stringify(error) }
});
I'm sending from frontend authorization token in headers and then I want to check validity of this token in some endpoints using middleware and context, but context is always empty.
I'm using type-graphql.
Frontend code (I check request in 'Network' tab and I can see my additional header):
private async mutate<T>(
mutation: DocumentNode,
data: unknown,
token?: string
) {
const response = await apolloClient.mutate<T>({
mutation: mutation,
context: {
headers: {
'auth-token': token || '',
},
},
variables: {
data: data,
},
});
return response.data;
}
Resolver code:
#Mutation(() => Token)
#UseMiddleware(authMiddleware)
async login(#Ctx() ctx: unknown, #Arg('data') data: LoginInput) {
console.log(ctx);
...
}
Middleware code:
export const authMiddleware: MiddlewareFn = ({ context }, next) => {
console.log(context);
try {
return next();
} catch (error) {
return next();
}
};
console.log is always equal to {}
I found the cause.
In declaration of ApollorServer the context was missing.
const server = new ApolloServer({
schema,
context: ({ req }) => {
const context = {
req,
};
return context;
},
cors: {
origin: '*',
credentials: true,
},
});
I can't figure it out, the answer comes in the network table but when I want to console.log it, this will display undefined. Do you have any idea why? I attach the pictures and the code.
Here is a image with my codes and response
Here is the code - first one is where I send the response. As I said, it's going well on network tab, I get a 200 status.
export const getAccountStatus = async (req, res) => {
const user = await User.findById(req.user._id).exec();
const account = await stripe.accounts.retrieve(user.stripe_account_id);
// console.log("user account retrieve", account);
const updatedUser = await User.findByIdAndUpdate(
user._id,
{
stripe_seller: account
},
{ new: true }
)
.select("-password")
.exec();
console.log(updatedUser);
res.send(updatedUser);
};
Here is the page where i want to console.log it:
const StripeCallback = ({ history }) => {
const { auth } = useSelector(state => ({ ...state }));
const dispatch = useDispatch();
useEffect(() => {
if (auth && auth.token) accountStatus();
}, [auth]);
const accountStatus = async () => {
try {
const res = await getAccountStatus(auth.token);
console.log(res);
} catch (err) {
console.log(err);
}
};
return <div>test</div>;
};
Ang here is the Axios.post (which is working well as I know):
export const getAccountStatus = async token => {
await axios.post(
`${process.env.REACT_APP_API}/get-account-status`,
{},
{
headers: {
Authorization: `Bearer ${token}`
}
}
);
};
Thank you!
getAccountStatus doesn't have a return statement, so res in const res = await getAccountStatus(auth.token); will always be undefined.
export const getAccountStatus = async token => {
return axios.post( // <----- added return
`${process.env.REACT_APP_API}/get-account-status`,
{},
{
headers: {
Authorization: `Bearer ${token}`
}
}
);
};
Reference Answer: https://stackoverflow.com/a/61407264/8518207
The author of the above answer mentions you can clone the request object before modifying it in your guard.
How can this be done?
My Goal: Guard will basically verify if the auth token is valid before proceeding to doing anything in the controller such as call FileInterceptor or print those console logs. if token is guard is verified, the FileInterceptor should be used to extract the file from the request.
Example Code:
#Put(':uid')
#UseGuards(AuthGuard)
#UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: './.tmp',
filename: editFileName,
}),
fileFilter: fileFilter,
}),
)
public async update(
#Req() req,
#UploadedFile() file,
) {
this.logger.log('update')
console.log('in controller')
console.log('req', req)
console.log('file', file)
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest()
const response = context.switchToHttp().getResponse()
const postMulterRequest: any = await new Promise((resolve, reject) => {
const multerReponse = multer().any()
multerReponse(request, response, err => {
const user_uid = request.body.user_uid
if (err) reject(err)
resolve(request)
})
})
if (request) {
// some validations happen here....
request.body = postMulterRequest.body
request.user = await this.validateToken(request.headers.cookie, user_uid)
return true
} else {
return true
}
}