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,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
modal: true,
|
||||
closeOnEscape: false,
|
||||
closeOnEscape: !!opts.cancelable,
|
||||
width: 600,
|
||||
resizable: false,
|
||||
draggable: false
|
||||
@ -43,17 +43,13 @@ RED.user = (function() {
|
||||
dataType: "json",
|
||||
url: "auth/login",
|
||||
success: function(data) {
|
||||
if (data.type == "credentials") {
|
||||
var i=0;
|
||||
|
||||
if (data.image) {
|
||||
$("#node-dialog-login-image").attr("src",data.image);
|
||||
} else {
|
||||
$("#node-dialog-login-image").attr("src","red/images/node-red-256.png");
|
||||
}
|
||||
if (data.type == "credentials") {
|
||||
|
||||
for (;i<data.prompts.length;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);
|
||||
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();
|
||||
});
|
||||
|
||||
} else if (data.type == "oauth") {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
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.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");
|
||||
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",
|
||||
options: []
|
||||
|
@ -279,3 +279,13 @@ span.logo {
|
||||
font-size: 16px;
|
||||
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,10 +81,23 @@ function getToken(req,res,next) {
|
||||
function login(req,res) {
|
||||
var response = {};
|
||||
if (settings.adminAuth) {
|
||||
if (settings.adminAuth.type === "credentials") {
|
||||
response = {
|
||||
"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) {
|
||||
response.image = theme.context().login.image;
|
||||
}
|
||||
@ -114,5 +127,46 @@ module.exports = {
|
||||
return server.errorHandler()(err,req,res,next);
|
||||
},
|
||||
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 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];
|
||||
if (user) {
|
||||
if (arguments.length === 2) {
|
||||
var password = arguments[1];
|
||||
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);
|
||||
}
|
||||
@ -51,7 +66,7 @@ function init(config) {
|
||||
users = {};
|
||||
passwords = {};
|
||||
defaultUser = null;
|
||||
if (config.type == "credentials") {
|
||||
if (config.type == "credentials" || config.type == "oauth") {
|
||||
if (config.users) {
|
||||
if (typeof config.users === "function") {
|
||||
api.get = config.users;
|
||||
@ -96,6 +111,6 @@ function init(config) {
|
||||
module.exports = {
|
||||
init: init,
|
||||
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(); }
|
||||
};
|
||||
|
@ -96,9 +96,7 @@ function init(_server,_runtime) {
|
||||
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
||||
editorApp.get("/icons/:module/:icon",ui.icon);
|
||||
theme.init(runtime);
|
||||
if (settings.editorTheme) {
|
||||
editorApp.use("/theme",theme.app());
|
||||
}
|
||||
editorApp.use("/",ui.editorResources);
|
||||
adminApp.use(editorApp);
|
||||
}
|
||||
@ -109,7 +107,9 @@ function init(_server,_runtime) {
|
||||
adminApp.get("/auth/login",auth.login,errorHandler);
|
||||
|
||||
if (settings.adminAuth) {
|
||||
//TODO: all passport references ought to be in ./auth
|
||||
if (settings.adminAuth.type === "oauth") {
|
||||
auth.oauthStrategy(adminApp,settings.adminAuth.strategy);
|
||||
} else if (settings.adminAuth.type === "credentials") {
|
||||
adminApp.use(passport.initialize());
|
||||
adminApp.post("/auth/token",
|
||||
auth.ensureClientSecret,
|
||||
@ -117,6 +117,7 @@ function init(_server,_runtime) {
|
||||
auth.getToken,
|
||||
auth.errorHandler
|
||||
);
|
||||
}
|
||||
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke,errorHandler);
|
||||
}
|
||||
if (settings.httpAdminCors) {
|
||||
|
@ -42,6 +42,8 @@ var themeContext = clone(defaultContext);
|
||||
var themeSettings = null;
|
||||
var runtime = null;
|
||||
|
||||
var themeApp;
|
||||
|
||||
function serveFile(app,baseUrl,file) {
|
||||
try {
|
||||
var stats = fs.statSync(file);
|
||||
@ -83,7 +85,7 @@ module.exports = {
|
||||
themeContext.version = runtime.version();
|
||||
}
|
||||
themeSettings = null;
|
||||
theme = settings.editorTheme;
|
||||
theme = settings.editorTheme || {};
|
||||
},
|
||||
|
||||
app: function() {
|
||||
@ -91,7 +93,7 @@ module.exports = {
|
||||
var url;
|
||||
themeSettings = {};
|
||||
|
||||
var themeApp = express();
|
||||
themeApp = express();
|
||||
|
||||
if (theme.page) {
|
||||
|
||||
@ -187,5 +189,8 @@ module.exports = {
|
||||
},
|
||||
settings: function() {
|
||||
return themeSettings;
|
||||
},
|
||||
serveFile: function(baseUrl,file) {
|
||||
return serveFile(themeApp,baseUrl,file);
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ describe("api auth middleware",function() {
|
||||
Users.init.restore();
|
||||
});
|
||||
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) {
|
||||
resp.should.have.a.property("type","credentials");
|
||||
resp.should.have.a.property("prompts");
|
||||
@ -100,6 +100,19 @@ describe("api auth middleware",function() {
|
||||
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() {
|
||||
var mockList = [
|
||||
'ui','nodes','flows','library','info','theme','locales','credentials'
|
||||
'ui','nodes','flows','library','info','locales','credentials'
|
||||
]
|
||||
before(function() {
|
||||
mockList.forEach(function(m) {
|
||||
|
Loading…
Reference in New Issue
Block a user