Sendgrid works locally with Nextjs but not when I host it on Vercel - next.js

I get this to work locally, but get a Response Error: Unauthorized on Vercel.
I've created a Single Sender Verification on Sendgrid which is verified.
I've tried creating a new API key on Sendgrid but it still dosen't work.
I'm out of options.
This is my code in the api route:
import type { NextApiRequest, NextApiResponse } from "next";
const sgMail = require("#sendgrid/mail");
type EmailData = {
to: string;
from: string;
subject: string;
text: string;
html: string;
};
type DataResponse = {
data?: any;
success: boolean;
error?: string;
};
const emailAddress = process.env.SENDGRID_EMAIL as string;
const emailAddressTo = process.env.SENDGRID_EMAIL_TO as string;
sgMail.setApiKey(process.env.SENDGRID_API_KEY as string);
export default async function _(req: NextApiRequest, res: NextApiResponse<DataResponse>) {
if (req.method !== "POST") {
res.status(400).json({ success: false, error: "Invalid request" });
return;
}
const data = JSON.parse(req.body);
const { name, email, phone = "", message = "" } = data;
console.log("sgMail", sgMail);
console.log("--------------");
console.log("process.env.SENDGRID_API_KEY", process.env.SENDGRID_API_KEY);
console.log("--------------");
console.log("SENDGRID_EMAIL", process.env.SENDGRID_EMAIL);
console.log("--------------");
console.log("SENDGRID_EMAIL_TO", process.env.SENDGRID_EMAIL_TO);
console.log("--------------");
const text = `
Name: ${name} \r\n
Email: ${email} \r\n
Phone: ${phone} \r\n
message: ${message} \r\n
`;
let emailData: EmailData = {
to: emailAddressTo,
from: emailAddress,
subject: "Form submission",
text: text,
html: `<div>${text.replace(/\r\n/g, "<br>")}</div>`,
};
try {
await sgMail.send(emailData);
} catch (error) {
console.log(error);
res.status(400).json({ success: false, error: "Error while sending email" });
return;
}
res.status(200).json({ success: true, data: {} });
}
This is the log from the server with the error messages:
sgMail MailService {
client: Client {
auth: 'Bearer HIDDEN BUT ITS THERE',
impersonateSubuser: '',
defaultHeaders: {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'sendgrid/7.7.0;nodejs'
},
defaultRequest: {
baseUrl: 'https://api.sendgrid.com/',
url: '',
method: 'GET',
headers: {},
maxContentLength: Infinity,
maxBodyLength: Infinity
}
},
substitutionWrappers: [ '{{', '}}' ],
secretRules: [],
MailService: [class MailService]
}
--------------
process.env.SENDGRID_API_KEY HIDDEN BUT ITS THERE
--------------
SENDGRID_EMAIL HIDDEN BUT ITS THERE
--------------
SENDGRID_EMAIL_TO: HIDDEN BUT ITS THERE
--------------
ResponseError: Unauthorized
at node_modules/#sendgrid/client/src/classes/client.js:146:29
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
code: 401,
response: {
headers: {
server: 'nginx',
date: 'Sun, 19 Feb 2023 20:57:56 GMT',
'content-type': 'application/json',
'content-length': '97',
connection: 'close',
'access-control-allow-origin': 'https://sendgrid.api-docs.io',
'access-control-allow-methods': 'POST',
'access-control-allow-headers': 'Authorization, Content-Type, On-behalf-of, x-sg-elas-acl',
'access-control-max-age': '600',
'x-no-cors-reason': 'https://sendgrid.com/docs/Classroom/Basics/API/cors.html',
'strict-transport-security': 'max-age=600; includeSubDomains'
},
body: { errors: [Array] }
}
}

Vercel deployments use dynamic IP addresses due to the dynamic nature of the platform and in Sendgrid their IP Access management was on, which meant that all requests from Vercel were blocked..
SOLUTION:
You can disable IP Access Management to reopen account access to any IP address and no longer maintain a list of allowed addresses.
From the Settings > IP Access Management page, click Disable Allow List.
A dialog will open asking you to confirm the decision. Click Disable.

Related

Next Auth Credentials Provider - SignIn() and Mapping to my API Dto

I am using Next.js and Next Auth to talk to my backend C# ASP.NET API.
My API's response is the following DTO:
{
"data": {
"accessToken": "string",
"refreshToken": "string",
"email": "user#example.com",
"username": "string",
"roles": [
"string"
]
},
"success": true,
"message": "string"
}
I am having a hard time getting that info into the next auth session so that I can grab it with useSession().
I'd also like to be able to display the API "message" to the user in the login form. Incase their account is locked or whatever.
This is what I have:
[...nextauth].js
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { API_URL } from "#/constants";
export const authOptions = {
// Configure one or more authentication providers
providers: [
// Add Your Providers Here
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const { usernme, password } = credentials;
const body = JSON.stringify({
username,
password,
});
// Login request to our API.
const res = await fetch(`${API_URL}/Login`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
body: body,
});
const data = await res.json();
// Our API's response contains
/*
{
"data": {
"accessToken": "string",
"refreshToken": "string",
"email": "user#example.com",
"username": "string",
"roles": [
"string"
]
},
"success": true,
"message": "string"
}
*/
const user = {
success: data.success,
message: data.message,
email: data.data.email,
username: data.data.username,
accessToken: data.data.accessToken,
refreshToken: data.data.refreshToken,
roles: data.data.roles,
};
// EVERYTHING TO HERE IS GOOD!
// I CAN GET THE user OBJECT FILLED.
if (res.ok && user) {
return user; //<---- is this actually returning the full user object to the session?
} else {
return null;
}
},
}),
],
pages: { signIn: "/login" },
};
export default NextAuth(authOptions);
Navbar Links:
<Nav.Link as={Link} href='/login' onClick={() => signIn()}>Login</Nav.Link>
<Nav.Link as={Link} href='/signout' onClick={() => signOut({callbackUrl: '/'})}>Signout</Nav.Link>
Login form:
// Get data from the form.
const nextAuthSettings = {
username: event.target.username.value,
password: event.target.password.value,
redirect: true,
callbackUrl: "/dashboard",
};
// Send the form data to Next Auth
const result = await signIn("credentials", nextAuthSettings);
// Error Handling
// THIS DOES NOT WORK
// I don't think signIn() returns a copy of the user object unfortunately...
if (!result.success) {
// Display the API Error Message On The Page
setErrorMsg(result.message);
}
And then in various pages, when I want to access the user object I am doing this :
import { useSession } from "next-auth/react";
const { data: session } = useSession();
// This only shows the email
<span>{session?.user.email}</span>;
// It looks like a JWT or something when I console log it
{
"user": {
"email": "users#email.com"
},
"expires": "2023-03-16T12:39:28.120Z"
}
Any help appreciated!
I need to be able to access the user object my API is returning throughout my app.
At the moment I'm just getting this session.user.email and nothing else ??
it's like I am not mapping the API's response to whatever Next Auth wants me to create...
you have to use callbacks :
callbacks: {
async jwt({ user, token }) {
// update token from user
if (user) {
token.user = user;
}
// return final_token
return token;
},
async session({ session, token }) {
// update session from token
session.user = token.user;
return session;
},
},
Now you can access your session with useSession() and your token with getToken()

next-auth with googleProvider returns error: TIMED OUT // Try signing in with a different account

Hi I am working with next.js with next-auth googleProvider.
I have finished coding in local environment and now I am testing in production.
The problem I faced is it google API returns an error when try to signIn. The symptom is like below
it prints "Try signing in with a different account." in the browser
it returns error message like below in server
>>>> redirect callback /welcome http://test.abc.com:5000
[next-auth][error][GET_AUTHORIZATION_URL_ERROR]
https://next-auth.js.org/errors#get_authorization_url_error connect ETIMEDOUT 172.217.26.237:443 {
message: 'connect ETIMEDOUT 172.217.26.237:443',
stack: 'Error: connect ETIMEDOUT 172.217.26.237:443\n' +
' at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1187:16)',
name: 'Error'
}
[next-auth][error][SIGNIN_OAUTH_ERROR]
https://next-auth.js.org/errors#signin_oauth_error connect ETIMEDOUT 172.217.26.237:443 {
error: {
message: 'connect ETIMEDOUT 172.217.26.237:443',
stack: 'Error: connect ETIMEDOUT 172.217.26.237:443\n' +
' at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1187:16)',
name: 'Error'
},
provider: {
id: 'google',
name: 'Google',
type: 'oauth',
wellKnown: 'https://accounts.google.com/.well-known/openid-configuration',
authorization: { params: [Object] },
idToken: true,
checks: [ 'pkce', 'state' ],
profile: [Function: profile],
clientId: 'private.info.apps.googleusercontent.com',
clientSecret: 'user_secret',
httpOptions: { timeout: 6000000, agent: false },
signinUrl: 'http://test.abc.com:5000/api/auth/signin/google',
callbackUrl: 'http://test.abc.com:5000/api/auth/callback/google'
},
message: 'connect ETIMEDOUT 172.217.26.237:443'
}
So... at first, I guess it is a firewall issue. However I could receive data from google endpoints.(i.e. curl https://accounts.google.com/.well-known/openid-configuration)
I was also able to fetch curl 172.217.26.237:443, but it returned zero bytes.
Below is my [...nextAuth.js].(Nothing special I think)
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
const AUTH_TIMEOUT = 60000;
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
prompt: 'consent',
access_type: 'offline',
response_type: 'code',
},
},
// https://github.com/nextauthjs/next-auth/issues/3920
httpOptions: {
timeout: AUTH_TIMEOUT,
},
}),
],
callbacks: {
async signIn({ account, profile }) {
console.debug('>>>> signIn callback', account, profile);
if (account.provider === 'google') {
return profile.email_verified && profile.email.endsWith('myhost.com');
}
return false;
},
async redirect({ url, baseUrl }) {
console.log(process.env.HTTPS_PROXY);
console.debug('>>>> redirect callback', url, baseUrl);
if (url.startsWith('/')) return `${baseUrl}${url}`;
if (new URL(url).origin === baseUrl) return url;
return baseUrl;
},
async session({ session, user, token }) {
console.debug('>>>> session callback', session, user, token);
const mergedSession = { ...session };
if (token && token.id_token) {
mergedSession.user.id_token = token.id_token;
}
return mergedSession;
},
async jwt({
token, user, account,
profile, isNewUser,
}) {
console.debug('>>>> jwt callback', token, user, account, profile, isNewUser);
const mergedTokenObject = { ...token };
if (account && !token.id_token) {
mergedTokenObject.id_token = account.id_token;
}
return mergedTokenObject;
},
},
secret: process.env.APP_SECRET,
});
Here is the question.
Could it be a firewall issue? - I just do not get it since I can fetching some data from those urls with curl.
If not, what kind of things I could try at this moment? thx

Nodemailer: mail command failed

I keep getting the error:
Mail command failed: 554 5.7.8 User [contact#example.com] not authorized to send on behalf of <test#test.com>
This is my code:
api/contact.js
import nodemailer from "nodemailer"
export default async (req, res) => {
const { name, email, phone, message} = req.body;
const transporter = nodemailer.createTransport({
host: "send.one.com",
port: 465,
secure: false,
auth: {
user:'contact#example.com',
pass: 'password'
},
tls: {
rejectUnauthorized: false
}
});
try {
await transporter.sendMail({
from: {
name: req.body.name,
address: email
},
to: 'contact#example',
subject: `Contact form submission from ${name}`,
html: `<p>You have received a contact form submission</p><br>
<p><strong>Email: </strong> ${email}</p><br>
<p><strong>Phone: </strong> ${phone}</p><br>
<p><strong>Message: </strong> ${message}</p><br>`
});
} catch (error) {
return res.status(500).json({error: error.message || error.toString() })
}
return res.status(200).json({ error: ""});
};
contact.js:
import { useState } from 'react'
export default function Contact() {
const [inputs, setInputs] = useState({
name: '',
email: '',
phone: '',
message: ''
})
const [form, setForm] = useState('')
const handleChange = (e) => {
setInputs((prev) => ({
...prev,
[e.target.id]: e.target.value
}))
}
const onSubmitForm = async (e) => {
e.preventDefault()
if (inputs.name && inputs.email && inputs.phone && inputs.message) {
setForm({ state: 'loading' })
try {
const res = await fetch(`api/contact`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(inputs)
})
const { error } = await res.json()
if (error) {
setForm({
state: 'error',
message: error
})
return
}
setForm({
state: 'success',
message: 'Your message was sent successfully.'
})
setInputs({
name: '',
email: '',
phone: '',
message: ''
})
} catch (error) {
setForm({
state: 'error',
message: 'Something went wrong.'
})
}
}
}
None of my Google searches seem to bear any fruit. Does it have something to do with my domain provider? I have tested my code with Gmail, and it works like a charm, but not with one.com.
I am open for suggestions. This error has had me stumbled for days now.
The reason you're trying to send an email from an unauthorized source is because your from option is using data from the request. You wont be able to send an email from a source you aren't authorized to use. You should be sending from contact#example.com.
I'm not sure of the exact goal of the form, but consider redesigning the flow of the email service to send emails from your own source (contact#example.com) otherwise, you have to take the users email authorization credentials as input which can and will go south quickly.

Paypal refund API gives UnAuthorized Error

const { paymentId } = req.body;
const refundURI = `https://api.sandbox.paypal.com/v2/payments/captures/${paymentId}/refund`;
// get access token
const { access_token } = await generateToken();
// console.log(JSON.stringify(access_token));
const refundObj = await Axios.post(refundURI, {
headers: {
Accept: `application/json`,
Authorization: `Bearer ${access_token}`,
},
body: JSON.stringify({
amount: {
currency_code: "USD",
value: "10.00",
},
}),
});
I am trying to refund a paypal payment .I am generating access token but still its giving unAuthorized with 401 status code .

fetch react native return 401 error

So I able to successfully send a request via postman, but whenever I throw it into fetch I get back a 401 error.
export const createUser = () => {
return async (dispatch) => {
dispatch({ type: CREATE_USER });
console.log('we are in the create user function');
try {
let response = await fetch('secret.com/v1/login/signup', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'test1231273123#test.com',
password: 'Asadasd123123',
first_name: 'joe',
last_name: 'doe',
phone_number: '373738'
})
});
console.log('response ' + JSON.stringify(response));
} catch (error) {
console.log(error);
}
};
};
Here is the error I keep receiving.
response {"type":"default","status":401,"ok":false,"headers":{"map":{"access-control-allow-methods":["GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"],"access-control-allow-origin":["*"],"connection":["keep-alive"],"access-control-allow-credentials":["true"],"content-length":["188"],"content-type":["text/html; charset=utf-8"],"access-control-allow-headers":["Content-Type, Accept, Authorization"],"www-authenticate":["Basic realm=\"Restricted\""],"date":["Thu, 12 Jan 2017 16:57:58 GMT"],"server":["nginx"]}},"url":"https://secret.com/v1/login/signup","_bodyInit":{},"_bodyBlob":{}}
My backend developer believes I ran into a cross domain issue and need to setup a proxy server? "set up some proxy server (I would recommend nginx) that would proxy ajax queries to our API domain"
I think it has something to do with fetch? Ideas?
I believe you need to provide the protocol, change:
await fetch('secret.com/v1/login/signup'...
to
await fetch('http://secret.com/v1/login/signup'

Resources