xero.apiCallback is not a function - node.js

I am getting an error in my xero code. It says, xero.apiCallback is not a function. The other functions in xero seems to work.
Here is my code:
require('dotenv').config();
const express = require('express');
const XeroRouter = express.Router();
const { XeroClient } = require('xero-node');
const { TokenSet } = require('openid-client');
const client_id = process.env.CLIENT_ID;
const client_secret = process.env.CLIENT_SECRET;
const redirectUrl = process.env.REDIRECT_URI;
const scopes = "openid profile email accounting.settings accounting.reports.read accounting.journals.read accounting.contacts accounting.attachments accounting.transactions offline_access";
const xero = new XeroClient({
clientId: client_id,
clientSecret: client_secret,
redirectUris: [redirectUrl],
scopes: scopes.split(" "),
});
if ( !client_secret || !redirectUrl) {
throw Error('Environment Variables not all set - please check your .env file in the project root or create one!')
}
XeroRouter.get('/', async (req, res) => {
try {
let consentUrl = await xero.buildConsentUrl();
res.redirect(consentUrl);
} catch (e) {
res.status(e.status || 500);
console.log(e);
}
});
XeroRouter.get("/callback", async (req, res) => {
let tokenSet = await xero.apiCallback(req.url);
console.log(req.url); //This has value
console.log(tokenSet); // this returns TokenSet {}
});
module.exports = XeroRouter;
I am not sure why apiCallback is not working but it says it does in the documentation.

I had the same problem and eventually got it working, i had to actually look at the code for the node module to work out what was going on.
You need to explicitly set the openIdClient on construction of XeroClient and it worked for me.
Change your constructor to look like below and it should work.
const xero = new XeroClient({
clientId: client_id,
clientSecret: client_secret,
redirectUris: [redirectUrl],
openIdClient: TokenSet,
scopes: scopes.split(" "),
});

Related

How to solve the problem of getting req.cookies being undefined in production environment?

//back end sign in controller
const signinController= async (req,res)=>
{
const{email,password}=req.body
try {
const user= await User.findOne({email:email})
if(!user)
{
res.status(400).json({errorMessage:"This email is not registered, please signup"})
}
else
{
let status= await bcrypt.compare(password,user.password)
if(!status)
{
res.status(400).json({errorMessage:"Incorrect password"})
}
else
{
const payload={
user:{
_id:user._id
}
}
jwt.sign(payload,jwtSecret,{expiresIn:jwtExpire},(err,token)=>
{
const{_id,username,email,role}=user
if(err)
{
console.log("jwterror : ",err)
}
else
{
res.status(200).json({token,user:{_id,username,email,role}})
}
})
}
}
} catch (error) {
if(error)
{
console.log(error)
res.status(400).json({errorMessage:"server error"})
}
}
}
// front end part
const handleSubmit=(e)=>
{
e.preventDefault()
if(isEmpty(email.trim())||isEmpty(password.trim()))
{
setFormData({...formData,errorMsg:"Fields are empty"})
}
else if(!isEmail(email))
{
setFormData({...formData,errorMsg:"Invalid email"})
}
else
{
setFormData({...formData,loading:true})
signinAuth({email,password}).then((res)=>
{
setFormData({...formData,loading:false,errorMsg:false})
setAuthentication(res.data.token,res.data.user)
if(isAuthenticated() && isAuthenticated().role===1)
{
history.push("/admin/dashboard")
}
else
{
history.push("/user/dashboard")
}
}).catch((err)=>
{
setFormData({...formData,loading:false,errorMsg:err.response.data.errorMessage})
})
}
}
export const signinAuth= async(data)=>
{
const config={
headers:{"Content-Type":"application/json"}
}
let response= await axios.post(`${api}/api/auth/signin`,data,config)
return response
}
export const setAuthentication=(token,user)=>
{
setCookies("token",token)
setLocalStorage("user",user)
}
export const setCookies=(key,value)=>
{
cookies.set(key,value,{expires:1})
}
I am unable to get req.cookies to verify JWT in production environment, but locally the code is working and I am able to get the req.cookies.
//jwt verification middleware
const jwt = require("jsonwebtoken")
const {jwtSecret}= require("../Config/Keys")
exports.jwtAuthenticator=(req,res,next)=>
{
const token = req.cookies.token
if(!token)
{
res.status(400).json({"errorMessage" :"authorisation denied because no token"})
}
try {
const decoded=jwt.verify(token,jwtSecret)
req.user=decoded.user
next()
} catch (error) {
console.log("jwt error: ",error)
res.status(401).json({"errorMessage" :"authorisation denied"})
}
// app.js
const express = require("express");
const mongoose=require("./database/db")
const cors= require("cors")
const authRouter= require("./Routes/Auth")
const categoryRoute= require("./Routes/Category")
const productRoute= require("./Routes/Product")
const cartRoute= require("./Routes/Cart")
const orderRoute=require("./Routes/Order")
const cookieParser = require("cookie-parser");
const morgan = require("morgan");
const dotenv= require("dotenv")
dotenv.config();
const app= express();
const PORT=process.env.PORT||5000
app.use(express.json())
app.use(express.urlencoded({ extended: true }));
const corsOptions ={
origin:'http://localhost:3000',
credentials:true //access-control-allow-credentials:true
}
app.use(cors(corsOptions))
app.use(morgan("dev"))
app.use(cookieParser())
app.use("/api/auth",authRouter)
app.use("/api/category",categoryRoute)
app.use("/api/product",productRoute)
app.use("/api/cart",cartRoute)
app.use("/api/order",orderRoute)
mongoose.connectDB()
app.get("/",(req,res)=>
{
res.send("server")
})
app.listen(PORT,()=>
{
console.log(`server connected to ${PORT}`)
})
I am unable to get req.cookies to verify JWT in production environment(render platform). However, I am able to fetch cookies locally.
I have tried these JWT middleware,
App.js code locally and it is working. However, after deploying, I couldn't able to get req.cookies.
Update2: give a try with this setting on the cookie if you want to set the cookie on the frontend, (i am not familiar though on the front)
export const setAuthentication=(token,user)=> {
setCookies(token)
setLocalStorage("user",user)
}
export const setCookies=(value)=> {
cookies.set("token", value, {
sameSite: "none" //none for cross domain cookie, If omitted, it's set to 'no_restriction'.
secure: true, //required for sameSite=none attribute, set to false by default in local dev
expires: 1,
})
}
Maybe have a look on the cookies.set() documentation https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/cookies/set
By default httpOnly is indeed false, that's why you can set cookie on the frontend.
2nd Solution: in case you just can't make it work, another common approch would be storing the jwt token in the localstorage just like how you do with the user, and retrieve it when you need by attaching it on your header, but again in my opinion setting this in localstorage or even manipulating cookie on the front is not the best practice, i believe setting all this stuff on the backend with httpOnly and secure cookie flags, or storing jwt in memory for direct use (some limitation) is still the best way to go.
export const setAuthentication=(token,user)=> {
setLocalStorage("token", token) // you have to make a function to store the token like how you do on the user.
setLocalStorage("user",user)
}
// addtoCard endpoint
const addToCart=async (productId)=> {
let user= localStorage.getItem("user")
user= JSON.parse(user)
let userId=user._id
let data={
userId,
productId
}
const token = localStorage.getItem("token");
const config={
headers:{
"Content-Type":"application/json",
Authorization: `Bearer ${token}`
},
"withCredentials": true
}
await axios.post(`${api}/api/cart/addtocart`,data,config).then((res)=> {
console.log(res.data.message)
})
}
than you just need to change the condition checking on the header instead of cookie and change the process in the middleware jwtAuthenticator:
exports.jwtAuthenticator=(req,res,next)=> {
const authHeader = req.headers['authorization'];
console.log(authHeader)
if(!authHeader) {
return res.status(400).json({"errorMessage" :"authorisation denied because no token"})
}
try {
const token = authHeader.split('')[1];
const decoded = jwt.verify(token, jwtSecret)
if(decoded) {
req.user = decoded.user
next();
}
} catch (error) {
console.log("jwt error: ",error)
res.status(403).json({"errorMessage": "forbidden token"})
}
};
//in signin i didn't need to use the jwt middleware. during signin I am only setting the generated token in cookie. I am using JWT middleware for verification is in route that used to add products to cart. and codes are mentioned below
//signin route
const express= require("express")
const {signupValidator,validatorResult}= require("../Middlewares/Validator")
const {signupController,signinController} =require("../Controllers/Auth")
const router= express.Router()
router.post("/signup",signupValidator,validatorResult,signupController)
router.post("/signin",signinController)
module.exports=router
//add to cart route
const express= require("express");
const { jwtAuthenticator } = require("../Middlewares/Authenticator");
const {AddToCart,GetCart,IncQuantity,DecQuantity,RemoveProduct,GetExactCart,RemoveCart} = require("../Controllers/Cart")
const router= express.Router()
router.post("/addtocart",jwtAuthenticator,AddToCart)
router.get("/getcart/:userId",GetCart)
router.get("/getexactcart/:userId",GetExactCart)
router.patch("/incquantity",jwtAuthenticator,IncQuantity)
router.patch("/decquantity",jwtAuthenticator,DecQuantity)
router.patch("/removeProduct",jwtAuthenticator,RemoveProduct)
router.patch("/removeCart/:userId",RemoveCart)
module.exports= router;
//front end part of calling this addtocart api
const addToCart=async (productId)=>
{
let user= localStorage.getItem("user")
user= JSON.parse(user)
let userId=user._id
let data={
userId,
productId
}
const config={
headers:{"Content-Type":"application/json"},
"withCredentials":true
}
await axios.post(`${api}/api/cart/addtocart`,data,config).then((res)=>
{
console.log(res.data.message)
})
}

createClickwrap returns 404 Not Found

When I call createClickwrap method, I got 404 error Not Found. If I run this method through Quickstart generated demo project, I don't get this error. However, if I run it through my project I get the error. If I debug the demo app and my app, the functions parameters are the same.
This is the code in my app:
docusign controller:
const docuSignService = require('./docusign_esign_service');
const demoDocumentsPath = path.resolve(__dirname, '../demo_documents');
const { createClickwrap } = require('./createClickWrap');
async getDocusignRecieptService() {
const authResponse = await docuSignService.authenticate();
if (authResponse) {
const docTermsPdf = 'Term_Of_Service.pdf';
const docFile = path.resolve(demoDocumentsPath, docTermsPdf);
const { basePath, accessToken, apiAccountId } = authResponse;
const { clickwrapId } = await createClickwrap({ docFile, basePath, accessToken, accountId: apiAccountId });
const res = await activateClickwrap({ clickwrapId, basePath, accessToken, accountId: apiAccountId });
console.log({ res });
}
}
docuSignService.js
const SCOPES = ['signature', 'impersonation', 'openid', 'click.manage', 'click.send'];
const fs = require('fs');
const docusign = require('docusign-esign');
class DocusingService {
async authenticate() {
const jwtLifeSec = 10 * 60, // requested lifetime for the JWT is 10 min
dsApi = new docusign.ApiClient();
dsApi.setOAuthBasePath(process.env.dsOauthServer.replace('https://', '')); // it should be domain only.
let rsaKey = fs.readFileSync(process.env.privateKeyLocation);
try {
const results = await dsApi.requestJWTUserToken(
process.env.dsJWTClientId,
process.env.impersonatedUserGuid,
SCOPES,
rsaKey,
jwtLifeSec
);
const accessToken = results.body.access_token;
// get user info
const userInfoResults = await dsApi.getUserInfo(accessToken);
// use the default account
let userInfo = userInfoResults.accounts.find((account) => account.isDefault === 'true');
return {
accessToken: results.body.access_token,
apiAccountId: userInfo.accountId,
basePath: `${userInfo.baseUri}/restapi`
};
} catch (e) {
let body = e.response && e.response.body;
// Determine the source of the error
if (body) {
// The user needs to grant consent
if (body.error && body.error === 'consent_required') {
if (this.getConsent()) {
return this.authenticate();
}
} else {
// Consent has been granted. Show status code for DocuSign API error
this._debug_log(`\nAPI problem: Status code ${e.response.status}, message body:
${JSON.stringify(body, null, 4)}\n\n`);
}
}
}
}
getConsent() {
var urlScopes = SCOPES.join('+');
// Construct consent URL
var redirectUri = 'https://developers.docusign.com/platform/auth/consent';
var consentUrl =
`${process.env.dsOauthServer}/oauth/auth?response_type=code&` +
`scope=${urlScopes}&client_id=${process.env.dsJWTClientId}&` +
`redirect_uri=${redirectUri}`;
throw new Error(`Open the following URL in your browser to grant consent to the application: ${consentUrl}`);
}
getArgs(apiAccountId, accessToken, basePath, signerEmail, signerName, id, agreementData, redirect_uri) {
const envelopeArgs = {
signerEmail: signerEmail,
signerName: signerName,
status: 'sent',
signerClientId: id,
dsReturnUrl: redirect_uri,
agreement: agreementData
};
const args = {
accessToken: accessToken,
basePath: basePath,
accountId: apiAccountId,
envelopeArgs: envelopeArgs
};
return args;
}
}
module.exports = new DocusingService();
createClickWrap.js
const createClickwrap = async ({ docFile, clickwrapName = 'clickwrapName', basePath, accessToken, accountId }) => {
// Step 3. Construct the request Body
// Create display settings model
const displaySettings = docusignClick.DisplaySettings.constructFromObject({
consentButtonText: 'I Agree',
displayName: 'Terms of Service',
downloadable: true,
format: 'modal',
hasAccept: true,
mustRead: true,
requireAccept: true,
documentDisplay: 'document'
});
// Create document model
// Read and encode file. Put encoded value to Document entity.
// The reads could raise an exception if the file is not available!
const documentPdfExample = fs.readFileSync(docFile);
const encodedExampleDocument = Buffer.from(documentPdfExample).toString('base64');
const document = docusignClick.Document.constructFromObject({
documentBase64: encodedExampleDocument,
documentName: 'Terms of Service',
fileExtension: 'pdf',
order: 0
});
// Create clickwrapRequest model
const clickwrapRequest = docusignClick.ClickwrapRequest.constructFromObject({
displaySettings,
documents: [document],
name: clickwrapName,
requireReacceptance: true
});
// Step 4. Call the Click API
const dsApiClient = new docusignClick.ApiClient();
dsApiClient.setBasePath(basePath);
dsApiClient.addDefaultHeader('Authorization', 'Bearer ' + accessToken);
const accountApi = new docusignClick.AccountsApi(dsApiClient);
// Create a clickwrap
let result = null;
try {
result = await accountApi.createClickwrap(accountId, {
clickwrapRequest
});
} catch (e) {
debugger;
console.log(e);
}
debugger;
console.log(`Clickwrap was created. ClickwrapId ${result.clickwrapId}`);
return result;
};
module.exports = { createClickwrap };
Parameters look like this in the demo app and it works:
and these are the parameters in my app:
The first parameter accountId is the same. Why I am getting this issue in my app if function gets the same parameters?
"Error: Not Found
at Request.callback (/Users/and/test/node_modules/docusign-click/node_modules/superagent/lib/node/index.js:696:15)
at IncomingMessage.<anonymous> (/Users/and/test/node_modules/docusign-click/node_modules/superagent/lib/node/index.js:906:18)
at IncomingMessage.emit (node:events:539:35)
at IncomingMessage.emit (node:domain:475:12)
at endReadableNT (node:internal/streams/readable:1345:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)"
Thanks to pointing out in the comments, when I changed basePath ${userInfo.baseUri}/restapi to ${userInfo.baseUri}/clickapi, it works now.

How do I debug server-side errors on MERN?

I have this front-end code:
export const CreatePage = () => {
const auth = useContext(AuthContext)
const {request} = useHttp()
const [content, setContent] = useState('')
const [title, setTitle] = useState('')
const [lead, setLead] = useState('')
useEffect(() => {
window.M.updateTextFields()
},[])
const postHandler = async () => {
try {
const data = await request('/api/post/generate', 'POST', {title: title, lead: lead, content: content}, {
Authorization: `Bearer ${auth.token}`
})
console.log(data)
} catch (e) {}
}
And this back-end code:
router.post('/generate', auth, async (req, res) => {
try {
const baseURL = config.get('baseURL')
const {title, lead, content} = req.body
// if (!title || !lead || !content) {
// return res.status(422).json({error: 'Please, input ALL fields'})
// }
const Post = new Post({
title, lead, content, owner: req.body.user.userId // req.user.userId
})
await Post.save()
res.status(201).json({Post})
} catch (e) {
res.status(500).json({message: 'Something went wrong'})
}})
I've tried a lot of things, but I still get this error. I know this is a server-side error, but that's all I have been able to figure out.
P.S. If there are any questions about the code, I will add it later.
UPD: By the way, could it be a problem's reason? Console log:
[1] Proxy error: Could not proxy request /api/post/generate from localhost:3000 to http://localhost:5000.
Probably, it's because of cors, you just can't send request from different url's. Try to install cors and configure it:
const cors = require("cors");
app.use("/", require('./src/routes'));
app.use(cors({
origin: '*'
}))

In Google Dialogflow, How to save variable till session end

I am not able to save token as a variable till the session end. this token will be used for further APIs.
here is my code
'use strict';
const axios = require('axios');
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const {Payload} = require('dialogflow-fulfillment');
var token = "";
function login(agent) {
const email = agent.parameters.email;
const password = agent.parameters.password;
const baseurl = "http://demo:3000/login/buyerlogin";
var data = { email: email, password: password };
return axios
.get(baseurl, { data: { email: email, password: password } })
.then((result) => {
if (result.data.status == 200) {
agent.add("Login Successfully 😊");
agent.add("Select an option to proceed with other queries 👇");
token = result.data.token; //token will be used for further APIs
agent.add(
new Payload(agent.UNSPECIFIED, payloadlog, {
rawPayload: true,
sendAsMessage: true,
})
);
}
})
.catch((error) => {
agent.add("else " + error.message);
});
}
I want to save token till the session end.
Please help me out on this.
Thank You
You can save these values in the parameters of a long-lasting Context that you set and later read in your webhook.
Setting the token value in a parameter in the Context might look something like
agent.context.set('info', 99, {
token: token
});
you can later get that Context and the token value with something like
const infoContext = agent.context.get('info');
const token = infoContext.parameters.token;

Domain-wide delegation using default credentials in Google Cloud Run

I'm using a custom service account (using --service-account parameter in the deploy command). That service account has domain-wide delegation enabled and it's installed in the G Apps Admin panel.
I tried this code:
app.get('/test', async (req, res) => {
const auth = new google.auth.GoogleAuth()
const gmailClient = google.gmail({ version: 'v1' })
const { data } = await gmailClient.users.labels.list({ auth, userId: 'user#domain.com' })
return res.json(data).end()
})
It works if I run it on my machine (having the GOOGLE_APPLICATION_CREDENTIALS env var setted to the path of the same service account that is assigned to the Cloud Run service) but when it's running in Cloud Run, I get this response:
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Bad Request",
"reason" : "failedPrecondition"
} ],
"message" : "Bad Request"
}
I saw this solution for this same issue, but it's for Python and I don't know how to replicate that behaviour with the Node library.
After some days of research, I finally got a working solution (porting the Python implementation):
async function getGoogleCredentials(subject: string, scopes: string[]): Promise<JWT | OAuth2Client> {
const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
})
const authClient = await auth.getClient()
if (authClient instanceof JWT) {
return (await new google.auth.GoogleAuth({ scopes, clientOptions: { subject } }).getClient()) as JWT
} else if (authClient instanceof Compute) {
const serviceAccountEmail = (await auth.getCredentials()).client_email
const unpaddedB64encode = (input: string) =>
Buffer.from(input)
.toString('base64')
.replace(/=*$/, '')
const now = Math.floor(new Date().getTime() / 1000)
const expiry = now + 3600
const payload = JSON.stringify({
aud: 'https://accounts.google.com/o/oauth2/token',
exp: expiry,
iat: now,
iss: serviceAccountEmail,
scope: scopes.join(' '),
sub: subject,
})
const header = JSON.stringify({
alg: 'RS256',
typ: 'JWT',
})
const iamPayload = `${unpaddedB64encode(header)}.${unpaddedB64encode(payload)}`
const iam = google.iam('v1')
const { data } = await iam.projects.serviceAccounts.signBlob({
auth: authClient,
name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
requestBody: {
bytesToSign: unpaddedB64encode(iamPayload),
},
})
const assertion = `${iamPayload}.${data.signature!.replace(/=*$/, '')}`
const headers = { 'content-type': 'application/x-www-form-urlencoded' }
const body = querystring.encode({ assertion, grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer' })
const response = await fetch('https://accounts.google.com/o/oauth2/token', { method: 'POST', headers, body }).then(r => r.json())
const newCredentials = new OAuth2Client()
newCredentials.setCredentials({ access_token: response.access_token })
return newCredentials
} else {
throw new Error('Unexpected authentication type')
}
}
What you can do here is define ENV variables in your yaml file as described in this documentation to set the GOOGLE_APPLICATION_CREDENTIALS to the path of the JSON key.
Then use a code such as the one mentioned here.
const authCloudExplicit = async ({projectId, keyFilename}) => {
// [START auth_cloud_explicit]
// Imports the Google Cloud client library.
const {Storage} = require('#google-cloud/storage');
// Instantiates a client. Explicitly use service account credentials by
// specifying the private key file. All clients in google-cloud-node have this
// helper, see https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
// const projectId = 'project-id'
// const keyFilename = '/path/to/keyfile.json'
const storage = new Storage({projectId, keyFilename});
// Makes an authenticated API request.
try {
const [buckets] = await storage.getBuckets();
console.log('Buckets:');
buckets.forEach(bucket => {
console.log(bucket.name);
});
} catch (err) {
console.error('ERROR:', err);
}
// [END auth_cloud_explicit]
};
Or follow an approach similar to the one mentioned here.
'use strict';
const {auth, Compute} = require('google-auth-library');
async function main() {
const client = new Compute({
serviceAccountEmail: 'some-service-account#example.com',
});
const projectId = await auth.getProjectId();
const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;
const res = await client.request({url});
console.log(res.data);
}
main().catch(console.error);

Resources