SendGrid email works locally but not on Azure app service - azure

I'm using sendgrid in next.js to send emails. It works perfectly fine on localhost, but when I deploy it to Azure app service and try to send an email, it throws an error and the error is an empty object.
I'm building the next app locally with docker and then uploading and deploying that image onto the azure app service. This is to allow the local environment variables to be included in the build.
/api/send-mail
import type { NextApiRequest, NextApiResponse } from 'next'
// https://github.com/sendgrid/sendgrid-nodejs/tree/main/packages/mail#quick-start-hello-email
// https://docs.sendgrid.com/for-developers/sending-email/quickstart-nodejs#build-your-api-call
import sgMail from '#sendgrid/mail'
sgMail.setApiKey(process.env.SEND_GRID_API_KEY)
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if(req.method == 'GET') {
const msg = "This is a test message"
sgMail.send({
to: process.env.SERVICES_INQUIRIES_EMAIL,
from: process.env.SEND_GRID_VERIFIED_EMAIL,
subject: "Test Email",
html: msg
}).then(() => {
return res.status(200).json({'success': true})
}, error => {
return res.status(500).json({'test error': error})
})
}
}
export default handler
When I hit this endpoint on localhost, I get {"success":true}, but when I deploy onto the app service, I get {"test error":{}}.
This shouldn't be an environment variable issue, because other env variables work on the live site. Any insight is much appreciated.

Did you used a environment variables in Azure App Service ?
If not please do so:
Hope this will help

Related

How to configure firebase-admin-sdk for `verifyIdToken` to pass?

I try to use Firebase in my application. The frontend logs the user in using the Web SDK, without any backend. Later, I would like to call some backend APIs. For this reason, I pass the idToken to the backend and try to validate the user as described in the Firebase docs.
When I do the above flow locally using the Firebase Emulator everything works as expected.
When I switch off the Emulator the idToken validation fails with
{
errorInfo: {
code: 'auth/argument-error',
message: 'Firebase ID token has invalid signature. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
},
codePrefix: 'auth'
}
I created a Google hosted Firebase function to check if I can get the idToken validated there. The above setup works when the validation happens within the Google infrastructure.
Based on the above, I think the issue is in my FirebaseApp setup in the API. What that issue might be?
This is my setup.
I define 3 environment variables:
FIREBASE_DB_URL=https://<project-id>.firebaseio.com
FIREBASE_PROJECT_ID=<project-id>
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
I checked and cat $GOOGLE_APPLICATION_CREDENTIALS prints the correct file.
I initialize Firebase in the API with
import admin from "firebase-admin";
if(admin.apps.length == 0) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: process.env.FIREBASE_DB_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
});
console.log('Firebase initialized')
} else {
console.warn('Firebase already initialized')
}
and this is the validating code
import { DecodedIdToken } from 'firebase-admin/lib/auth/token-verifier';
import { getAuth } from 'firebase-admin/auth';
import './initializeFirebase';
export default async function needsLoggedInUser(idToken: string): Promise<DecodedIdToken|false> {
try {
return await getAuth().verifyIdToken(idToken)
} catch(err) {
console.error(err)
return false
}
}
I use the above in a NextJS API code as
import { NextApiRequest, NextApiResponse } from 'next'
import { getDatabase } from 'firebase-admin/database';
import 'services/backend/initializeFirebase';
import needsLoggedInUser from 'services/backend/needsLoggedInUser';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// As an admin, the app has access to read and write all data, regardless of Security Rules
const decodedToken = await needsLoggedInUser(req.body.user)
if(!decodedToken) {
return res.status(403).send("403 Forbidden")
}
/// ... rest of the API
}

How to track an error generated by providing wrong instrumentation key in Azure App Insights

I'm using Azure app Insights for collecting analytical data.I want to handle a case where I'm getting a 400 error due to providing the wrong instrumentation key in the AppInsights.js file. I have created a profile for encrypting instrumentation key in App. Where can I catch this error in code and display fallback UI in a React App.The backend is in Java.
import { ApplicationInsights from'#microsoft/applicationinsightsweb';
import {ReactPlugin,AppInsightsErrorBoundary,} from
'#microsoft/applicationinsights-react-js';
import { createBrowserHistory } from 'history';
import { ClickAnalyticsPlugin } from '#microsoft/applicationinsights-
clickanalytics-js';
import io from 'react/IOGlobal';
const clickPluginInstance = new ClickAnalyticsPlugin();
const browserHistory = createBrowserHistory({ basename: '' });
const reactPlugin = new ReactPlugin();
const clickPluginConfig = {
autoCapture: true,
dataTags: {
useDefaultContentNameOrId: true,
},
};
const appInsights = new ApplicationInsights({
config: { instrumentationKey:
***io.platform.getProfileValue('APP_INSTRUMENTATION_KEY')***,
extensions: [reactPlugin, clickPluginInstance],
extensionConfig: {
[reactPlugin.identifier]: { history: browserHistory },
[clickPluginInstance.identifier]: clickPluginConfig,
},
},
});
appInsights.loadAppInsights();
export default { reactPlugin, appInsights };
Also please help with any other implementation in App Insights to catch the same error.
Thanks In Advance.
Update index.js by putting the following route at the end of the route declaration, before app.listen():
…
// this matches all routes and all methods
app.use((req, res, next) => {
res.status(404).send({
status: 404,
error: ‘Not found’
// You can add your alert
})
})
Check here for more info here
First need to set the Instrumentation key .setup(“Instrumentation key”) To get the instance of the default client that is configured with .setup("Instrumentation key") use appInsights.client. Alternatively, if you want a new client just use getClient("Instrumentation key").
Just like for setup, you can skip providing the Instrumentation key in code if it's in the special instrumentation key environment variable.
Refer for similar kind of issue here

Nextjs api and authentication

I'm in the process of building an application for stripe payments. This application generates a form that passes the data to the Stripe api via nextjs api. I just need to build in some basic authentication so only those submitting their payments via my form have access to the api. How would I go about adding some basic auth to my api without requiring users to login? Would I do this via env variable? I'm fairly new to the nextjs/vercel world and come from the python/django/react realm, so maybe my thoughts on this are backwards... I'm planning on hosting the api on vercel and the react form on a php site. So the react form will essentially push data to the vercel app api.
(The reason I'm not building the api in php is because I don't know php and because I'm attempting to build something with as little footprint in the current php site as possible.) Any help or guidance on this would be much appreciated!
My pages/api/customers.js file
import Stripe from 'stripe'
const stripe = new Stripe(process.env.SECRET_KEY)
export default async (req, res) => {
if (req.method === 'POST') {
try {
const { email, name, address, phone, source } = req.body
// Check for customer
const customerExist = await stripe.customers.list(
{
email: email,
limit: 0
})
// console.log('customerExist', customerExist.data[0])
if (customerExist.data.length < 1) {
const customer = await stripe.customers.create({
email,
name,
address,
phone,
source
})
res.status(200).send(customer.id)
} else {
res.status(200).send(customerExist.data[0].id)
}
} catch (err) {
res.status(500).json({ statusCode: 500, message: err.message })
}
} else {
res.setHeader('Allow', 'POST')
res.status(405).end('Method Not Allowed')
}
}
Part of my checkout form
// Function to check/create a user account via api
const checkUserAccount = async (billingDetails, source) => {
try {
const customer = await axios.post('/api/customers', {
email: billingDetails.email,
name: billingDetails.name,
phone: billingDetails.phone,
address: billingDetails.address,
source: source
})
return customer.data
} catch (err) {
console.log(err)
}
}
UPDATE:
Alright, so I added a "TOKEN" to my .env file and now require my api to receive that specific token.
I added this to my checkout form:
...
axios.defaults.headers.common.Authorization = process.env.AUTH_TOKEN
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
...
and then added this to the api:
if (req.method === 'POST' && req.headers.authorization === process.env.AUTH_TOKEN)...
Since I'm not using a login/logout system, I'm hoping this is enough. Thoughts or feedback are more than welcome.

Dynamic GatsbyJS + Node Express deployment

I have a GatsbyJS app to which I added a dynamic side, starting with a contact form using Axios which on POST calls the Express server and sends an email.
Everything is now working locally, but I am having trouble moving to my provider as I am new to backend technology.
Locally, I followed https://www.gatsbyjs.org/docs/client-only-routes-and-user-authentication/ and added the server as a proxy in the package configuration. My host is using cPanel which allows configuring a NodeJS app, which I tried and setup the app but I cannot make it work properly with the front
gatsby-node.js
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// Only update the `/app` page.
if (page.path.match(/^\/app/)) {
// page.matchPath is a special key that's used for matching pages
// with corresponding routes only on the client.
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
ContactForm.js
const handleSubmit = e => {
e.preventDefault()
axios({
method: "POST",
url: "http://websiteaddress.com/post",
data: { name: name, email: email, message: message },
}).then (...)
app.js
const Router = () => {
return (
<MyRouter basepath="/more">
<ContactForm path="/contact" />
</MyRouter>
)
}
server.js is basically using nodemailer and working fine on localhost
My current online Node + static files uploaded to the server render only the static part (the form is not rendered). The Node app is running
How to adapt the configuration (for example the "proxy": "http://localhost:3000" which worked locally) to make it behave on the host as it should?

Azure Function returning 401 when passing params using proxy

I have an angular service that has a GET request with params currentPage and postsPerPage. I'm trying to pass the parameters to an azure function and I'm following a tutorial which tells me to configure proxies.
if I use the this url which is provided when I run func start
}>(`http://localhost:7071/api/GetListingsWithPagination/`, { params })
then I can hit the endpoint and the error is currentPage is undefined. Which is fine because I'm not passing currentPage with proxy.
So moving on to configuring proxy:
with this configuration I have my angular service
using this endpoint in angular service
}>(`https://[sitename].azurewebsites.net/api/listings/${postsPerPage}/${currentPage}`, { params })
but it's returning
401 unauthorized in browser console and never outputs in azure function console.
How can I get my params to work?
angular service
getWins(bidderId: string, currentPage: number, listingRoute: string) {
const postsPerPage = 10;
const queryParams = postsPerPage + "/" + currentPage;
const params = new HttpParams()
.set("bidderId", bidderId)
.set("listingRoute", listingRoute);
return this.http
.get<{
message: string;
posts: any;
maxPosts: number;
activeBidsList: string;
}>(`https://[sitename].azurewebsites.net/api/listings/${postsPerPage}/${currentPage}`, { params })
.pipe(
map(retrievedData => {
return {
posts: retrievedData.posts.map(post => {
return {
title: post.title,
startingBid: post.startingBid,
increments: post.increments,
shippingCost: post.shippingCost,
bidderId: post.bidderId,
id: post._id
};
}),
maxPosts: retrievedData.maxPosts
};
})
);
}
azure function
var MongoClient = require('mongodb').MongoClient;
//var Post = require('./model/post');
module.exports = function (context, req) {
MongoClient.connect(process.env.CosmosDBConnectionString, (err, client) => {
let send = response(client, context);
if (err) send(500, err.message);
console.log("DBNAME: " + process.env.dbName);
let db = client.db(process.env.dbName);
let params = ({ currentPage, postsPerPage } = req.params);
let postPerPage = +params.postPerPage;
let currentPage = params.currentPage;
console.log(postsPerPage);
console.log(currentPage);
return;
});
}
According to the description of your question, it seems your function is on azure portal and the backend url is deployed on local. I think you need to deploy the project which corresponding to the http://localhost:7071/api/GetListingsWithPagination to azure funciton and then put its url on azure to the "backend url" input box. If you just run it on local, your azure function can not call it successfully and the proxy also can not route to this url.
Update:
I'm a little confused about the latest description in comments, so here I provide a sample for your reference.
What I mean is you can deploy your localhost api as a function to azure portal, after deploy it to azure you can get the function url of it (such as https://fromlocal.azurewebsites.net/api/HttpTrigger1). Then you can go to your nodejs function to configure the proxy, set this url as the backend url(shown as below screenshot):
Then, you can request the route url(proxy url) in your nodejs code.
By the way, your original requirement is request the localhost api. For this requirement, you set the proxy to call the localhost api. But you have deploy the localhost api as a function on azure, so I think you can also request the function's url directly(without setting the proxy).

Resources