Notion API OAuth Not working `invalid client` - node.js

I've tried setting up Notion OAuth and everything works fine on postman but on my application it's not.
I've tried many ways and this is the last implementation I've did so far.
router
.get("/notion/authorize", async (req : Request, res: Response) => {
let redirectURL = `${process.env.NOTION_AUTH_URL}?owner=user&client_id=${process.env.NOTION_CLIENT_ID}&response_type=code&redirect_uri=${encodeURI(process.env.NOTION_REDIRECT_URI)}`
res.redirect(redirectURL)
})
.get("/notion/callback", async (req: Request<any,any, any, {code : string } >, res: Response) => {
try {
const {code} = req.query;
console.log(code)
if(!code) {
throw new Error("Code is required from notion")
}
const response = await axios.post('https://api.notion.com/v1/oauth/token', {
data:JSON.stringify({
"grant_type":"authorization_code",
"code":code,
"redirect_uri": "http://localhost:9000/notion/callback"
}),
headers: {
"Authorization": `Basic ${Buffer.from(`${process.env.NOTION_CLIENT_ID}:${process.env.NOTION_CLIENT_SECRET}`).toString('base64')}`,
"Content-Type": "application/json",
}
})
const {access_token, bot_id, workspace_id, workspace_name} =response.data;
//.. more code
}catch(err) {
if(axios.isAxiosError(err)) {
console.log(err.response.data, err.response.status)
}
return res.status(400).json({message: `Failed to register user ${err}`})
}
})
Stil I get this as error { error: 'invalid_client' } 401
Also yes, checked the keys and redirect URI as well.
Please help me with this :)

It seems like your header is wrong. Check if the field authorization gives the same value as the one you use in postman.
Other thing is your post request. I think you're messing up something. Axios can take one single object when using the request method or 3 parameters (url, data, config) when using the post method (Axios Documentation). In your code, the data and the headers are together and should be apart in different objects.

Related

Node Axios Create Global Token Variable for Use in Separate Variable Header

Using Node & Axios
What I Want to Do
In my server.js file, I want to call an api for a token that always changes, use axios (or other solution) to create a global token variable, and provide the global token to an app.get request header within an object, again, all within my server.js file.
What I'm Trying
I get the data using...
var data = '<tsRequest>\r\n\t<credentials name="name" password="pword">\r\n\t\t<site contentUrl="" />\r\n\t</credentials>\r\n</tsRequest>';
var config = {
method: 'post',
url: 'https://url.uni.edu/api/3.13/auth/signin',
headers: {
'Content-Type': 'text/plain'
},
data : data
};
I try to create the global token variable (this is where I'm struggling)...
const token= axios(config)
.then((response) => {
console.log(response.data.credentials.token);
}).catch((err) => {
console.log(err);
});
console.log(token)
I have a functioning app.get request where instead of manually providing the token, I want to use the const token...
app.get('/gql', async (req, res) => {
var someObject = {
'method': 'POST',
'url': 'https://diffurl.uni.edu/api/metadata/graphql',
'headers': {
'X-Some-Auth': token,
'Content-Type': 'application/json'
},
The Current Results
What I have for the var data = and the var config = and the axios(config) all work to return the token via console.log, but I have 2 issues using axios.
The Axios Issues
In my hopes of creating a global token variable, I only understand how to get a console.log result instead of returning a 'useful data object'.
In just about every piece of documentation or help found, the example uses console.log, which is not a practical example for learners to move beyond just returning the data in their console.
What do I need to provide in axios to create a global token object in addition to or instead of console.log?
I realize 1. is my current blocker, but if I run my app, I get the following:
Promise { <pending> }
Express server running on port 1234
abc123 [the console.logged token via axios]
I'm not sure what the Promise { <pending> } means, how do I fix that?
Beyond The Axios Issues
If the axios problem is fixed, am I passing the const token correctly into my app.get... var someObject... headers?
Thank you for any help you can provide.
This is what Axios interceptors are for.
You can create an Axios instance with an interceptor that waits for the token request to complete before adding the token to the outgoing request.
A response interceptor can be added to handle 401 statuses and trigger a token renewal.
const data = "<tsRequest>...</tsRequest>";
let renew = true;
let getTokenPromise;
const renewToken = () => {
if (renew) {
renew = false; // prevent multiple renewal requests
getTokenPromise = axios
.post("https://url.uni.edu/api/3.13/auth/signin", data, {
headers: {
"content-type": "text/plain", // are you sure it's not text/xml?
},
})
.then((res) => res.data.credentials.token);
}
return getTokenPromise;
};
const client = axios.create();
// Request interceptor to add token
client.interceptors.request.use(async (config) => ({
...config,
headers: {
"X-Some-Auth": await renewToken(),
...config.headers,
},
}));
// Response interceptor to handle expiry
client.interceptors.response.use(
(res) => res,
(error) => {
if (error.response?.status === 401) {
// Auth expired, renew and try again
renew = true;
return client(error.config);
}
return Promise.reject(error);
}
);
// if putting this in a module...
// export default client;
The first time you try to make a request, the token will be retrieved. After that, it will continue to use the last value until it expires.
if you want to create a to send the token with every request in axios you should create a custom axios instance or change the global axios default
you will find the way to do it here, about promise problem you need to resolve it using .then
this how i think you should do it
// first create axios instance
// you can set config defaults while creating by passing config object see the docs
const instance = axios.create();
// then get the token from API
axios(config).then(response=>{
instance.defaults.headers.common["header you want to set"]=response.data.credentials.token
});
// then use the instance to make any request you want that should have the token

Nodejs - Axios not using Cookie for post request

I'm struggling with AXIOS: it seems that my post request is not using my Cookie.
First of all, I'm creating an Axios Instance as following:
const api = axios.create({
baseURL: 'http://mylocalserver:myport/api/',
header: {
'Content-type' : 'application/json',
},
withCredentials: true,
responseType: 'json'
});
The API I'm trying to interact with is requiring a password, thus I'm defining a variable containing my password:
const password = 'mybeautifulpassword';
First, I need to post a request to create a session, and get the cookie:
const createSession = async() => {
const response = await api.post('session', { password: password});
return response.headers['set-cookie'];
}
Now, by using the returned cookie (stored in cookieAuth variable), I can interact with the API.
I know there is an endpoint allowing me to retrieve informations:
const readInfo = async(cookieAuth) => {
return await api.get('endpoint/a', {
headers: {
Cookie: cookieAuth,
}
})
}
This is working properly.
It's another story when I want to launch a post request.
const createInfo = async(cookieAuth, infoName) => {
try {
const data = JSON.stringify({
name: infoName
})
return await api.post('endpoint/a', {
headers: {
Cookie: cookieAuth,
},
data: data,
})
} catch (error) {
console.log(error);
}
};
When I launch the createInfo method, I got a 401 status (Unauthorized). It looks like Axios is not using my cookieAuth for the post request...
If I'm using Postman to make the same request, it works...
What am I doing wrong in this code? Thanks a lot for your help
I finally found my mistake.
As written in the Axios Doc ( https://axios-http.com/docs/instance )
The specified config will be merged with the instance config.
after creating the instance, I must follow the following structure to perform a post requests:
axios#post(url[, data[, config]])
My requests is working now :
await api.post('endpoint/a', {data: data}, {
headers: {
'Cookie': cookiesAuth
}
});

Add new fields to context variable in strapi backend for user management

I am using strapi as backend and react in the front-end. So the use case is that the user will signup and that signup will be done using auth0. I have defined some roles for the users signing up as shown on auth0
Roles based on plan taken by user
const _ = require("lodash");
const axios = require("axios");
const jwt = require("../jwt");
module.exports = async (ctx, next) => {
let role;
if (ctx.state.user) {
// request is already authenticated in a different way
return next();
}
try {
const tokenInfo = await axios({ method: "post",
url: `${process.env.AUTH0_URL}/userinfo`,
headers: { Authorization: ctx.request.header.authorization,
},
});
let user_id = tokenInfo.data.sub;
var config = { method: "get",
url: `${process.env.AUTH0_URL}/api/v2/users/${user_id}/roles`,
headers: {Authorization: `Bearer ${jwt.jwtSecret}`,
},
};
axios(config).then(function (response) {
ctx.state.roles = response.data[0].name; // This part does not work in the next policy as ctx.state.role gives undefined in route specific policy
}).catch(function (error) {
console.log(error);
});
// console.log(tokenInfo.data, "tokeninfo");
if (tokenInfo && tokenInfo.data) {
return await next();
}
} catch (err) {
console.log(err.message);
return handleErrors(ctx, err, "unauthorized");
}
Currently these will be managed here only. Now I have a collection which has some research articles which can only be accessed depending upon the plan user has been assigned. In order to protect the route and strapi access I have installed user-permissions plugin in strapi and managing userinfo using a global policy as shown
Project Structure
. So here is the code through which I am checking the user info on every route
Now there are two ways in which I tried solving my problem. First I read the tokenInfo data from userInfo route but unfortunately auth0 is not returning roles assigned. It is only returning standard data like
"name": "ansh5#gmail.com",
"nickname": "ansh5",
"picture": "https://s.gravatar.com/avatar/6fdb83f10321dd7712ac2457b11ea34e?
s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fan.png",
"updated_at": "2021-07-19T08:03:50.461Z",
"user_id": "auth0|60ec0b3721224b0078ac95f4",
So in order to get user role I used the other API and configured it with my auth0 account.
${process.env.AUTH0_URL}/api/v2/users/${user_id}/roles
I am getting the correct response but when I am doing this assignment.
ctx.state.roles = response.data[0].name;
I am getting undefined in my ctx.state.roles in my route specific policy. Does anybody have idea how we manage strapi and auth0 together.
Yes, it's because the axios calls are asynchronous in nature. So as per your code, axios will try to get the user information over a network call, but strapi will not really wait for the response. Instead it will just move forward to the next policy, hence resulting in an undefined user role. To fix this, you need to await for the api response from axios. Try the code below:
const axios = require("axios");
const jwt = require("../jwt");
module.exports = async (ctx, next) => {
let role;
if (ctx.state.user) {
// request is already authenticated in a different way
return next();
}
try {
const tokenInfo = await axios({
method: "post",
url: `${process.env.AUTH0_URL}/userinfo`,
headers: {
Authorization: ctx.request.header.authorization,
},
});
let user_id = tokenInfo.data.sub;
var config = {
method: "get",
url: `${process.env.AUTH0_URL}/api/v2/users/${user_id}/roles`,
headers: {
Authorization: `Bearer ${jwt.jwtSecret}`,
},
};
const resp = await axios(config);
ctx.state.roles = response.data[0].name;
console.log(ctx.state.roles);
// console.log(tokenInfo.data, "tokeninfo");
if (tokenInfo && tokenInfo.data) {
return await next();
}
} catch (err) {
console.log(err.message);
return handleErrors(ctx, err, "unauthorized");
}
}

AWS-Lambda 302 Not Redirecting after getting response from Axios (Frontend)

I'm trying to setup a Google-OAuth flow using serverless and AWS-Lambdas. To start, I have a button that kicks off the process by hitting a lambda endpoint. However, the page never actually redirects to the authentication page. Instead I get an error on the FE:
Request failed with status code 302
Frontend logic:
const redirectToGoogleOAuth = async (user) => {
try {
const endpoint = process.env.GOOGLE_PATH_ENDPOINT;
const response = await axios.get(endpoint, {
responseType: 'text',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${user}`,
},
});
// Expect redirect at this point
return response.data.data;
} catch (err) {
throw new Error(err.message);
}
};
Lambda Endpoint:
module.exports = async (event, context) => {
const responseType = 'code'
const googleAuthorizeURL = 'https://accounts.google.com/o/oauth2/v2/auth'
const scope = 'openid email https://www.googleapis.com/auth/contacts.readonly'
const accessType = 'offline'
try {
const params = [
`response_type=${responseType}`,
`client_id=${googleClientId}`,
`redirect_uri=${baseURL}`,
`scope=${scope}`,
`state="state"`,
`access_type=${accessType}`
]
const googleOAuthEndPath = `${googleAuthorizeURL}?${params.join('&')}`
const response = {
statusCode: 302,
body: '',
headers: {
location: googleOAuthEndPath
}
}
return response
} catch (err) {
return response(400, err.message)
}
}
In the lambda-response, I've added a header for location with the google-path. However, the frontend does not seem to consume the response correctly. The frontend interprets the 302 as in error instead of redirecting to the specific page. Any ideas on how I may resolve this so it actually redirects?
Axios uses XHR, which always follows redirects by itself and therefore Axios can't do anything about it (unless you rely on hacks, discussed in the same link).
You might have to use something other than Axios for this part, such as the Fetch API, which supports manual redirects.
GitHub user parties suggested the fetch() equivalent in the same Axios issue linked above:
fetch("/api/user", {
redirect: "manual"
}).then((res) => {
if (res.type === "opaqueredirect") {
window.location.href = res.url;
} else {
return res;
}
}).catch(handleFetchError);

Getting 400 Bad Request When POSTing to Get Transaction Token

I'm trying to integrate our website with Converge API with Hosted Payments Page. Here is the link to their documentation https://developer.elavon.com/#/api/eb6e9106-0172-4305-bc5a-b3ebe832f823.rcosoomi/versions/5180a9f2-741b-439c-bced-5c84a822f39b.rcosoomi/documents?converge-integration-guide/book/integration_methods/../../book/integration_methods/hosted_payments.html
I'm having troubles getting past the first step which is requesting a transaction token from their API endpoint. I'm sending a POST request from my server using axios with the correct parameters and URL, but when I try and POST i get 400 Bad Request. When I make the same request in POSTMAN I get a 200 response with the transaction token. I talked to their developers and they said that everything I was doing was correct and that nothing seemed odd within my code, so even they were stumped as to why I couldn't make a POST request to their endpoint. Obviously there is something within my code that their API is not liking, or else I wouldn't be here trying to find answers for this.
Here is how I'm making the POST request:
app.get('/converge_token_req', (request, response) => {
let params = {
ssl_merchant_id: '*****',
ssl_user_id: '*****',
ssl_pin: '*****',
ssl_transaction_type: 'ccsale',
ssl_amount: '1.00'
}
axios.post('https://api.demo.convergepay.com/hosted-payments/transaction_token', params, {
headers: { 'Content_Type' : 'application/x-www-form-urlencoded' }
}).then((res) => {
response.send(res.data)
}).catch((error) => {
console.log('there was an error getting transaction token')
response.send(error.message)
})
})
Here are the Request Headers:
I'm honestly out of ideas to try. The developers say that everything looks just fine yet I'm unable to make a successful request to their API. If anyone has any thoughts on this that would be great. Thanks!
This code below worked for me:
app.get('/converge_token_req', (request, response) => {
let params = {
ssl_merchant_id: '*****',
ssl_user_id: '*****',
ssl_pin: '*****',
ssl_transaction_type: 'ccsale',
ssl_amount: '1.00'
}
axios({
method: 'post',
url: 'https://api.demo.convergepay.com/hosted-payments/transaction_token',
params: params
}).then((res) => { response.send(res.data)
}).catch((error) => {
console.log('there was an error getting transaction token: ',
error)
})
})
I've since found out the solution to my problem. The issue here is that converge expects a x-www-form-urlencoded string that needs to be Stringified before submitting the request. I found a library that works well for this called qs and I used it like so:
let params = qs.stringify({ // need this if content_type is application/x-www-form-urlencoded
ssl_merchant_id: env.CONVERGE.MERCHANT_ID,
ssl_user_id: env.CONVERGE.USER_ID,
ssl_pin: env.CONVERGE.PIN,
ssl_transaction_type: request.query.type,
ssl_amount: request.query.amount,
ssl_email: request.query.email,
ssl_company: request.query.company,
ssl_avs_address: request.query.address,
ssl_avs_zip: request.query.zip,
ssl_description: request.query.desc,
})
axios.post('https://api.convergepay.com/hosted-payments/transaction_token', params, {
headers: {
'Content_Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then((res) => {
response.send(res.data)
}).catch((error) => {
console.log('there was an error getting transaction token')
response.send(error.message)
})
I think you could also get away with just using JSON.stringify() but this way worked for me.

Resources