AngularJS authentication - Incompatibility between directives? - authentication

I'm really new on Angular JS development and I'm trying to implement the following authentication module : https://github.com/witoldsz/angular-http-auth (more info at http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application), to my project .
THE MODULE
The module has been thought to allow the following scenario :
user asks for: something.com/secured/formXyz,
server sends a login form,
user logs in, fills a long and complicated form, but they are doing it so long that theirs session expires,
user submits a form, but since the session is not valid anymore, login screen appears,
once user logs in, server can process the submitted form, **no need to re-enter everything again**.
The solution to do such a thing is :
server side behavior :
for every /resources/* call, if user is not authorized, response a 401 status. Otherwise, when user is authorized or when not a /resources/* request, send what client asked for.
client side behavior :
capture 401 response,
save the request parameters, so in the future we can reconstruct original request,
create and return new object representing server’s future answer (instead of returning the original failed response),
broadcast that login is required, so application can react, in particular login form can appear,
listen to login successful events, so we can gather all the saved request parameters, resend them again and trigger all the ‘future’ objects (returned previously).
MY PROJECT
My project is a basic one, which uses the $route service. When I try to add the directive which catch the events :
scripts/directives/login-directive.js
angular.module('myApp', ['http-auth-interceptor','content-mocks'])
/**
* This directive will find itself inside HTML as a class,
* It is responsible for showing/hiding login form.
*/
.directive('authDirective', function() {
return {
restrict: 'C',
link: function(scope, elem, attrs) {
var login = elem.find('#login');
var main = elem.find('#main');
login.hide();
main.show();
scope.$on('event:auth-loginRequired', function() {
login.show();
main.hide();
});
scope.$on('event:auth-loginConfirmed', function() {
main.show();
login.hide();
});
}
}
});
to my index.html :
<body ng-app='myApp' class='auth-directive'>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an outdated browser. Upgrade your browser today or install Google Chrome Frame to better experience this site.</p>
<![endif]-->
<!--[if lt IE 9]>
<script src="components/es5-shim/es5-shim.js"></script>
<script src="components/json3/lib/json3.min.js"></script>
<![endif]-->
<div id="login" name="login">
<p>login</p>
</div>
<div id="main" name="main">
<p>main</p>
<div class="container" ng-view></div>
</div>
<script src="components/angular/angular.js"></script>
<script src="components/angular-resource/angular-resource.js"></script>
<script src="components/angular-cookies/angular-cookies.js"></script>
<script src="components/angular-sanitize/angular-sanitize.js"></script>
<script src="components/angular-http-auth/angular-mocks-1.0.1.js" type="text/javascript"></script>
<script src="components/angular-http-auth/http-auth-interceptor.js" type="text/javascript"></script>
<script src="scripts/app.js"></script>
<script src="scripts/controllers/main.js"></script>
<script src="scripts/directives/login-directive.js"></script>
<script src="scripts/mocks/content-mocks.js" type="text/javascript"></script>
</body>
The directive ngView doesn't work anymore. Nothing appears on console log. Is there any incompatibility between the two directives ?

By the look of your login-directive files it seems that you are (improperly) reinitialising you Angular app by passing the dependencies array as a second parameter to your angular.module call:
angular.module('myApp', ['http-auth-interceptor','content-mocks'])
Dependencies should be defines only once, when app is initialised (probably in app.js), and when you want to reference your application in another file you should call the angular.module without the second parameter:
angular.module('myApp')
.directive('authDirective', function() { ...

Related

Creating Vue instance comments out everything

I'm trying to create an Chrome extension with web accessible resources. Since my extension tries to create a modal with a list of available data, I decided to use VueJS to handle the dynamic nature of content.
However, When I create the sample files and initialize VueJS, it simply leads to all the DOM being commented out and my app not working.
Here's the code I'm using:
web_resources/vue.html
<html>
<head>
<script type="text/javascript" src="/web_resources/vue.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script type="text/javascript" src="/web_resources/app.js"></script>
</body>
</html>
web_resources/app.js
window.app = new Vue({
el: '#app',
data: {
message: 'Hello World'
}
})
When I run this, the entire div is replaced with: <!----> and nothing works.
On searching online for VueJS inside iframe, I came across this post which has this fiddle which surprisingly produces a blank page for me with no content. On inspecting the result, I find that the div has been replaced with <!----> here as well.
Why is VueJS not initializing properly within an iframe?
The problem was Content Security Policy (CSP). Since I was using a local copy of vue.min.js, I couldn't see any of the error messages. It looked like everything was initializing as expected except it wasn't. Once I replaced this with a un-minified version, I saw an error on the lines of:
I simply had to go update the CSP in manifest.json which I was able to do by adding:
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",

Google-plus sign in: the code runs twice, user was logged out right after he logs in

this is a page built on example
<html>
<head>
<title>Demo: Getting an email address using the Google+ Sign-in button</title>
<style type="text/css">
html, body { margin: 0; padding: 0; }
.hide { display: none;}
.show { display: block;}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script>
<!--<script src="https://apis.google.com/js/plusone.js" type="text/javascript"></script>-->
<script src="https://apis.google.com/js/client:plusone.js" type="text/javascript"></script>
<script type="text/javascript">
/*
* Triggered when the user accepts the sign in, cancels, or closes the
* authorization dialog.
*/
function loginFinishedCallback(authResult) {
if (authResult) {
console.log('authResult : ',authResult);
if (authResult['error'] == undefined){
gapi.auth.setToken(authResult); // Store the returned token.
toggleElement('signin-button'); // Hide the sign-in button after successfully signing in the user.
getEmail(); // Trigger request to get the email address.
} else {
console.log('An error occurred');
}
} else {
console.log('Empty authResult'); // Something went wrong
}
}
/*
* Initiates the request to the userinfo endpoint to get the user's email
* address. This function relies on the gapi.auth.setToken containing a valid
* OAuth access token.
*
* When the request completes, the getEmailCallback is triggered and passed
* the result of the request.
*/
function getEmail(){
// Load the oauth2 libraries to enable the userinfo methods.
gapi.client.load('oauth2', 'v2', function() {
var request = gapi.client.oauth2.userinfo.get();
request.execute(getEmailCallback);
});
}
function getEmailCallback(obj){
var el = document.getElementById('email');
var email = '';
console.log("OBJ = ",obj)
if (obj['email']) {
email = 'Email: ' + obj['email'];
}
//console.log(obj); // Uncomment to inspect the full object.
el.innerHTML = email;
toggleElement('email');
}
function toggleElement(id) {
var el = document.getElementById(id);
if (el.getAttribute('class') == 'hide') {
el.setAttribute('class', 'show');
} else {
el.setAttribute('class', 'hide');
}
}
</script>
</head>
<body>
<div id="signin-button" class="show">
<div class="g-signin" data-callback="loginFinishedCallback"
data-approvalprompt="auto"
data-clientId="751931329576.apps.googleusercontent.com"
data-scope="https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email"
data-height="short"
data-cookiepolicy="http://semicon-equip.com"
>
</div>
<!-- In most cases, you don't want to use approvalprompt=force. Specified
here to facilitate the demo.-->
</div>
<div id="email" class="hide"></div>
</body>
</html>
Question 1: It always fails with "Uncaught TypeError: Cannot read property 'load' of undefined",
until I use
<script src="https://apis.google.com/js/client:plusone.js" type="text/javascript"></script>
instead of the example code:
<script src="https://apis.google.com/js/plusone.js" type="text/javascript"></script>
What's the difference between plusone.js and client:plusone.js ?
Question 2: Why the code run twice per page loads ?
Qestion 3: the user was logged out after he just signed in, how to fix ?
error demo page for the above (all the errors are in the background console).
This is not really an answer to the question, but a step by step procedure to reproduce it.
Below the simple html page I'm using to test (similar to the example from Ray C Lin).
I've made it as simple as possible to avoid interactions with other part of the code :
<html>
<head>
<script type="text/javascript"
src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
</head>
<body>
<input type="button" id="signOut" value="Sign out"></button>
<span id="signinButton">
<span class="g-signin"
data-accesstype="offline"
data-callback="signinCallback"
data-clientid="YOUR_CLIENT_ID_HERE"
data-cookiepolicy="single_host_origin"
data-scope="email"
</span>
</span>
<script type="text/javascript">
$('#signOut').on('click', function() {
gapi.auth.signOut();
});
function signinCallback(authResult) {
console.log("signinCallback: ", authResult);
}
(function() {
var po = document.createElement('script');
po.type = 'text/javascript';
po.async = true;
po.src = 'https://apis.google.com/js/client:plusone.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(po, s);
})();
</script>
</body>
</html>
You will have to update data-clientid with your own google client id, and display this page from an authorized javascript origin.
Please note that this might not work from localhost, as Ian suggested in a comment to this post https://plus.google.com/102746521318753162868/posts/Z5Gkro9YXVs
First, sign in using your Google account : you will see a successful callback in the console.
If you click on Sign out, you will see a callback with 'user_signed_out' in the console.
So far so good.
Sign-in again, and wait 1 hour, until the token expires (this is awful to test, as I don't know how to reduced the token lifetime).
After one hour, click on the sign out button : no callback is called.
Click on the sign-in button again :
you get a successfull callback with an authorization code and access token
immediately after, you get a 'user_signed_out' callback.
Once a session has expired, there is no way to return to a "normal" situation, you always get this second callback with 'user_signed_out'.
Actually, there is one way to return to a "normal" situation: revoke the access to the app from the google dashboard.
This is not really an issue for me as I'm using Google+ only to sign-in the user to my app using the one time authorization code, and I'm not using the access token from the client.
But this prevent automatic login from working, as the user is immediately considered as "signed out" from a google perspective.
Q1: client:plusone.js is just telling the loader to automatically load the "client" module. It's basically automatically doing gapi.load("client"), except it is already packaged up for you in one download. You could do it the other way around as well, plusone:client.js!
Q2: Not sure, it may be some JS quirk. In general, try and make your code able to handle multiple callbacks, you may get another if the state changes (e.g. the user logs out of their google account).
Q3: I don't see that on the test page - I am signed OK, and still signed in on refresh! Check you're not blocking third party cookies in your browser or similar?

IBM Worklight - How to bypass WP's " Unable to add dynamic content" warning?

I have tried to run a Windows Phone 8 App written in Sencha Touch in Worklight. I used sencha in the following way:
<script src="//Microsoft.WinJS.1.0/js/base.js"></script><script src="//Microsoft.WinJS.1.0/js/ui.js"></script><script>
// Define WL namespace.
var WL = WL ? WL : {};
/**
* WLClient configuration variables.
* Values are injected by the deployer that packs the gadget.
*/
WL.StaticAppProps = {
"APP_DISPLAY_NAME": "Cockpit",
"APP_ID": "Cockpit",
"APP_SERVICES_URL": "http:\/\/192.168.99.11:10080\/CockpitProj\/apps\/services\/",
"APP_VERSION": "1.2.1",
"ENVIRONMENT": "windows8",
"LOGIN_DISPLAY_TYPE": "embedded",
"WORKLIGHT_PLATFORM_VERSION": "6.1.0.00.20131219-1900",
"WORKLIGHT_ROOT_URL": "http:\/\/192.168.99.11:10080\/CockpitProj\/apps\/services\/api\/Cockpit\/windows8\/"
};</script>
<script src="worklight/cordova.js"></script>
<script src="worklight/wljq.js"></script>
<script src="worklight/worklight.js"></script>
</head>
<body>
<div class="loadingSpinner bigLoad" id="appLoadingIndicator"></div>
<script src="js/initOptions.js" type="text/javascript"></script>
<script src="js/sencha-touch-all.js" type="text/javascript"></script>
<script src="js/app.js" type="text/javascript"></script>
<script src="js/messages.js" type="text/javascript"></script>
</body>
</html>
If I run this code, I get an exception:
Unable to add dynamic content. A script attempted to inject dynamic
content, or elements previously modified dynamically, that might be
unsafe. For example, using the innerHTML property to add script or
malformed HTML will generate this exception. Use the toStaticHTML
method to filter dynamic content, or explicitly create elements and
attributes with a method such as createElement. For more information,
see http://go.microsoft.com/fwlink/?LinkID=247104.
This can be solved by every sencha call inside the sencha-touch-all into a
MSApp.execUnsafeLocalFunction(function () {}
This can only be a workaround, as there are too many of these sencha calls.
What is the general concept of using Sencha for Windows Phone, like
these steps from Phonegap:
IMO there is no good way around it in Windows Phone development. Doesn't matter if it is a pure Cordova app, or a Worklight-based app.
See the second option in the following discussion. Perhaps you could set it in a way that it will work for all code parts required by your app:
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/e35017b4-e21b-4807-8668-fc3c332c6b32/javascript-runtime-error
Copy-paste:
The host enforcement code will throw an access denied exception in the
event that you try to set the innerHTML (and outerHTML and a couple of
others) of an element to HTML which doesn't conform to a whitelist of
known safe HTML. You can get around this in a couple of ways:
call toStaticHTML() on your string first which should strip out everything which is disallowed
use WinJS.Utilities.setInnerHTMLUnsafe(element, text), this allows you to set innerHTML to anything you like
use msWWA.execUnsafeLocalFunction, for instance:
msWWA.execUnsafeLocalFunction(function () { element.innerHTML = text; })
Option 2 is implemented in terms of 3. Be aware that if you set
innerHTML of an element to html that you don't control (e.g. something
you downloaded off the web like an RSS feed) it may contain script
which will be able to access the WinRT and do bad things which is why
the names of the functions in 2) and 3) are purposefully a little
scary.

Apps Script webapp: how to authenticate bigquery?

I am trying to get my apps script webapp to execute as "user accessing the webapp", but its bigquery should run as me, the developer. (If I run the webapp as me, everything works...) I looked at the documentation at https://developers.google.com/bigquery/docs/authorization. There is no apps script example, so I tried to get the javascript example working.
<html>
<head>
<script src="https://apis.google.com/js/client.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
function auth() {
gapi.auth.authorize(config, function() {
gapi.client.load('bigquery', 'v2');
$('#client_initiated').html('BigQuery client authorized');
$('#auth_button').fadeOut();
$('#dataset_button').fadeIn();
});
}
// User Submitted Variables
var projectNumber = 'XXXXXXXXXX';
var clientId = 'XXXXXXXXXX.apps.googleusercontent.com';
var config = {
'client_id': clientId,
'scope': 'https://www.googleapis.com/auth/bigquery'
};
function listDatasets() {
var request = gapi.client.bigquery.datasets.list({
'projectId':projectNumber
});
request.execute(function(response) {
$('#result_box').html(JSON.stringify(response.result.datasets, null));
});
}
</script>
</head>
<body>
<button id="auth_button" onclick="auth();">Authorize</button>
<div id="client_initiated"></div>
<button id="dataset_button" style="display:none;" onclick="listDatasets();">Show datasets</button>
<div id="result_box"></div>
</body>
</html>
I generated a client id as a browser app with https://script.google.com as the server address. With the code above, I get this error: Cannot read property 'authorize_m___' of undefined.
My question is twofold: 1) Would an apps script webapp authenticate in the same way as the javascript app authenticates? I.e. can I use that code as a guide for my apps script?
And 2) any suggestions about how to debug the javascript sample code? Note that I ran this code as an apps script webapp.... That is probably an error....
The answer... or workaround answer is given here: How to pass parameters from one Google-Apps-Script to another and execute?
I can use two stage authentication in place of direct authentication: the user logs in as him/herself, I get the user's name to find their files, then switch to a webapp that uses BigQuery and execs as me, the developer.
A good workaround for advanced services authentication under apps scripts....

blueimp Basic Plugin: Not uploading in IE, all versions

I am using the blueimp fileupload basic plugin in my project. It all works well in Safari, Firefox, Chrome but there is a problem with Internet Explorer 9 and below:
The start callback gets called and in the network tab of developer tools I see the ajax call being executed. However the file is never being upload (I checked on the server, too) and the call eventually ends up in a 408 request timeout.
Any hints on what could be the reason?
Here are my relevant code parts:
<input class="input-file" id="fileupload" name="files[]" data-url="/app_dev.php/backend/ajax/upload/wish/1850cf918a43d42" type="file">
<script type="text/javascript" src="js/jquery/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="js/uploader/vendor/jquery.ui.widget.js"></script>
<script type="text/javascript" src="js/uploader/jquery.fileupload.js"></script>
<script type="text/javascript" src="js/uploader/jquery.iframe-transport.js"></script>
<script>
$(document).ready(function() {
$('#fileupload').fileupload({
dataType: 'json',
dropZone: null,
start: function (e, data){
console.log('start'); //fires in all browsers = fine
},
progress: function (e, data){
console.log('progress'); //fires in Safari, FF, Chrome = fine
},
done: function (e, data) {
console.log('done'); //never getting here in IE cause file doesn't get uploaded.
}
});
</script>
Problem fixed!
There were two issues. One had to do with local network settings.
The other was to implement the correct handling of content type negotiation. See https://github.com/blueimp/jQuery-File-Upload/wiki/Setup for more details.
Just my 5 ¢:
I had a very hard time trying to make it work with pretty links! Following dumps were totally empty!
var_dump($_FILES);
var_dump($_POST);
var_dump($_GET);
So:
$('#fileupload').fileupload({
url: 'http://code.dev/products/postUpload' // <--- remove trailing slash!!!
});