Add Git access feature via SSH and Enhance SSH Key management

This commit is contained in:
Hideki Nakamura
2017-12-16 00:07:47 +09:00
parent 3a311c9584
commit fe10b8650f
7 changed files with 571 additions and 84 deletions

View File

@@ -16,6 +16,7 @@
RED.projects.userSettings = (function() {
var gitconfigContainer;
var gitUsernameInput;
var gitEmailInput;
@@ -24,11 +25,9 @@ RED.projects.userSettings = (function() {
var currentGitSettings = RED.settings.get('git') || {};
currentGitSettings.user = currentGitSettings.user || {};
var title = $('<h3></h3>').text("Committer Details").appendTo(pane);
var gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
$('<div style="color:#aaa;"></div>').appendTo(gitconfigContainer).text("Leave blank to use system default");
var row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
@@ -40,77 +39,245 @@ RED.projects.userSettings = (function() {
$('<label for=""></label>').text('Email').appendTo(row);
gitEmailInput = $('<input type="text">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||"");
// var sshkeyTitle = $('<h4></h4>').text("SSH Keys").appendTo(gitconfigContainer);
// var generateSshKeyButton = $('<button class="editor-button editor-button-small" style="float: right;">generate new ssh key</button>')
// .appendTo(sshkeyTitle)
// .click(function(evt) {
// console.log('click generateSshKeyButton');
// });
// row = $('<div class="user-settings-row projects-dialog-remote-list"></div>').appendTo(gitconfigContainer);
// var sshkeysList = $('<ol>').appendTo(row);
// sshkeysList.editableList({
// addButton: false,
// height: 'auto',
// addItem: function(outer,index,entry) {
var sshkeyTitle = $('<h4></h4>').text("SSH Keys").appendTo(gitconfigContainer);
var editSshKeyListButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>')
.appendTo(sshkeyTitle)
.click(function(evt) {
editSshKeyListButton.hide();
formButtons.show();
sshkeyInputRow.show();
$(".projects-dialog-sshkey-list-button-remove").css('display', 'inline-block');
});
var sshkeyListOptions = {
height: "300px",
deleteAction: function(entry, header) {
sendSSHKeyManagementAPI("DELETE_KEY", entry.name, function(data) {
hideSSHKeyGenerateForm();
utils.refreshSSHKeyList(sshkeysList);
});
},
selectAction: function(entry, header) {
sendSSHKeyManagementAPI("GET_KEY_DETAIL", entry.name, function(data) {
setDialogContext(entry.name, data.publickey);
dialog.dialog("open");
});
}
};
var sshkeysListRow = $('<div class="user-settings-row projects-dialog-sshkeylist"></div>').appendTo(gitconfigContainer);
var sshkeysList = utils.createSSHKeyList(sshkeyListOptions).appendTo(sshkeysListRow);
// var header = $('<div class="projects-dialog-remote-list-entry-header"></div>').appendTo(outer);
// entry.header = $('<span>').text(entry.path||"Add new remote").appendTo(header);
// var body = $('<div>').appendTo(outer);
// entry.body = body;
// if (entry.path) {
// entry.removeButton = $('<button class="editor-button editor-button-small projects-dialog-remote-list-entry-delete">remove</button>')
// // .hide()
// .appendTo(header)
// .click(function(e) {
// entry.removed = true;
// body.fadeOut(100);
// entry.header.css("text-decoration","line-through")
// entry.header.css("font-style","italic")
// if (entry.copyToClipboard) {
// entry.copyToClipboard.hide();
// }
// $(this).hide();
// });
// if (entry.data) {
// entry.copyToClipboard = $('<button class="editor-button editor-button-small projects-dialog-remote-list-entry-copy">copy</button>')
// // .hide()
// .appendTo(header)
// .click(function(e) {
// var textarea = document.createElement("textarea");
// textarea.style.position = 'fixed';
// textarea.style.top = 0;
// textarea.style.left = 0;
// textarea.style.width = '2em';
// textarea.style.height = '2em';
// textarea.style.padding = 0;
// textarea.style.border = 'none';
// textarea.style.outline = 'none';
// textarea.style.boxShadow = 'none';
// textarea.style.background = 'transparent';
// textarea.value = entry.data;
// document.body.appendChild(textarea);
// textarea.select();
// try {
// var ret = document.execCommand('copy');
// var msg = ret ? 'successful' : 'unsuccessful';
// console.log('Copy text command was ' + msg);
// } catch (err) {
// console.log('Oops unable to copy');
// }
// document.body.removeChild(textarea);
// });
// }
// }
// }
// });
var sshkeyInputRow = $('<div class="user-settings-row"></div>').hide().appendTo(gitconfigContainer);
var sshkeyNameLabel = $('<label for=""></label>').text('Key Name').appendTo(sshkeyInputRow);
var sshkeyNameInput = $('<input type="text">').appendTo(sshkeyInputRow);
var sshkeyPassphraseLabel = $('<label for=""></label>').text('Passphrase').appendTo(sshkeyInputRow);
var sshkeyPassphraseInput = $('<input type="password">').appendTo(sshkeyInputRow);
var sshkeySamePassphraseLabel = $('<label for=""></label>').text('Same Passphrase').appendTo(sshkeyInputRow);
var sshkeySamePassphraseInput = $('<input type="password">').appendTo(sshkeyInputRow);
// var remoteListAddButton = row.find(".red-ui-editableList-addButton").hide();
var formButtonArea = $('<div style="width: 100%; height: 35px;"></div>').appendTo(gitconfigContainer);
var formButtons = $('<span class="button-group" style="position: absolute; right: 0px; margin-right: 0px;"></span>')
.hide().appendTo(formButtonArea);
function hideSSHKeyGenerateForm() {
editSshKeyListButton.show();
formButtons.hide();
sshkeyInputRow.hide();
sshkeyNameInput.val("");
sshkeyPassphraseInput.val("");
sshkeySamePassphraseInput.val("");
if ( sshkeyNameInput.hasClass('input-error') ) {
sshkeyNameInput.removeClass('input-error');
}
if ( sshkeyPassphraseInput.hasClass('input-error') ) {
sshkeyPassphraseInput.removeClass('input-error');
}
if ( sshkeySamePassphraseInput.hasClass('input-error') ) {
sshkeySamePassphraseInput.removeClass('input-error');
}
$(".projects-dialog-sshkey-list-button-remove").hide();
}
$('<button class="editor-button">Cancel</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideSSHKeyGenerateForm();
});
var generateButton = $('<button class="editor-button">Generate</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
if ( sshkeyNameInput.hasClass('input-error') ) {
sshkeyNameInput.removeClass('input-error');
}
if ( sshkeyPassphraseInput.hasClass('input-error') ) {
sshkeyPassphraseInput.removeClass('input-error');
}
if ( sshkeySamePassphraseInput.hasClass('input-error') ) {
sshkeySamePassphraseInput.removeClass('input-error');
}
var valid = true;
if ( sshkeyNameInput.val() === "" ) {
sshkeyNameInput.addClass('input-error');
valid = false;
}
if ( sshkeyPassphraseInput.val() !== sshkeySamePassphraseInput.val() ) {
sshkeySamePassphraseInput.addClass('input-error');
valid = false;
}
if ( valid ) {
sendSSHKeyManagementAPI("GENERATE_KEY",
{
name: sshkeyNameInput.val(),
email: gitEmailInput.val(),
password: sshkeyPassphraseInput.val(),
size: 4096
},
function() {
hideSSHKeyGenerateForm();
utils.refreshSSHKeyList(sshkeysList);
},
function(err) {
console.log('err message:', err.message);
if ( err.message.includes('Some SSH Keyfile exists') ) {
sshkeyNameInput.addClass('input-error');
}
else if ( err.message.includes('Failed to generate ssh key files') ) {
sshkeyPassphraseInput.addClass('input-error');
sshkeySamePassphraseInput.addClass('input-error');
}
}
);
}
});
}
function sendSSHKeyManagementAPI(type, param, successCallback, failCallback) {
var url;
var method;
var payload;
switch(type) {
case 'GET_KEY_LIST':
method = 'GET';
url = "settings/user/keys";
break;
case 'GET_KEY_DETAIL':
method = 'GET';
url = "settings/user/keys/" + param;
break;
case 'GENERATE_KEY':
method = 'POST';
url = "settings/user/keys";
payload= param;
break;
case 'DELETE_KEY':
method = 'DELETE';
url = "settings/user/keys/" + param;
break;
default:
console.error('Unexpected type....');
return;
}
var spinner = utils.addSpinnerOverlay(gitconfigContainer);
var done = function(err) {
spinner.remove();
if (err) {
console.log(err);
return;
}
};
console.log('method:', method);
console.log('url:', url);
utils.sendRequest({
url: url,
type: method,
responses: {
0: function(error) {
if ( failCallback ) {
failCallback(error);
}
done(error);
},
200: function(data) {
if ( successCallback ) {
successCallback(data);
}
done();
},
400: {
'unexpected_error': function(error) {
console.log(error);
if ( failCallback ) {
failCallback(error);
}
done(error);
}
},
}
},payload);
}
var dialog;
var dialogBody;
function createPublicKeyDialog() {
dialog = $('<div id="projects-dialog" class="hide node-red-dialog projects-edit-form"><form class="form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 600,
resize: false,
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
},
close: function(e) {
}
});
dialogBody = dialog.find("form");
dialog.dialog('option', 'title', 'SSH public key');
dialog.dialog('option', 'buttons', [
{
text: RED._("common.label.close"),
click: function() {
$( this ).dialog( "close" );
}
},
{
text: "Copy to Clipboard",
click: function() {
var target = document.getElementById('public-key-data');
document.getSelection().selectAllChildren(target);
var ret = document.execCommand('copy');
var msg = ret ? 'successful' : 'unsuccessful';
console.log('Copy text command was ' + msg);
$( this ).dialog("close");
}
}
]);
dialog.dialog({position: { 'my': 'center', 'at': 'center', 'of': window }});
var container = $('<div class="projects-dialog-screen-start"></div>');
$('<div class="projects-dialog-ssh-public-key-name"></div>').appendTo(container);
$('<div class="projects-dialog-ssh-public-key"><pre id="public-key-data"></pre></div>').appendTo(container);
dialogBody.append(container);
}
function setDialogContext(name, data) {
var title = dialog.find("div.projects-dialog-ssh-public-key-name");
title.text(name);
var context = dialog.find("div.projects-dialog-ssh-public-key>pre");
context.text(data);
}
function createSettingsPane(activeProject) {
var pane = $('<div id="user-settings-tab-gitconfig" class="project-settings-tab-pane node-help"></div>');
createRemoteRepositorySection(pane);
createPublicKeyDialog();
return pane;
}

View File

@@ -54,6 +54,7 @@ RED.projects = (function() {
var projectRepoUserInput;
var projectRepoPasswordInput;
var projectNameSublabel;
var projectRepoSSHKeySelect;
var projectRepoPassphrase;
var projectRepoRemoteName
var projectRepoBranch;
@@ -126,12 +127,18 @@ RED.projects = (function() {
if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(repo)) {
$(".projects-dialog-screen-create-row-creds").hide();
$(".projects-dialog-screen-create-row-passphrase").show();
$(".projects-dialog-screen-create-row-sshkey").show();
if ( !getSelectedSSHKey(projectRepoSSHKeySelect) ) {
valid = false;
}
} else if (/^https?:\/\//.test(repo)) {
$(".projects-dialog-screen-create-row-creds").show();
$(".projects-dialog-screen-create-row-passphrase").hide();
$(".projects-dialog-screen-create-row-sshkey").hide();
} else {
$(".projects-dialog-screen-create-row-creds").show();
$(".projects-dialog-screen-create-row-passphrase").hide();
$(".projects-dialog-screen-create-row-sshkey").hide();
}
@@ -331,6 +338,16 @@ RED.projects = (function() {
$('<label for="projects-dialog-screen-create-project-repo-pass">Password</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(container);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH Private key file</label>').appendTo(row);
projectRepoSSHKeySelect = createSSHKeyList({
height: "120px",
selectAction: function(entry, header) {
$('.projects-dialog-sshkey-list-entry').removeClass('selected');
header.addClass('selected');
}
}).appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-passphrase"></div>').hide().appendTo(container);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH key passphrase</label>').appendTo(row);
projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password" style="width: calc(100% - 250px);"></input>').appendTo(row);
@@ -397,14 +414,29 @@ RED.projects = (function() {
projectData.copy = copyProject.name;
} else if (projectType === 'clone') {
// projectData.credentialSecret = projectSecretInput.val();
projectData.git = {
remotes: {
'origin': {
url: projectRepoInput.val(),
username: projectRepoUserInput.val(),
password: projectRepoPasswordInput.val()
var repoUrl = projectRepoInput.val();
var metaData = {};
if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(repoUrl)) {
projectData.git = {
remotes: {
'origin': {
url: repoUrl,
key_file: getSelectedSSHKey(projectRepoSSHKeySelect).name,
passphrase: projectRepoPassphrase.val()
}
}
}
};
}
else {
projectData.git = {
remotes: {
'origin': {
url: repoUrl,
username: projectRepoUserInput.val(),
password: projectRepoPasswordInput.val()
}
}
};
}
}
@@ -434,6 +466,8 @@ RED.projects = (function() {
projectRepoUserInput.addClass("input-error");
projectRepoPasswordInput.addClass("input-error");
// getRepoAuthDetails(req);
projectRepoSSHKeySelect.addClass("input-error");
projectRepoPassphrase.addClass("input-error");
console.log("git auth error",error);
},
'unexpected_error': function(error) {
@@ -694,6 +728,100 @@ RED.projects = (function() {
return container;
}
// var selectedSSHKey = null;
// var sshkeyList = null;
$.fn.isVisible = function() {
return $.expr.filters.visible(this[0]);
}
function createSSHKeyList(options) {
options = options || {};
var minHeight = "33px";
var maxHeight = options.height || "120px";
// var container = $('<div></div>',{style:"min-height: "+height+"; height: "+height+";"});
var container = $('<div></div>',{style:"max-height: "+maxHeight+";"});
// var sshkeyList = $('<ol>',{class:"projects-dialog-sshkey-list", style:"height:"+height}).appendTo(container).editableList({
var sshkeyList = $('<ol>',{class:"projects-dialog-sshkey-list", style:"max-height:"+maxHeight+";min-height:"+minHeight+";"}).appendTo(container).editableList({
addButton: false,
scrollOnAdd: false,
addItem: function(row,index,entry) {
var header = $('<div></div>',{class:"projects-dialog-sshkey-list-entry"}).appendTo(row);
$('<span class="projects-dialog-sshkey-list-entry-icon"><i class="fa fa-key"></i></span>').appendTo(header);
$('<span class="projects-dialog-sshkey-list-entry-name" style=""></span>').text(entry.name).appendTo(header);
var deleteButton = $('<span/>',{class:"projects-dialog-sshkey-list-entry-icon projects-dialog-sshkey-list-button-remove editor-button editor-button-small"})
.hide()
.appendTo(header)
.click(function(evt) {
evt.preventDefault();
console.log('deleteButton --- click');
if ( options.deleteAction ) {
options.deleteAction(entry, header);
}
return false;
});
$('<i/>',{class:"fa fa-trash-o"}).appendTo(deleteButton);
header.addClass("selectable");
row.click(function(evt) {
if ( !deleteButton.isVisible() ) {
if ( options.selectAction ) {
options.selectAction(entry, header);
}
$.data(container[0], 'selected', entry);
}
return false;
})
}
});
$.getJSON("settings/user/keys", function(data) {
data.keys.forEach(function(key) {
console.log('key:', key);
if ( sshkeyList ) {
sshkeyList.editableList('addItem',key);
}
else {
console.log('[create] Error! selectedSSHKey is not set up.');
}
});
});
if ( sshkeyList ) {
sshkeyList.addClass("projects-dialog-sshkey-list-small");
$.data(container[0], 'selected', null);
$.data(container[0], 'sshkeys', sshkeyList);
}
console.log('container.sshkeys:', container.data('sshkeys'));
return container;
}
function getSelectedSSHKey(container) {
var selected = $.data(container[0], 'selected');
if ( container && selected ) {
return selected;
}
else {
return null;
}
}
function refreshSSHKeyList(container) {
console.log('refreshSSHKeyList');
var sshkeyList = $.data(container[0], 'sshkeys');
console.log(' ---> container:', container);
console.log(' ---> container.sshkeyList:', sshkeyList);
if ( container && sshkeyList ) {
sshkeyList.empty();
$.getJSON("settings/user/keys", function(data) {
var keyList = $.data(container[0], 'sshkeys');
data.keys.forEach(function(key) {
console.log('key:', key);
if ( keyList ) {
keyList.editableList('addItem',key);
}
else {
console.log('[refresh] Error! selectedSSHKey is not set up.');
}
});
});
}
}
function sendRequest(options,body) {
// dialogBody.hide();
console.log(options.url,body);
@@ -1013,7 +1141,9 @@ RED.projects = (function() {
var projectsAPI = {
sendRequest:sendRequest,
createBranchList:createBranchList,
addSpinnerOverlay:addSpinnerOverlay
addSpinnerOverlay:addSpinnerOverlay,
createSSHKeyList:createSSHKeyList,
refreshSSHKeyList:refreshSSHKeyList
};
RED.projects.settings.init(projectsAPI);
RED.projects.userSettings.init(projectsAPI);

View File

@@ -654,6 +654,94 @@
float: right;
}
.projects-dialog-sshkey-list {
li {
padding: 0 !important;
}
&.projects-dialog-sshkey-list-small {
.projects-dialog-sshkey-list-entry {
padding: 6px 0;
i {
font-size: 1em;
}
}
.projects-dialog-sshkey-list-entry-name {
font-size: 1em;
}
.projects-dialog-sshkey-list-entry-current {
margin-right: 10px;
padding-top: 2px;
}
}
}
.red-ui-editableList-container {
.projects-dialog-sshkey-list {
li:last-child {
border-bottom: 0px none;
}
}
}
.projects-dialog-sshkey-list-entry {
padding: 12px 0;
border-left: 3px solid #fff;
border-right: 3px solid #fff;
&.sshkey-list-entry-current {
&:not(.selectable) {
background: #f9f9f9;
}
i {
color: #999;
}
}
&.selectable {
cursor: pointer;
&:hover {
background: #f3f3f3;
border-left-color: #aaa;
border-right-color: #aaa;
}
}
i {
color: #ccc;
font-size: 2em;
}
&.selected {
background: #efefef;
border-left-color:#999;
border-right-color:#999;
}
span {
display: inline-block;
vertical-align:middle;
}
.projects-dialog-sshkey-list-entry-icon {
margin: 0 10px 0 5px;
}
.projects-dialog-sshkey-list-entry-name {
font-size: 1.2em;
}
.projects-dialog-sshkey-list-entry-current {
float: right;
margin-right: 20px;
font-size: 0.9em;
color: #999;
padding-top: 4px;
}
.projects-dialog-sshkey-list-button-remove {
position: absolute;
right: 4px;
}
}
div.projects-dialog-ssh-public-key {
pre {
word-break: break-all;
}
}
/*
.expandable-list-entry {
.exandable-list-entry-header {