diff --git a/google-userinfo/README.md b/google-userinfo/README.md new file mode 100644 index 0000000..ff41025 --- /dev/null +++ b/google-userinfo/README.md @@ -0,0 +1,33 @@ +# google-userinfo + +This add-on fetches the user's info from their Google account and displays their name in a notification. + +In detail, it adds a browser action. When the user clicks the browser action, the add-on: + +* uses `identity.launchWebAuthFlow()` to get an access token from Google. This asks the user to sign into Google, if they are not already signed in (authentication), then asks the user if they grant the WebExtension permission to get their user info, if the user has not already granted this permission (authorization). + +* validates the access token + +* passes the access token into a Google API that returns the user's info + +* displays a notification containing the user's name. + +This is following essentially the process documented here: https://developers.google.com/identity/protocols/OAuth2UserAgent. + +## Setup ## + +There's some basic setup you must do before you can use this example. + +* **getting the redirect URL**: this represents the end point of the flow, where the access token is delivered to the WebExtension. The redirect URL is derived from the WebExtension's ID. To get the redirect URL for this example, install it, visit about:addons, and open its "Preferences" page. It will look something like "https://dc6ae45f54e3d55036b819b93a1876228e5f5f7b.extensions.allizom.org/". + +* **registering your add-on with Google as an OAuth2 client**. + * Visit https://console.developers.google.com/apis/credentials + * Click "Create credentials", and select "OAuth client ID" + * Select "Web application", and give it a name. The name is shown to the user to help them understand whether to authorize the add-on. + * Paste the redirect URL into the " Authorized redirect URIs" box. + * Click "Create" + * You'll see a popup containing a Client ID and a secret. Copy the client ID (you can ignore the secret). + * Paste this value into authorize.js in place of YOUR-CLIENT-ID. + * Reload the add-on. + +Note that because you have to edit authorize.js, we can't provide a prebuilt, presigned version of this add-on in the "builds" directory of this repo, as we can for other examples. So to run this example in Firefox you'll need to use the ["Load Temporary Add-on"](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox) feature, or use the [web-ext](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Getting_started_with_web-ext) tool. diff --git a/google-userinfo/background/authorize.js b/google-userinfo/background/authorize.js new file mode 100644 index 0000000..633c41b --- /dev/null +++ b/google-userinfo/background/authorize.js @@ -0,0 +1,74 @@ +const REDIRECT_URL = browser.identity.getRedirectURL(); +const CLIENT_ID = "YOUR-CLIENT-ID"; +const SCOPES = ["openid", "email", "profile"]; +const AUTH_URL = +`https://accounts.google.com/o/oauth2/auth +?client_id=${CLIENT_ID} +&response_type=token +&redirect_uri=${encodeURIComponent(REDIRECT_URL)} +&scope=${encodeURIComponent(SCOPES.join(' '))}`; +const VALIDATION_BASE_URL="https://www.googleapis.com/oauth2/v3/tokeninfo"; + +function extractAccessToken(redirectUri) { + let m = redirectUri.match(/[#\?](.*)/); + if (!m || m.length < 1) + return null; + let params = new URLSearchParams(m[1].split("#")[0]); + return params.get("access_token"); +} + +/** +Validate the token contained in redirectURL. +This follows essentially the process here: +https://developers.google.com/identity/protocols/OAuth2UserAgent#tokeninfo-validation +- make a GET request to the validation URL, including the access token +- if the response is 200, and contains an "aud" property, and that property +matches the clientID, then the response is valid +- otherwise it is not valid + +Note that the Google page talks about an "audience" property, but in fact +it seems to be "aud". +*/ +function validate(redirectURL) { + const accessToken = extractAccessToken(redirectURL); + if (!accessToken) { + throw "Authorization failure"; + } + const validationURL = `${VALIDATION_BASE_URL}?access_token=${accessToken}`; + const validationRequest = new Request(validationURL, { + method: "GET" + }); + + function checkResponse(response) { + return new Promise((resolve, reject) => { + if (response.status != 200) { + reject("Token validation error"); + } + response.json().then((json) => { + if (json.aud && (json.aud === CLIENT_ID)) { + resolve(accessToken); + } else { + reject("Token validation error"); + } + }); + }); + } + + return fetch(validationRequest).then(checkResponse); +} + +/** +Authenticate and authorize using browser.identity.launchWebAuthFlow(). +If successful, this resolves with a redirectURL string that contains +an access token. +*/ +function authorize() { + return browser.identity.launchWebAuthFlow({ + interactive: true, + url: AUTH_URL + }); +} + +function getAccessToken() { + return authorize().then(validate); +} diff --git a/google-userinfo/background/main.js b/google-userinfo/background/main.js new file mode 100644 index 0000000..ac70222 --- /dev/null +++ b/google-userinfo/background/main.js @@ -0,0 +1,23 @@ +function notifyUser(user) { + browser.notifications.create({ + "type": "basic", + "title": "Google info", + "message": `Hi ${user.name}` + });} + +function logError(error) { + console.error(`Error: ${error}`); +} + +/** +When the button's clicked: +- get an access token using the identity API +- use it to get the user's info +- show a notification containing some of it +*/ +browser.browserAction.onClicked.addListener(() => { + getAccessToken() + .then(getUserInfo) + .then(notifyUser) + .catch(logError); +}); diff --git a/google-userinfo/background/userinfo.js b/google-userinfo/background/userinfo.js new file mode 100644 index 0000000..84a5427 --- /dev/null +++ b/google-userinfo/background/userinfo.js @@ -0,0 +1,22 @@ +/** +Fetch the user's info, passing in the access token in the Authorization +HTTP request header. +*/ +function getUserInfo(accessToken) { + const requestURL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"; + const requestHeaders = new Headers(); + requestHeaders.append('Authorization', 'Bearer ' + accessToken); + const driveRequest = new Request(requestURL, { + method: "GET", + headers: requestHeaders + }); + + return fetch(driveRequest).then((response) => { + if (response.status === 200) { + return response.json(); + } else { + throw response.status; + } + }); + +} diff --git a/google-userinfo/icons/LICENSE b/google-userinfo/icons/LICENSE new file mode 100644 index 0000000..18fae49 --- /dev/null +++ b/google-userinfo/icons/LICENSE @@ -0,0 +1 @@ +The "person-32.png" "person-48.png" icons are taken from the Ionicons iconset (http://ionicons.com/), and are used here under the MIT license: http://opensource.org/licenses/MIT. diff --git a/google-userinfo/icons/person-32.png b/google-userinfo/icons/person-32.png new file mode 100644 index 0000000..38a16bd Binary files /dev/null and b/google-userinfo/icons/person-32.png differ diff --git a/google-userinfo/icons/person-48.png b/google-userinfo/icons/person-48.png new file mode 100644 index 0000000..0cb787b Binary files /dev/null and b/google-userinfo/icons/person-48.png differ diff --git a/google-userinfo/manifest.json b/google-userinfo/manifest.json new file mode 100644 index 0000000..e582748 --- /dev/null +++ b/google-userinfo/manifest.json @@ -0,0 +1,41 @@ +{ + + "name": "Google User Info", + "version": "1", + "manifest_version": 2, + "applications": { + "gecko": { + "id": "google-user-info@mozilla.org", + "strict_min_version": "53a1" + } + }, + + "icons": { + "48": "icons/person-48.png" + }, + + "browser_action": { + "browser_style": true, + "default_icon": "icons/person-32.png" + }, + + "permissions": [ + "identity", + "notifications", + "*://www.googleapis.com/*", + "*://accounts.google.com/*" + ], + + "background": { + "scripts": [ + "background/authorize.js", + "background/userinfo.js", + "background/main.js" + ] + }, + + "options_ui": { + "page": "options/options.html" + } + +} diff --git a/google-userinfo/options/options.html b/google-userinfo/options/options.html new file mode 100644 index 0000000..9920792 --- /dev/null +++ b/google-userinfo/options/options.html @@ -0,0 +1,16 @@ + + + +
+ + + + + +