I am writing a O365 Send Mail API using Node. My Node act as a server, Angular and Xamarin act as client(Web and Mobile). I have gone through the documentation. According to that I have included Microsoft SDK and registered my app in Microsoft Azure to get Client ID. Then here comes the mail API code.
Code
const options = {
authProvider,
};
const client = Client.init(options);
const sendMail = {
message: {
subject: "Meet for lunch?",
body: {
contentType: "Text",
content: "The new cafeteria is open."
},
toRecipients: [
{
emailAddress: {
address: "fannyd#contoso.onmicrosoft.com"
}
}
],
ccRecipients: [
{
emailAddress: {
address: "danas#contoso.onmicrosoft.com"
}
}
]
},
saveToSentItems: "false"
};
let res = await client.api('/me/sendMail')
.post(sendMail);
To Create authProvider, I have to scenarios here
Web App that calls web APIs
Mobile app that calls web APIs
I have no idea how to create authProvider. The documentation is confusing. Can someone help me out
You need to put that code in your backend (node.js). Just make a dynamic helper function for sending a mail like this
utils.js
const options = {
authProvider,
};
const client = Client.init(options);
const utils = {};
utils.sendMail = async (to, subject, body, cc, isSave) => {
const mailOptions = {
message: {
subject: subject || '',
body: { ...body },
toRecipients: to,
},
saveToSentItems: `${isSave}`,
};
if (cc) {
mailOptions.message.ccRecipients = cc;
}
const result = await client.api('/me/sendMail').post(mailOptions);
return result;
};
module.exports = utils;
And calling that function like this
const test = async () => {
try {
const to = [
{
emailAddress: {
address: 'fannyd#contoso.onmicrosoft.com',
},
},
];
const cc = [
{
emailAddress: {
address: 'danas#contoso.onmicrosoft.com',
},
},
];
const subject = 'Test';
const body = {
contentType: 'Text',
content: 'The new cafeteria is open.',
};
const saveToSentItems = true;
const result = await utils.sendMail(to, subject, body, cc, saveToSentItems);
return result;
} catch (err) {
console.log(err);
throw err;
}
};
test();
I suggest you put your logic in the backend When you have multiple clients such as android, iOs, PWA then you need to rewrite your code in all 3 platforms, If you put your common business logic to the backend then a single code can serve to all 3 platforms.
Related
How do I use the Google Oauth in featherjs with an existing access token? The docs do not give an example on this. The only example is through the browser as shown here.
When going through the browser, http://localhost:3030/oauth/google works ok. I can successfully add the user to my DB. Here is my code:
const { LocalStrategy } = require("#feathersjs/authentication-local");
const { expressOauth } = require("#feathersjs/authentication-oauth");
const { OAuthStrategy } = require("#feathersjs/authentication-oauth");
var uniqid = require("uniqid");
const { AuthenticationService, JWTStrategy } = require('#feathersjs/authentication');
class GoogleStrategy extends OAuthStrategy {
async getEntityData(profile) {
const baseData = await super.getEntityData(profile);
console.log({ profile });
return {
...baseData,
profilePicture: profile.picture,
email: profile.email,
password: uniqid.time(),
};
}
}
module.exports = (app) => {
const authentication = new AuthenticationService(app);
authentication.register("local", new LocalStrategy());
authentication.register('jwt', new JWTStrategy());
authentication.register("google", new GoogleStrategy());
app.use("/authentication", authentication);
app.configure(expressOauth());
};
However if I try using an access token,like so
POST http://localhost:3030/authentication
data: {
"strategy": "google",
"accessToken": "ya29.A0ARrdaM_UJa6idfZr-4taqwkJ6qGBV1Dp9wbxF-wsult8dNPaVNCVg6Fndmrqv7BhRSwxa5gAKllPvbKtsjyxS39WdmWmqkmE42HOsVZaJWHVEttxbebel3zdpD5BSxWtRiG7NuZLNVedMUaK5AdgIRrJk1u"
}
I do not get the google profile, no user is added to my DB and I get this error:
{
"name": "GeneralError",
"message": "401 Unauthorized",
"code": 500,
"className": "general-error",
"data": {},
"errors": {}
}
I have added "google" to my list of authStrategies in default.json
So my question is what do I need to do?
So I found the solution. Thought I might share. Add this to authentication.js
//add this method
async getProfile(authResult) {
const accessToken = authResult.accessToken;
const { data } = await axios
.get(
`https://openidconnect.googleapis.com/v1/userinfo?access_token=${accessToken}`
)
.then((res) => {
return res;
})
.catch((error) => console.log("autherr", error));
return data;
}
I am using Next-Auth Credentials provider to authenticate using our existing API.
When I follow the directions on https://next-auth.js.org/configuration/callbacks
like this:
callbacks: {
async jwt({ token, user }) {
if (user) {
token.accessToken = user.jwt
}
return token
},
async session({ session, token, user }) {
session.accessToken = token.accessToken
return session
}
}
the resulting session object from useSession() looks like this:
{
expires: "2022-03-22T18:29:02.799Z",
user: {email: 'john#nextIsGreat.com'}
}
I can't use that as it does not have the token available.
So I was able to make up my own working solution, but it is kind of strange because of the way things are grouped together. Here is what I am doing now, that I am trying to figure out how to do better. I use comments to point out the problem areas:
[...nextauth].js:
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import axios from 'axios'
export default NextAuth({
providers: [
Credentials({
name: 'Email and Password',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' }
},
authorize: async (credentials) => {
const url = process.env.API_URL + '/authenticate'
const result = await axios.post(url, {
username: credentials.username,
password: credentials.password
})
const user = result.data
console.log(user)
//It logs this:
/*
{
jwt: 'eyJhbasU1OTJ9.NQ356H4Odya62KmN...', //<---***This is the token i pass in to all of my API calls****
user: {
userId: 207,
email: 'john#nextIsGreat.com',
firstName: 'John',
lastName: 'Doe',
roleId: 1,
}
}
*/
if (user) {
return Promise.resolve(user)
} else {
return Promise.resolve(null)
}
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
if (user.jwt) {
token = { accessToken: user.jwt, restOfUser: user.user }
}
}
return token
},
async session(seshProps) {
return seshProps
}
}
})
Home.js:
export const Home = () => {
const { data: session } = useSession()
console.log(session)
//LOGS THIS --->
/*
{
"session": { "user":{}, "expires":"2022-03-22T17:06:26.937Z"},
"token":{
"accessToken":"eyJ...",
"iat":1645376785,
"exp":1647968785,
"jti":"41636a35-7b9a-42fd-8ded-d3dfgh123455a"
"restOfUser": {
"userId":207,
"email":"john#nextIsGreat.com",
"firstName":"John",
"lastName":"Doe",
"roleId":1
}
}
{
*/
const getPosts=()=> {
const url = 'localhost:4000/posts'
const {data} = axios.get(url, {
Authorization: session.token.accessToken <--**This is the way I am calling my API
})
console.log(data)
}
return (
<div onClick={getPosts}>
Hello, {session.token.restOfUser.firstName}
/* I have to access it like this now, which seems wrong ***** */
</div>
)
}
Cheers for creating your own solution but you do not need it. NextAuth CredentialsProvider handles it already by setting your NextAuth session configuration to session: {strategy: "jwt", ... }.
You can also remove your callbacks for jwt() and session() and remove your owned generated JWT access token. As you do not need it, this way you can authenticate your existing system.
And at your CredentialsProvider({authorize(){}} authorize method. If you had directly connected to the user database, you can directly look up the user credential without doing a post request since it is already considered a server-side function.
Maybe you can help me with the following problem.
I'm currently working on a project.
Github Project // On Netifly
On the client side, I'm using Axios to perform a post request with route /getContent. Once on the server, the content is sent to a third party API to perform analysis. The Response is then send back to the client side.
Everything is working well locally but when deploying the project on Netifly, I have an error 404 when the browser is targeting that route `/getContent'
POST request on the client Side:
const getData = async (txt) => {
const response = await axios.post("/getContent", { txt });
return response;
};
On the server side with express
/**
* MeaningCloud Object
* main param + url
*/
const meaningCloudReq = {
params: {
key: null,
lang: "uk",
txt: null,
txtf: "plain",
},
url: `https://api.meaningcloud.com/sentiment-2.1`,
getData: async function () {
const { params } = this;
const response = await axios.get(this.url, { params });
return response.data;
},
};
const callApi = async (req, res) => {
const { txt } = req.body;
const key = process.env.API_KEY;
const params = { ...meaningCloudReq.params, txt, key };
const requestApi = { ...meaningCloudReq, params };
try {
const data = await requestApi.getData();
res.send(data);
} catch (error) {
const status = {
status: "500",
msg: "Request did not go throught, contact support.",
};
res.send({ status });
}
};
app.post("/getContent", callApi); // the getContent route
Yet, I kept receiving the following error:
XHR POST https://fervent-rosalind-abbe0e.netlify.app/getContent
[HTTP/2 404 Not Found 1072ms]
Let me know if you have any idea about what's going on.
I follow modular or component based strucutre. I found a sample repo.
https://github.com/sujeet-agrahari/node-express-clean-architecture
So, there is a main component.module.js files which is responsible for connecting all other pieces like controller, route, and services.
For controller, services are being injected using higher order functions. Now, controller are super easy to test, I can stub or mock services easily.
auth.module.js
const router = require('express').Router();
const {
makeExpressCallback,
makeValidatorCallback,
} = require('../../middlewares');
// validator
const AuthValidator = require('./auth.validator');
// service
const { doRegister, doLogin, doCheckUserExist } = require('./auth.service');
const { BadRequestError } = require('../../utils/api-errors');
// controller
const controller = require('./auth.controller');
const register = controller.register({ BadRequestError, doCheckUserExist, doRegister });
const login = controller.login({ doCheckUserExist, doLogin });
const AuthController = { register, login };
// routes
const routes = require('./auth.routes')({
router,
AuthController,
AuthValidator,
makeValidatorCallback,
makeExpressCallback,
});
module.exports = {
AuthController,
AuthService: {
doCheckUserExist,
doLogin,
doRegister,
},
AuthRoutes: routes,
};
auth.controller.js
const login = (doCheckUserExist, doLogin) => async (httpRequest) => {
const { username, password } = httpRequest.body;
const userData = await doCheckUserExist({ username });
const loginData = {
username,
role: userData.role_id,
passedPassword: password,
actualPassword: userData.password,
};
const loginResult = await doLogin(loginData);
return {
statusCode: 200,
body: {
success: true,
message: 'Successfully logged in!',
data: loginResult,
},
};
};
const register = ({ BadRequestError, doCheckUserExist, doRegister }) => async (httpRequest) => {
const { username, password } = httpRequest.body;
try {
await doCheckUserExist({ username });
} catch (error) {
// user doesn't exist
const registerResult = await doRegister({ username, password });
return {
statusCode: 200,
body: {
success: true,
message: 'Registered successfully!',
data: registerResult,
},
};
}
throw new BadRequestError('User already exist!');
};
module.exports = { register, login };
Things are fine with the controller, now the problem is with the services. I can't find any pattern to make them thin and clean.
auth.services.js
const {
JWT_ACCESS_TOKEN_SECRET,
ACCESS_TOKEN_EXPIRES_IN,
SIGN_OPTION,
} = require('config');
const bcrypt = require('bcryptjs');
const { User } = require('../../db');
const { generateJWT } = require('./jwt.service');
const { NotFoundError, BadRequestError } = require('../../utils/api-errors');
const doRegister = async ({ username, password }) => {
const user = await User.create({
username,
password,
role_id: 1, // assign role id here
});
// generate access token
const payload = {
username,
role: user.role_id,
};
const token = await generateJWT({
secretKey: JWT_ACCESS_TOKEN_SECRET,
payload,
signOption: {
...SIGN_OPTION,
expiresIn: ACCESS_TOKEN_EXPIRES_IN,
},
});
return {
access_token: token,
...payload,
};
};
const doLogin = async ({
username, userRole, passedPassword, actualPassword,
}) => {
const isValidPass = bcrypt.compareSync(passedPassword, actualPassword);
if (!isValidPass) throw new BadRequestError('Username or Password is invalid!');
// generate access token
const payload = {
username,
role: userRole,
};
const token = await generateJWT({
secretKey: JWT_ACCESS_TOKEN_SECRET,
payload,
signOption: {
...SIGN_OPTION,
expiresIn: ACCESS_TOKEN_EXPIRES_IN,
},
});
return {
access_token: token,
...payload,
};
};
const doCheckUserExist = async ({ username }) => {
const user = await User.findOne({
where: {
username,
},
});
if (!user) throw new NotFoundError('User not found!');
return user;
};
module.exports = { doRegister, doLogin, doCheckUserExist };
A lot is happening in the services, model imports, constants imports, and other utils.
Now services become really hard to test.
Is there any way or pattern I can separate some logic from services and make them lighter?
I can implement reository pattern for db methods, but I am not aware how I can implement using sequelize?
Should I use also higher order function to inject all the utils and constants in the service like I did for controller?
I'm using firebase admin SDK on cloud functions to create users using
admin.auth().createUser({
email: someEmail,
password: somePassword,
})
now I want user to signIn using signInWithEmailAndPassword('someEmail', 'somePassword') but I cannot.
I get the following error
{code: "auth/user-not-found", message: "There is no user record corresponding to this identifier. The user may have been deleted."}
There doesn't seem to be a reason to Stringify/Parse. This worked after I struggled with an unrelated typo...
FUNCTION CALL FROM REACT JS BUTTON CLICK
<Button onClick={() => {
var data = {
"email": "name#example.com",
"emailVerified": true,
"phoneNumber": "+15551212",
"password": "randomPW",
"displayName": "User Name",
"disabled": false,
"sponsor": "Extra Payload #1 (optional)",
"study": "Extra Payload #2 (optional)"
};
var createUser = firebase.functions().httpsCallable('createUser');
createUser( data ).then(function (result) {
// Read result of the Cloud Function.
console.log(result.data)
});
}}>Create User</Button>
And in the index.js in your /functions subdirectory:
const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp();
// CREATE NEW USER IN FIREBASE BY FUNCTION
exports.createUser = functions.https.onCall(async (data, context) => {
try {
const user = await admin.auth().createUser({
email: data.email,
emailVerified: true,
password: data.password,
displayName: data.displayName,
disabled: false,
});
return {
response: user
};
} catch (error) {
throw new functions.https.HttpsError('failed to create a user');
}
});
Screen shot of console output
In 2022 there still is no method built into the Admin SDK that would allow to create users in the emulator.
What you can do is to use the REST API of the emulator to create users there directly. The API is documented here: https://firebase.google.com/docs/reference/rest/auth#section-create-email-password
Provided you have got and nanoid installed you can use the following code to create users in the emulator.
import { nanoid } from 'nanoid'
import httpClientFor from '../lib/http-client/client.js'
const httpClient = httpClientFor('POST')
export const createTestUser = async ({ email = `test-${nanoid(5)}#example.io`, password = nanoid(10), displayName = 'Tony' } = {}) => {
const key = nanoid(31)
const { body: responseBody } = await httpClient(`http://localhost:9099/identitytoolkit.googleapis.com/v1/accounts:signUp?key=${key}`, {
json: {
email,
password,
displayName
}
})
const responseObject = JSON.parse(responseBody)
const { localId: userId, email: userEmail, idToken, refreshToken } = responseObject
return { userId, userEmail, idToken, refreshToken }
}
Please note: As there is no error handling implemented, this snippet is not suitable for production use.
Try like that
And please be ensure that user is created from the panel
admin.auth().createUser({
email: "user#example.com",
emailVerified: false,
phoneNumber: "+11234567890",
password: "secretPassword",
displayName: "John Doe",
photoURL: "http://www.example.com/12345678/photo.png",
disabled: false
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully created new user:", userRecord.uid);
})
.catch(function(error) {
console.log("Error creating new user:", error);
});
Just in case anyone else comes across this I was able to fix it with the help of this.
Here is a working example inside of an onCreate cloud function:
exports.newProjectLead = functions.firestore
.document('newProjectForms/{docId}')
.onCreate(async (snapshot) => {
const docId = snapshot.id
// this is what fixed it the issue
// stringify the data
const data = JSON.stringify(snapshot.data())
// then parse it back to JSON
const obj = JSON.parse(data)
console.log(obj)
const email = obj.contactEmail
console.log(email)
const password = 'ChangeMe123'
const response = await admin.auth().createUser({
email,
password
})
data
const uid = response.uid
const dbRef = admin.firestore().collection(`clients`)
await dbRef.doc(docId).set({
id: docId,
...data,
uid
}, {
merge: true
})
console.log('New Client Created')
})