Hello there.
Long time no see because I've been messing around with Ghost blogs on localhost. However, I'm currently on a machine that doesn't have Ghost and I don't want to create another one here due to fragmentation. Eventually I'll be hosting my own Ghost server so I can use that...
I'm installing Tomcat7 on this box. Standard install location is /opt.
I want to make it a service so I can run
service tomcat start|stop|restart.
I remembered there was something called chkconfig to do that, so I looked it up.
http://www.thegeekstuff.com/2011/06/chkconfig-examples/
Whoops, this doesn't exist on Mac OS X. Unsure if it's worth learning how to create services/daemonize things in Mac.
http://batsov.com/articles/2012/12/09/from-linux-to-osx-meet-your-new-apps/
In OS X, you use launchctl...
a ragtag collection of things I couldn't find in a quick 5-minute search on google, as well as some curiosities and what not.
Friday, May 29, 2015
Monday, June 30, 2014
sencha cmd, skip theme build.
Found Newbridge Green's bootstrap theme for Extjs4. Looks pretty good.
Except, he understands how this crap actually works and wrote the thing in plain sass. (not the extjs theme extension thing). So, I can't really add it to the framework generated by
sencha generate app
So, I don't. Now I need to figure out how to automatically copy his css over when I do
sencha app build
and figure out how to get rid of the stupid ruby compass compilation thingy that takes forever anyways.
I also want to not use YUI to compress, but rather uglifyjs2. This can be put into a grunt task.
Here's how you get rid of the sass build and slicing.
Go to
.sencha/app/sencha.cfg
and add the lines
skip.sass=1
skip.slice=1
Finally got rid of that crap.
Now, we want to stop using YUI.
Go to
.sencha/app/build.properties
and set the following lines.
When I tried to use Uglify, I got the following error:
Since closure takes forever, the best option is probably just to not use compression, and just use a grunt task to build after everything is concatenated. That includes uglifying the concatenated app.js file, copying the bootstrap theme to the build folder, and gzipping both of those files.
Here's the gruntfile config:
And that will do it! Just make sure that
Except, he understands how this crap actually works and wrote the thing in plain sass. (not the extjs theme extension thing). So, I can't really add it to the framework generated by
sencha generate app
So, I don't. Now I need to figure out how to automatically copy his css over when I do
sencha app build
and figure out how to get rid of the stupid ruby compass compilation thingy that takes forever anyways.
I also want to not use YUI to compress, but rather uglifyjs2. This can be put into a grunt task.
Here's how you get rid of the sass build and slicing.
Go to
.sencha/app/sencha.cfg
and add the lines
skip.sass=1
skip.slice=1
Finally got rid of that crap.
Now, we want to stop using YUI.
Go to
.sencha/app/build.properties
and set the following lines.
build.compression.yui=0
build.compression.closure=1
build.compression.uglify=0
When I tried to use Uglify, I got the following error:
[ERR]
[ERR] BUILD FAILED
[ERR] java.lang.UnsupportedOperationException: Not Yet Implemented
[ERR]
[ERR] Total time: 10 seconds
Laughable.Since closure takes forever, the best option is probably just to not use compression, and just use a grunt task to build after everything is concatenated. That includes uglifying the concatenated app.js file, copying the bootstrap theme to the build folder, and gzipping both of those files.
Here's the gruntfile config:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
shell: {
sencha_app_build: {
command: 'sencha app build'
}
},
jshint: {
options: {
reporter: require('jshint-stylish')
},
main: ['Gruntfile.js', 'app/**/*.js']
},
uglify: {
options: {
banner: '/*\n Copyright <%= pkg.author %> \n <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> \n*/\n'
},
main: {
files: {
'build/production/<%= pkg.name %>/app.js': 'build/production/<%= pkg.name %>/app.js'
}
}
},
copy: {
main: {
src: 'resources/css/bootstrap.css',
dest: 'build/production/<%= pkg.name %>/resources/'
}
},
compress: {
options: {
mode: 'gzip'
},
js: {
expand: true, //use whole filename
src: ['build/production/<%= pkg.name %>/*.js'],
dest: '.'
},
css: {
expand: true,
src: ['build/production/<%= pkg.name %>/resources/*.css'],
dest: '.'
}
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-shell'); //or grunt-exec, unsure which is better
grunt.registerTask('default', ['shell', 'uglify', 'copy', 'compress']);
};
- your package name in package.json is the same as your Sencha project name
- bootstrap.css is under resources/css
- you have all the dependencies in npm and run npm install!
Friday, June 27, 2014
New server, nginx + nodejs, github
Got a new AWS server.
Decided to switch from apache to nginx.
Had to figure out how to configure it.
Default inst. locations on RedHat are:
http://stackoverflow.com/questions/2505096/cloning-a-private-github-repo
Oh, so cloning private repos uses the git@github.com:username/repo.git format...
You also need to generate SSH keys to authenticate with Github:
https://help.github.com/articles/generating-ssh-keys#platform-linux
Terse.
Decided to switch from apache to nginx.
Had to figure out how to configure it.
Default inst. locations on RedHat are:
- webroot at
/usr/share/nginx/html - config file at
/etc/nginx/nginx.conf
Needed to learn how to clone a private repo from a new machine. Also trying to actually learn how to use Github.
And check out this nice site: https://www.atlassian.com/git/
http://stackoverflow.com/questions/2505096/cloning-a-private-github-repo
Oh, so cloning private repos uses the git@github.com:username/repo.git format...
You also need to generate SSH keys to authenticate with Github:
https://help.github.com/articles/generating-ssh-keys#platform-linux
Terse.
Saturday, May 17, 2014
Securing RESTful APIs with Node and Passport
Stated pretty well
Thursday, May 8, 2014
Really crappy post on Jakarta Connectors
https://tomcat.apache.org/connectors-doc-archive/jk2/jk2/confighowto.html
/etc/httpd/modules/mod_jk2.so
/etc/httpd/conf/httpd.conf
LoadModule jk2_module modules/mod_jk2.so
workers 2 properties
workers 2 properties
Tuesday, May 6, 2014
Combining OAuth2 and Phonegap
So you've made a web application. You use OAuth2 for authentication and authorization. Now, you want it to have a mobile client. You don't fancy coding all your mobile apps natively -- at least not yet. You use Phonegap.
Then, the inevitable question: how can my Phonegap app support OAuth? It must be redirected to the OAuth page, and the user must grant consent. But then he's redirected to my original webpage created for the browser!
Here's how I've done it.
I started with a tutorial here.
Step 1: Download Phonegap's In App Browser. (If you have Phonegap 3.0 or above) Do this from your Cordova directory (the one with the .cordova folder. For me, since I am using SenchaCMD, it's under the phonegap folder of the main project directory)
Step 2: Test your In App Browser:
========================================================================
So, this is all nice. It works. All I had to do was implement a method on the server side that
did the Exchange Auth Code for Tokens request, get the response, and do another request to the actual API at https://www.googleapis.com/oauth2/v2/userinfo.
So, in all for the user, they're only redirected to a set of 3 pages. First, the Google Login Page. Then, the grant consent page. Then, Google redirects them to my REST call that does this 2 step dance (get tokens from auth code and call the API). Then, they get a response.
My hack to get the response page to actually be a real page was to return an HTML that redirects the user (for the last time) to my application page. My redirect HTML response also sets a cookie with the auth stuff in it on the browser.
So, everything is super nice and hunky dory until this step. How will my redirect page differentiate between mobile and browser clients? We don't want the client to get redirected to my browser client if they're on mobile.
We can solve this one without too much trouble. Google's OAuth API query parameters let you send a state param, which you can set to be "mobile" when you're on a mobile client. Then, google's redirect will include this as a query parameter to your 2 step dance method. You can use this query param to determine whether or not you redirect to the set of browser pages. This is fine.
But then we have another problem: How do we set a cookie on Phonegap's In App Browser?
This one took some creativity, but here's my hackish solution. The only way (as far as I could tell) that the In App Browser can communicate information to the actual Phonegap app is through some properties of the InAppBrowserEvent object. These properties are:
So, what's the trick?
After you get the token information and all that other jazz from the endpoint of the OAuth process (ie, the return html of your favorite 2 step dance REST API), you redirect to another page which contains all this information in the url. Then, you use JS to tell the window to close itself (which is an event that can be listened for).
Then, you get the InAppBrowserEvent object, you ask for its URL, and bam! You've gotten all the information you need within your Phonegap app.
Hacky, but that's how it goes.
A lot of these last few paragraphs probably makes sense to nobody but me. For anyone who stumbles upon this, it may be no surprise that it was intended to be read by me :)
Cheers
Allan
Then, the inevitable question: how can my Phonegap app support OAuth? It must be redirected to the OAuth page, and the user must grant consent. But then he's redirected to my original webpage created for the browser!
Here's how I've done it.
I started with a tutorial here.
Step 1: Download Phonegap's In App Browser. (If you have Phonegap 3.0 or above) Do this from your Cordova directory (the one with the .cordova folder. For me, since I am using SenchaCMD, it's under the phonegap folder of the main project directory)
cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-inappbrowser.git
Bam, downloaded. Great.Step 2: Test your In App Browser:
function onBtnClick() {var ref = window.open('http://apache.org', '_blank', 'location=yes');ref.addEventListener('loadstart', function(event) { alert('start: ' + event.url); });ref.addEventListener('loadstop', function(event) { alert('stop: ' + event.url); });ref.addEventListener('loaderror', function(event) { alert('error: ' + event.message); });ref.addEventListener('exit', function(event) { alert(event.type); });}
It should work quite well. You get event listeners for stuff. Yay.
Step 3: Direct it to the right URI!
Go to https://developers.google.com/accounts/docs/OAuth2WebServer to figure out what all the GET query parameters are.
Then, go test stuff out at the Google OAuth2 Playground! Here's what I got from the playground thing:
(Yes, I could have used better styling on this. Oops).
========================================================================
Google OAuth 2 Scheme:
Request / Response
HTTP/1.1 302 Found
GET /oauthplayground/?code=4/QrO4XICKVwOcWRoIjcD9hS_ftIjb.0v99D-kGD1cY3oEBd8DOtND9QdaliwI HTTP/1.1
Host: developers.google.com
Exchange Auth Code for Tokens
Request
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-length: 250
content-type: application/x-www-form-urlencoded
user-agent: google-oauth-playground
code=4%2FQrO4XICKVwOcWRoIjcD9hS_ftIjb.0v99D-kGD1cY3oEBd8DOtND9QdaliwI&redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&client_id=407408718192.apps.googleusercontent.com&scope=&client_secret=************&grant_type=authorization_code
Response
HTTP/1.1 200 OK
Alternate-protocol: 443:quic
Content-length: 1071
X-xss-protection: 1; mode=block
X-content-type-options: nosniff
Content-disposition: attachment; filename="json.txt"; filename*=UTF-8''json.txt
Expires: Fri, 01 Jan 1990 00:00:00 GMT
X-google-cache-control: remote-fetch
Server: GSE
Via: HTTP/1.1 GWA
Pragma: no-cache
Cache-control: no-cache, no-store, max-age=0, must-revalidate
Date: Tue, 06 May 2014 19:01:39 GMT
X-frame-options: SAMEORIGIN
Content-type: application/json; charset=utf-8
-content-encoding: gzip
{
"access_token": "ya29.1.AADtN_UYHaNZzrpYU_fyR98OPAbWCiPN39olWxlWiJiVEXOxsafxy1qu2p6ANDc",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1/geII4ucPZGDTgn_rfwyVhkUAdtWGzp2H4A5kpO3EAEQ",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZkNTFhZjgwYzQ4NGIwZWZjZTI4Zjk0NjQ1YWQ4YjY0YjA4ZGNhNGEifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiaWQiOiIxMDgwNTAzMDY2Njg5Mzg4ODEzNTYiLCJzdWIiOiIxMDgwNTAzMDY2Njg5Mzg4ODEzNTYiLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6ImppYW5ndHNAc3RhbmZvcmQuZWR1IiwiYXRfaGFzaCI6IjBWZjBlVFhUVV9oMDNleFRVSGozR2ciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJzdGFuZm9yZC5lZHUiLCJ0b2tlbl9oYXNoIjoiMFZmMGVUWFRVX2gwM2V4VFVIajNHZyIsInZlcmlmaWVkX2VtYWlsIjp0cnVlLCJjaWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjEzOTk0MDI1OTksImV4cCI6MTM5OTQwNjQ5OX0.vG2RVojWyn0cHl6-PhIoAZIhS5HVUXmOJ-X0nxDvJX4RwwWLv3yGLF4-V1QlfaRRVmqBwZc7PrJx5peGQmFyBKN-6IXRFwsiyJ5WRPlmeY1LuzPGi_KKrzT-S9hAB4Pjgi-3HYyJh01T3E34m59PspPkjzcKt5Fb05NL6g3kGjU"
}
Refresh Request (Optional)
Request
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-length: 163
content-type: application/x-www-form-urlencoded
user-agent: google-oauth-playground
client_secret=************&grant_type=refresh_token&refresh_token=1%2FgeII4ucPZGDTgn_rfwyVhkUAdtWGzp2H4A5kpO3EAEQ&client_id=407408718192.apps.googleusercontent.com
Response
HTTP/1.1 200 OK
Alternate-protocol: 443:quic
Content-length: 1002
X-xss-protection: 1; mode=block
X-content-type-options: nosniff
Content-disposition: attachment; filename="json.txt"; filename*=UTF-8''json.txt
Expires: Fri, 01 Jan 1990 00:00:00 GMT
X-google-cache-control: remote-fetch
Server: GSE
Via: HTTP/1.1 GWA
Pragma: no-cache
Cache-control: no-cache, no-store, max-age=0, must-revalidate
Date: Tue, 06 May 2014 19:02:08 GMT
X-frame-options: SAMEORIGIN
Content-type: application/json; charset=utf-8
-content-encoding: gzip
{
"access_token": "ya29.1.AADtN_UeoAgso1pgdnAtlFnLWtlxA0pfSlKM6EAOBvzoMzTs0v-NcCQHdpUVXfc",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZkNTFhZjgwYzQ4NGIwZWZjZTI4Zjk0NjQ1YWQ4YjY0YjA4ZGNhNGEifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiaWQiOiIxMDgwNTAzMDY2Njg5Mzg4ODEzNTYiLCJzdWIiOiIxMDgwNTAzMDY2Njg5Mzg4ODEzNTYiLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6ImppYW5ndHNAc3RhbmZvcmQuZWR1IiwiYXRfaGFzaCI6Im5KS2hKM0dMdjROX1RHX0NReEF6aVEiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJzdGFuZm9yZC5lZHUiLCJ0b2tlbl9oYXNoIjoibkpLaEozR0x2NE5fVEdfQ1F4QXppUSIsInZlcmlmaWVkX2VtYWlsIjp0cnVlLCJjaWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJpYXQiOjEzOTk0MDI2MjgsImV4cCI6MTM5OTQwNjUyOH0.pfWKLrTA3426WErPPoFnx_iYgaDjw4XQRJYG3Bpcl5514T5nDfiYuidgc-h_AHCQah13zLHIjPyQht1TByrXTyebkbLw7HFKmt1JpmSE2h6NI8FBNhj5E950JQTBZL6OYewYnLweKN6BkHdP7vCvcrp6vmddiUgn-tlYR6NrEsE"
}
Configure request to API
(To: https://www.googleapis.com/oauth2/v2/userinfo)
Request
GET /oauth2/v2/userinfo HTTP/1.1
Host: www.googleapis.com
Content-length: 0
Authorization: Bearer ya29.1.AADtN_UeoAgso1pgdnAtlFnLWtlxA0pfSlKM6EAOBvzoMzTs0v-NcCQHdpUVXfc
Response
HTTP/1.1 200 OK
Content-length: 277
X-xss-protection: 1; mode=block
Content-location: https://www.googleapis.com/oauth2/v2/userinfo
X-content-type-options: nosniff
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Server: GSE
Pragma: no-cache
Cache-control: no-cache, no-store, max-age=0, must-revalidate
Date: Tue, 06 May 2014 19:03:15 GMT
X-frame-options: SAMEORIGIN
Content-type: application/json; charset=UTF-8
{
"family_name": "",
"name": "",
"picture": "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
"email": "[your_email_addr]@stanford.edu",
"given_name": "",
"id": "[long string of numbers here]",
"hd": "stanford.edu",
"verified_email": true
}
========================================================================
So, this is all nice. It works. All I had to do was implement a method on the server side that
did the Exchange Auth Code for Tokens request, get the response, and do another request to the actual API at https://www.googleapis.com/oauth2/v2/userinfo.
So, in all for the user, they're only redirected to a set of 3 pages. First, the Google Login Page. Then, the grant consent page. Then, Google redirects them to my REST call that does this 2 step dance (get tokens from auth code and call the API). Then, they get a response.
My hack to get the response page to actually be a real page was to return an HTML that redirects the user (for the last time) to my application page. My redirect HTML response also sets a cookie with the auth stuff in it on the browser.
So, everything is super nice and hunky dory until this step. How will my redirect page differentiate between mobile and browser clients? We don't want the client to get redirected to my browser client if they're on mobile.
We can solve this one without too much trouble. Google's OAuth API query parameters let you send a state param, which you can set to be "mobile" when you're on a mobile client. Then, google's redirect will include this as a query parameter to your 2 step dance method. You can use this query param to determine whether or not you redirect to the set of browser pages. This is fine.
But then we have another problem: How do we set a cookie on Phonegap's In App Browser?
This one took some creativity, but here's my hackish solution. The only way (as far as I could tell) that the In App Browser can communicate information to the actual Phonegap app is through some properties of the InAppBrowserEvent object. These properties are:
- type: the eventname, either
loadstart,loadstop,loaderror, orexit. (String) - url: the URL that was loaded. (String)
- code: the error code, only in the case of
loaderror. (Number) - message: the error message, only in the case of
loaderror. (String)
So, what's the trick?
After you get the token information and all that other jazz from the endpoint of the OAuth process (ie, the return html of your favorite 2 step dance REST API), you redirect to another page which contains all this information in the url. Then, you use JS to tell the window to close itself (which is an event that can be listened for).
Then, you get the InAppBrowserEvent object, you ask for its URL, and bam! You've gotten all the information you need within your Phonegap app.
Hacky, but that's how it goes.
A lot of these last few paragraphs probably makes sense to nobody but me. For anyone who stumbles upon this, it may be no surprise that it was intended to be read by me :)
Cheers
Allan
Subscribe to:
Comments (Atom)