How to authenticate to google OAuth2 in Vue.js? - javascript

As a beginner to vue.js I'm struggling with this problem for days.
I know that there are few plugins for that:
vue-google-auth
and
vue-google-signin-button
and
vue-authenticate
But none of these come with good documentations, so my attempts to make use of them failed.
I also could not find any tutorial on vue.js with OAuth2 authentication after extensive googling. So appreciate if someone could come up with a full working example or refer me to some complete code.

Example of usage without any plugins:
index.html
<meta name="google-signin-client_id" content="your-client-id.apps.googleusercontent.com"
/>
<script src="https://apis.google.com/js/platform.js"></script>
App.vue
<template>
<div v-show="!profile" id="g-signin2"></div>
<div v-if="profile">
<pre>{{ profile }}</pre>
<button #click="signOut">Sign Out</button>
</div>
</template>
<script>
export default {
mounted() {
this.initGoogleAuth();
this.renderGoogleAuthButton();
},
data() {
return {
profile: null
};
},
methods: {
onSignIn(user) {
const profile = user.getBasicProfile();
const fullName = profile.getName();
const email = profile.getEmail();
const imageUrl = profile.getImageUrl();
this.profile = { fullName, email, imageUrl };
},
signOut() {
var auth2 = window.gapi.auth2.getAuthInstance();
auth2.signOut().then(() => {
console.log("User signed out");
this.profile = null;
});
},
initGoogleAuth() {
window.gapi.load("auth2", function () {
window.gapi.auth2.init();
});
},
renderGoogleAuthButton() {
window.gapi.signin2.render("g-signin2", {
onsuccess: this.onSignIn
});
}
}
};
</script>

This is a working example with vue-google-oauth2.
You can install it with:
npm i vue-google-oauth2
Then you need to place these 2 lines of code in your APP ENTRY file, e.g. src/main.js
import GAuth from 'vue-google-oauth2'
Vue.use(GAuth, {clientId: 'XXXXXXXX'})
Where XXXXXXXX is the clientId you get from https://console.cloud.google.com/apis/
I will assume you have been there if you've tried to login with Google before.
Then you create this component
<template>
<div>
<h1>Test</h1>
<button #click="handleClickGetAuth" :disabled="!isInit">get auth code</button>
<button #click="handleClickSignIn" v-if="!isSignIn" :disabled="!isInit">signIn</button>
<button #click="handleClickSignOut" v-if="isSignIn" :disabled="!isInit">signOout</button>
</div>
</template>
<script>
export default {
name: 'test',
data () {
return {
isInit: false,
isSignIn: false
}
},
methods: {
async handleClickGetAuth() {
try {
const authCode = await this.$gAuth.getAuthCode()
const response = await this.$http.post('http://your-backend-server.com/auth/google', { code: authCode, redirect_uri: 'postmessage' })
} catch (error) {
// On fail do something
}
},
async handleClickSignIn(){
try {
const googleUser = await this.$gAuth.signIn()
console.log('user', googleUser)
this.isSignIn = this.$gAuth.isAuthorized
} catch (error) {
// On fail do something
console.error(error);
return null;
}
},
async handleClickSignOut(){
try {
await this.$gAuth.signOut()
this.isSignIn = this.$gAuth.isAuthorized
} catch (error) {
// On fail do something
}
}
},
mounted(){
let that = this
let checkGauthLoad = setInterval(function(){
that.isInit = that.$gAuth.isInit
that.isSignIn = that.$gAuth.isAuthorized
if(that.isInit) clearInterval(checkGauthLoad)
}, 1000);
}
}
</script>
All credits goes to
https://github.com/guruahn/vue-google-oauth2/blob/master/sample.html

Related

TypeError: verifier.verify is not a function- Vue.Js and Firebase

I am trying out firebase signInWithPhoneNumber for the first time using vue.js. I have followed the documentation. The Recaptcha is working but when it comes to signInWithPhoneNumber
I get this error:
TypeError: verifier.verify is not a function
at _verifyPhoneNumber (phone.ts?47a7:178:1)
at signInWithPhoneNumber (phone.ts?47a7:106:1)
at VueComponent.signIn (SignUp.vue?611a:68:1)
at callback (SignUp.vue?611a:50:1)
at eval (recaptcha_verifier.ts?c937:220:1)
at lW.Y.lW.o (recaptcha__en.js:788:178)
at M.R (recaptcha__en.js:437:30)
at new Promise (<anonymous>)
at lk.C (recaptcha__en.js:437:2)
at Array.<anonymous> (recaptcha__en.js:415:375)
I don't know what is causing it. I have searched for the solution but failed to do so? Could you please point out my mistake?
Here is my code:
Template:
<template>
<div>
<h2>SignUp</h2>
+60<input type="number" v-model="phNo" placeholder="Phone Number" />
<button id="sign-in-button" #click="sendOtp">Get Recaptcha</button>
<div id="recaptcha-container"></div>
<br />
<input type="number" v-model="otp" placeholder="OTP" />
<button #click="verifyOtp">Verify</button><br />
<button #click="sendOtp()">Resend OTP</button>
</div>
</template>
Script:
<script>
import { initializeApp } from "#firebase/app";
import firebaseConfig from "#/firebaseConfig";
import {
getAuth,
signInWithPhoneNumber,
RecaptchaVerifier,
} from "firebase/auth";
const app = initializeApp(firebaseConfig);
const auth = getAuth();
export default {
name: "SignUp",
data() {
return {
phNo: "",
appVerifier: "",
otp: "",
};
},
methods: {
sendOtp() {
window.recaptchaVerifier = new RecaptchaVerifier(
"recaptcha-container",
{
size: "normal",
callback: (response) => {
// reCAPTCHA solved, allow signInWithPhoneNumber.
this.appVerifier = response;
this.signIn(response);
},
"expired-callback": () => {
// Response expired. Ask user to solve reCAPTCHA again.
},
},
auth
);
recaptchaVerifier.render().then((widgetId) => {
window.recaptchaWidgetId = widgetId;
});
},
signIn(something) {
console.log("running");
let phoneNumber = "+60" + this.phNo;
signInWithPhoneNumber(auth, phoneNumber, something)
.then((confirmationResult) => {
// SMS sent. Prompt user to type the code from the message, then sign the
// user in with confirmationResult.confirm(code).
window.confirmationResult = confirmationResult;
this.verifyOtp();
})
.catch((error) => {
// Error; SMS not sent
// ...
console.log(error);
});
},
//
verifyOtp() {
//
console.log("running");
let vm = this;
let code = this.otp;
window.confirmationResult
.confirm(code)
.then(function (result) {
// User signed in successfully.
var user = result.user;
// ...
//route to set password !
vm.$router.push({ path: "/setPassword" });
})
.catch(function (error) {
// User couldn't sign in (bad verification code?)
// ...
});
},
},
created() {},
};
</script>

How to change network in metamask using react js

I am developing my first Dapp, I am using metamask and web3 for this. As far now, I am able to get my wallet balance and connect account to metamask. Now I am trying switch between two networks, I am using handleChainChanged, also I am passing chainId and Networkversion but, it is giving me error. I am uncertain about returning anything from changeNetwork function or I only have to pass chainId and Networkversion.
import { useStoreApi } from "./storeApi";
import { useState } from "react";
import useWeb3 from "./useWeb3";
import { Button, TextField } from "#material-ui/core";
import "./App.css";
function App() {
const { balance, address, message, setAddress, setBalance } = useStoreApi();
const web3 = useWeb3();
// get user account on button click
const getUserAccount = async () => {
if (window.ethereum) {
try {
await window.ethereum.enable();
web3.eth.getAccounts().then((accounts) => {
setAddress(accounts[0]);
updateBalance(accounts[0]);
console.log(accounts);
});
} catch (error) {
console.error(error);
}
} else {
alert("Metamask extensions not detected!");
}
web3.eth.getChainId().then(console.log);
};
const updateBalance = async (fromAddress) => {
await web3.eth.getBalance(fromAddress).then((value) => {
setBalance(web3.utils.fromWei(value, "ether"));
});
};
const changeNetwork = async () => {
if (window.ethereum) {
try {
await window.ethereum.enable();
window.ethereum._handleChainChanged({
chainId: 0x1,
networkVersion: 1,
});
} catch (error) {
console.error(error);
}
}
};
return (
<div className="App">
<header className="App-header">
{address ? (
<>
<p> Balance: {balance} </p>
</>
) : null}
<Button
onClick={() => getUserAccount()}
variant="contained"
color="primary"
>
Connect your account
</Button>
<Button onClick={changeNetwork} variant="contained" color="primary">
Switch to mainnet ethereum
</Button>
</header>
</div>
);
}
export default App;
What if the user doesn't have the required network added? Here is an expanded version which tries to switch, otherwise add the network to MetaMask:
const chainId = 137 // Polygon Mainnet
if (window.ethereum.networkVersion !== chainId) {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: web3.utils.toHex(chainId) }]
});
} catch (err) {
// This error code indicates that the chain has not been added to MetaMask
if (err.code === 4902) {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [
{
chainName: 'Polygon Mainnet',
chainId: web3.utils.toHex(chainId),
nativeCurrency: { name: 'MATIC', decimals: 18, symbol: 'MATIC' },
rpcUrls: ['https://polygon-rpc.com/']
}
]
});
}
}
}
You can use wallet_switchEthereumChain method of RPC API of Metamask
Visit: https://docs.metamask.io/guide/rpc-api.html#wallet-switchethereumchain
const changeNetwork = async () => {
if (window.ethereum) {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: Web3.utils.toHex(chainId) }],
});
});
} catch (error) {
console.error(error);
}
}
changeNetwork()
export async function switchToNetwork({
library,
chainId,
}: SwitchNetworkArguments): Promise<null | void> {
if (!library?.provider?.request) {
return
}
const formattedChainId = hexStripZeros(
BigNumber.from(chainId).toHexString(),
)
try {
await library.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: formattedChainId }],
})
} catch (error) {
// 4902 is the error code for attempting to switch to an unrecognized chainId
// eslint-disable-next-line #typescript-eslint/no-explicit-any
if ((error as any).code === 4902) {
const info = CHAIN_INFO[chainId]
await library.provider.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: formattedChainId,
chainName: info.label,
rpcUrls: [info.addNetworkInfo.rpcUrl],
nativeCurrency: info.addNetworkInfo.nativeCurrency,
blockExplorerUrls: [info.explorer],
},
],
})
// metamask (only known implementer) automatically switches after a network is added
// the second call is done here because that behavior is not a part of the spec and cannot be relied upon in the future
// metamask's behavior when switching to the current network is just to return null (a no-op)
try {
await library.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: formattedChainId }],
})
} catch (error) {
console.debug(
'Added network but could not switch chains',
error,
)
}
} else {
throw error
}
}
}

Mocking Secrets Manager module for JavaScript jest unit tests

I'm having trouble getting the AWS Secrets Manager module mocked for the jest unit tests... The part it errors on is the .promise(). When I remove that, the code doesn't work for the real Secrets Manager so I think it needs to stay there. How do I mock the getSecretData function so that getSecretData.promise() will work for the mock?
Here is the SecretsManager.js code:
import AWS from 'aws-sdk';
export class SecretsManager {
constructor() {
AWS.config.update({
region: 'us-east-1',
});
this.secretsManager = new AWS.SecretsManager();
}
async getSecretData(secretName) {
try {
const response = await this.secretsManager.getSecretValue({
SecretId: secretName,
}).promise();
const secretString = response.SecretString;
const parsedSecret = JSON.parse(secretString);
return parsedSecret;
} catch (e) {
console.log('Failed to get data from AWS Secrets Manager.');
console.log(e);
throw new Error('Unable to retrieve data.');
}
}
}
Here is the SecretsManager.test.js code:
import { SecretsManager } from '../utils/SecretsManager';
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
async getSecretValue({
SecretId: secretName
}) {
return {
promise: function () {
return {
UserName: 'test',
Password: 'password',
};
}
};
}
};
}
}
});
describe('SecretsManager.js', () => {
describe('Given I have a valid secret name', () => {
describe('When I send a request for test_creds', () => {
it('Then the correct data is returned.', async () => {
const mockReturnValue = {
UserName: 'test',
Password: 'password',
};
const logger = getLogger();
const secretManager = new SecretsManager();
const result = await secretManager.getSecretData('test_creds');
expect(result).toEqual(mockReturnValue)
});
});
describe('When I send a request without data', () => {
it('Then an error is thrown.', async () => {
const secretManager = new SecretsManager();
await expect(secretManager.getSecretData()).rejects.toThrow();
});
});
});
});
This is the error I get when running the tests:
this.secretsManager.getSecretValue(...).promise is not a function
Any suggestions or pointers are greatly appreciated!
Thank you for looking at my post.
I finally got it to work... figures it'd happen shortly after posting the question, but instead of deleting the post I'll share how I changed the mock to make it work incase it helps anyone else.
Note: This is just the updated mock, the tests are the same as in the question above.
// I added this because it's closer to how AWS returns data for real.
const mockSecretData = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
}
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
getSecretValue: function ( { SecretId } ) {
{
// Adding function above to getSecretValue: is what made the original ".promise() is not a function" error go away.
if (SecretId === 'test_creds') {
return {
promise: function () {
return mockSecretData;
}
};
} else {
throw new Error('mock error');
}
}
}
};
}
}});
I ran into this issue as well. There may be a more elegant way to handle this that also allows for greater control and assertion, but I haven't found one. Note that the in-test option may work better with newer versions of Jest.
I personally solved this issue by making use of manual mocks and a custom mock file for aws-sdk. In your case, it would look something like the following:
# app_root/__tests__/__mocks__/aws-sdk.js
const exampleResponse = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
};
const mockPromise = jest.fn().mockResolvedValue(exampleResponse);
const getSecretValue = jest.fn().mockReturnValue({ promise: mockPromise });
function SecretsManager() { this.getSecretValue = getSecretValue };
const AWS = { SecretsManager };
module.exports = AWS;
Then in your test file:
// ... imports
jest.mock('aws-sdk');
// ... your tests
So, in a nutshell:
Instead of mocking directly in your test file, you're handing mocking control to a mock file, which Jest knows to look for in the __mocks__ directory.
You create a mock constructor for the SecretsManager in the mock file
SecretsManager returns an instance with the mock function getSecretValue
getSecretValue returns a mock promise
the mock promise returns the exampleResponse
Bada boom, bada bing. You can read more here.
I ran into a same issue, I have tried to solve as below. It worked perfectly in my case.
Terminalsecret.ts
import AWS from 'aws-sdk';
AWS.config.update({
region: "us-east-1",
});
const client = new AWS.SecretsManager();
export class Secret {
constructor(){}
async getSecret(secretName: string) {
let secret: any;
const data = await client.getSecretValue({ SecretId: secretName).promise();
if ('SecretString' in data) {
secret = data.SecretString;
} else {
const buff = Buffer.alloc(data.SecretBinary as any, 'base64');
secret = buff.toString('ascii');
}
const secretParse = JSON.parse(secret);
return secretParse[secretName];
}
}
Terminalsecret.test.ts
import { SecretsManager as fakeSecretsManager } from 'aws-sdk';
import { Secret } from './terminalSecret';
jest.mock('aws-sdk');
const setup = () => {
const mockGetSecretValue = jest.fn();
fakeSecretsManager.prototype.getSecretValue = mockGetSecretValue;
return { mockGetSecretValue };
};
describe('success', () => {
it('should call getSecretValue with the argument', async () => {
const { mockGetSecretValue } = setup();
mockGetSecretValue.mockReturnValueOnce({
promise: async () => ({ SecretString: '{"userName": "go-me"}' })
});
const fakeName = 'userName';
const terminalSecretMock: TerminalSecret = new TerminalSecret()
terminalSecretMock.getTerminalSecret(fakeName);
expect(mockGetSecretValue).toHaveBeenCalledTimes(1);
});
});

Update DOM after fetching from API in VueJS

I am having troubles updating the DOM after fetching from an API.
My object is fetching the data correctly but the DOM is being rendered before and it won't update after receiving the API Data, I can't seem to understand why is not updating itself.
Here is my code:
<template>
<div>
<h1>Weather</h1>
{{ weather }}
</div>
</template>
<script>
export default {
name: 'Weather',
data() {
return {
weather : {},
}
},
created() {
this.getWeather()
},
methods: {
async getWeather() {
let self = this;
try {
const response = await fetch('https://api.weatherbit.io/v2.0/current?city=Berlin&country=DE&key=KEY');
const myJson = await response.json();
self.weather.temp = myJson.data[0].temp;
self.weather.sensation = myJson.data[0].app_temp;
self.weather.description = myJson.data[0].weather.description;
} catch (error) {
console.error(error);
}
}
</script>
You should assign the response value to the weather property directly like this.
methods: {
async getWeather() {
let self = this;
try {
const response = await fetch('https://api.weatherbit.io/v2.0/current?city=Berlin&country=DE&key=dcbea1b771ab41f09cd6b138d8cd50c2');
const myJson = await response.json();
self.weather = myJson.data[0].temp;
console.log(self.weather);
} catch (error) {
console.error(error);
}
}
}
Here is the working example.
https://jsfiddle.net/srfpw785/
I think you should insert your logic inside mounted() , not in created() , this should fix your problem with rendering.
<template>
<div>
<h1>Weather</h1>
{{ weather }}
</div>
</template>
<script>
export default {
name: 'Weather',
data() {
return {
weather : {},
}
},
mounted() {
this.getWeather()
},
methods: {
async getWeather() {
let self = this;
try {
const response = await fetch('https://api.weatherbit.io/v2.0/current?city=Berlin&country=DE&key=dcbea1b771ab41f09cd6b138d8cd50c2');
const myJson = await response.json();
self.weather.temp = myJson.data[0].temp;
self.weather.sensation = myJson.data[0].app_temp;
self.weather.description = myJson.data[0].weather.description;
} catch (error) {
console.error(error);
}
}
</script>
These are the steps in Vue lifecycle :
beforCreate,
created,
beforeMount,
mounted,
beforeUpdate,
updated,
beforeDestroy,
destroyed
Hope this will help you to understand Vue lifecycle :)

Why are API requests being made to the wrong URL (Vue.js 2)?

I'm fooling around trying to learn stuff (Vue.js 2 with routing) and I was wondering why whenever I was on any other routes other than the home ('/') route, said localhost url gets prepended to the appropriate url when making API calls. An example would be the following:
const url = `'https://www.reddit.com/r/'${ this.sub }/.json?limit=10'`; // this.sub being the corresponding subreddit string
which results in something like this:
'http://localhost:5000/subreddits/politics/https://www.reddit.com/r/politics/.json?limit=10'
Below is the relevant code:
<script>
export default {
data() {
return {
sub: this.$route.params.sub,
posts: [],
}
},
watch: {
'$route'(to, from) {
this.sub = to.params.sub;
}
},
methods: {
fetchPosts: async function () {
const url = `'https://www.reddit.com/r/'${ this.sub }/.json?limit=10'`;
try {
const res = await (await fetch(url)).json();
this.posts = await (res.data.children);
} catch(err) {
console.error(err);
}
}
},
mounted() {
this.fetchPosts();
},
}
</script>
There are 2 problems in your project.
1. The request whose host is reddit can't be send within localhost.
2. if you use back quote, single quote is redundant.
IF YOU USED VUE-CLI TO INIT THE PROJECT, to solve these problem, there are 2 step you should do.
in /config/index.js file, find proxyTable, and add this:
```
proxyTable: {
'/reddit': {
target: 'https://www.reddit.com/r',
changeOrigin: true,
pathRewrite: {
'^/reddit': ''
}
}
}
in the relevant code:
```
<script>
export default {
data() {
return {
sub: this.$route.params.sub,
posts: [],
}
},
watch: {
'$route'(to, from) {
this.sub = to.params.sub;
}
},
methods: {
fetchPosts: async function () {
const url = `/reddit/'${ this.sub }/.json?limit=10`;
try {
const res = await (await fetch(url)).json();
this.posts = await (res.data.children);
} catch(err) {
console.error(err);
}
}
},
mounted() {
this.fetchPosts();
},
}
</script>
```

Categories