Autenticación por token

This commit is contained in:
Juan Ferrer Toribio 2017-05-17 21:23:47 +02:00
parent 5db994e263
commit 4eff9d451b
44 changed files with 331 additions and 359 deletions

View File

@ -9,3 +9,4 @@ rules:
operator-linebreak: 0
radix: 0
guard-for-in: 0
camelcase: 0

42
.vscode/launch.json vendored
View File

@ -37,50 +37,10 @@
"name": "Asociar al proceso",
"type": "node",
"request": "attach",
"processId": "${command.PickProcess}",
"processId": "${command:PickProcess}",
"port": 5858,
"sourceMaps": false,
"outFiles": []
},
{
"name": "Loopback",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/services/client/server/server.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
"preLaunchTask": null,
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "development"
},
"console": "internalConsole",
"sourceMaps": false,
"outFiles": []
},
{
"name": "gulp debug",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}\\@salix\\node_modules\\gulp\\bin\\gulp.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}\\@salix",
"preLaunchTask": null,
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "development"
},
"console": "internalConsole",
"sourceMaps": false,
"outFiles": []
}
]
}

View File

@ -1,4 +1,3 @@
export * from './module';
import './ngModule';
import './config';
export {component as Login} from './login/login';
import './login/index';

View File

@ -1,4 +1,4 @@
import {module} from './module';
import ngModule from './ngModule';
config.$inject = ['$translatePartialLoaderProvider', '$httpProvider'];
export function config($translatePartialLoaderProvider, $httpProvider) {
@ -7,4 +7,4 @@ export function config($translatePartialLoaderProvider, $httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
module.config(config);
ngModule.config(config);

View File

@ -0,0 +1,18 @@
<div>
<div class="box-wrapper">
<div class="box">
<img src="./logo.svg"/>
<form name="form" ng-submit="$ctrl.submit()">
<vn-textfield label="User" model="$ctrl.email"></vn-textfield>
<vn-textfield label="Password" model="$ctrl.password" type="password"></vn-textfield>
<div class="footer">
<vn-submit label="Enter"></vn-submit>
<div class="spinner-wrapper">
<vn-spinner enable="$ctrl.loading"></vn-spinner>
</div>
</div>
</form>
</div>
</div>
<vn-snackbar vn-id="snackbar"></vn-snackbar>
</div>

View File

@ -0,0 +1,75 @@
import ngModule from '../ngModule';
import './style.scss';
export default class Controller {
constructor($element, $scope, $window, $http) {
this.$element = $element;
this.$ = $scope;
this.$window = $window;
this.$http = $http;
}
submit() {
if (!(this.email && this.password)) {
this.showMessage('Please insert your email and password');
return;
}
this.loading = true;
let params = {
email: this.email,
password: this.password,
location: this.$window.location.href
};
this.$http.post('/auth/login', params).then(
json => this.onLoginOk(json),
json => this.onLoginErr(json)
);
}
onLoginOk(json) {
this.loading = false;
let data = json.data;
let params = {
token: data.token,
continue: data.continue
};
this.$window.location = `${data.loginUrl}?${this.encodeUri(params)}`;
}
encodeUri(object) {
let uri = '';
for (var key in object)
if (object[key]) {
if (uri.length > 0)
uri += '&';
uri += encodeURIComponent(key) + '=' + encodeURIComponent(object[key]);
}
return uri;
}
onLoginErr(json) {
this.loading = false;
this.model.password = '';
let message;
switch (json.status) {
case 401:
message = 'Invalid credentials';
break;
case -1:
message = 'Can\'t contact with server';
break;
default:
message = 'Something went wrong';
}
this.showMessage(message);
}
showMessage(message) {
this.$.snackbar.show({message: message});
}
}
Controller.$inject = ['$element', '$scope', '$window', '$http'];
ngModule.component('vnLogin', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,18 +0,0 @@
<div>
<div class="box-wrapper">
<div class="box">
<img src="./logo.svg"/>
<form name="form" ng-submit="$ctrl.submit()">
<vn-textfield label="User" field="$ctrl.model.email"></vn-textfield>
<vn-password label="Password" field="$ctrl.model.password"></vn-password>
<div class="footer">
<vn-submit label="Enter"></vn-submit>
<div class="spinner-wrapper">
<vn-spinner enable="$ctrl.loading"></vn-spinner>
</div>
</div>
</form>
</div>
</div>
<vn-snackbar></vn-snackbar>
</div>

View File

@ -1,57 +0,0 @@
import {module} from '../module';
import './login.scss';
export const component = {
template: require('./login.html'),
controller: controller
};
module.component('vnLogin', component);
controller.$inject = ['$http', '$element', '$window'];
function controller($http, $element, $window) {
Object.assign(this, {
submit: function() {
let model = this.model;
if (!(model && model.email && model.password)) {
this.showMessage('Please insert your email and password');
return;
}
this.loading = true;
model.appId = $window.location.href;
$http.post('/auth', this.model).then(
json => this.onLoginOk(json),
json => this.onLoginErr(json)
);
},
onLoginOk: function(json) {
this.loading = false;
let data = json.data;
$window.location = `${data.location}?access_token=${data.location}`;
},
onLoginErr: function(json) {
this.loading = false;
this.model.password = '';
let message;
switch (json.status) {
case 401:
message = 'Invalid credentials';
break;
case -1:
message = 'Can\'t contact with server';
break;
default:
message = 'Something went wrong';
}
this.showMessage(message);
},
showMessage: function(message) {
let snackbar = $element.find('vn-snackbar').controller('vnSnackbar');
snackbar.show({message: message});
}
});
}

View File

@ -44,4 +44,3 @@ vn-login > div {
overflow: visible;
}
}

View File

@ -1,4 +0,0 @@
import {ng} from 'vendor';
import * as core from 'core';
export const module = ng.module('vnAuth', [core.NAME]);

View File

@ -0,0 +1,5 @@
import {ng} from 'vendor';
import 'core';
let ngModule = ng.module('vnAuth', ['vnCore']);
export default ngModule;

View File

@ -1,5 +1,4 @@
import {ng} from 'vendor';
import * as core from 'core';
import 'core';
export const NAME = 'client';
export const module = ng.module(NAME, []);
export const module = ng.module('client', []);

View File

@ -24,8 +24,16 @@
on-open="$ctrl.onPassOpen()"
on-response="$ctrl.onPassChange(response)">
<dbody>
<vn-password label="New password" model="$ctrl.newPassword"></vn-password>
<vn-password label="Repeat password" model="$ctrl.repeatPassword"></vn-password>
<vn-textfield
type="password"
label="New password"
model="$ctrl.newPassword">
</vn-textfield>
<vn-textfield
type="password"
label="Repeat password"
model="$ctrl.repeatPassword">
</vn-textfield>
</dbody>
<buttons>
<button response="CANCEL" translate>Cancel</button>

View File

@ -12,6 +12,7 @@ import './confirm/index';
import './title/index';
import './subtitle/index';
import './spinner/index';
import './snackbar/index';
export {NAME as BUTTON, directive as ButtonDirective} from './button/button';
export {NAME as BUTTON_MDL, factory as buttonMdl} from './button/button.mdl';
@ -25,12 +26,8 @@ export {NAME as LABEL, directive as LabelDirective} from './label/label';
export {NAME as LABEL_MDL, factory as labelMdl} from './label/label.mdl';
export {NAME as ICON_BUTTON, directive as IconButtonDirective} from './icon-button/icon-button';
export {NAME as ICON_BUTTON_MDL, factory as iconButtonMdl} from './icon-button/icon-button.mdl';
export {NAME as PASSWORD, directive as PasswordDirective} from './password/password';
export {NAME as PASSWORD_MDL, factory as passwordMdl} from './password/password.mdl';
export {NAME as SUBMIT, directive as SubmitDirective} from './submit/submit';
export {NAME as SUBMIT_MDL, factory as submitMdl} from './submit/submit.mdl';
export {NAME as SNACKBAR, directive as SnackbarDirective} from './snackbar/snackbar';
export {NAME as SNACKBAR_MDL, factory as snackbarMdl} from './snackbar/snackbar.mdl';
export {NAME as COMBO, directive as ComboDirective} from './combo/combo';
export {NAME as COMBO_MDL, factory as comboMdl} from './combo/combo.mdl';
export {NAME as DATE_PICKER, directive as DatePickerDirective} from './date-picker/date-picker';

View File

@ -7,10 +7,10 @@ export function config($translateProvider, $translatePartialLoaderProvider) {
let conf = {urlTemplate: '/static/locale/{part}/{lang}.json'};
let langs = ['en', 'es'];
let localLangs = {
'en_US': 'en',
'en_UK': 'en',
'es_ES': 'es',
'es_AR': 'es'
en_US: 'en',
en_UK: 'en',
es_ES: 'es',
es_AR: 'es'
};
$translateProvider
.useSanitizeValueStrategy('escape')

View File

@ -23,11 +23,9 @@
margin-top: -10em;
margin-left: -14em;
}
.button-bar {
margin-top: 1.5em;
text-align: right;
}
button {
background: none;
@ -45,4 +43,5 @@
background-color: rgba(1,1,1,.1);
}
}
}
}

View File

@ -1,6 +1,5 @@
import * as vendors from 'vendor';
import {getModuleName, getVendorDependencies} from './lib/util';
import {getVendorDependencies} from './lib/util';
const DEPENDENCIES = getVendorDependencies(vendors);
export const NAME = getModuleName('core');
export const module = vendors.ng.module(NAME, DEPENDENCIES);
export const module = vendors.ng.module('vnCore', DEPENDENCIES);

View File

@ -1,27 +0,0 @@
import {module as _module} from '../module';
import * as resolveFactory from '../lib/resolveDefaultComponents';
import * as normalizerFactory from '../lib/inputAttrsNormalizer';
import * as util from '../lib/util';
const _NAME = 'password';
export const NAME = util.getName(_NAME);
directive.$inject = [resolveFactory.NAME, normalizerFactory.NAME];
export function directive(resolve, normalizer) {
return {
restrict: 'E',
template: function(_, attrs) {
normalizer.normalize(attrs);
return resolve.getTemplate(_NAME, attrs);
},
link: function(scope, element, attrs) {
scope.$watch(attrs.model, () => {
let mdlField = element[0].firstChild.MaterialTextfield;
if (mdlField)
mdlField.updateClasses_();
});
componentHandler.upgradeElement(element[0].firstChild);
}
}
}
_module.directive(NAME,directive);

View File

@ -1,4 +0,0 @@
<div class="mdl-textfield mdl-js-textfield *[className]*">
<input class="mdl-textfield__input" type="password" name="*[name]*" ng-model="*[model]*" rule="*[rule]*" *[enabled]*/>
<label class="mdl-textfield__label" translate>*[label]*</label>
</div>

View File

@ -1,17 +0,0 @@
import {module} from '../module';
import template from './password.mdl.html';
export const NAME = 'vnPasswordMdlFactory';
export function factory() {
return {
template: template,
default: {
label: 'Password',
enabled: 'enabled',
className: 'mdl-textfield--floating-label'
}
}
}
module.factory(NAME, factory);

View File

@ -1,4 +1,4 @@
<div class="mdl-js-snackbar mdl-snackbar *[className]*" style="z-index: 200;">
<div class="mdl-js-snackbar mdl-snackbar" style="z-index: 200;">
<div class="mdl-snackbar__text"></div>
<button class="mdl-snackbar__action" type="button"></button>
</div>

View File

@ -0,0 +1,25 @@
import {module} from '../module';
/**
* A simple component to show non-obstructive notifications to the user.
*/
export default class Controller {
constructor($element) {
this.snackbar = $element[0].firstChild;
componentHandler.upgradeElement(this.snackbar);
}
/**
* Shows a notification.
*
* @param {Object} data The message data
*/
show(data) {
this.snackbar.MaterialSnackbar.showSnackbar(data);
}
}
Controller.$inject = ['$element'];
module.component('vnSnackbar', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,28 +0,0 @@
import {module} from '../module';
import * as resolveFactory from '../lib/resolveDefaultComponents';
import * as util from '../lib/util';
const _NAME = 'snackbar';
export const NAME = util.getName(_NAME);
directive.$inject = [resolveFactory.NAME];
export function directive(resolve) {
return {
restrict: 'E',
template: function(_, attrs) {
return resolve.getTemplate(_NAME, attrs);
},
controller: controller
}
}
module.directive(NAME, directive);
controller.$inject = ['$scope', '$element'];
function controller($scope, $element) {
let snackbar = $element[0].firstChild;
componentHandler.upgradeElement (snackbar);
this.show = function(data) {
snackbar.MaterialSnackbar.showSnackbar(data);
}
}

View File

@ -1,15 +0,0 @@
import {module} from '../module';
import template from './snackbar.mdl.html';
export const NAME = 'vnSnackbarMdlFactory';
export function factory() {
return {
template: template,
default: {
message: 'Default message'
}
}
}
module.factory(NAME, factory);

View File

@ -15,7 +15,7 @@ export default class Spinner extends Component {
/**
* Enables/disables the spinner.
*
* @param {Boolean} %true to enable, %false to disable
* @param {Boolean} value %true to enable, %false to disable
*/
set enable(value) {
if (value)

View File

@ -6,7 +6,12 @@
ng-model="*[model]*"
vn-validation="*[rule]*"
*[enabled]*/>
<button type="button" class="mdl-chip__action" title="Clear text">
<button
type="button"
class="mdl-chip__action"
tabindex="-1"
title="Clear text"
style="visibility: hidden">
<i class="material-icons">clear</i>
</button>
<label class="mdl-textfield__label" translate>*[label]*</label>

View File

@ -1,12 +1,12 @@
vn-textfield {
.mdl-chip__action{
.mdl-chip__action {
position: absolute;
top: 0px;
right: -6px;
margin: 22px 0px;
visibility: hidden;
}
.material-icons{
.material-icons {
font-size: 18px;
}
}

View File

@ -1,5 +1,5 @@
import {ng} from 'vendor';
import {NAME as SALIX} from './module';
import './module';
export const bootstrap = () => {
const selector = 'selector';
@ -11,7 +11,7 @@ export const bootstrap = () => {
let _element = _script && document.querySelector(_script.getAttribute(selector));
if (!_element) {
throw new Error("element is not defined");
throw new Error('Element is not defined');
}
ng.bootstrap(_element, [SALIX]);
ng.bootstrap(_element, ['salix']);
};

View File

@ -9,16 +9,14 @@ export const COMPONENT = {
};
module.component(NAME, COMPONENT);
controller.$inject = ['$translate'];
function controller($translate, $translatePartialLoader) {
controller.$inject = ['$translate', '$window', '$document'];
function controller($translate, $window, $document) {
this.onLogoutClick = function() {
let appName = 'salix';
document.cookie = `${appName}-session=; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
window.location = `/auth?api_key=${appName}`;
$window.location = 'salix/logout';
};
this.onChangeLanguage = function() {
let lang = $translate.use() == 'en' ? 'es' : 'en';
$translate.use(lang);
console.log (`Locale changed: ${lang}`);
console.log(`Locale changed: ${lang}`);
};
}

View File

@ -1,6 +1,4 @@
import * as vendors from 'vendor';
import * as core from 'core';
import {NAME as CORE} from 'core';
import {ng} from 'vendor';
import 'core';
export const NAME = 'salix';
export const module = vendors.ng.module(NAME, [CORE]);
export const module = ng.module('salix', ['vnCore']);

View File

@ -1,3 +1,4 @@
{
"default": "salix/login",
"salix": "/salix/login"
}

View File

@ -1,9 +0,0 @@
module.exports = function(app) {
/*
var User = app.models.User;
User.create ({
email: 'admin@admin.com',
password: '1234'
}, function (err, user) {});
*/
};

View File

@ -1,12 +1,8 @@
module.exports = function(server) {
// Install a `/` route that returns server status
server.enableAuth();
var router = server.loopback.Router();
router.get('/status', server.loopback.status());
router.get(['/'],function(req,res){
res.render('index.ejs');
});
server.use(router);
};

View File

@ -1,12 +1,14 @@
var url = require ('url');
module.exports = function(app) {
var User = app.models.User;
var applications = app.get('applications');
var queryObject;
let applications = app.get('applications');
app.post('/', function(req, res) {
User.login({
app.get('/',function(req, res){
res.render('index.ejs');
});
app.post('/login', function(req, res) {
app.models.User.login({
email: req.body.email,
password: req.body.password
}, 'user', function(err, token) {
@ -19,14 +21,23 @@ module.exports = function(app) {
redirectToLinkText: 'Try again'
}));
} else {
queryObject = url.parse (req.body.appId,true).query;
let query = url.parse(req.body.location, true).query;
let loginUrl = applications[query.apiKey];
if (!loginUrl)
loginUrl = applications.default;
res.send(JSON.stringify({
location: applications[queryObject.api_key],
accessToken: token.id
token: token.id,
continue: query.continue,
loginUrl: loginUrl,
}));
}
});
});
};
require('q');
app.get('/logout', function (req, res) {
app.models.User.logout(req.accessToken.id, function(err) {
res.redirect('/');
});
});
};

View File

@ -19,8 +19,7 @@
},
"AccessToken": {
"dataSource": "auth",
"public": true,
"strict": true
"public": true
},
"ACL": {
"dataSource": "auth",

View File

@ -10,7 +10,7 @@ var app = module.exports = loopback();
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname,'../client'));
app.use(loopback.static(path.resolve(__dirname, '../client')));
app.set('applications', require("./application.json"));
app.set('applications', require('./application.json'));
// fin
app.start = function() {

View File

@ -1,4 +0,0 @@
module.exports = function(server) {
//server.enableAuth();
};

View File

@ -1,5 +1,7 @@
module.exports = function(server) {
server.enableAuth();
let router = server.loopback.Router();
router.get('/status', server.loopback.status());
server.use(router);

View File

@ -14,25 +14,19 @@
]
},
"User": {
"dataSource": "auth",
"public": true
"dataSource": "auth"
},
"AccessToken": {
"dataSource": "auth",
"public": true,
"strict": true
"dataSource": "auth"
},
"ACL": {
"dataSource": "auth",
"public": true
"dataSource": "auth"
},
"RoleMapping": {
"dataSource": "auth",
"public": true
"dataSource": "auth"
},
"Role": {
"dataSource": "auth",
"public": true
"dataSource": "auth"
},
"Client": {
"dataSource": "vn",

View File

@ -11,12 +11,13 @@
"compression": "^1.0.3",
"cors": "^2.5.2",
"helmet": "^1.3.0",
"loopback": "^2.22.0",
"loopback-boot": "^2.6.5",
"loopback-component-explorer": "^2.4.0",
"serve-favicon": "^2.0.1",
"strong-error-handler": "^1.0.1",
"loopback-connector-mysql": "^4.1.0",
"loopback-datasource-juggler": "^2.39.0",
"loopback": "^2.22.0"
"serve-favicon": "^2.0.1",
"strong-error-handler": "^1.0.1"
},
"devDependencies": {
"eslint": "^2.13.1",

View File

@ -1,7 +1,7 @@
'use strict';
module.exports = function(server) {
// Install a `/` route that returns server status
server.enableAuth();
var router = server.loopback.Router();
router.get('/status', server.loopback.status());
server.use(router);

View File

@ -1,24 +1,70 @@
module.exports = function (app) {
var apiKey = app.get('api key');
var authUrl = app.get('url auth');
app.get('/', function (req, res) {
if (req.cookies['salix-session']) {
let token = req.cookies.vnToken;
validateToken(token, function(isValid) {
if (isValid)
res.render('index.ejs');
}
else {
res.redirect(`${authUrl}/?api_key=${apiKey}`);
}
else
redirectToAuth(res, req.get('origin'));
});
});
app.get('/login', function (req, res) {
var token = req.query.access_token;
if (token) {
res.cookie('salix-session', token, { httpOnly: true, path: '/salix' });
res.redirect('/salix');
}
else {
res.redirect(authUrl);
let token = req.query.token;
let continueUrl = req.query.continue;
validateToken(token, function(isValid) {
if (isValid) {
res.cookie('vnToken', token, {httpOnly: true});
res.redirect(continueUrl ? continueUrl : '/salix');
}
else
redirectToAuth(res);
});
});
app.get('/logout', function (req, res) {
let token = req.cookies.vnToken;
app.models.User.logout(token, function(err) {
redirectToAuth(res);
});
});
function validateToken(tokenId, cb) {
if (!tokenId) {
cb(false);
return;
}
app.models.AccessToken.findById(tokenId, function(err, token) {
if (token) {
token.validate (function (err, isValid) {
cb(isValid === true);
});
}
else
cb(false);
});
}
function redirectToAuth (res, continueUrl) {
let authUrl = app.get('url auth');
let params = {
apiKey: app.get('api key'),
continue: continueUrl
};
res.clearCookie ('vnToken');
res.redirect(`${authUrl}/?${encodeUri(params)}`);
}
};
function encodeUri(object) {
let uri = '';
for (var key in object)
if (object[key]) {
if (uri.length > 0)
uri += '&';
uri += encodeURIComponent(key) + '=' + encodeURIComponent(object[key]);
}
return uri;
}

View File

@ -2,5 +2,15 @@
"db": {
"name": "db",
"connector": "memory"
},
"auth": {
"name": "mysql",
"connector": "mysql",
"database": "salix",
"debug": false,
"host": "localhost",
"port": 3306,
"username": "root",
"password": ""
}
}

View File

@ -13,9 +13,19 @@
"./mixins"
]
},
"User": {
"dataSource": "auth"
},
"AccessToken": {
"dataSource": "db",
"public": false
"dataSource": "auth"
},
"ACL": {
"dataSource": "auth"
},
"RoleMapping": {
"dataSource": "auth"
},
"Role": {
"dataSource": "auth"
}
}