浏览代码

[add] login with google

B Thibault 8 年之前
父节点
当前提交
13af9c2db2

+ 50 - 0
srv/public/btn_google_dark_normal_ios.svg

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="46px" height="46px" viewBox="0 0 46 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+    <!-- Generator: Sketch 3.3.3 (12081) - http://www.bohemiancoding.com/sketch -->
+    <title>btn_google_dark_normal_ios</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.168 0" in="shadowBlurOuter1" type="matrix" result="shadowMatrixOuter1"></feColorMatrix>
+            <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter2"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter2" result="shadowBlurOuter2"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.084 0" in="shadowBlurOuter2" type="matrix" result="shadowMatrixOuter2"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="shadowMatrixOuter2"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="40" height="40" rx="2"></rect>
+        <rect id="path-3" x="5" y="5" width="38" height="38" rx="1"></rect>
+    </defs>
+    <g id="Google-Button" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+        <g id="9-PATCH" sketch:type="MSArtboardGroup" transform="translate(-608.000000, -219.000000)"></g>
+        <g id="btn_google_dark_normal" sketch:type="MSArtboardGroup" transform="translate(-1.000000, -1.000000)">
+            <g id="button" sketch:type="MSLayerGroup" transform="translate(4.000000, 4.000000)" filter="url(#filter-1)">
+                <g id="button-bg">
+                    <use fill="#4285F4" fill-rule="evenodd" sketch:type="MSShapeGroup" xlink:href="#path-2"></use>
+                    <use fill="none" xlink:href="#path-2"></use>
+                    <use fill="none" xlink:href="#path-2"></use>
+                    <use fill="none" xlink:href="#path-2"></use>
+                </g>
+            </g>
+            <g id="button-bg-copy">
+                <use fill="#FFFFFF" fill-rule="evenodd" sketch:type="MSShapeGroup" xlink:href="#path-3"></use>
+                <use fill="none" xlink:href="#path-3"></use>
+                <use fill="none" xlink:href="#path-3"></use>
+                <use fill="none" xlink:href="#path-3"></use>
+            </g>
+            <g id="logo_googleg_48dp" sketch:type="MSLayerGroup" transform="translate(15.000000, 15.000000)">
+                <path d="M17.64,9.20454545 C17.64,8.56636364 17.5827273,7.95272727 17.4763636,7.36363636 L9,7.36363636 L9,10.845 L13.8436364,10.845 C13.635,11.97 13.0009091,12.9231818 12.0477273,13.5613636 L12.0477273,15.8195455 L14.9563636,15.8195455 C16.6581818,14.2527273 17.64,11.9454545 17.64,9.20454545 L17.64,9.20454545 Z" id="Shape" fill="#4285F4" sketch:type="MSShapeGroup"></path>
+                <path d="M9,18 C11.43,18 13.4672727,17.1940909 14.9563636,15.8195455 L12.0477273,13.5613636 C11.2418182,14.1013636 10.2109091,14.4204545 9,14.4204545 C6.65590909,14.4204545 4.67181818,12.8372727 3.96409091,10.71 L0.957272727,10.71 L0.957272727,13.0418182 C2.43818182,15.9831818 5.48181818,18 9,18 L9,18 Z" id="Shape" fill="#34A853" sketch:type="MSShapeGroup"></path>
+                <path d="M3.96409091,10.71 C3.78409091,10.17 3.68181818,9.59318182 3.68181818,9 C3.68181818,8.40681818 3.78409091,7.83 3.96409091,7.29 L3.96409091,4.95818182 L0.957272727,4.95818182 C0.347727273,6.17318182 0,7.54772727 0,9 C0,10.4522727 0.347727273,11.8268182 0.957272727,13.0418182 L3.96409091,10.71 L3.96409091,10.71 Z" id="Shape" fill="#FBBC05" sketch:type="MSShapeGroup"></path>
+                <path d="M9,3.57954545 C10.3213636,3.57954545 11.5077273,4.03363636 12.4404545,4.92545455 L15.0218182,2.34409091 C13.4631818,0.891818182 11.4259091,0 9,0 C5.48181818,0 2.43818182,2.01681818 0.957272727,4.95818182 L3.96409091,7.29 C4.67181818,5.16272727 6.65590909,3.57954545 9,3.57954545 L9,3.57954545 Z" id="Shape" fill="#EA4335" sketch:type="MSShapeGroup"></path>
+                <path d="M0,0 L18,0 L18,18 L0,18 L0,0 Z" id="Shape" sketch:type="MSShapeGroup"></path>
+            </g>
+            <g id="handles_square" sketch:type="MSLayerGroup"></g>
+        </g>
+    </g>
+</svg>

+ 1 - 0
srv/public/login.css

@@ -1,3 +1,4 @@
 body { text-align: center; display: flex; }
 .services h1 { font-family: Lato; }
 .services { display: inline-block; border: 1px solid #e0e1e2; width: 250px; margin: auto; padding: 14px; }
+.services a { display: block; }

+ 37 - 0
srv/src/googleOAuth.js

@@ -0,0 +1,37 @@
+
+const GOOGLE_OAUTH_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token"
+    ,GOOGLE_USERINFO_URI = "https://www.googleapis.com/oauth2/v1/userinfo";
+
+const googleConfig = require('../config.js').login.google
+    ,httpsRequest = require('./httpsRequest.js').httpsRequest
+    ,httpsPost = require('./httpsRequest.js').httpsPost
+;
+
+function getUserMail(code, cb) {
+    httpsPost(GOOGLE_OAUTH_TOKEN_URI, {
+        "client_id": googleConfig.clientId
+        ,"client_secret": googleConfig.clientSecret
+        ,"redirect_uri": googleConfig.redirect_uri
+        ,"grant_type": "authorization_code"
+        ,"code": code
+    },
+    (status, resp) => {
+        if (status === 200 && resp && resp.access_token) {
+            httpsRequest(GOOGLE_USERINFO_URI +"?access_token="+resp.access_token,
+            (status, resp) => {
+                if (status === 200 && resp && resp.id) {
+                    cb(resp.id);
+                } else {
+                    cb(null);
+                }
+            });
+        } else {
+            cb(null);
+        }
+    });
+}
+
+module.exports.GoogleOAuth = {
+    getUserMail: getUserMail
+};
+

+ 67 - 0
srv/src/httpsRequest.js

@@ -0,0 +1,67 @@
+
+const https = require('https')
+    ,querystring = require('querystring')
+    ,urlParse = require('url').parse;
+
+module.exports.httpsRequest = function(url, cb) {
+    try {
+        https.get(url, (res) => {
+            if (res.statusCode !== 200) {
+                cb(res.statusCode, null);
+                return;
+            }
+            var body = null;
+            res.on('data', (chunk) => {
+                body = body ? Buffer.concat([body, chunk], body.length +chunk.length) : Buffer.from(chunk);
+            });
+            res.on('end', () => {
+                try {
+                    body = body.toString("utf8");
+                    body = JSON.parse(body);
+                } catch (e) {}
+                cb && cb(res.statusCode, body);
+            });
+        });
+    }
+    catch (e) {
+        console.error("Error in https request: ", e);
+        cb && cb(0, null);
+    }
+};
+
+module.exports.httpsPost = function(url, postData, cb) {
+    var urlObj = urlParse(url);
+    var rawData = querystring.stringify(postData);
+    try {
+        var req = https.request({
+                protocol: urlObj.protocol
+                ,hostname: urlObj.host
+                ,path: urlObj.path
+                ,method: "POST"
+                ,headers: {
+                    "Content-Type": "application/x-www-form-urlencoded",
+                    "Content-Length": Buffer.byteLength(rawData)
+                }
+            }, (res) => {
+                if (cb) {
+                    var body = null;
+                    res.on('data', (chunk) => {
+                        body = body ? Buffer.concat([body, chunk], body.length +chunk.length) : Buffer.from(chunk);
+                    });
+                    res.on('end', () => {
+                        try {
+                            body = JSON.parse(body.toString("utf8"));
+                        } catch (e) {}
+                        cb(res.statusCode, body);
+                    });
+                }
+        });
+        req.write(rawData);
+        req.end();
+    }
+    catch (e) {
+        console.error("Error in https request: ", e);
+        cb && cb(0, null);
+    }
+};
+

+ 1 - 25
srv/src/slack.js

@@ -7,6 +7,7 @@ const
     ,SlackData = require("./slackData.js").SlackData
     ,SlackHistory = require("./slackHistory.js").SlackHistory
     ,config = require("../config.js")
+    ,httpsRequest = require('./httpsRequest.js').httpsRequest
 ;
 
 const SLACK_ENDPOINT = "https://slack.com/api/"
@@ -72,31 +73,6 @@ Slack.prototype.onRequest = function(knownVersion, cb) {
     }
 };
 
-function httpsRequest(url, cb) {
-    try {
-        https.get(url, (res) => {
-            if (res.statusCode !== 200) {
-                cb(res.statusCode, null);
-                return;
-            }
-            var body = null;
-            res.on('data', (chunk) => {
-                body = body ? Buffer.concat([body, chunk], body.length +chunk.length) : Buffer.from(chunk);
-            });
-            res.on('end', () => {
-                try {
-                    body = JSON.parse(body.toString("utf8"));
-                } catch (e) {}
-                cb && cb(res.statusCode, body);
-            });
-        });
-    }
-    catch (e) {
-        console.error("Error in https request: ", e);
-        cb && cb(0, null);
-    }
-}
-
 Slack.prototype.connect = function(knownVersion) {
     var _this = this;
 

+ 1 - 1
srv/src/url.js

@@ -7,7 +7,7 @@ function Url(url) {
     while (this.url.length && this.url[0] === '/') {
         this.url = this.url.substr(1);
     }
-    urlParts = this.url.split("/");
+    var urlParts = this.url.split("/");
     this.urlParts = [];
     for (var i =0, nbParts = urlParts.length; i < nbParts; i++) {
         if (urlParts[i])

+ 4 - 2
srv/template/_templates.js

@@ -1,9 +1,11 @@
 
+const config = require('./../config.js');
+
 module.exports = {
     header: function(title, cssFiles, moreHeader) {
         var res = `<html><head><title>${title}</title><link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet" />`;
         (cssFiles || []).forEach(function(i) {
-            res += '<link href="' +i +'" rel="stylesheet"/>';
+            res += '<link href="' +config.rootUrl +i +'" rel="stylesheet"/>';
         });
         (moreHeader || []).forEach(function(i) {
             res += '<link href="favicon_err.png" type="image/png" rel="icon" id="linkFavicon" />';
@@ -13,7 +15,7 @@ module.exports = {
     ,footer: function(jsFiles) {
         var res = "";
         (jsFiles || []).forEach(function(i) {
-            res += '<script src="' +i +'"></script>';
+            res += '<script src="' +config.rootUrl +i +'"></script>';
         });
         return res +`</body></html>`;
     }

+ 36 - 6
srv/template/login.js

@@ -1,6 +1,7 @@
 
 const config = require("../config.js")
     ,Slack = require("../src/slack.js").Slack
+    ,GoogleOAuth = require("../src/googleOAuth.js").GoogleOAuth
     ,slackManager = require("../src/slackManager.js").SlackManager
     ,accountManager = require("../src/accounts.js").accountManager
     ,templates = require('./_templates.js');
@@ -10,7 +11,29 @@ function checkTokens(service, req, cb) {
         case "slack":
             if (req.urlObj.queryTokens.code) {
                 Slack.getUserMail(req.urlObj.queryTokens.code, config.login.slack.redirect_uri, (email) => {
-                    cb(email);
+                    if (email) {
+                        console.log("from slack email " +email);
+                        var account = accountManager.fromSlackEmailAuth(email);
+                        cb(account);
+                    } else {
+                        cb(null);
+                    }
+                });
+            } else {
+                cb(null);
+            }
+        break;
+
+        case "google":
+            if (req.urlObj.queryTokens.code) {
+                GoogleOAuth.getUserMail(req.urlObj.queryTokens.code, (id) => {
+                    if (id) {
+                        console.log("from google id " +id);
+                        var account = accountManager.fromGoogleIdAuth(id);
+                        cb(account);
+                    } else {
+                        cb(null);
+                    }
                 });
             } else {
                 cb(null);
@@ -24,14 +47,21 @@ function checkTokens(service, req, cb) {
 }
 
 function makeLoginPage() {
-    var slackUri = "https://slack.com/oauth/authorize"
+    const
+    slackUri = config.login.slack.endpoint
         +"?client_id=" +config.login.slack.clientId
         +"&scope=" +slackManager.getAuthScope().join(',')
-        +"&redirect_uri=" +config.login.slack.redirect_uri;
+        +"&redirect_uri=" +config.login.slack.redirect_uri,
+    googleUri = config.login.google.endpoint
+        +"?client_id=" +config.login.google.clientId
+        +"&scope=" +(["openid", "email", "profile"]).join("%20")
+        +"&redirect_uri=" +config.login.google.redirect_uri
+        +"&response_type=code";
 
     return templates.header("Mimou - login", ["login.css"])
         +`<div class="services"><h1>Login</h1>`
         +`<a href="${slackUri}"><img src="https://platform.slack-edge.com/img/sign_in_with_slack.png" srcset="https://platform.slack-edge.com/img/sign_in_with_slack.png 1x, https://platform.slack-edge.com/img/sign_in_with_slack@2x.png 2x" /></a>`
+        +`<a href="${googleUri}"><img src="https://developers.google.com/identity/images/btn_google_signin_light_normal_web.png" alt="Sign in with Google" class="attempt-right"></a>`
         +`</div>`
         +templates.footer();
 }
@@ -49,9 +79,9 @@ module.exports.exec = function(req, res) {
     if (!req.urlObj.urlParts[1]) {
         res.end(makeLoginPage());
     } else {
-        checkTokens(req.urlObj.urlParts[1], req, (email) => {
-            if (email) {
-                req.account = accountManager.fromSlackEmail(email)
+        checkTokens(req.urlObj.urlParts[1], req, (account) => {
+            if (account) {
+                req.account = account;
                 req.session = sessionManager.lazyForRequest(req);
                 req.session.setSlackToken(req.reqT, token);
                 res.writeHeader("302", {