Using Google Oauth in FeathersJs with an existing access token - javascript

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

Related

TypeError when instantiaing SpotifyWebApi object

im trying to write a bot to do some playlist handling in spotify. I'm using the spotify-web-api-node package : https://github.com/thelinmichael/spotify-web-api-node
I installed the package and whenever I try to create an object with the following code:
const SpotifyWebApi = require('spotify-web-api-node');
const spotifyApp = SpotifyWebApi();
I keep getting this error:
this._credentials = credentials || {};
^
TypeError: Cannot set properties of undefined (setting '_credentials')
this is the contructor signature in the src file:
constructor(credentials?: Credentials);
Any thoughts ?
You need an access-token for access API call.
In the documentation,
If you've got an access token and want to use it for all calls, simply use the API object's set method. Handling credentials is described in detail in the Authorization section.
spotifyApi.setAccessToken('<your_access_token>');
Demo code: get access token then get Elvis' albums
const SpotifyWebApi = require('spotify-web-api-node');
const axios = require('axios')
const getToken = async () => {
try {
const response = await axios.post(
url = 'https://accounts.spotify.com/api/token',
data = '',
config = {
params: {
'grant_type': 'client_credentials'
},
auth: {
username: '<your client id>',
password: '<your client secret>'
}
}
);
return Promise.resolve(response.data.access_token);
} catch (error) {
return Promise.reject(error);
}
}
getToken()
.then(token => {
const spotifyApi = new SpotifyWebApi();
spotifyApi.setAccessToken(token);
// Passing a callback - get Elvis' albums in range [0...1]
spotifyApi.getArtistAlbums('43ZHCT0cAZBISjO8DG9PnE', { limit: 2, offset: 0 }).then(
(data) => {
console.log('Artist albums', data.body);
},
(err) => {
console.error(err);
}
);
})
.catch(error => {
console.log(error.message);
});
Result

Modifying localStorage gives A cross-origin error

I'm making a web application using reactjs, and when development is suddenly this error appears:"A cross-origin error was thrown. React doesn't have access to the actual error object in development".I AuthContext witch gives user data to localStorage :
const AuthProvider = ({ children }) => {
const token = localStorage.getItem("token");
const userInfo = localStorage.getItem("userInfo");
const expiresAt = localStorage.getItem("expiresAt");
const [authState, setAuthState] = useState({
token,
expiresAt,
userInfo: userInfo ? JSON.parse(userInfo) : {},
});
const setAuthInfo = ({ token, userInfo, expiresAt }) => {
localStorage.setItem("token", token);
localStorage.setItem("userInfo", JSON.stringify(userInfo));
localStorage.setItem("expiresAt", expiresAt);
setAuthState({
token,
userInfo,
expiresAt,
});
};
Unfortunately i have a problem with updating user details.
In context i made another function:
Edit: I forgot to write that after the function is executed userDetails changes to undefined so I get a cross-origin error
const setUserInfo = ({ userInfo }) => {
localStorage.removeItem("userInfo");
localStorage.setItem("userInfo", JSON.stringify(userInfo));
setAuthState({
userInfo,
});
};
I have rest-api route to update-details , exemplary response:
{
"success": true,
"user": {
"role": role,
"_id": id,
"username": name,
"email": email,
"createdAd": createdAt,
"__v": 0
}
}
And user rest-api response when user login, example:
{
"success": true,
"token": token,
"expiresAt": expiresAt,
"userInfo": {
"role": role,
"_id": id,
"username": name,
"email": email,
"_id": id
}
}
Updating details working when i call it but after i get cross site error.
And heres Submit function
const handleUpdate = async (info) => {
setError("");
setLoading(true);
try {
const { data } = await fetchContext.authAxios.put(
"http://localhost:5000/api/v1/auth/update-details",
info
);
const user = data.user;
console.log(user);
auth.setUserState(data);
setError(null);
setLoading(false);
} catch (err) {
setLoading(false);
// const { data } = err.response;
// setError(data.error);
console.log(err);
}
};
Ok i dumb
const setUserInfo = ({ userInfo }) => {};
should be
const setUserInfo = ( userInfo ) => {};

Injecting dependecy in services in component based strucuture

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?

O365 Mail API using Node as Server

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.

Ember js, Can't push already normalized json api data into store

I'm unable to push a response into the store. According the the docs since I'm returning jsonapi spec I should be able to push it straight in without normalizing it first.
The code to push the response into the store looks like this:
getCurrentUser() {
return get(this, 'store').queryRecord('user', {self: true})
.then((response) => {
const user = get(this, 'store').push(response);
set(this, 'account', user);
return user;
});
}
This throws You must include an 'id' for undefined in an object passed to 'push'. However if I create a dummy response using the output from the server
getCurrentUser() {
return get(this, 'store').queryRecord('user', {self: true})
.then((response) => {
const testResponse =
{
"data": {
"type": "user",
"id": "578846b3e5438b26ebbce7d4",
"attributes": {
"identification": "admin",
"display-name": "Administrator"
},
"relationships": {
"servers": {
"data": []
},
"jobs": {
"data": []
}
}
}
};
const user = get(this, 'store').push(testResponse);
set(this, 'account', user);
return user;
});
}
Everything works as expected. The data in testResponse is a straight copy and paste from postman, so should it not be the same as the data in response or response.data?
So I tried playing around with normalizing it anyway but get all sorts of errors.
getCurrentUser() {
return get(this, 'store').queryRecord('user', {self: true})
.then((response) => {
const [data] = response.data;
const normalizedData = get(this, 'store').normalize('user', data);
const user = get(this, 'store').push(normalizedData);
set(this, 'account', user);
return user;
});
}
});
throws Invalid attempt to destructure non-iterable instance TypeError
I've tried a few different combinations of above get various errors.
Any pointers would be appreciated.

Categories

Resources