Implement web3 frontend eauth

Signed-off-by: Yukai Huang <yukaihuangtw@gmail.com>
This commit is contained in:
Yukai Huang 2019-03-02 19:28:59 +08:00
parent f98994b9a7
commit 0d405d1939
9 changed files with 217 additions and 3 deletions

1
app.js
View File

@ -213,6 +213,7 @@ app.locals.authProviders = {
oauth2: config.isOAuth2Enable,
oauth2ProviderName: config.oauth2.providerName,
openID: config.isOpenIDEnable,
eauth: config.isEAuthEnable,
email: config.isEmailEnable,
allowEmailRegister: config.allowEmailRegister
}

115
lib/auth/eauth/index.js Normal file
View File

@ -0,0 +1,115 @@
'use strict'
const Router = require('express').Router
const passport = require('passport')
const Eauth = require('express-eauth')
const config = require('../../config')
const models = require('../../models')
const logger = require('../../logger')
const {
setReturnToFromReferer
} = require('../utils')
const eauth = module.exports = Router()
class EAuthStreategy extends passport.Strategy {
constructor (options, verify) {
if (typeof options === 'function') {
verify = options
options = undefined
}
options = options || {}
super(options)
this.name = options.name || 'eauth'
this._verify = verify
this._passReqToCallback = options.passReqToCallback || false
}
authenticate (req) {
const verified = (error, user, info) => {
if (error) {
return this.error(error)
}
if (!user) {
return this.fail(info)
}
this.success(user, info)
}
try {
if (this._passReqToCallback && req) {
this._verify(req, verified)
} else {
this._verify(verified)
}
} catch (e) {
return this.error(e)
}
}
}
passport.use(new EAuthStreategy({
passReqToCallback: true
}, function (req, done) {
const address = req.eauth.recoveredAddress
if (!address) {
return done(new Error('EAuth failed'), null)
}
// construct profile
const profile = {
provider: 'eauth',
id: `eauth-${address}`,
emails: []
}
const stringifiedProfile = JSON.stringify(profile)
models.User.findOrCreate({
where: {
profileid: address
},
defaults: {
profile: stringifiedProfile
}
}).spread(function (user, created) {
if (user) {
var needSave = false
if (user.profile !== stringifiedProfile) {
user.profile = stringifiedProfile
needSave = true
}
if (needSave) {
user.save().then(function () {
if (config.debug) { logger.debug('user login: ' + user.id) }
return done(null, user)
})
} else {
if (config.debug) { logger.debug('user login: ' + user.id) }
return done(null, user)
}
}
}).catch(function (err) {
logger.error('eth auth failed: ' + err)
return done(err, null)
})
}))
const { signature, message, address, banner } = config.eauth
const eauthMiddleware = new Eauth({ signature, message, address, banner })
eauth.get('/auth/eauth/:Address', eauthMiddleware, function (req, res) {
return req.eauth.message ? res.send(req.eauth.message) : res.status(400).send()
})
eauth.post('/auth/eauth/:Message/:Signature', eauthMiddleware, function (req, res, next) {
setReturnToFromReferer(req)
passport.authenticate('eauth', {
successReturnToOrRedirect: true
})(req, res, next)
})

View File

@ -47,6 +47,7 @@ if (config.isSAMLEnable) authRouter.use(require('./saml'))
if (config.isOAuth2Enable) authRouter.use(require('./oauth2'))
if (config.isEmailEnable) authRouter.use(require('./email'))
if (config.isOpenIDEnable) authRouter.use(require('./openid'))
if (config.isEAuthEnable) authRouter.use(require('./eauth'))
// logout
authRouter.get('/logout', function (req, res) {

View File

@ -165,6 +165,13 @@ module.exports = {
email: undefined
}
},
eauth: {
enable: false,
signature: 'Signature',
message: 'Message',
address: 'Address',
banner: 'codimd-eauth'
},
plantuml: {
server: 'https://www.plantuml.com/plantuml'
},

View File

@ -137,6 +137,13 @@ module.exports = {
email: process.env.CMD_SAML_ATTRIBUTE_EMAIL
}
},
eauth: {
enable: toBooleanConfig(process.env.CMD_EAUTH_ENABLE),
signature: process.env.CMD_EAUTH_SIGNATURE,
message: process.env.CMD_EAUTH_MESSAGE,
address: process.env.CMD_EAUTH_ADDRESS,
banner: process.env.CMD_EAUTH_BANNER
},
plantuml: {
server: process.env.CMD_PLANTUML_SERVER
},

View File

@ -124,6 +124,7 @@ config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clie
config.isLDAPEnable = config.ldap.url
config.isSAMLEnable = config.saml.idpSsoUrl
config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret
config.isEAuthEnable = config.eauth.enable
config.isPDFExportEnable = config.allowPDFExport
// Check gitlab api version

View File

@ -1,6 +1,8 @@
/* eslint-env browser, jquery */
/* global moment, serverurl */
import './lib/eauth-helper'
import {
checkIfAuth,
clearLoginState,

View File

@ -0,0 +1,75 @@
/* global web3, $ */
const { serverurl } = require('./config')
let data
let message
let signature
$('#eth-auth-login').on('click', function () {
// #region Detect metamask
if (typeof web3 !== 'undefined') {
console.log('web3 is detected.')
if (web3.currentProvider.isMetaMask === true) {
if (web3.eth.accounts[0] === undefined && !web3.currentProvider.enable) {
return window.alert('Please login metamask first.')
}
}
} else {
return window.alert('No web3 detected. Please install metamask')
}
// #endregion
function authStart () {
return $.get(`${serverurl}/auth/eauth/${web3.eth.accounts[0]}`, res => {
data = ''
message = ''
const method = 'eth_signTypedData' // $('#method')[0].value
if (method === 'personal_sign') {
data = '0x' + Array.from(res).map(x => x.charCodeAt(0).toString(16)).join('')
message = res
} else if (method === 'eth_signTypedData') {
data = res
message = res[1].value
}
// Call metamask to sign
const from = web3.eth.accounts[0]
const params = [data, from]
web3.currentProvider.sendAsync({
method,
params,
from
}, async (err, result) => {
if (err) {
return console.error(err)
}
if (result.error) {
return console.error(result.error)
}
signature = result.result
if (message !== null && signature !== null) {
const form = document.createElement('form')
document.body.appendChild(form)
form.method = 'post'
form.action = `${serverurl}/auth/eauth/${message}/${signature}`
form.submit()
}
})
}).fail(function () {
// TODO: flash error
})
}
if (web3.currentProvider.enable) {
web3.currentProvider.enable()
.then(function () {
authStart()
})
} else if (web3.eth.accounts[0]) {
authStart()
}
})

View File

@ -58,7 +58,12 @@
<i class="fa fa-mail-forward"></i> <%= __('Sign in via %s', authProviders.oauth2ProviderName || 'OAuth2') %>
</a>
<% } %>
<% if ((authProviders.facebook || authProviders.twitter || authProviders.github || authProviders.bitbucket || authProviders.gitlab || authProviders.mattermost || authProviders.dropbox || authProviders.google || authProviders.saml || authProviders.oauth2) && authProviders.ldap) { %>
<% if (authProviders.eauth) { %>
<button type="button" id="eth-auth-login" class="btn btn-lg btn-block btn-social btn-reddit">
<i class="fa fa-btc"></i> <%= __('Sign in via %s', 'Eauth') %>
</button>
<% } %>
<% if ((authProviders.facebook || authProviders.twitter || authProviders.github || authProviders.bitbucket || authProviders.gitlab || authProviders.mattermost || authProviders.dropbox || authProviders.google || authProviders.saml || authProviders.oauth2 || authProviders.eauth) && authProviders.ldap) { %>
<hr>
<% }%>
<% if (authProviders.ldap) { %>
@ -83,7 +88,7 @@
</div>
</form>
<% } %>
<% if ((authProviders.facebook || authProviders.twitter || authProviders.github || authProviders.bitbucket || authProviders.gitlab || authProviders.mattermost || authProviders.dropbox || authProviders.google || authProviders.ldap || authProviders.oauth2) && authProviders.openID) { %>
<% if ((authProviders.facebook || authProviders.twitter || authProviders.github || authProviders.bitbucket || authProviders.gitlab || authProviders.mattermost || authProviders.dropbox || authProviders.google || authProviders.ldap || authProviders.oauth2 || authProviders.eauth) && authProviders.openID) { %>
<hr>
<% }%>
<% if (authProviders.openID) { %>
@ -102,7 +107,7 @@
</div>
</form>
<% } %>
<% if ((authProviders.facebook || authProviders.twitter || authProviders.github|| authProviders.bitbucket || authProviders.gitlab || authProviders.mattermost || authProviders.dropbox || authProviders.google || authProviders.ldap || authProviders.oauth2 || authProviders.openID) && authProviders.email) { %>
<% if ((authProviders.facebook || authProviders.twitter || authProviders.github|| authProviders.bitbucket || authProviders.gitlab || authProviders.mattermost || authProviders.dropbox || authProviders.google || authProviders.ldap || authProviders.oauth2 || authProviders.openID || authProviders.eauth) && authProviders.email) { %>
<hr>
<% }%>
<% if (authProviders.email) { %>