I want to get the messages of users by gmail api. For that google authorization is needed. I managed to authorize the user by following code -
let authBtn = document.getElementById('authorize_button');
const CLIENT_ID = 'XXXXXX-XXXXXXXXXXX.apps.googleusercontent.com';
const API_KEY = 'XXXXXX-XXXXXXXXXXXXXXX';
const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest';
const SCOPES = 'https://www.googleapis.com/auth/gmail.readonly';
let tokenClient;
let gapiInited = false;
let gisInited = false;
authBtn.style.visibility = 'hidden';
function gapiLoaded() {
gapi.load('client', intializeGapiClient);
}
async function intializeGapiClient() {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
}
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '',
});
gisInited = true;
maybeEnableButtons();
}
function maybeEnableButtons() {
if (gapiInited && gisInited) {
authBtn.style.visibility = 'visible';
}
}
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) throw (resp);
authBtn.innerText = 'Refresh';
await getMessages();
};
if (gapi.client.getToken() === null) {
tokenClient.requestAccessToken({prompt: 'consent'});
} else {
tokenClient.requestAccessToken({prompt: ''});
}
}
In above code gapi.client.getToken() === null is always false. Everytime I refresh the page I have to reauthorize user with prompt: 'consent'.
I also want user to stay signed in until user sign out.
How can I achieve by modifying the above code?
Can Please someone help me?
You are using a system that requires a server-side authentication flow, read about properly handling that here:
https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
The gapi JavaScript is browser code (you obviously know this because the question specifies all sorts of DOM related code), and therefore Authentication is fundamentally not going to be possible entirely in the browser without a server-side flow to handle the callbacks from Google that occur out-of-band from the browser.
The only exception I can find to the rule of having a server-side component is to credential manager API:
https://developers.google.com/identity/gsi/web/guides/display-browsers-native-credential-manager
It seems to significantly simplify things, but from what I can tell supports Chrome only (maybe including chrome-based browsers Edge, Brave, etc. but maybe not Chromium as it seems to be needing Google accounts in the browser itself, e.g. login is not managed by your code for your website but the user using the browser directly before they visit your site)
Related
I have a node.js push notification server (with websocket server also running on it), and it is sending push events whenever the websocket server receives a message. The problem with this is that no matter what happens, when the websocket server gets a message, it broadcasts a message no matter what. While this is fine, it can become a problem for clients actively on the website, because the notification is unnecessary as the person can already see what happened, and the serviceworker can't access the DOM so it can't tell if the window is open or not. I tried the navigator.serviceWorker.controller.postMessage method to tell the server to either "stop" the notification service, or "start" the notification service in the below code. It all seems correct and I'm not getting any errors, but it doesn't seem to be working as it shows a notification whether the window is open or not.
(main.js)
window.onload = function() {
if (Notification.permission !== "granted") {
Notification.requestPermission((permit)=>{
console.log(permit);
});
}
subscribe().catch(error => console.error(error));
navigator.serviceWorker.controller.postMessage("stop"); //Tell serviceWorker to not show notifications
}
window.onbeforeunload = function() {
navigator.serviceWorker.controller.postMessage("start"); //Tell serviceWorker that it is allowed show notifications
}
function urlBase64ToUint8Array(base64String) {const padding = '='.repeat((4 - base64String.length % 4) % 4);const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');const rawData = window.atob(base64);const outputArray = new Uint8Array(rawData.length);for (let i = 0; i < rawData.length; ++i) {outputArray[i] = rawData.charCodeAt(i);}return outputArray;}
const publicVapidKey = <key here>;
async function subscribe() {
if ('serviceWorker' in navigator) {
//register serviceworker
const register = await navigator.serviceWorker.register('/sw.js', {
scope: '/'
});
//subscribe to notifications
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicVapidKey),
});
//send subscription to server
await fetch('http://remoteserver.com:8080/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'Content-Type': 'application/json'
}
});
} else {console.error('Service workers are not supported in this browser');}
}
(sw.js)
var allowed;
self.addEventListener('message', event => {
if (event.data == "stop") {
allowed = false;
} else if (event.data == "start") {
allowed = true;
}
});
self.addEventListener('push', event => {
if (allowed == true) {
const data = event.data.json();
self.registration.showNotification(data.title, {
body: data.body,
icon: data.icon
});
}
});
Does anyone know what is going on?
The service worker is a mediary between your client app and server.
If the client is determined to be active (up to you to make that determination in your code — see links below), it should communicate to the sw informing it as such.
You can implement a heartbeat/ping-pong between the sw and your client (not unlike websockets) that occurs at an interval, and if the client fails to respond (or responds in an enumeration that isn't "active"), then your sw will know, and can respond appropriately (whether that's simply not emitting the notifications received from the server, or unsubscribing entirely, etc.)
More reading: Page Visibility API, Document.hasFocus(), etc.
I am trying to implement an identity check for my chrome extension since I want to sell it to some users.
The code below is usig the chrome.identity API to get the unique OpenID in combination with the email that is logged in. Then it is fetching data from a pastebin and checks if the id is included or not.
If not I would want to block the user from using the extension. What would be the best approach?
My code:
// license check
chrome.identity.getProfileUserInfo({ 'accountStatus': 'ANY' }, async function (info) {
email = info.email;
console.log(info.id);
let response = await fetch('https://pastebin.com/*****');
let data = await response.text();
console.log(data.indexOf(info.id));
if (data.indexOf(info.id) !== -1) {
console.log('included');
} else {
console.log('not included');
// block chrome extension usage;
}
});
I found a pretty simple solution that should work for most cases.
// license check
chrome.identity.getProfileUserInfo({ 'accountStatus': 'ANY' }, async function (info) {
email = info.email;
console.log(info.id);
let response = await fetch('https://pastebin.com/*****');
let data = await response.text();
console.log(data.indexOf(info.id));
if (data.indexOf(info.id) !== -1) {
console.log('included');
} else {
console.log('not included');
// block chrome extension usage;
chrome.browserAction.setPopup({ popup: 'index.html' }); // index.html has to
be in the
extension folder and can have e. g. a <h1> which says "Invalid license"
}
});
In a situation where the user reboots his/her pc without closing chrome, the tabs will automatically return when chrome is being re-opened.
What I want to know is how to distinguish that the user opened a new tab.
I have the following function:
const interceptRequests = () => {
chrome.webRequest.onBeforeRequest.addListener(
async details => {
if (requestIsUserInitiated(details)) {
const tabs = await getAllTabs();
console.log('get all the tabs', tabs);
console.log('details', details);
}
},
{urls: ["<all_urls>"]},
["blocking"]
);
};
Where:
const requestIsUserInitiated = details =>
details.type === 'main_frame'
&& !details.initiator // ignoring requests made by the website
&& details.method === 'GET';
The thing is, there doesn't seem to be a straightforward way to distinguish whether the browser initiated the request, or the user, unless there is a property that I'm not seeing:
Of course there is the following listener:
chrome.tabs.onCreated.addListener(function(tab) {
});
However, when Chrome opens all the tabs, then it will probably get triggered anyway.
So how can one make the distinction? I want to trigger a function when it is certain that the user initiated the new tab to make the request.
import { interceptRequests } from './requestInterceptor/index';
import { cleanTabs } from './tabCleaner/index';
let extensionIsInitialized = false;
const startBackgroundProcess = () => {
if (! extensionIsInitialized) {
interceptRequests();
cleanTabs();
extensionIsInitialized = true;
}
};
chrome.runtime.onStartup.addListener(startBackgroundProcess);
chrome.runtime.onInstalled.addListener(startBackgroundProcess);
I am trying to configure my Angular app to use the OAuth2 library (angular-oauth2-oidc).
In the file auth.service.ts my OAuthService is configured:
this.oauthService.loginUrl = 'https://serverdomain.com/authorization/';
this.oauthService.redirectUri = 'http://localhost:4200/auth';
this.oauthService.clientId = '1111-2222-3333-4444-5555-6666-7777';
this.oauthService.setStorage(localStorage);
this.oauthService.requireHttps = false;
this.oauthService.responseType = 'token';
this.oauthService.tokenEndpoint = 'https://serverdomain.com/token/';
this.oauthService.oidc = false;
this.oauthService.issuer = 'https://serverdomain.com/authorization/';
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.requestAccessToken = true;
this.oauthService.showDebugInformation = true;
this.oauthService.scope = 'openid profile email';
this.oauthService.tryLogin({
onTokenReceived: context => {
console.log(context);
}
});
obtainAccessToken() {
this.oauthService.initImplicitFlow();
}
isLoggedIn() {
if (this.oauthService.getAccessToken() === null) {
return false;
}
return true;
}
logout() {
this.oauthService.logOut();
location.reload();
}
logAuthData() {
console.log(this.oauthService.hasValidAccessToken());
}
In my home component I added a button to trigger the implicit flow and get an access token.
After initialization of the implicit flow the app redirects to the correct login page of the provider where I log in and get redirected to my redirectUri of my OAuth configuration.
BUT
If I try to get a state, for example I call isLoggedIn method, I get always false. Also, there is a false return at hasValidAccessToken().
Can anybody show how to correctly configure angular 5 and oauth2?
I need also a possibility to store my given access token to use them in my rest methods to get data.
Need to add JWKs token Validator in your configration. And set Jwks as per your Response type
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
I'm upgrading/rewriting an existing angular app to use angular2. My problem is that I want to open a OAuth flow in a new pop up window and once the OAuth flow is completed use window.postMessage to communicate back to the angular 2 app that the OAuth flow was successful.
Currently what I have is in the angular 2 service is
export class ApiService {
constructor(private _loggedInService: LoggedInService) {
window.addEventListener('message', this.onPostMessage, false);
}
startOAuthFlow() {
var options = 'left=100,top=10,width=400,height=500';
window.open('http://site/connect-auth', , options);
}
onPostMessage(event) {
if(event.data.status === "200") {
// Use an EventEmitter to notify the other components that user logged in
this._loggedInService.Stream.emit(null);
}
}
}
This template that is loaded at the end of the OAuth flow
<html>
<head>
<title>OAuth callback</title>
<script>
var POST_ORIGIN_URI = 'localhost:8000';
var message = {"status": "200", "jwt":"2"};
window.opener.postMessage(message, POST_ORIGIN_URI);
window.close();
</script>
</head>
</html>
Using window.addEventListener like this seems to completely break the angular 2 app, dereferencing this.
So my question is can I use window.addEventListener or should I not use postMessage to communicate back to the angular2 app?
** Complete angular2 noob so any help is appreciated
I have a complete Angular2 OAuth2 skeleton application on Github that you can refer to.
It makes use of an Auth service for OAuth2 Implicit grants that in turn uses a Window service to create the popup window. It then monitors that window for the access token on the URL.
You can access the demo OAuth2 Angular code (with Webpack) here.
Here is the login routine from the Auth service, which will give you an idea of what's going on without having to look at the entire project. I've added a few extra comments in there for you.
public doLogin() {
var loopCount = this.loopCount;
this.windowHandle = this.windows.createWindow(this.oAuthTokenUrl, 'OAuth2 Login');
this.intervalId = setInterval(() => {
if (loopCount-- < 0) { // if we get below 0, it's a timeout and we close the window
clearInterval(this.intervalId);
this.emitAuthStatus(false);
this.windowHandle.close();
} else { // otherwise we check the URL of the window
var href:string;
try {
href = this.windowHandle.location.href;
} catch (e) {
//console.log('Error:', e);
}
if (href != null) { // if the URL is not null
var re = /access_token=(.*)/;
var found = href.match(re);
if (found) { // and if the URL has an access token then process the URL for access token and expiration time
console.log("Callback URL:", href);
clearInterval(this.intervalId);
var parsed = this.parse(href.substr(this.oAuthCallbackUrl.length + 1));
var expiresSeconds = Number(parsed.expires_in) || 1800;
this.token = parsed.access_token;
if (this.token) {
this.authenticated = true;
}
this.startExpiresTimer(expiresSeconds);
this.expires = new Date();
this.expires = this.expires.setSeconds(this.expires.getSeconds() + expiresSeconds);
this.windowHandle.close();
this.emitAuthStatus(true);
this.fetchUserInfo();
}
}
}
}, this.intervalLength);
}
Feel free to ask if you have any questions or problems getting the app up and running.
So with a bit of investigation found out the problem. I was de-referencing this. This github wiki helped me understand it a bit more.
To solve it for my case needed to do a couple of things. Firstly I created a service that encapsulated the adding of an eventListener
import {BrowserDomAdapter} from 'angular2/platform/browser';
export class PostMessageService {
dom = new BrowserDomAdapter();
addPostMessageListener(fn: EventListener): void {
this.dom.getGlobalEventTarget('window').addEventListener('message', fn,false)
}
}
Then using this addPostMessageListener I can attach a function in my other service to fire
constructor(public _postMessageService: PostMessageService,
public _router: Router) {
// Set up a Post Message Listener
this._postMessageService.addPostMessageListener((event) =>
this.onPostMessage(event)); // This is the important as it means I keep the reference to this
}
Then it works how I expected keeping the reference to this
I think this is the Angular2 way:
(Dart code but TS should be quite similar)
#Injectable()
class SomeService {
DomAdapter dom;
SomeService(this.dom) {
dom.getGlobalEventTarget('window').addEventListener("message", fn, false);
}
}
I fiddled around with this for ages but in the end, the most robust way for me was to redirect the user to the oath page
window.location.href = '/auth/logintwitter';
do the oath dance in the backend (I used express) and then redirect back to a receiving front end page...
res.redirect(`/#/account/twitterReturn?userName=${userName}&token=${token}`);
There are some idiosyncracies to my solution because e.g. I wanted to use only JsonWebToken on the client regardless of login type, but if you are interested, whole solution is here.
https://github.com/JavascriptMick/learntree.org