update ldap account linking to use Alternate Login system

auto-build-api-doc-with-code
Moe 2021-04-06 20:55:16 -07:00
parent cf588837dd
commit 268ce83784
10 changed files with 379 additions and 220 deletions

View File

@ -4550,75 +4550,6 @@ module.exports = function(s,config,lang){
}
]
},
"LDAP": {
"evaluation":"details.use_ldap!=='0'",
"name": lang["LDAP"],
"color": "forestgreen",
"info": [
{
"name": "detail=ldap_enable",
"selector":"ldap_i",
"field": lang.Enabled,
"description": "Enable LDAP authentication for this Group.",
"default": "0",
"example": "",
"fieldType": "select",
"possible": [
{
"name": lang.No,
"value": "0"
},
{
"name": lang.Yes,
"value": "1"
}
]
},
{
"form-group-class": "ldap_i_input ldap_i_1",
"name": "detail=ldap_url",
"field": lang.URL,
"description": "",
"example": "",
"possible": ""
},
{
"placeholder":lang.Example + " : cn=admin,dc=test,dc=com",
"form-group-class": "ldap_i_input ldap_i_1",
"name": "detail=ldap_bindDN",
"field": lang.bindDN,
"description": "",
"example": "",
"possible": ""
},
{
"form-group-class": "ldap_i_input ldap_i_1",
"name": "detail=ldap_bindCredentials",
"field": lang['Bind Credentials'],
"description": "",
"example": "",
"possible": ""
},
{
"placeholder": "cn={{username}}",
"form-group-class": "ldap_i_input ldap_i_1",
"name": "detail=ldap_searchFilter",
"field": lang['Search Filter'],
"description": "",
"example": "",
"possible": ""
},
{
"placeholder": "dc=test,dc=com",
"form-group-class": "ldap_i_input ldap_i_1",
"name": "detail=ldap_searchBase",
"field": lang['Search Base'],
"description": "",
"example": "",
"possible": ""
},
]
},
"Preferences": {
"name": lang.Preferences,
"color": "navy",
@ -5454,6 +5385,85 @@ module.exports = function(s,config,lang){
]
},
}
}
},
"LDAP": {
"section": "LDAP",
"blocks": {
"LDAP": {
"evaluation":"details.use_ldap!=='0'",
"name": lang["LDAP"],
"color": "forestgreen",
"info": [
{
"name": "ldap_enable",
"field": lang.Enabled,
"description": "Enable LDAP authentication for this Group.",
"default": "0",
"example": "",
"fieldType": "select",
"possible": [
{
"name": lang.No,
"value": "0"
},
{
"name": lang.Yes,
"value": "1"
}
]
},
{
"name": "ldap_url",
"field": lang.URL,
"description": "",
"example": "",
"possible": ""
},
{
"name": "username",
"field": lang.Username,
"description": "",
"example": "",
"possible": ""
},
{
"name": "password",
"field": lang.Password,
"description": "",
"example": "",
"possible": ""
},
{
"name": "ldap_bindDN",
"field": lang.bindDN,
"description": "",
"example": "cn=admin,dc=test,dc=com",
"possible": ""
},
{
"name": "ldap_searchBase",
"field": lang['Search Base'],
"description": "",
"example": "dc=test,dc=com",
"possible": ""
},
{
"name": "ldap_searchFilter",
"field": lang['Search Filter'],
"description": "",
"example": "uid={{username}}",
"possible": ""
},
{
"fieldType": "btn",
"forForm": true,
"attribute": `type="submit"`,
"class": `btn-success`,
"btnContent": `<i class="fa fa-check"></i> &nbsp; ${lang['Save']}`,
},
]
}
}
}
}
}

View File

@ -121,6 +121,7 @@
"Home": "Home",
"alreadyLinked": "Already Linked to an Account",
"Link Google Account": "Link Google Account",
"Link LDAP Account": "Link LDAP Account",
"noLoginTokensAdded": "There are no Alternate Logins associated to this account.",
"tokenNotUserBound": "This Login Handle is not linked to a user on this server!",
"tokenNotUserBoundPt2": "Type your credentials then use the Google Sign-In button to link quickly.",

View File

@ -9,7 +9,7 @@ module.exports = (s,config,lang,app) => {
bindLoginIdToUser,
refreshLoginTokenAccessDate,
} = require('./alternateLogins.js')(s,config,lang)
console.error(`Google App ID : ${config.appIdGoogleSignIn}`)
console.log(`Google App ID : ${config.appIdGoogleSignIn}`)
const client = new OAuth2Client(config.appIdGoogleSignIn);
async function verifyToken(userLoginToken) {
const ticket = await client.verifyIdToken({
@ -61,7 +61,7 @@ module.exports = (s,config,lang,app) => {
return response
}
s.onProcessReady(() => {
config.renderPaths.loginTokenAddGoogle = `pages/loginTokenAddGoogle`
config.renderPaths.loginTokenAddGoogle = `pages/blocks/loginTokenAddGoogle`
s.alternateLogins['google'] = async (params) => {
const response = { ok: false }
const loginToken = params.alternateLoginToken

220
libs/auth/ldap.js Normal file
View File

@ -0,0 +1,220 @@
const LdapAuth = require('ldapauth-fork');
module.exports = (s,config,lang,app) => {
const {
basicAuth,
} = require('./utils.js')(s,config,lang)
const {
getLoginToken,
deleteLoginToken,
bindLoginIdToUser,
refreshLoginTokenAccessDate,
} = require('./alternateLogins.js')(s,config,lang)
async function ldapAuth(params) {
return new Promise((resolve,reject) => {
const response = { ok: false }
let ldapUrl = params.url
ldapUrl = ldapUrl.startsWith('ldap://') ? ldapUrl : `ldap://${ldapUrl}`
const host = ldapUrl.split('://')[1]
const username = params.username// || 'ubuntu2'
const password = params.password// || 'moeiscool'
const bindDN = params.bindDN// || 'uid=ubuntu2,ou=People,dc=example,dc=com'
const searchBase = params.searchBase// || 'ou=People,dc=example,dc=com'
let searchFilter = params.searchFilter// || '(uid={{username}})'
searchFilter = searchFilter.startsWith('(') ? searchFilter : '(' + searchFilter
searchFilter = searchFilter.endsWith(')') ? searchFilter : searchFilter + ')'
const ldap = new LdapAuth({
url: ldapUrl,
bindDN: bindDN,
bindCredentials: password,
searchBase: searchBase,
searchFilter: searchFilter,
reconnect: true
})
ldap.authenticate(username, password, function(err, user) {
if(err){
response.err = err
}else{
response.ok = true
response.user = {
id: host + '_' + user.uidNumber,
name: user.givenName + ' ' + user.sn,
}
}
resolve(response)
})
})
}
async function loginWithLdapAccount(params) {
const response = {ok: false, ldapSignedIn: false}
const tokenResponse = await ldapAuth(params)
if(tokenResponse.ok){
const user = tokenResponse.user
response.ldapSignedIn = true
response.ldapUser = user
const foundToken = await getLoginToken(user.id,'ldap')
if(foundToken){
const userResponse = await s.knexQueryPromise({
action: "select",
columns: '*',
table: "Users",
where: [
['uid','=',foundToken.uid],
['ke','=',foundToken.ke],
],
limit: 1
})
response.ok = true
userResponse.rows[0].details = s.parseJSON(userResponse.rows[0].details)
response.user = userResponse.rows[0]
}else{
response.msg = lang.tokenNotUserBound
// make new if no users?
}
}
return response
}
async function updateLdapBaseDetails(params,updateFields){
const enabled = updateFields.ldap_enable
const url = updateFields.ldap_url
const bindDN = updateFields.ldap_bindDN
const searchBase = updateFields.ldap_searchBase
const searchFilter = updateFields.ldap_searchFilter
const userResponse = await s.knexQueryPromise({
action: "select",
columns: "*",
table: "Users",
where: [
['ke','=',params.groupKey],
['uid','=',params.userId],
],
})
const userDetails = JSON.parse(userResponse.rows[0].details)
userDetails.ldap_enable = enabled
userDetails.ldap_url = url
userDetails.ldap_bindDN = bindDN
userDetails.ldap_searchBase = searchBase
userDetails.ldap_searchFilter = searchFilter
await s.knexQueryPromise({
action: "update",
table: "Users",
update: {
details: JSON.stringify(userDetails)
},
where: [
['ke','=',params.groupKey],
['uid','=',params.userId],
]
})
}
s.onProcessReady(() => {
config.renderPaths.loginTokenAddLDAP = `pages/blocks/loginTokenAddLDAP`
s.alternateLogins['ldap'] = async (params) => {
const response = { ok: false }
const groupKey = params.key
const userResponse = await s.knexQueryPromise({
action: "select",
columns: "*",
table: "Users",
where: [
['ke','=',params.key],
['details','NOT LIKE','%"sub"%'],
],
})
if(userResponse.rows[0]){
const userDetails = JSON.parse(userResponse.rows[0].details)
const url = userDetails.ldap_url
const bindDN = userDetails.ldap_bindDN
const searchBase = userDetails.ldap_searchBase
const searchFilter = userDetails.ldap_searchFilter
const username = params.mail
const password = params.pass
const ldapLoginResponse = await loginWithLdapAccount({
url: url,
username: username,
password: password,
bindDN: bindDN,
searchBase: searchBase,
searchFilter: searchFilter,
})
if(ldapLoginResponse.user){
const user = ldapLoginResponse.user
response.ok = true
response.user = user
refreshLoginTokenAccessDate(ldapLoginResponse.ldapUser.id,'ldap')
}else{
response.msg = ldapLoginResponse.msg
}
}
return response
}
s.definitions["Account Settings"].blocks["AlternateLogins"].info.push({
"form-group-class-pre-layer": "form-group",
"fieldType": "btn",
"class": `btn-info ldap-sign-in`,
"btnContent": `<i class="fa fa-group"></i> &nbsp; ${lang['Link LDAP Account']}`,
})
s.customAutoLoadTree['LibsJs'].push(`dash2.ldapSignIn.js`)
})
/**
* API : Add Token Window (Sign-In to LDAP) (GET)
*/
app.get(config.webPaths.apiPrefix+':auth/loginTokenAddLDAP/:ke', function (req,res){
s.auth(req.params,(user) => {
s.renderPage(req,res,config.renderPaths.loginTokenAddLDAP,{
lang: lang,
define: s.getDefinitonFile(user.details.lang),
config: s.getConfigWithBranding(req.hostname),
$user: user
})
},res,req);
});
/**
* API : Add Token Window (Sign-In to LDAP) (POST)
*/
app.post(config.webPaths.apiPrefix+':auth/loginTokenAddLDAP/:ke', function (req,res){
const response = {ok: false};
s.auth(req.params,async (user) => {
const userId = user.uid
const groupKey = req.params.ke
const url = req.body.ldap_url
const bindDN = req.body.ldap_bindDN
const searchBase = req.body.ldap_searchBase
const searchFilter = req.body.ldap_searchFilter
const username = req.body.username
const password = req.body.password
const authPostBody = {
url: url,
username: username,
password: password,
bindDN: bindDN,
searchBase: searchBase,
searchFilter: searchFilter,
}
const tokenResponse = await ldapAuth(authPostBody)
if(tokenResponse.ok){
const ldapUser = tokenResponse.user
const loginId = ldapUser.id
if(!user.details.sub){
updateLdapBaseDetails({
groupKey: groupKey,
userId: user.uid,
},req.body)
}
const bindResponse = await bindLoginIdToUser({
loginId: loginId,
ke: groupKey,
uid: userId,
name: ldapUser.name,
type: 'ldap'
})
response.ok = bindResponse.ok
response.msg = bindResponse.msg
}
s.closeJsonResponse(res,response)
},res,req);
});
return {
loginWithLdapAccount: loginWithLdapAccount,
}
}

View File

@ -5,4 +5,8 @@ module.exports = (s,config,lang,app) => {
s.debugLog('Google')
require('./google.js')(s,config,lang,app)
}
if(config.allowLdapSignOn){
s.debugLog('LDAP')
require('./ldap.js')(s,config,lang,app)
}
}

View File

@ -26,9 +26,6 @@ module.exports = function(s,config,lang,app,io){
twoFactorVerification,
ldapLogin,
} = require('./auth/utils.js')(s,config,lang)
if(config.productType === 'Pro'){
var LdapAuth = require('ldapauth-fork');
}
s.renderPage = function(req,res,paths,passables,callback){
passables.window = {}
passables.data = req.params
@ -447,138 +444,7 @@ module.exports = function(s,config,lang,app,io){
failedAuthentication(req.body.function,req.body.mail)
}
}
if(LdapAuth&&req.body.function==='ldap'&&req.body.key!==''){
s.knexQuery({
action: "select",
columns: "*",
table: "Users",
where: [
['ke','=',req.body.key],
['details','NOT LIKE','%"sub"%'],
],
},(err,r) => {
if(r&&r[0]){
r=r[0]
r.details=JSON.parse(r.details)
r.lang=s.getLanguageFile(r.details.lang)
if(r.details.use_ldap!=='0'&&r.details.ldap_enable==='1'&&r.details.ldap_url&&r.details.ldap_url!==''){
req.mailArray={}
req.body.mail.split(',').forEach(function(v){
v=v.split('=')
req.mailArray[v[0]]=v[1]
})
if(!r.details.ldap_bindDN||r.details.ldap_bindDN===''){
r.details.ldap_bindDN=req.body.mail
}
if(!r.details.ldap_bindCredentials||r.details.ldap_bindCredentials===''){
r.details.ldap_bindCredentials=req.body.pass
}
if(!r.details.ldap_searchFilter||r.details.ldap_searchFilter===''){
r.details.ldap_searchFilter=req.body.mail
if(req.mailArray.cn){
r.details.ldap_searchFilter='cn='+req.mailArray.cn
}
if(req.mailArray.uid){
r.details.ldap_searchFilter='uid='+req.mailArray.uid
}
}else{
r.details.ldap_searchFilter=r.details.ldap_searchFilter.replace('{{username}}',req.body.mail)
}
if(!r.details.ldap_searchBase||r.details.ldap_searchBase===''){
r.details.ldap_searchBase='dc=test,dc=com'
}
req.auth = new LdapAuth({
url:r.details.ldap_url,
bindDN:r.details.ldap_bindDN,
bindCredentials:r.details.ldap_bindCredentials,
searchBase:r.details.ldap_searchBase,
searchFilter:'('+r.details.ldap_searchFilter+')',
reconnect:true
});
req.auth.on('error', function (err) {
console.error('LdapAuth: ', err);
});
req.auth.authenticate(req.body.mail, req.body.pass, function(err, user) {
if(user){
//found user
if(!user.uid){
user.uid=s.gid()
}
const userInfo = {
ke:req.body.key,
uid:user.uid,
auth:s.createHash(s.gid()),
mail:user.mail,
pass:s.createHash(req.body.pass),
details:JSON.stringify({
sub:'1',
ldap:'1',
allmonitors:'1',
filter: {}
})
}
s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Success'],msg:{user:user}})
s.knexQuery({
action: "select",
columns: "*",
table: "Users",
where: [
['ke','=',req.body.key],
['mail','=',user.cn],
],
},function(err,rr) {
if(rr&&rr[0]){
//already registered
rr = rr[0]
userInfo = rr;
rr.details = JSON.parse(rr.details)
userInfo.lang = s.getLanguageFile(rr.details.lang)
s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User Authenticated'],msg:{user:user,shinobiUID:rr.uid}})
s.knexQuery({
action: "update",
table: "Users",
update: {
auth: userInfo.auth
},
where: [
['ke','=',userInfo.ke],
['uid','=',rr.uid],
]
})
}else{
//new ldap login
s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP User is New'],msg:{info:r.lang['Creating New Account'],user:user}})
userInfo.lang = r.lang
s.knexQuery({
action: "insert",
table: "Users",
insert: userInfo,
})
}
userInfo.details = JSON.stringify(userInfo.details)
userInfo.auth_token = userInfo.auth
userInfo.ok = true
checkRoute(req.body.function,userInfo)
})
return
}
s.userLog({ke:req.body.key,mid:'$USER'},{type:r.lang['LDAP Failed'],msg:{err:err}})
//no user
regularLogin()
});
req.auth.close(function(err) {
})
}else{
regularLogin()
}
}else{
regularLogin()
}
})
}else if(req.body.function === 'super'){
if(req.body.function === 'super'){
const superLoginResponse = await superLogin(req.body.mail,req.body.pass);
if(superLoginResponse.ok){
renderPage(config.renderPaths.super,{

View File

@ -0,0 +1,13 @@
$(document).ready(function(){
$('#settings').on('click','.ldap-sign-in',function(){
var signInWindow = window.open(getApiPrefix('loginTokenAddLDAP'),'popup','width=300,height=300,scrollbars=no,resizable=no');
if(!signInWindow || signInWindow.closed || typeof signInWindow.closed=='undefined'){
alert(`Your Popup Blocker is disabling this feature.`)
}else{
signInWindow.onbeforeunload = function(){
drawAlternateLoginsToSettings()
}
}
return false;
})
})

View File

@ -0,0 +1,47 @@
<%
details = $user.details
window.libURL = originalURL + global.s.checkCorrectPathEnding(config.webPaths.home)
%>
<link rel="stylesheet" href="<%-window.libURL%>libs/css/bootstrap4.min.css" />
<link rel="stylesheet" href="<%-window.libURL%>libs/css/dash2.forms.css">
<script src="<%-window.libURL%>libs/js/jquery.min.js"></script>
<script src="<%-window.libURL%>libs/js/jquery.serialize.js"></script>
<style>
body {
background: #333;
}
</style>
<%
var drawBlock
var buildOptions
%>
<form class="dark">
<%
include fieldBuilders.ejs
%>
<%
drawBlock(define['LDAP'].blocks.LDAP)
%>
</form>
<script>
$(document).ready(function() {
var theForm = $('form')
theForm.submit(function(e) {
e.preventDefault()
console.log('Logged in to LDAP! Binding...')
$.post(location.href,theForm.serializeObject(),function(data){
if(data.ok){
window.close()
}else{
console.log(data)
$('.monitor-section-header ').html(data.msg || 'Failed to Save').css({
color: "#fff",
textAlign: "center",
fontFamily: "monospace",
})
}
})
return false;
})
})
</script>

View File

@ -81,18 +81,11 @@
<div class="form-group f_i_input f_i_ldap" style="display:none">
<input name="key" id="key" tabindex="2" class="monospace form-control wide-text" placeholder="Group Key">
</div>
<div class="form-group" style="display:none">
<select class="form-control wide-text" name="alternateLogin">
<option value="" selected>Default</option>
<option value="google">Google</option>
</select>
<input style="display:none" name="alternateLoginToken" class="monospace form-control wide-text" placeholder="Group Key">
</div>
<% if(config.showLoginSelector === true){ %>
<div class="form-group">
<div class="form-group f_i_input f_i_">
<div class="row">
<div class="col-md-12 monospace">
<select class="form-control wide-text" name="function" selector="f_i">
<select class="form-control wide-text" name="function">
<% switch(screen){
case'super': %>
<option value="super"><%- lang.Superuser %></option>
@ -102,9 +95,6 @@
<% break;
default: %>
<option value="dash" selected><%- lang.Dashboard %></option>
<% if(config.productType==='Pro'){ %>
<option value="ldap"><%- lang.LDAP %></option>
<% } %>
<option value="streamer"><%- lang.Streamer %></option>
<option value="cam"><%- lang.Dashcam %> (<%- lang.Streamer %> v2)</option>
<% break;
@ -128,6 +118,14 @@
} %>
</select>
<% } %>
<div class="form-group">
<select class="form-control wide-text" name="alternateLogin" selector="f_i">
<option value="" selected><%- lang.Default %></option>
<option value="google">Google</option>
<option value="ldap"><%- lang.LDAP %></option>
</select>
<input style="display:none" name="alternateLoginToken" class="monospace form-control wide-text" placeholder="Group Key">
</div>
<div class="form-group">
<button type="submit" name="login-submit" id="login-submit" tabindex="4" class="btn btn-success btn-block wide-text" style="color:#FFF"><i class="fa fa-key"></i> <%- lang.Login %></button>
</div>
@ -233,7 +231,7 @@ $('[selector]').change(function(e){
<script>
function onGoogleSignIn(googleUser) {
var id_token = googleUser.getAuthResponse().id_token;
$.ccio.f.find('[name="mail"],[name="pass"],.g-signin2').hide()
$.ccio.f.find('[name="mail"],[name="pass"],[name="alternateLogin"],.g-signin2').hide()
$.ccio.f.find('[name="alternateLogin"]').val('google')
$.ccio.f.find('[name="alternateLoginToken"]').val(id_token)
$.ccio.f.find('[name="login-submit"]').html(`<i class="fa fa-google"></i> <%- lang.Login %>`)