Would my environement variables be exposed to the browser? - node.js

I have coded an e-mail contact form and was wondering if refactoring my code outside of Next.js' api folder could compromise my API key.
Unless mistaken — I am aware that environmental variables aren't exposed to the browser if they:
Aren't prefixed by NEXT_PUBLIC_;
Are used within the api folder of the framework;
Are used within the getStaticProps,getStaticPaths and getServerSideProps server-side functions.
However, what would happen if I "mentioned" my process.env.SENDGRID_API_KEY outside of the api folder in a utilities folder as shown below? Would the browser have any way of accessing it?
root
|
├───pages
│ └───api
| └─────myRoute.ts
├───.env.local
|
├───utilities
│ └───myFunction.ts
│
/utilities/myFunction.ts
const sendgrid = require("#sendgrid/mail");
sendgrid.setApiKey(process.env.SENDGRID_API_KEY); // WILL THIS BE AN ISSUE?
export const sendEmail = async (
name: string,
email: string,
message: string
) => {
const incomingEmail = `
From: ${name}\r\n
Email: ${email}\r\n
Message: ${message}
`;
await sendgrid.send({
to: process.env.RECEIVING_EMAIL, // WILL THIS BE AN ISSUE?
from: process.env.SENDGRID_SENDER_EMAIL, // WILL THIS BE AN ISSUE?
subject: "New message!",
text: incomingEmail,
html: incomingEmail.replace(/\r\n/g, "<br>"),
});
};
pages/api/myRoute.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { sendEmail, acknowledgeReceipt } from "../../utilities/sendGrid"; // Refactors
type Data = {};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const { lang, name, email, message, token } = req.body;
// Irrelevant validation logic...
try {
// Sending e-mail & acknowledging receipt
await Promise.all([
sendEmail(name, email, message),
acknowledgeReceipt(name, email, lang),
]);
return res.status(200).json({ status: "success", msg: "E-mail sent!" });
} catch (err) {
// Irrelevant
}
}

It looks fine. Server side code like the email sending utility should only be imported into api files, not into frontend component files, which it looks like you are already doing that correctly.

Related

how to make a newsletter button

Need some quick guidance regarding the newsletter button in react webpage. I wanna make a newsletter button that accepts, emails from users and stores them in a spreadsheet. Basically, I need some code guidance such that when someone enters their email and clicks submit button. They will get the template email like 'Thanks for subscribing to our newsletter and company email and we get their email in the spreadsheet.
In order for this to work you would have your React.js frontend that would get the e-mail address from the user and send a request to your backend service containing that e-mail address.
This could look similar to this.
import { useState } from "react";
import "./styles.css";
function isValidEmailAddress(email) {
// validate here
return true;
}
export default function App() {
const [address, setAddress] = useState("");
async function subscribeNewsletter() {
if (isValidEmailAddress(address)) {
try {
const response = await fetch("https://your-backend/subscription", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email: address
})
});
// check if request was successful (codes 200-299)
// in this implementation we expect code "201 Created"
if (!response.ok) {
const parsed = JSON.parse(response.body);
// show error message received from backend
alert(parsed.msg);
} else {
alert("Subscribed.");
}
} catch (error) {
alert("Subscription failed");
}
}
}
return (
<div className="App">
<input onChange={(e) => setAddress(e.target.value)} value={address} />
<button onClick={() => subscribeNewsletter()}>Subscribe</button>
</div>
);
}
See CodeSandbox for a minimal working demo of the frontend.
Within your backend you have to handle the request to the /subscription endpoint. In a handler you will probably validate the email address again and then write it into a database or spreadsheet (if you really want to use spreadsheets which I would not recommend).
Last but not least you need to send the welcome email. The easiest way to do this is to use a 3rd party API or you use something like SMTP JS to send an email. What you will need in that case is a SMTP server. Have a look on this thread for more details.
The backend service could then look similar to this.
Note: this is not a perfect implementation but a skeleton that should help you getting started.
import express from "express";
// create app
var app = express();
// middleware to parse json
app.use(express.json())
app.get('/', function (req, res) {
// serve your react application
res.sendFile("index.html");
});
app.post("/subscription", function (req, res) {
const emailAddress = req.body.email;
if (!isValidEmailAddress(emailAddress)) {
// malformed email address
res.status(400).send({
msg: "email address is invalid"
})
}
insertNewSubscriber(emailAddress);
sendWelcomeEmail(emailAddress);
// resource subsription has been created
res.status(201).send();
});
// listen for request on port 3000
const port = 3000;
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
function isValidEmailAddress(email) {
// validate here
return true;
}
function insertNewSubscriber(email) {
// write to database/ file/ spreadsheet etc.
}
function sendWelcomeEmail(email) {
// send email e.g. using 3rd party API
}

Re-captcha token verification fails in AWS, but not in vercel

Below is my Next.js (backend API) code to verify recaptcha token (created from the client side) and send a mail.
import { NextApiRequest, NextApiResponse } from "next";
import NextCors from 'nextjs-cors';
import { recaptchaAxios } from "../../axios/axiosBackend";
import sendGridMail from '#sendgrid/mail';
sendGridMail.setApiKey(process.env.SENDGRID_API_KEY);
interface FormData {
contactName: string;
contactEmail: string;
contactPhone: string;
contactSubject: string;
contactMessage: string;
token: string;
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
await NextCors(req, res, {
// Options
methods: ['GET','POST'],
origin: '*',
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
});
const formData: FormData = req.body;
console.log("form Data >>>>>>>>>>>>>>",formData)
const human = await validateHuman(formData.token);
if (!human) {
res.status(400);
return res.json({ success: false, errors: ["You are not authenticated"] });
}
const message = {
to: process.env.SENDGRID_MAIL_RECEIVER,
from: process.env.SENDGRID_MAIL_SENDER, // Change to your verified sender
subject: formData.contactSubject,
text: `Name: ${formData.contactName}\n
Contact: ${formData.contactPhone} \n
Email: ${formData.contactEmail} \n
Message: ${formData.contactMessage}`,
html: `Name: ${formData.contactName}
Contact: ${formData.contactPhone}
Email: ${formData.contactEmail}
Message: ${formData.contactMessage}`,
}
try {
await sendGridMail.send(message);
res.status(200);
return res.json({ success: true, errors: [] });
} catch (error) {
console.log(error);
res.status(500);
return res.json({ success: false, errors: ['Error occured while trying to send your details. Please contact your Administrator.']});
}
};
async function validateHuman(token: string): Promise<boolean> {
const secret = process.env.RECAPTCHA_SECRET_KEY;
const response = await recaptchaAxios.post(`/siteverify?secret=${secret}&response=${token}`,{}, {});
const success = response.data['success'];
console.log("server siteverify >>>>>>>>>>>>>",response);
return success;
}
recaptchaAxios has the baseURL as below
const recaptchaAxios = axios.create({
baseURL: `https://www.google.com/recaptcha/api`,
});
I have deployed the same code in vercel as well as using AWS Amplify.
In vercel when called to the above mail API, the Recaptcha token is verified and the mail is sent.
But unfortunately in AWS it gives the error
{ success: false, errors: ["You are not authenticated"] }
I have added all the environment variables in AWS which I have in vercel and the values are the same.
All the domains are added in reCaptch v3 console for the site.
So at this point I am stuck on why in AWS gives the error, but not vercel for the same code base
Is there anything that I am missing in AWS??
Cheers
My first pointer would be to console.log the environment variables on script load, also each time the recaptcha validation is triggered. This way you can be sure the ENV vars are all loaded correctly. You would be suprised to have a small case sensitivity typo, leave you without an important env variable.
Otherwise, I would check if I need to allow outgoing traffic (firewall rules) on AWS amplify, but this is less common, since AWS Amplify spawns a public site.
Issue was in the below code
const secret = process.env.RECAPTCHA_SECRET_KEY;
Even though the RECAPTCHA_SECRET_KEY was available in the environment variables in AWS, it was not accessible.
Fix was to introduce this key in next.config.js file
module.exports = {
images: {
domains: [],
},
env: {
RECAPTCHA_SECRET_KEY: process.env.RECAPTCHA_SECRET_KEY,
},
};
This solved the problem

How to Authenticate a Service Account for the Gmail API in Node Js?

So I'm using the Node.js Gmail library to send an email to another user. I was thinking of using a Service account to do just that. I've followed their documentation of passing the keyFile property but when I try to run the code, I get a 401 error, Login Required.
Here's what I got so far:
const { gmail } = require("#googleapis/gmail");
function createMessage(from, to, subject, message) {
// logic that returns base64 email
const encodedMail=[...];
return encodedMail;
}
export default function handler(req, res) {
const auth = gmail({
version: "v1",
keyFile: './google_service.json',
scopes: ["https://www.googleapis.com/auth/gmail.send"],
});
const raw = createMessage(
process.env.SERVICE_EMAIL,
"someone#gmail.com",
"Subject",
"This is a test",
);
const post = auth.users.messages.send({
userId: "me",
requestBody: {
raw,
},
});
post
.then((result) => {
console.log(result.data);
})
.catch((err) => {
console.log(err);
});
}
I've already got my Service Account credential json file and placed it at the root of my project. Is there something I'm doing wrong?

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.

Creating user with email and password in admin console results in anonymous user

I'm creating users using the admin SDK and I'm wanting them to be able to login with email and password. For some reason when I create users through the client using only email and password, the user can login using those credentials, but when I create a user using the admin SDK, the user is shown as anonymous in the auth dashboard, and the user can't login using their email and password. No errors are shown client side or Firebase side.
How can I create a Firebase user using the admin SDK and have that user linked to email authentication?
Node:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUser = functions.https.onRequest(async (req, res) => {
//grab the email and password parameters
await admin.auth().createUser({
email: req.query.email,
password: req.query.password
})
//create the user
.then(function(userRecord) {
const child = userRecord.uid;
console.log('Successfully created new user:', userRecord.uid);
res.json({
status: 201,
data: {
"message": userRecord.uid
}
});
})
//handle errors
.catch(function(error) {
console.log();
res.json({
status: 500,
data: {
"error": 'error creating user: ', error
}
});
});
});
Swift:
func createChild(for parent: Parent,
with firstName: String,
lastName: String,
displayName: String?,
chores: [Chore]?,
username: String,
password: String,
completion: #escaping () -> Void = { }) {
let funcCallDict = [
"email": username,
"password": password
]
functions.httpsCallable(addChildIdentifier).call(funcCallDict) { (result, error) in
if let error = error {
NSLog("error: adding child with firebase function: \(error)")
completion()
return
}
}
completion()
}
Your function is an HTTP type trigger:
exports.createUser = functions.https.onRequest
But you're trying to invoke it as a callable type trigger:
functions.httpsCallable(addChildIdentifier).call(funcCallDict)
(Note that a callable trigger would be defined with onCall, not onRequest.)
As you can see from the documentation links, they are not the same thing. You are probably invoking the HTTP trigger, and it's not actually getting the arguments you expect from the client, since the protocol is different between them. Try logging req.query.email in the function to see what I mean.
You will have to either make your function a proper callable so it can be invoked from the client using the provided library, or change the way you invoke it on the client to use a regular http library instead of the Firebase library.

Resources