add google authentication method

This commit is contained in:
Evert Prants 2017-10-13 19:18:17 +03:00
parent 4a6004aa7c
commit 1e027c4b88
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
9 changed files with 240 additions and 15 deletions

View File

@ -57,6 +57,10 @@
# api=""
# api_secret=""
[google]
# api=""
# api_secret=""
# reCAPTCHA configuration
[security]
[security.recaptcha]

View File

@ -18,4 +18,6 @@
<p>By logging in with Facebook, we will only ask you for your Public Profile and Email Address. We will use your Name as your Display Name, which can be changed from your Account Settings after logging in. Your profile picture may be downloaded onto our servers and used as your network-wide profile image. You may change your profile picture from your Account Settings at any time. Your Email Address will only be used to send you updates, which you can opt-out of. We can not: post on your behalf, see your friends list nor see your posts.</p>
<h3>Discord</h3>
<p>By logging in with Discord, we will only ask you for your Username and Email Address for the above-mentioned purposes. We do not ask you for any other information and we will not know which Discord Servers you're on.</p>
<h3>Google</h3>
<p>By logging in with Google, we will only ask you for your Name and Email Address for the above-mentioned purposes. We do not ask you for any other information and we will not have access to anything other than your public profile information.</p>
</div>

View File

@ -292,6 +292,75 @@ const API = {
return {error: null, user: newUser}
}
},
Google: {
callback: async (user, data, ipAddress) => {
let uid
try {
let test = await http.GET('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + data.id_token)
if (!test) throw new Error('No response!')
let jsondata = JSON.parse(test)
if (!jsondata || !jsondata.email || !jsondata.name) throw new Error('Please allow Basic Profile and Email.')
if (jsondata.email !== data.email || jsondata.name !== data.name) throw new Error('Conflicting data. Please try again!')
if (new Date(parseInt(jsondata.exp) * 1000) < Date.now()) throw new Error('Expired token! Please try again!')
uid = jsondata.sub
} catch (e) {
return {error: e.message}
}
let exists = await API.Common.getExternal('google', uid)
if (user) {
// Get bans for user
let bans = await API.Common.getBan(user)
if (bans.length) return { banned: bans, ip: false }
if (exists) return {error: null, user: user}
await API.Common.new('google', uid, user)
return {error: null, user: user}
}
// Callback succeeded with user id and the external table exists, we log in the user
if (exists) {
// Get bans for user
let bans = await API.Common.getBan(exists.user)
if (bans.length) return { banned: bans, ip: false }
return {error: null, user: exists.user}
}
// Get bans for IP
let bans = await API.Common.getBan(null, ipAddress)
if (bans.length) return { banned: bans, ip: true }
// Determine profile picture
let profilepic = null
if (data.image) {
let imgdata = await API.Common.saveAvatar(data.image)
if (imgdata && imgdata.fileName) {
profilepic = imgdata.fileName
}
}
// Create a new user
let newUData = {
username: data.name.replace(/\W+/gi, ''),
display_name: data.name,
email: data.email || '',
avatar_file: profilepic,
ip_address: ipAddress
}
let newUser = await API.Common.newUser('google', uid, newUData)
if (!newUser) return {error: 'Failed to create user.'}
return {error: null, user: newUser}
}
},
Discord: {
oauth2App: function () {
if (discordApp) return

View File

@ -243,6 +243,64 @@ router.get('/external/discord/remove', wrap(async (req, res) => {
res.redirect('/user/manage')
}))
router.get('/external/discord/remove', wrap(async (req, res) => {
if (!req.session.user) return res.redirect('/login')
let done = await APIExtern.Common.remove(req.session.user, 'discord')
if (!done) {
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
}
res.redirect('/user/manage')
}))
/** GOOGLE LOGIN
* Google Token Verification
* Tokens in configs
*/
router.get('/external/google/login', wrap(async (req, res) => {
if (!config.google || !config.google.api) return res.redirect('/')
res.redirect('/login')
}))
router.post('/external/google/callback', wrap(async (req, res) => {
if (!config.google || !config.google.api) return res.redirect('/login')
if (!req.body.id_token) {
return JsonData(req, res, 'Invalid or missing ID token!', '/login')
}
let response = await APIExtern.Google.callback(req.session.user, req.body, req.realIP)
if (response.banned) {
return JsonData(req, res, 'Banned user.', '/login')
}
if (response.error) {
return JsonData(req, res, response.error, '/login')
}
if (!req.session.user) {
let user = response.user
createSession(req, user)
}
JsonData(req, res, null, '/login')
}))
router.get('/external/google/remove', wrap(async (req, res) => {
if (!req.session.user) return res.redirect('/login')
let done = await APIExtern.Common.remove(req.session.user, 'google')
if (!done) {
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
}
res.redirect('/user/manage')
}))
/* ========
* NEWS
* ========

View File

@ -42,7 +42,6 @@ function setSession (req, user) {
function redirectLogin (req, res) {
let uri = '/'
console.log('goto', req.session.redirectUri)
if (req.session.redirectUri) {
uri = req.session.redirectUri
delete req.session.redirectUri
@ -116,6 +115,10 @@ function extraButtons (req, res, next) {
res.locals.facebook_auth = config.facebook.client
}
if (config.google && config.google.api) {
res.locals.google_auth = config.google.api
}
next()
}
@ -184,10 +187,11 @@ router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
totpEnabled = await API.User.Login.totpTokenRequired(req.session.user)
}
// Decide whether we need a disconnect or a log in with button for social account logins
if (config.twitter && config.twitter.api) {
if (!socialStatus.enabled.twitter) {
res.locals.twitter_auth = true
} else if (!socialStatus.source && socialStatus.source !== 'twitter') {
} else if (socialStatus.source !== 'twitter') {
res.locals.twitter_auth = false
}
}
@ -195,7 +199,7 @@ router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
if (config.discord && config.discord.api) {
if (!socialStatus.enabled.discord) {
res.locals.discord_auth = true
} else if (!socialStatus.source && socialStatus.source !== 'discord') {
} else if (socialStatus.source !== 'discord') {
res.locals.discord_auth = false
}
}
@ -203,11 +207,19 @@ router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
if (config.facebook && config.facebook.client) {
if (!socialStatus.enabled.fb) {
res.locals.facebook_auth = config.facebook.client
} else if (!socialStatus.source && socialStatus.source !== 'fb') {
} else if (socialStatus.source !== 'fb') {
res.locals.facebook_auth = false
}
}
if (config.google && config.google.api) {
if (!socialStatus.enabled.google) {
res.locals.google_auth = config.google.api
} else if (socialStatus.source !== 'google') {
res.locals.google_auth = false
}
}
res.render('user/settings', {totp: totpEnabled, password: socialStatus.password})
}))

View File

@ -267,20 +267,37 @@ $(document).ready(function () {
data: response,
success: function (data) {
if (data.error) {
$('.message').addClass('error')
$('.message span').text(data.error)
alert(data.error)
return
}
window.location.reload()
}
}).fail(function () {
$('.message').addClass('error')
$('.message span').text('An error occured.')
alert('An error occured.')
})
})
}
window.googlePOST = function (response) {
$.ajax({
type: 'post',
url: '/api/external/google/callback',
dataType: 'json',
data: response,
success: function (data) {
if (data.error) {
alert(data.error)
return
}
window.location.reload()
}
}).fail(function () {
alert('An error occured.')
})
}
$('.loginDiag').click(function (e) {
e.preventDefault()
var url = $(this).attr('href')

View File

@ -274,14 +274,14 @@ input:not([type="submit"])
border-radius: 5px
text-decoration: none
i
color: #03A9F4;
font-size: 22px;
color: #03A9F4
font-size: 22px
span
color: #000;
display: inline-block;
vertical-align: top;
margin-top: 3px;
margin-left: 12px;
color: #000
display: inline-block
vertical-align: top
margin-top: 3px
margin-left: 12px
.discordLogin
display: block
@ -302,6 +302,26 @@ input:not([type="submit"])
width: 45px
display: inline-block
.googleLogin
padding: 10px
width: 215px
margin-top: 5px
background-color: #4285f4
border: 1px solid #ddd
border-radius: 5px
text-decoration: none
display: inline-block
cursor: pointer
i
color: #fff
font-size: 22px
span
color: #fff
display: inline-block
vertical-align: top
margin-top: 3px
margin-left: 12px
.accdisconnect
margin-top: 5px
padding: 10px

View File

@ -20,6 +20,45 @@
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
fb:login-button(scope="public_profile,email", onlogin="checkLoginState();" data-max-rows="1", data-size="large", data-button-type="login_with", data-show-faces="false", data-auto-logout-link="false", data-use-continue-as="false")
if google_auth
script(src="https://apis.google.com/js/api:client.js")
a.googleLogin
i.fa.fa-fw.fa-google
span Log in With Google
script.
var googleUser = {};
var startApp = function() {
gapi.load('auth2', function(){
// Retrieve the singleton for the GoogleAuth library and set up the client.
auth2 = gapi.auth2.init({
client_id: '#{google_auth}',
cookiepolicy: 'single_host_origin',
fetch_basic_profile: true
});
attachSignin(document.querySelector('.googleLogin'));
});
};
function attachSignin(element) {
auth2.attachClickHandler(element, {},
function (googleUser) {
let profile = googleUser.getBasicProfile();
let dataTree = {
id_token: googleUser.getAuthResponse().id_token,
name: profile.getName(),
email: profile.getEmail(),
image: profile.getImageUrl()
};
if (window.googlePOST) {
window.googlePOST(dataTree);
}
}, function(error) {
alert('Failed to log you in using Google.');
});
}
startApp()
if twitter_auth
a.twitterLogin.loginDiag(href="/api/external/twitter/login")
i.fa.fa-fw.fa-twitter

View File

@ -33,6 +33,10 @@ block body
h3 Social Media Accounts
.specify(title="You can add social media accounts to your account for ease of login. Once added, logging in from linked sources logs you into this account automatically.") ?
include ../includes/external.pug
if google_auth == false
a.option.accdisconnect(href="/api/external/google/remove")
i.fa.fa-fw.fa-times
|Unlink Google
if twitter_auth == false
a.option.accdisconnect(href="/api/external/twitter/remove")
i.fa.fa-fw.fa-times