218 changes: 196 additions & 22 deletions app/shell-window/ui/navbar.js
@@ -1,4 +1,4 @@
import { remote } from 'electron'
import { remote, ipcRenderer } from 'electron'
import * as pages from '../pages'
import * as zoom from '../pages/zoom'
import * as yo from 'yo-yo'
Expand Down Expand Up @@ -27,6 +27,48 @@ var autocompleteCurrentValue = null
var autocompleteCurrentSelection = 0
var autocompleteResults = null // if set to an array, will render dropdown

var isSafeAppAuthenticating = false
var safeAuthNetworkState = -1
var safeAuthData = null
var safeAuthPopupDiv = yo`<div></div>`

ipcRenderer.send('registerSafeNetworkListener');
ipcRenderer.send('registerOnAuthReq');
ipcRenderer.send('registerOnContainerReq');

ipcRenderer.on('onNetworkStatus', function(event, status) {
console.log('Authenticator network state :: ', status)
safeAuthNetworkState = status
if (status === -1) {
hideSafeAuthPopup();
}
update()
})

ipcRenderer.on('onAuthReq', function(event, data) {
if (data) {
safeAuthData = data
showSafeAuthPopup()
}
})

ipcRenderer.on('onContainerReq', function(event, data) {
if (data) {
safeAuthData = data
showSafeAuthPopup(true)
}
})

ipcRenderer.on('onAuthDecisionRes', function(event, data) {
isSafeAppAuthenticating = false
update()
})

ipcRenderer.on('onContDecisionRes', function(event, data) {
isSafeAppAuthenticating = false
update()
})

// exported functions
// =

Expand Down Expand Up @@ -83,7 +125,7 @@ export function clearAutocomplete () {

export function update (page) {
// fetch current page, if not given
page = page || pages.getActive()
page = pages.getActive()

// render
yo.update(page.navbarEl, render(page.id, page))
Expand All @@ -103,6 +145,103 @@ export function updateLocation (page) {
}
}

export function handleSafeAuthAuthentication(url) {
ipcRenderer.send('decryptRequest', url)
clearAutocomplete()
if (safeAuthNetworkState === -1) {
onClickOpenSafeAuthHome()
}
}

function authDecision(isAllowed, isContainerReq) {
isSafeAppAuthenticating = true
update()
if (isContainerReq) {
ipcRenderer.send('registerContainerDecision', safeAuthData, isAllowed);
return
}
ipcRenderer.send('registerAuthDecision', safeAuthData, isAllowed);
}

function onClickAllowBtn(e) {
hideSafeAuthPopup()
authDecision(true, (parseInt(e.target.dataset.type, 10) === 0))
}

function onClickDenyBtn(e) {
hideSafeAuthPopup()
authDecision(false, (parseInt(e.target.dataset.type, 10) === 0))
}

function hideSafeAuthPopup() {
yo.update(safeAuthPopupDiv, yo`<div></div>`)
}

function showSafeAuthPopup(isContainerReq) {
var arrToYo = function(arr) {
var getPermissionPhrase = function(perm) {
switch (perm) {
case 'Read':
return yo`<span>Allow application to <i>read</i> data</span>`;
case 'Insert':
return yo`<span>Allow application to <i>add new</i> data</span>`;
case 'Update':
return yo`<span>Allow application to <i>update</i> existing data</span>`;
case 'Delete':
return yo`<span>Allow application to <i>delete</i> data</span>`;
case 'ManagePermissions':
return yo`<span>Grant application <i>full control</i> of the container</span>`;
}
};

return arr.map(function(item) {
return yo`<li>${getPermissionPhrase(item)}</li>`;
})
}
var reqKey = isContainerReq ? 'contReq' : 'authReq';
var allowBtn = yo`<button class="allow-btn" onclick=${onClickAllowBtn} data-type="${isContainerReq ? 0 : 1}">Allow</button>`
var denyBtn = yo`<button class="deny-btn" onclick=${onClickDenyBtn} data-type="${isContainerReq ? 0 : 1}">Deny</button>`
var contPara = (safeAuthData[reqKey].containers.length === 0) ? 'requesting for authorisation.' : 'requesting access for the following contaniers';

var popupBase = yo`<div class="popup">
<div class="popup-base">
<div class="popup-title">Authorisation request</div>
<div class="popup-i">
<div class="popup-cnt">
<div class="popup-cnt-main">
<h3>${safeAuthData[reqKey].app.name}</h3>
<h4>${safeAuthData[reqKey].app.vendor}</h4>
<p>${contPara}</p>
</div>
<div class="popup-cnt-ls">
<span class="list">
${
safeAuthData[reqKey].containers.map(function(container) {
if (typeof container.access === 'object') {
return yo`<div class="list-i" onclick=${togglePermissions}>
<h3>${container.cont_name}</h3>
<ul>
${arrToYo(container.access)}
</ul>
</div>`;
}
return yo`<div class="list-i"><h3>${container.cont_name}</h3></div>`;
})
}
</span>
</div>
</div>
<div class="popup-foot">
${denyBtn}
${allowBtn}
</div>
</div>
</div>
</div>`

yo.update(safeAuthPopupDiv, popupBase)
}

// internal helpers
// =

Expand All @@ -111,12 +250,12 @@ function render (id, page) {

var webContents = remote.getCurrentWindow().webContents;

var isSafe = webContents.isSafe;
// var isSafe = webContents.isSafe;

if( typeof isSafe === 'undefined' )
{
isSafe = true;
}
// if( typeof isSafe === 'undefined' )
// {
// isSafe = true;
// }

// back/forward should be disabled if its not possible go back/forward
var backDisabled = (page && page.canGoBack()) ? '' : 'disabled'
Expand All @@ -131,15 +270,27 @@ function render (id, page) {
<span class="icon icon-ccw"></span>
</button>`


// render safe btn
var safeBtn = (isSafe)
? yo`<button class="toolbar-btn safe-btn-safe" onclick=${onClickToggleSafe}>
<span class="icon icon-rocket"></span>
</button>`
: yo`<button class="toolbar-btn safe-btn-not-safe" onclick=${onClickToggleSafe}>
<span class="icon icon-alert"></span>
var safeNetworkStatusBtn = (isSafeAppAuthenticating)
? yo`<button class="toolbar-btn loading" onclick=${onClickOpenSafeAuthHome}>
<span class="icon icon-hourglass"></span>
</button>`
: yo`<button class="toolbar-btn ${(function() {
if (safeAuthNetworkState === 0) {
return 'connecting'
} else if (safeAuthNetworkState === 1) {
return 'connected'
} else if (safeAuthNetworkState === 2) {
return 'terminated'
}
})()}" onclick=${onClickOpenSafeAuthHome}>
<span class="icon icon-rocket"></span>
</button>`

// render safe btn
var safeBtn = yo`<span class="safe-btn-safe">
${safeNetworkStatusBtn}
${safeAuthPopupDiv}
</span>`

// `page` is null on initial render
// and the toolbar should be hidden on initial render
Expand Down Expand Up @@ -283,10 +434,10 @@ function handleAutocompleteSearch (results) {
if (isProbablyUrl && !v.includes('://') && !(v.startsWith('beaker:') || v.startsWith('ipfs:/'))) {
if (isHashRegex.test(v))
vWithProtocol = 'dat://'+v
else {
vWithProtocol = 'safe://'+v
isGuessingTheScheme = true // note that we're guessing so that, if this fails, we can try http://
}
// else {
// vWithProtocol = 'safe://'+v
// isGuessingTheScheme = true // note that we're guessing so that, if this fails, we can try http://
// }
}

// set the top results accordingly
Expand Down Expand Up @@ -419,9 +570,24 @@ function onClickReload (e) {
page.reload()
}

export function onClickToggleSafe ( e )
{
pages.toggleSafe();
function togglePermissions(e) {
var targetNode = e.currentTarget;
if (!targetNode.classList.contains('list-i')) {
return;
}
if (targetNode.classList.contains('show')) {
return targetNode.classList.remove('show');
}
targetNode.classList.add('show');
}

// export function onClickToggleSafe ( e )
// {
// pages.toggleSafe();
// }

function onClickOpenSafeAuthHome(e) {
pages.setActive(pages.create(pages.SAFE_AUTH_DEFAULT_URL))
}

function onClickCancel (e) {
Expand Down Expand Up @@ -492,6 +658,14 @@ function onKeydownLocation (e) {
var page = getEventPage(e)
if (page) {
var selection = getAutocompleteSelection()
// laod safeauth page
if (new URL(selection.url).protocol === pages.SAFE_AUTH_SCHEME) {
if (pages.handleSafeAuthScheme(selection.url)) {
e.target.blur()
return
}
}

page.loadURL(selection.url, { isGuessingTheScheme: selection.isGuessingTheScheme })
e.target.blur()
}
Expand Down
171 changes: 170 additions & 1 deletion app/stylesheets/shell-window/toolbar-actions.less
Expand Up @@ -71,4 +71,173 @@
}
}
}
}
.safe-btn-safe {
position: relative;
.connected {
color: #5592d7;
}
.connecting {
transition: color 1s linear;
animation: safeConnecting 1s infinite;
}
.terminated {
color: #e26464;
}
.loading {
animation: safeLoading 1.5s infinite
}
> .popup {
position: absolute;
width: 400px;
z-index: 99;
background: white;
border: 1px solid #AAA;
top: 100%;
left: 0;
margin-top: 6px;
box-shadow: 0 1px 16px rgba(0, 0, 0, 0.26);
.popup-base {
margin: 0;
padding: 0;
list-style: none;
text-align: left;
.popup-title {
font-size: 15px;
line-height: 30px;
font-weight: 600;
position: relative;
padding: 12px 12px 12px 54px;
background-color: #EFEFEF;
&:before {
content: "";
background-image: url(beaker:safe-auth-logo);
width: 30px;
height: 30px;
display: inline-block;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
position: absolute;
top: 12px;
left: 12px;
}
}
.popup-i {
position: relative;
padding-top: 16px;
max-height: 250px;
overflow: auto;
.popup-cnt {
min-height: 150px;
padding: 0 16px;
.popup-cnt-main {
padding-bottom: 12px;
h3 {
font-size: 20px;
font-weight: 500;
margin: 0;
padding: 0;
}
h4 {
font-size: 14px;
font-weight: 500;
opacity: 0.5;
margin: 0;
padding: 0;
}
p.p {
font-size: 14px;
opacity: 0.5;
padding: 24px 0 0;
display: inline-block;
}
}
.popup-cnt-ls {
font-size: 14px;
margin-bottom: 16px;
.list {
.list-i {
font-size: 16px;
&.show {
ul {
display: block;
}
}
h3 {
font-size: 16px;
font-weight: 500;
line-height: 24px;
margin: 0 0 5px;
padding: 0;
cursor: pointer;
&:hover {
color: #5592d7;
}
}
ul {
margin: 0 0 16px;
font-size: 14px;
opacity: 0.8;
padding-left: 24px;
display: none;
li {
line-height: 24px;
i {
opacity: 0.8;
}
}
}
}
}
&:last-child {
margin-bottom: 0;
}
}
}
.popup-foot {
width: 100%;
padding: 16px;
text-align: right;
button {
border: 0;
background: transparent;
font-size: 15px;
cursor: pointer;
outline: none;
padding: 8px 16px;
text-transform: uppercase;
border-radius: 3px;
&:hover {
box-shadow: 0 1px 5px rgba(0,0,0,0.26);
}
&.allow-btn {
color: #5592d7;
}
}
}
}
}
}
}
}

@keyframes safeLoading {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}

@keyframes safeConnecting {
0% {
color: #666666;
}
50% {
color: #5592d7;
}
}
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -6,6 +6,7 @@
"electron": "1.4.1",
"electron-builder": "6.5.2",
"electron-localshortcut": "^1.0.0",
"electron-rebuild": "^1.4.0",
"fs-jetpack": "^0.9.0",
"gulp": "^3.9.0",
"gulp-batch": "^1.0.5",
Expand Down Expand Up @@ -72,7 +73,8 @@
"protocols": {
"name": "SAFE Network URL",
"schemes": [
"safe"
"safe",
"safe-auth"
]
},
"linux": {
Expand Down