Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Rapid App Development

Using Ionic, Sails, and Mongo to get apps off the ground quickly
by

Chad Furman

on 18 September 2015

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of Rapid App Development

Rapid App Development
Overview
Auth
API
App
Q&A
Shoulders of Giants
100% Remote
https://hire.clevertech.biz
$ sails new api
Note
Take your life back.
*
Generator M
Services
The Plan
$ sails generate api
User and Thing
API + Model

Shortcut Routes

Auto-routing
Policies
Blueprint & Generators
+
+
* Unofficial words of wisdom
chad@chadfurman.com
http://chads.website
Generator M
Clean Ionic Scaffold

Passport
Clean OAuth 2.0

Sails, Ionic, Mongo

Angular, Cordova, Yeoman

Gulp, Sass, Bower
REST API
List 'things'
Full CRUD

Google OAuth 2.0
Passport
Server-side

HTML5 Android App
Login with Google
List things

Waterline
Generate Controllers
login-ctrl.js
list-ctrl.js

Generate Templates
login.html
list.html

Add routes
/login
/list -- requires auth
Route Auth
Auth
InAppBrowser
Persists token


API
Retrieves token
listThings()
Setup whitelist
cordova-plugin-whitelist
├── api
│   ├── controllers
│   ├── models
│   ├── policies
│   │   └── sessionAuth.js
│   ├── responses
│   └── services
├── assets
├── config
│   ├── bootstrap.js
│   ├── connections.js
│   ├── http.js
│   ├── models.js
│   ├── policies.js
│   └── routes.js
├── node_modules
├── tasks
├── app.js
└── views
Install Node dependencies
sails
sails-mongo
passport
passport-google-oauth2
dotenv

config/connections.js
mongo credentials
config/models.js
connection: 'someMongodbServer'
migrate: 'safe'
config/cors.js
origin: '*'
headers: 'content-type, X-Bearer-Token

Add dotenv to bootstrap
Creds in Repo = pwn3d
Things we won't talk about
How to JavaScript - I don't Node-ude.

Angular - Controllers, Services, etc...

NoSQL - Our ORM will do the lifting

OAuth2 - Passport to the rescue

Tool chains (Gulp, Sass, etc.)

Why Mongo is Bad (and why I picked it)

Cryptographic Security and Randomness
├── api
│   ├── controllers
│   │   └── UserController.js
│   ├── models
│   │   ├── Thing.js
│   │   └── User.js

Add attributes to models
Things have a name
Users have email and accessToken

Define relationships
A User has things
A Thing has an author

Database agnostic
Mongo, MySQL, Postgres
Manages document store relations
Object-Relational Mappings
Sails defaults to `sails-disk`
non-production use only

Blueprint APIs can be over-ridden
Find, create, update, destroy
Add, remove (relationships)

Turn off shortcut routes
Auth Controller and Routing
Strategies and Serialization
Note


var passport = require('passport'); // shared module
var GoogleStrategy = require('passport-google-oauth2');

passport.serializeUser( function (user, done) { // take a user object, save the email
sails.log('Serializing user: ' + user.email);
done(null, user.email); // pass along serialized user, aka email address
});

passport.deserializeUser( function (serializedUser, done) { // spit out a user object by email
sails.log('Deseriailizing: ' + serializedUser);
User.findOne({email: serializedUser}, function(err, user) {
sails.log('Deseriailized User: ' + user.email);
done(err, user); // pass along full user object
});
});

passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'http://localhostpc.com:1337/auth/google/callback', // FQDN mapped to 127.0.0.1 for callback
passReqToCallback: true
}, function (req, accessToken, refreshToken, user, done) { // passport callback handler
var passportToken = req.query.code;

User.findOrCreate({email: user.emails[0].value}, function(err, user){
User.update({email: user.email}, {accessToken: passportToken}, function (err, updatedUsers) {
if (!err) {
sails.log('Token assigned: ' + passportToken);
sails.log('Token assigned to: ' + user.email);
}

done(err, user); // after we update the user, pass along results
});
});
}));


Passport configuration data

Serialization
User in from passport method
User.email out to session

Deserialization
Email in from session
User out to callback

Strategies
Pre-configured interfaces
Twitter, Google, Local, etc
Restrict access to routes

Middleware authentication
Auth? next()
No auth? return

After authentication
Checks for token in header
Token returned from login

$ sails generate controller auth
_config: { actions, shortcuts, rest }

AuthController::google()
/auth/google
Performs token dance
Initiates redirect

AuthController::googleCallback()
/auth/google/callback
Receives OAuth provider response
Passes control to passport
Auth response in passport callback
`sails-generate-auth` is inactive


Disable magic routing in AuthController


Auth code is presented in URL
Easy access with InAppBrowser


/etc/hosts entry for `127.0.0.1 localhostpc.com

Register middleware in /config/http.js
passportInit: require('passport').initialize();
passportSession: require('passport').session();
add to order after existing 'session'
└── app/ - your application folder
│ └── bower_components/ - local installation of bower packages
│ └── main/ - ---main module---
│ │ ├── assets/ - assets: fonts, images, translation, etc... goes here
│ │ ├── constants/ - angular constants
│ │ ├── controllers/ - angular controllers
│ │ ├── directives/ - angular directives
│ │ ├── filters/ - angular filters
│ │ ├── services/ - angular services
│ │ ├── styles/ - scss styles
│ │ ├── templates/ - angular templates
│ │ └── main.js - angular module definition, routing etc...
│ └── anotherModule/ - ---another module---
│ │ ├── ...
│ ├── app.js - application module, includes main module, ionic, ui-router etc ...
│ └── index.html - angular entry point, injects: app files, bower files, fonts, ...
├── gulp/ - gulp tasks
├── hooks/ - cordova hooks
├── nodes_modules/ - local installation of node modules
├── platforms/ - cordova platforms
├── plugins/ - corodova plugins
├── res/ - resources folder for splash screens and app icons
├── test/ - unit and integration tests
├── www/ - your gulp build goes here, cordova starts building from here
├── ... - config files
module.exports = function (req, res, next) {
var accessToken = req.header('X-Bearer-Token');
sails.log('token policy');

if (!accessToken) {
sails.log('Access token not provided');
return res.json(false);
}

User.findOne({accessToken: accessToken}, function(err, user) {
if (err || !user) {
sails.log('Invalid auth');
return res.json(false);
} else {
sails.log('token policy passed');
next();
}
});
};

/* api/policies/token.js */
/* config/passport.js */
var passport = require('passport');
var util = require('util');

module.exports = {
_config: {
actions: false,
shortcuts: false,
rest: false
},
google: function(req, res) {
sails.log('google auth');
return passport.authenticate('google', {scope:['email']})(req, res);
},
googleCallback: function(req, res, next) {
sails.log('google callback');
return passport.authenticate('google', function(err, user, info) {
var email = user.email;

sails.log('google callback inside');
if (!user || err) {
sails.log('User: ' + email);
sails.log('Err: ' + err);
return res.json(false);
}

sails.log('Preparing to Log in user: ' + email);
req.logIn(user, function(err) {
if (err) {
sails.log('Err: ' + err);
return res.json(false);
} else {
User.findOne({email: email}, function (err, user) {
if (err || !user) {
sails.log('Error logging in user: ' + err);
return res.json(false);
}
var token = user.accessToken;
sails.log('Logged In User: ' + util.inspect(user));
sails.log('Token Granted: ' + token);
return res.json({token: token});
});
}
});
})(req, res, next);
}
};


/* api/controllers/AuthController.js */
Build and Test
$ gulp build
$ ionic emulate android --consolelogs --livereload
$ adb remount
$ adb pull /system/etc/hosts ./adb-hosts
$ echo '10.0.2.2 localhostpc.com' >> adb-hosts
$ adb push ./adb-hosts /system/etc/hosts
/* app/main/main.js */

'use strict';
angular.module('main', [
'ionic',
'ngCordova',
'ui.router',
])
.config(function ($stateProvider, $urlRouterProvider) {
console.log('Allo! Allo from your module: ' + 'main');

// ROUTING with ui.router
$urlRouterProvider.otherwise('/login');
$stateProvider
// this state is placed in the <ion-nav-view> in the index.html
.state('login', {
url: '/login',
templateUrl: 'main/templates/login.html',
controller: 'LoginCtrl as ctrl',
data: {
requireAuth: false
}
})
.state('list', {
url: '/list',
templateUrl: 'main/templates/list.html',
controller: 'ListCtrl as ctrl',
data: {
requireAuth: true
}
})
;
})
/* app/main/services/auth-serv.js */

'use strict';
angular.module('main')
.service('Auth', function () {

this.token = '';

this.persistToken = function (token) {
console.log('persisting token: ' + token);
this.token = token;
};

this.retrieveToken = function () {
console.log('retrieving token: ' + this.token);
return this.token;
};

this.isAuthenticated = function () {
console.log('checking for authentication');
var authenticated = this.retrieveToken() ? true : false;
console.log('authentication check: ' + authenticated);
return authenticated;
};
});
Added in module.run()
gives us access to services

Inject Auth service

$rootScope.$on('$stateChangeStart')

Checks for token, prevents default
/* app/main/main.js */
'use strict';
angular.module('main', [ ... ])
.config(function (...) { })
.run(function ($rootScope, $state, Auth) {
if (Auth) {
console.log('Auth Service Loaded');
}

$rootScope.$on('$stateChangeStart',
function (event, toState, toParams, fromState) {
if (toState.data.requireAuth) { // from UI router
if (!Auth.isAuthenticated()) { // from Auth service
console.log('Unauthorized');
$state.transitionTo('login');
event.preventDefault(); // stop state change
} else {
console.log('Authorized');
}
}
}
);
});
API: https://github.com/ShadeLotus/oauth-trials-and-tribulations

App: https://github.com/ShadeLotus/ionic-thing-app

Questions?
OR
$ gulp watch
/* app/main/services/api-serv.js */

'use strict';
angular.module('main')
.service('Api', function (Auth, $http) {
var baseUrl = 'http://localhostpc.com:1337';

this.listThings = function (cb) {
console.log('calling listThings API endpoint');

console.log(baseUrl + '/thing');
$http.get(baseUrl + '/thing', {
headers: {'X-Bearer-Token': Auth.retrieveToken()}
}).then(
function (successResponse) {
console.log('success: ' + angular.toJson(successResponse, true));
cb(successResponse);
},
function (failureResponse) {
console.log('failure: ' + angular.toJson(failureResponse, true));
cb(false);
}
);
};
});

/* api/models/User.js */
module.exports = {

attributes: {
email: {
type: 'email',
required: true,
unique: true
},
accessToken: {
type: 'string'
},
things: {
collection: 'thing',
via: 'author'
}
}
};

/* api/models/Thing.js */
module.exports = {

attributes: {
name: {
type: 'string',
required: true
},
author: {
model: 'user'
}
}
};

Code...
chad@chadfurman.com
http://chads.website

twitter.com/chadfurman
linkedin.com/in/chadfurman

C456 2527 842E E748 0093 AD63 0D84 09CE 2974 6971
creators of
http://eskimo.io/

'use strict';
angular.module('main')
.service('Api', function (Auth, $http) {
var baseUrl = 'http://localhostpc.com:1337';

this.listThings = function (cb) {
console.log('calling listThings API endpoint');

console.log(baseUrl + '/thing');
$http.get(baseUrl + '/thing', {
headers: {'X-Bearer-Token': Auth.retrieveToken()}
}).then(
function (successResponse) {
console.log('success: ' + angular.toJson(successResponse, true));
cb(successResponse);
},
function (failureResponse) {
console.log('failure: ' + angular.toJson(failureResponse, true));
cb(false);
});
};
});

/* app/main/services/api-serv.js */
'use strict';
angular.module('main')
.service('Api', function (Auth, $http) {
var baseUrl = 'http://localhostpc.com:1337';

this.listThings = function (cb) {
console.log('calling listThings API endpoint');

$http.get(baseUrl + '/user/1/things', {
headers: {'X-Bearer-Token': Auth.retrieveToken()}
}).then(
function (successResponse) {
console.log('success: ' + angular.toJson(successResponse, true));
cb(successResponse);
},
function (failureResponse) {
console.log('failure: ' + angular.toJson(failureResponse, true));
cb(false);
});
};
});
Full transcript