Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(automotive): qr code login flow for automotive app #3974

Open
wants to merge 10 commits into
base: next
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import querystring from 'querystring';
import url from 'url';
import {EventEmitter} from 'events';

import {base64, oneFlight, whileInFlight} from '@webex/common';
import {grantErrors, WebexPlugin} from '@webex/webex-core';
Expand Down Expand Up @@ -66,6 +67,18 @@ const Authorization = WebexPlugin.extend({

namespace: 'Credentials',


/**
* Stores the interval ID for QR code polling
* @instance
* @memberof AuthorizationBrowserFirstParty
* @type {?number}
* @private
*/
pollingRequest: null,

eventEmitter: new EventEmitter(),

/**
* Initializer
* @instance
Expand Down Expand Up @@ -240,6 +253,147 @@ const Authorization = WebexPlugin.extend({
});
},

/**
* Get an OAuth Login URL for QRCode. Generate QR code based on the returned URL.
* @instance
* @memberof AuthorizationBrowserFirstParty
* @throws {Error} When the request fails
* @returns {Promise<{verification_uri_complete: string, verification_uri: string, user_code: string, device_code: string, interval: number, expires_in: number}>}
*/
initQRCodeLogin() {
return this.webex
.request({
method: 'POST',
service: 'oauth-helper',
resource: '/actions/device/authorize',
form: {
client_id: this.config.client_id,
scope: this.config.scope,
},
auth: {
user: this.config.client_id,
pass: this.config.client_secret,
sendImmediately: true,
},
maxinteger marked this conversation as resolved.
Show resolved Hide resolved
})
.then((res) => {
const {user_code, verification_uri, verification_uri_complete} = res.body;
this.eventEmitter.emit('qrcode-login', {
eventType: 'user-code',
userData: {
user_code,
verification_uri,
verification_uri_complete,
}
});
// if device authorization success, then start to poll server to check whether the user has completed authorization
this.startQRCodePolling(res.body);
return res.body;
})
.catch((res) => {
return Promise.reject(res);
});
},

/**
* Polling the server to check whether the user has completed authorization
* @instance
* @memberof AuthorizationBrowserFirstParty
* @param {Object} options
* @throws {Error} When the request fails
* @returns {Promise}
*/
startQRCodePolling(options = {}) {
if (!options.device_code) {
return Promise.reject(new Error('A deviceCode is required'));
}

if (this.pollingRequest) {
return Promise.reject(new Error('There is already a polling request'));
}

const {device_code: deviceCode, interval = 2, expires_in: expiresIn = 300} = options;

let attempts = 0;
const maxAttempts = expiresIn / interval;

return new Promise((resolve, reject) => {
this.pollingRequest = setInterval(() => {
attempts += 1;

const currentAttempts = attempts;
this.webex
.request({
method: 'POST',
service: 'oauth-helper',
resource: '/actions/device/token',
form: {
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: deviceCode,
client_id: this.config.client_id,
},
auth: {
user: this.config.client_id,
pass: this.config.client_secret,
sendImmediately: true,
},
})
.then((res) => {
this.eventEmitter.emit('qrcode-login', {
eventType: 'authorization_success',
authorized: true,
});
this.cancelQRCodePolling();
resolve(res.body);
})
.catch((res) => {
if (currentAttempts >= maxAttempts) {
reject(new Error('Authorization timed out'));
this.eventEmitter.emit('qrcode-login', {
eventType: 'authorization_failed',
error: 'Authorization timed out',
});
this.cancelQRCodePolling();
return;
}
// if the statusCode is 428 which means that the authorization request is still pending
// as the end user hasn't yet completed the user-interaction steps. So keep polling.
if (res.statusCode === 428) {
this.eventEmitter.emit('qrcode-login', {
eventType: 'authorization_pending',
error: res.body
});
return;
}

this.cancelQRCodePolling();

reject(res);
this.eventEmitter.emit('qrcode-login', {
eventType: 'authorization_failed',
error: res.body
});
});
}, interval * 1000);
});
},
xinhyao marked this conversation as resolved.
Show resolved Hide resolved

/**
* cancel polling request
* @instance
* @memberof AuthorizationBrowserFirstParty
* @returns {void}
*/
cancelQRCodePolling() {
if (this.pollingRequest) {
clearInterval(this.pollingRequest);
this.eventEmitter.emit('qrcode-login', {
eventType: 'polling_canceled',
});
this.pollingRequest = null;
}
},

/**
* Extracts the orgId from the returned code from idbroker
* Description of how to parse the code can be found here:
Expand Down
Loading
Loading