mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Add support for oauth adminAuth configs
This commit is contained in:
parent
bfb548636e
commit
c54cf26848
@ -32,7 +32,7 @@ RED.user = (function() {
|
|||||||
autoOpen: false,
|
autoOpen: false,
|
||||||
dialogClass: "ui-dialog-no-close",
|
dialogClass: "ui-dialog-no-close",
|
||||||
modal: true,
|
modal: true,
|
||||||
closeOnEscape: false,
|
closeOnEscape: !!opts.cancelable,
|
||||||
width: 600,
|
width: 600,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
draggable: false
|
draggable: false
|
||||||
@ -43,17 +43,13 @@ RED.user = (function() {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
url: "auth/login",
|
url: "auth/login",
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
if (data.type == "credentials") {
|
var i=0;
|
||||||
var i=0;
|
|
||||||
|
if (data.type == "credentials") {
|
||||||
|
|
||||||
if (data.image) {
|
|
||||||
$("#node-dialog-login-image").attr("src",data.image);
|
|
||||||
} else {
|
|
||||||
$("#node-dialog-login-image").attr("src","red/images/node-red-256.png");
|
|
||||||
}
|
|
||||||
for (;i<data.prompts.length;i++) {
|
for (;i<data.prompts.length;i++) {
|
||||||
var field = data.prompts[i];
|
var field = data.prompts[i];
|
||||||
var row = $("<div/>",{id:"rrr"+i,class:"form-row"});
|
var row = $("<div/>",{class:"form-row"});
|
||||||
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
|
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
|
||||||
var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
|
var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
|
||||||
|
|
||||||
@ -112,13 +108,48 @@ RED.user = (function() {
|
|||||||
});
|
});
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
if (opts.cancelable) {
|
|
||||||
$("#node-dialog-login-cancel").button().click(function( event ) {
|
} else if (data.type == "oauth") {
|
||||||
$("#node-dialog-login").dialog('destroy').remove();
|
i = 0;
|
||||||
|
for (;i<data.prompts.length;i++) {
|
||||||
|
var field = data.prompts[i];
|
||||||
|
var row = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
|
||||||
|
|
||||||
|
var loginButton = $('<a href="#"></a>',{style: "padding: 10px"}).appendTo(row).click(function() {
|
||||||
|
document.location = field.url;
|
||||||
});
|
});
|
||||||
|
if (field.image) {
|
||||||
|
$("<img>",{src:field.image}).appendTo(loginButton);
|
||||||
|
} else if (field.label) {
|
||||||
|
var label = $('<span></span>').text(field.label);
|
||||||
|
if (field.icon) {
|
||||||
|
$('<i></i>',{class: "fa fa-2x "+field.icon, style:"vertical-align: middle"}).appendTo(loginButton);
|
||||||
|
label.css({
|
||||||
|
"verticalAlign":"middle",
|
||||||
|
"marginLeft":"8px"
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
label.appendTo(loginButton);
|
||||||
|
}
|
||||||
|
loginButton.button();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
dialog.dialog("open");
|
if (opts.cancelable) {
|
||||||
|
$("#node-dialog-login-cancel").button().click(function( event ) {
|
||||||
|
$("#node-dialog-login").dialog('destroy').remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginImageSrc = data.image || "red/images/node-red-256.png";
|
||||||
|
|
||||||
|
$("#node-dialog-login-image").load(function() {
|
||||||
|
dialog.dialog("open");
|
||||||
|
}).attr("src",loginImageSrc);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -170,8 +201,15 @@ RED.user = (function() {
|
|||||||
if (RED.settings.user) {
|
if (RED.settings.user) {
|
||||||
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
|
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
|
||||||
|
|
||||||
$('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>')
|
var userMenu = $('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"></a></li>')
|
||||||
.prependTo(".header-toolbar");
|
.prependTo(".header-toolbar");
|
||||||
|
if (RED.settings.user.image) {
|
||||||
|
$('<span class="user-profile"></span>').css({
|
||||||
|
backgroundImage: "url("+RED.settings.user.image+")",
|
||||||
|
}).appendTo(userMenu.find("a"));
|
||||||
|
} else {
|
||||||
|
$('<i class="fa fa-user"></i>').appendTo(userMenu.find("a"));
|
||||||
|
}
|
||||||
|
|
||||||
RED.menu.init({id:"btn-usermenu",
|
RED.menu.init({id:"btn-usermenu",
|
||||||
options: []
|
options: []
|
||||||
|
@ -279,3 +279,13 @@ span.logo {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#btn-usermenu .user-profile {
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
display: inline-block;
|
||||||
|
width: 40px;
|
||||||
|
height: 35px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
@ -81,9 +81,22 @@ function getToken(req,res,next) {
|
|||||||
function login(req,res) {
|
function login(req,res) {
|
||||||
var response = {};
|
var response = {};
|
||||||
if (settings.adminAuth) {
|
if (settings.adminAuth) {
|
||||||
response = {
|
if (settings.adminAuth.type === "credentials") {
|
||||||
"type":"credentials",
|
response = {
|
||||||
"prompts":[{id:"username",type:"text",label:"Username"},{id:"password",type:"password",label:"Password"}]
|
"type":"credentials",
|
||||||
|
"prompts":[{id:"username",type:"text",label:"Username"},{id:"password",type:"password",label:"Password"}]
|
||||||
|
}
|
||||||
|
} else if (settings.adminAuth.type === "oauth") {
|
||||||
|
response = {
|
||||||
|
"type":"oauth",
|
||||||
|
"prompts":[{type:"button",label:settings.adminAuth.strategy.label, url:"/auth/oauth"}]
|
||||||
|
}
|
||||||
|
if (settings.adminAuth.strategy.icon) {
|
||||||
|
response.prompts[0].icon = settings.adminAuth.strategy.icon;
|
||||||
|
}
|
||||||
|
if (settings.adminAuth.strategy.image) {
|
||||||
|
response.prompts[0].image = theme.serveFile('/login/',settings.adminAuth.strategy.image);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (theme.context().login && theme.context().login.image) {
|
if (theme.context().login && theme.context().login.image) {
|
||||||
response.image = theme.context().login.image;
|
response.image = theme.context().login.image;
|
||||||
@ -114,5 +127,46 @@ module.exports = {
|
|||||||
return server.errorHandler()(err,req,res,next);
|
return server.errorHandler()(err,req,res,next);
|
||||||
},
|
},
|
||||||
login: login,
|
login: login,
|
||||||
revoke: revoke
|
revoke: revoke,
|
||||||
|
oauthStrategy: function(adminApp,strategy) {
|
||||||
|
var session = require('express-session');
|
||||||
|
adminApp.use(session({
|
||||||
|
secret: 'keyboard cat', // TODO: pull this out
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized:false
|
||||||
|
}));
|
||||||
|
//TODO: all passport references ought to be in ./auth
|
||||||
|
adminApp.use(passport.initialize());
|
||||||
|
adminApp.use(passport.session());
|
||||||
|
|
||||||
|
var options = strategy.options;
|
||||||
|
passport.use(new strategy.strategy(options,
|
||||||
|
function(token, tokenSecret, profile, done) {
|
||||||
|
Users.authenticate(profile).then(function(user) {
|
||||||
|
if (user) {
|
||||||
|
Tokens.create(user.username,"node-red-editor",user.permissions).then(function(tokens) {
|
||||||
|
log.audit({event: "auth.login",username:user.username,scope:user.permissions});
|
||||||
|
user.tokens = tokens;
|
||||||
|
done(null,user);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.audit({event: "auth.login.fail.oauth",username:profile.id});
|
||||||
|
done(null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
adminApp.get('/auth/oauth', passport.authenticate(strategy.name));
|
||||||
|
adminApp.get('/auth/oauth/callback',
|
||||||
|
passport.authenticate(strategy.name, {session:false, failureRedirect: '/' }),
|
||||||
|
function(req, res) {
|
||||||
|
var tokens = req.user.tokens;
|
||||||
|
delete req.user.tokens;
|
||||||
|
// Successful authentication, redirect home.
|
||||||
|
res.redirect('/?access_token='+tokens.accessToken);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,29 @@ var users = {};
|
|||||||
var passwords = {};
|
var passwords = {};
|
||||||
var defaultUser = null;
|
var defaultUser = null;
|
||||||
|
|
||||||
function authenticate(username,password) {
|
function authenticate() {
|
||||||
|
var username;
|
||||||
|
if (arguments.length === 2) {
|
||||||
|
username = arguments[0];
|
||||||
|
} else {
|
||||||
|
username = arguments[0].username;
|
||||||
|
}
|
||||||
var user = users[username];
|
var user = users[username];
|
||||||
if (user) {
|
if (user) {
|
||||||
return when.promise(function(resolve,reject) {
|
if (arguments.length === 2) {
|
||||||
bcrypt.compare(password, passwords[username], function(err, res) {
|
var password = arguments[1];
|
||||||
resolve(res?user:null);
|
return when.promise(function(resolve,reject) {
|
||||||
|
bcrypt.compare(password, passwords[username], function(err, res) {
|
||||||
|
resolve(res?user:null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
|
// Try to extract common profile information
|
||||||
|
if (arguments[0].hasOwnProperty('photos') && arguments[0].photos.length > 0) {
|
||||||
|
user.image = arguments[0].photos[0].value;
|
||||||
|
}
|
||||||
|
return when.resolve(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return when.resolve(null);
|
return when.resolve(null);
|
||||||
}
|
}
|
||||||
@ -51,7 +66,7 @@ function init(config) {
|
|||||||
users = {};
|
users = {};
|
||||||
passwords = {};
|
passwords = {};
|
||||||
defaultUser = null;
|
defaultUser = null;
|
||||||
if (config.type == "credentials") {
|
if (config.type == "credentials" || config.type == "oauth") {
|
||||||
if (config.users) {
|
if (config.users) {
|
||||||
if (typeof config.users === "function") {
|
if (typeof config.users === "function") {
|
||||||
api.get = config.users;
|
api.get = config.users;
|
||||||
@ -96,6 +111,6 @@ function init(config) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
init: init,
|
init: init,
|
||||||
get: function(username) { return api.get(username) },
|
get: function(username) { return api.get(username) },
|
||||||
authenticate: function(username,password) { return api.authenticate(username,password) },
|
authenticate: function() { return api.authenticate.apply(null, arguments) },
|
||||||
default: function() { return api.default(); }
|
default: function() { return api.default(); }
|
||||||
};
|
};
|
||||||
|
@ -96,9 +96,7 @@ function init(_server,_runtime) {
|
|||||||
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
||||||
editorApp.get("/icons/:module/:icon",ui.icon);
|
editorApp.get("/icons/:module/:icon",ui.icon);
|
||||||
theme.init(runtime);
|
theme.init(runtime);
|
||||||
if (settings.editorTheme) {
|
editorApp.use("/theme",theme.app());
|
||||||
editorApp.use("/theme",theme.app());
|
|
||||||
}
|
|
||||||
editorApp.use("/",ui.editorResources);
|
editorApp.use("/",ui.editorResources);
|
||||||
adminApp.use(editorApp);
|
adminApp.use(editorApp);
|
||||||
}
|
}
|
||||||
@ -109,14 +107,17 @@ function init(_server,_runtime) {
|
|||||||
adminApp.get("/auth/login",auth.login,errorHandler);
|
adminApp.get("/auth/login",auth.login,errorHandler);
|
||||||
|
|
||||||
if (settings.adminAuth) {
|
if (settings.adminAuth) {
|
||||||
//TODO: all passport references ought to be in ./auth
|
if (settings.adminAuth.type === "oauth") {
|
||||||
adminApp.use(passport.initialize());
|
auth.oauthStrategy(adminApp,settings.adminAuth.strategy);
|
||||||
adminApp.post("/auth/token",
|
} else if (settings.adminAuth.type === "credentials") {
|
||||||
auth.ensureClientSecret,
|
adminApp.use(passport.initialize());
|
||||||
auth.authenticateClient,
|
adminApp.post("/auth/token",
|
||||||
auth.getToken,
|
auth.ensureClientSecret,
|
||||||
auth.errorHandler
|
auth.authenticateClient,
|
||||||
);
|
auth.getToken,
|
||||||
|
auth.errorHandler
|
||||||
|
);
|
||||||
|
}
|
||||||
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke,errorHandler);
|
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke,errorHandler);
|
||||||
}
|
}
|
||||||
if (settings.httpAdminCors) {
|
if (settings.httpAdminCors) {
|
||||||
|
@ -42,6 +42,8 @@ var themeContext = clone(defaultContext);
|
|||||||
var themeSettings = null;
|
var themeSettings = null;
|
||||||
var runtime = null;
|
var runtime = null;
|
||||||
|
|
||||||
|
var themeApp;
|
||||||
|
|
||||||
function serveFile(app,baseUrl,file) {
|
function serveFile(app,baseUrl,file) {
|
||||||
try {
|
try {
|
||||||
var stats = fs.statSync(file);
|
var stats = fs.statSync(file);
|
||||||
@ -83,7 +85,7 @@ module.exports = {
|
|||||||
themeContext.version = runtime.version();
|
themeContext.version = runtime.version();
|
||||||
}
|
}
|
||||||
themeSettings = null;
|
themeSettings = null;
|
||||||
theme = settings.editorTheme;
|
theme = settings.editorTheme || {};
|
||||||
},
|
},
|
||||||
|
|
||||||
app: function() {
|
app: function() {
|
||||||
@ -91,7 +93,7 @@ module.exports = {
|
|||||||
var url;
|
var url;
|
||||||
themeSettings = {};
|
themeSettings = {};
|
||||||
|
|
||||||
var themeApp = express();
|
themeApp = express();
|
||||||
|
|
||||||
if (theme.page) {
|
if (theme.page) {
|
||||||
|
|
||||||
@ -187,5 +189,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
settings: function() {
|
settings: function() {
|
||||||
return themeSettings;
|
return themeSettings;
|
||||||
|
},
|
||||||
|
serveFile: function(baseUrl,file) {
|
||||||
|
return serveFile(themeApp,baseUrl,file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ describe("api auth middleware",function() {
|
|||||||
Users.init.restore();
|
Users.init.restore();
|
||||||
});
|
});
|
||||||
it("returns login details - credentials", function(done) {
|
it("returns login details - credentials", function(done) {
|
||||||
auth.init({settings:{adminAuth:{}},log:{audit:function(){}}})
|
auth.init({settings:{adminAuth:{type:"credentials"}},log:{audit:function(){}}})
|
||||||
auth.login(null,{json: function(resp) {
|
auth.login(null,{json: function(resp) {
|
||||||
resp.should.have.a.property("type","credentials");
|
resp.should.have.a.property("type","credentials");
|
||||||
resp.should.have.a.property("prompts");
|
resp.should.have.a.property("prompts");
|
||||||
@ -100,6 +100,19 @@ describe("api auth middleware",function() {
|
|||||||
done();
|
done();
|
||||||
}});
|
}});
|
||||||
});
|
});
|
||||||
|
it("returns login details - oauth", function(done) {
|
||||||
|
auth.init({settings:{adminAuth:{type:"oauth",strategy:{label:"test-oauth",icon:"test-icon"}}},log:{audit:function(){}}})
|
||||||
|
auth.login(null,{json: function(resp) {
|
||||||
|
resp.should.have.a.property("type","oauth");
|
||||||
|
resp.should.have.a.property("prompts");
|
||||||
|
resp.prompts.should.have.a.lengthOf(1);
|
||||||
|
resp.prompts[0].should.have.a.property("type","button");
|
||||||
|
resp.prompts[0].should.have.a.property("label","test-oauth");
|
||||||
|
resp.prompts[0].should.have.a.property("icon","test-icon");
|
||||||
|
|
||||||
|
done();
|
||||||
|
}});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ describe("api index", function() {
|
|||||||
|
|
||||||
describe("can serve auth", function() {
|
describe("can serve auth", function() {
|
||||||
var mockList = [
|
var mockList = [
|
||||||
'ui','nodes','flows','library','info','theme','locales','credentials'
|
'ui','nodes','flows','library','info','locales','credentials'
|
||||||
]
|
]
|
||||||
before(function() {
|
before(function() {
|
||||||
mockList.forEach(function(m) {
|
mockList.forEach(function(m) {
|
||||||
|
Loading…
Reference in New Issue
Block a user