CORS error using angular httpClient with firebase functions - node.js

I have this ngrx effect in my angular app that send an http request using httpClient
#Injectable()
export class MyEffects {
constructor(private actions$: Actions, private httpClient: HttpClient) {
}
sendEmail$: Observable<Action> = createEffect(() => this.actions$.pipe(
ofType(sendEmail),
concatMap(action =>
this.httpClient.post('https://us-central1-<PROJECT_ID>.cloudfunctions.net/myApi', action.data).pipe(
map(() => actionFinished({status: HttpActionsStatus.SUCCESS})),
catchError(() => of(actionFinished({status: HttpActionsStatus.ERROR})))
)
)
));
}
When I run this with firebase emulator (http://localhost:5001/<PROJECT_ID>/us-central1/myApi it works just fine, but when I deploy the app and trigger the request I get this error
Access to XMLHttpRequest at 'https://us-central1-<PROJECT_ID>.cloudfunctions.net/myApi' from origin 'https://<PROJECT_ID>.web.app' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This is my functions file
const fs = require('fs');
const {promisify} = require('util');
const readFile = promisify(fs.readFile);
const admin = require('firebase-admin');
const handlebars = require('handlebars');
const nodemailer = require('nodemailer');
const functions = require('firebase-functions');
const email = decodeURIComponent(functions.config().gmail.email);
const password = encodeURIComponent(functions.config().gmail.password);
const cors = require('cors')({origin: true, optionsSuccessStatus: 200});
admin.initializeApp();
const transporter = nodemailer.createTransport({
service: 'gmail',
secure: false,
port: 25,
auth: {
user: email,
pass: password
},
tls: {
rejectUnauthorized: false
}
});
exports.myApi = functions.https.onRequest((request: any, response: any) =>
cors(request, response, () =>
readHTMLFile('../src/assets/html/new_message.html', (err: any, html: string) => {
const template = handlebars.compile(html);
const htmlToSend = template(request.body);
const mailOptions = {
to: email,
from: request.body.senderEmail,
subject: request.body.subject,
html: htmlToSend
};
return transporter.sendMail(mailOptions, async (error: any, {}) => {
if (!error) {
const responseEmailOptions = {
to: request.body.senderEmail,
from: email,
subject: request.body.subject,
html: await readFile('../src/assets/html/auto_reply.html'),
};
transporter.sendMail(responseEmailOptions);
return response.status(200).send(request.body);
}
throw error;
});
})
)
);
const readHTMLFile = (path: string, callback: any) => {
fs.readFile(path, {encoding: 'utf-8'}, (err: any, html: string) => {
if (!err) {
return callback(null, html);
}
throw err;
});
};
Basically what it does it to send an email and a verification email back to the sender.
I'll Appreciate your help!

Related

Axios and Oauth1.0 - 'status: 400, Bad Request'

I'm new on Nodejs and all the modules related with Node. I've been trying to use axios for send a Oauth1.0 Autorization signature, but i'm getting: response: { status: 400, statusText: 'Bad Request', ...}
import { BASE_URL } from '../../../config/config.js';
import axios from 'axios';
import status from 'http-status';
import OAuth from 'oauth-1.0a';
import { createHmac } from 'crypto';
import dotenv from 'dotenv';
dotenv.config();
const CONSUMERKEY = process.env.consumer_key;
const CONSUMERSECRET = process.env.consumer_secret;
const TOKENKEY = process.env.access_token;
const TOKENSECRET = process.env.token_secret;
export const oauth = OAuth({
consumer: {
key: CONSUMERKEY,
secret: CONSUMERSECRET,
},
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
return createHmac('sha1', key)
.update(base_string)
.digest('base64')
},
})
export const token = {
key: TOKENKEY,
secret: TOKENSECRET,
}
const doRequest = async (query) => {
const request_data = {
url: `${BASE_URL}`,
method: 'GET',
params: { q: `${query}` },
};
const authHeader = oauth.toHeader(oauth.authorize(request_data, token));
return await axios.get(request_data.url, request_data.params, { headers: authHeader });
};
const searchU = async (term) => {
return await doRequest(`${term}`);
};
export const userS = async (req, res, next) => {
try {
const { query } = req;
const { data } = await searchU(query.q);
const string = JSON.stringify(data);
const Rs = JSON.parse(string);
const response = {
code: 1,
message: 'sucess',
response: Rs
};
res.status(status.OK).send(response);
} catch (error) {
next(error);
if (error.response){
console.log("Response: ");
console.log(error.response);
} else if(error.request){
console.log("Request: ");
console.log(error.request)
} else if(error.message){
console.log("Message: ");
console.log(error.message)
}
}
};
I've been also trying the solution given On this post: but there's no way I can make this work, no idea what i could be doing wron...
When i try the following code (see below), using Request module (which is deprecated) works well, but I really need to do it with Axios...
const request_data = {
url: `${BASE_URL}`,
method: 'GET',
params: { q: `${query}` },
};
const authHeader = oauth.toHeader(oauth.authorize(request_data, token));
request(
{
url: request_data.url,
method: request_data.method,
form: request_data.params,
headers: authHeader,
},
function(error, response, body) {
console.log(JSON.parse(body));
}
)
Any thoughts on what I'm doing wrong on this?? Thank you very much!!
Refer to the following link for the Request Config for Axios. I believe you need to have the query params after the header in the axios.get()
Axios Request Config
Try, the following and see how it goes:-
return await axios.get(request_data.url, { headers: authHeader }, request_data.params);

Firebase email function not finding user

I have a contact form on my website that upon sending clicking to submit, runs a firebase function to execute the request. The issue i'm having all of a sudden is the following error (taken from the firebase logs)
Error: There is no user record corresponding to the provided identifier.
at FirebaseAuthError.FirebaseError [as constructor] (/workspace/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (/workspace/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseAuthError (/workspace/node_modules/firebase-admin/lib/utils/error.js:147:16)
at /workspace/node_modules/firebase-admin/lib/auth/auth-api-request.js:513:15
at /workspace/node_modules/firebase-admin/lib/auth/auth-api-request.js:1347:13
at process._tickCallback (internal/process/next_tick.js:68:7)
The function in charge of the sending is the following:
sendRequest() {
this.loading = true
return new Promise(resolve => {
const newUrl = `https://us-central1-easymediakit.cloudfunctions.net/contactForm?name=${
this.form.name
}&email=${this.form.email}&message=${encodeURI(
this.form.message
)}&uid=${this.uid}`
fetch(`${newUrl}`, {
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
.then(() => {})
.then(async response => {
this.loading = false
this.success = 'Your email has been sent.'
resolve(response)
})
}).catch(error => {
this.loading = false
console.log('Send error', error)
})
}
It was working and I haven't made any changes to the function that handles the email sending so I'm not sure where the issue is coming from, any guidance is greatly appreciated.
Update:
in index.js
const contactFormModule = require('./email/email-contact-form')
exports.contactForm = functions.https.onRequest((req, res) => {
return contactFormModule.handler(req, res)
the contact form:
const cors = require('cors')({ origin: true })
const domain = 'mg.epkbuilder.com'
const apiKey = 'f27b2755b05f3a366542ea538a00c6a2-d32d817f-dec22e2e'
const mailgun = require('mailgun-js')({ apiKey, domain })
const admin = require('firebase-admin')
const MailComposer = require('nodemailer/lib/mail-composer')
exports.handler = (req, res) => {
cors(req, res, async () => {
const uid = req.query.uid
const name = req.query.name
const email = req.query.email
const message = req.query.message
console.log('uid', uid)
console.log('message', `${message}`)
const user = await admin.auth().getUser(uid)
console.log('user', user)
const mailOptions = {
from: `${name} noreply#easymediakit.io`,
replyTo: `${name} ${email}`,
to: user.email,
subject: `Direct message from ${name}`,
text: `${message}`
}
let mail = new MailComposer(mailOptions).compile()
console.log('mail', mail)
return mail.build((error, message) => {
if (error) {
console.log('Email unsuccessful', error)
res.status(400).send(error)
}
const dataToSend = {
to: user.email,
message: message.toString('ascii')
}
return mailgun.messages().sendMime(dataToSend, sendError => {
if (sendError) {
console.log('Email unsuccessful', error)
res.status(400).send(error)
}
return res.send('Email successfully sent!')
})
})
})
}
The error seems to come from this line:
const user = await admin.auth().getUser(uid)
The error indicates that no user exists in Firebase Authentication for the uid value that you specified. You might want to double check the this.uid value in )}&uid=${this.uid} in your client-side code.

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: '*'
}))

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);

NodeMailer - Cannot read property 'getSocket' of undefined

Node: v8.6.0
Nodemailer: v4.6.4
This is my code:
const transport = nodemailer.createTransport({
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS
}
});
const generateHTML = (filename, options = {}) => {
const html = pug.renderFile(`${__dirname}/../views/email/${filename}.pug`,
options);
const inlined = juice(html);
return inlined;
}
exports.send = async (options) => {
const html = generateHTML(options.filename, options);
const text = htmlToText.fromString(html);
const mailOptions = {
from: `Site <noreply#domain.com>`,
to: options.user.email,
subject: options.subject,
html,
text
};
const sendMail = P.promisify(transport.sendMail, transport);
return sendMail(mailOptions);
}
When i execute sendMail i get this fail:
TypeError: Cannot read property 'getSocket' of undefined↵ at sendMail (/Users/...../node_modules/nodemailer/lib/mailer/index.js:143:24
I check the mention line and is this one:
if (typeof this.getSocket === 'function') {
this.transporter.getSocket = this.getSocket;
this.getSocket = false;
}
In my case, I received this error when I was trying to promisify the transport. Omit the callback parameter and it will natively return a promise. No need to promisify.
Try this.
const transport = nodemailer.createTransport({
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS
}
});
const generateHTML = (filename, options = {}) => {
const html = pug.renderFile(`${__dirname}/../views/email/${filename}.pug`,
options);
const inlined = juice(html);
return inlined;
}
exports.send = async (options) => {
const html = generateHTML(options.filename, options);
const text = htmlToText.fromString(html);
const mailOptions = {
from: `Site <noreply#domain.com>`,
to: options.user.email,
subject: options.subject,
html,
text
};
return transport.sendMail(mailOptions)
.then((stuff) => { console.log(stuff); })
.catch((err) => { console.log(err); }) ;
}

Resources