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 operator-linebreak: 0
radix: 0 radix: 0
guard-for-in: 0 guard-for-in: 0
camelcase: 0

42
.vscode/launch.json vendored
View File

@ -37,50 +37,10 @@
"name": "Asociar al proceso", "name": "Asociar al proceso",
"type": "node", "type": "node",
"request": "attach", "request": "attach",
"processId": "${command.PickProcess}", "processId": "${command:PickProcess}",
"port": 5858, "port": 5858,
"sourceMaps": false, "sourceMaps": false,
"outFiles": [] "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'; import './config';
import './login/index';
export {component as Login} from './login/login';

View File

@ -1,4 +1,4 @@
import {module} from './module'; import ngModule from './ngModule';
config.$inject = ['$translatePartialLoaderProvider', '$httpProvider']; config.$inject = ['$translatePartialLoaderProvider', '$httpProvider'];
export function config($translatePartialLoaderProvider, $httpProvider) { export function config($translatePartialLoaderProvider, $httpProvider) {
@ -7,4 +7,4 @@ export function config($translatePartialLoaderProvider, $httpProvider) {
$httpProvider.defaults.useXDomain = true; $httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With']; 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; 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 {ng} from 'vendor';
import * as core from 'core'; import 'core';
export const NAME = 'client'; export const module = ng.module('client', []);
export const module = ng.module(NAME, []);

View File

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

View File

@ -12,6 +12,7 @@ import './confirm/index';
import './title/index'; import './title/index';
import './subtitle/index'; import './subtitle/index';
import './spinner/index'; import './spinner/index';
import './snackbar/index';
export {NAME as BUTTON, directive as ButtonDirective} from './button/button'; export {NAME as BUTTON, directive as ButtonDirective} from './button/button';
export {NAME as BUTTON_MDL, factory as buttonMdl} from './button/button.mdl'; 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 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, 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 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, directive as SubmitDirective} from './submit/submit';
export {NAME as SUBMIT_MDL, factory as submitMdl} from './submit/submit.mdl'; 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, directive as ComboDirective} from './combo/combo';
export {NAME as COMBO_MDL, factory as comboMdl} from './combo/combo.mdl'; 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'; 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 conf = {urlTemplate: '/static/locale/{part}/{lang}.json'};
let langs = ['en', 'es']; let langs = ['en', 'es'];
let localLangs = { let localLangs = {
'en_US': 'en', en_US: 'en',
'en_UK': 'en', en_UK: 'en',
'es_ES': 'es', es_ES: 'es',
'es_AR': 'es' es_AR: 'es'
}; };
$translateProvider $translateProvider
.useSanitizeValueStrategy('escape') .useSanitizeValueStrategy('escape')

View File

@ -23,26 +23,25 @@
margin-top: -10em; margin-top: -10em;
margin-left: -14em; margin-left: -14em;
} }
.button-bar { .button-bar {
margin-top: 1.5em; margin-top: 1.5em;
text-align: right; text-align: right;
}
button { button {
background: none; background: none;
border: none; border: none;
text-transform: uppercase; text-transform: uppercase;
font-size: 1.1em; font-size: 1.1em;
color: #ffa410; color: #ffa410;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
padding: .5em; padding: .5em;
margin: -0.5em; margin: -0.5em;
margin-left: .5em; margin-left: .5em;
&:hover { &:hover {
background-color: rgba(1,1,1,.1); background-color: rgba(1,1,1,.1);
}
} }
} }
} }

View File

@ -1,6 +1,5 @@
import * as vendors from 'vendor'; import * as vendors from 'vendor';
import {getModuleName, getVendorDependencies} from './lib/util'; import {getVendorDependencies} from './lib/util';
const DEPENDENCIES = getVendorDependencies(vendors); const DEPENDENCIES = getVendorDependencies(vendors);
export const NAME = getModuleName('core'); export const module = vendors.ng.module('vnCore', DEPENDENCIES);
export const module = vendors.ng.module(NAME, 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> <div class="mdl-snackbar__text"></div>
<button class="mdl-snackbar__action" type="button"></button> <button class="mdl-snackbar__action" type="button"></button>
</div> </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

@ -14,8 +14,8 @@ export default class Spinner extends Component {
} }
/** /**
* Enables/disables the spinner. * Enables/disables the spinner.
* *
* @param {Boolean} %true to enable, %false to disable * @param {Boolean} value %true to enable, %false to disable
*/ */
set enable(value) { set enable(value) {
if (value) if (value)
@ -25,7 +25,7 @@ export default class Spinner extends Component {
} }
/** /**
* Returns the current spinner state. * Returns the current spinner state.
* *
* @return {Boolean} %true if it's enabled, %false otherwise * @return {Boolean} %true if it's enabled, %false otherwise
*/ */
get enable() { get enable() {

View File

@ -6,7 +6,12 @@
ng-model="*[model]*" ng-model="*[model]*"
vn-validation="*[rule]*" vn-validation="*[rule]*"
*[enabled]*/> *[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> <i class="material-icons">clear</i>
</button> </button>
<label class="mdl-textfield__label" translate>*[label]*</label> <label class="mdl-textfield__label" translate>*[label]*</label>

View File

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

View File

@ -1,5 +1,5 @@
import {ng} from 'vendor'; import {ng} from 'vendor';
import {NAME as SALIX} from './module'; import './module';
export const bootstrap = () => { export const bootstrap = () => {
const selector = 'selector'; const selector = 'selector';
@ -11,7 +11,7 @@ export const bootstrap = () => {
let _element = _script && document.querySelector(_script.getAttribute(selector)); let _element = _script && document.querySelector(_script.getAttribute(selector));
if (!_element) { 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); module.component(NAME, COMPONENT);
controller.$inject = ['$translate']; controller.$inject = ['$translate', '$window', '$document'];
function controller($translate, $translatePartialLoader) { function controller($translate, $window, $document) {
this.onLogoutClick = function() { this.onLogoutClick = function() {
let appName = 'salix'; $window.location = 'salix/logout';
document.cookie = `${appName}-session=; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
window.location = `/auth?api_key=${appName}`;
}; };
this.onChangeLanguage = function() { this.onChangeLanguage = function() {
let lang = $translate.use() == 'en' ? 'es' : 'en'; let lang = $translate.use() == 'en' ? 'es' : 'en';
$translate.use(lang); $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 {ng} from 'vendor';
import * as core from 'core'; import 'core';
import {NAME as CORE} from 'core';
export const NAME = 'salix'; export const module = ng.module('salix', ['vnCore']);
export const module = vendors.ng.module(NAME, [CORE]);

View File

@ -1,3 +1,4 @@
{ {
"default": "salix/login",
"salix": "/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) { module.exports = function(server) {
// Install a `/` route that returns server status server.enableAuth();
var router = server.loopback.Router();
router.get('/status', server.loopback.status()); var router = server.loopback.Router();
router.get(['/'],function(req,res){ router.get('/status', server.loopback.status());
res.render('index.ejs'); server.use(router);
});
server.use(router);
}; };

View File

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

View File

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

View File

@ -10,7 +10,7 @@ var app = module.exports = loopback();
app.set('view engine', 'ejs'); app.set('view engine', 'ejs');
app.set('views', path.join(__dirname,'../client')); app.set('views', path.join(__dirname,'../client'));
app.use(loopback.static(path.resolve(__dirname, '../client'))); app.use(loopback.static(path.resolve(__dirname, '../client')));
app.set('applications', require("./application.json")); app.set('applications', require('./application.json'));
// fin // fin
app.start = function() { 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) { module.exports = function(server) {
server.enableAuth();
let router = server.loopback.Router(); let router = server.loopback.Router();
router.get('/status', server.loopback.status()); router.get('/status', server.loopback.status());
server.use(router); server.use(router);

View File

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

View File

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

View File

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

View File

@ -1,24 +1,70 @@
module.exports = function (app) { module.exports = function (app) {
var apiKey = app.get('api key');
var authUrl = app.get('url auth');
app.get('/', function (req, res) { app.get('/', function (req, res) {
if (req.cookies['salix-session']) { let token = req.cookies.vnToken;
res.render('index.ejs'); validateToken(token, function(isValid) {
} if (isValid)
else { res.render('index.ejs');
res.redirect(`${authUrl}/?api_key=${apiKey}`); else
} redirectToAuth(res, req.get('origin'));
});
}); });
app.get('/login', function (req, res) { app.get('/login', function (req, res) {
var token = req.query.access_token; let token = req.query.token;
if (token) { let continueUrl = req.query.continue;
res.cookie('salix-session', token, { httpOnly: true, path: '/salix' });
res.redirect('/salix'); validateToken(token, function(isValid) {
} if (isValid) {
else { res.cookie('vnToken', token, {httpOnly: true});
res.redirect(authUrl); 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": { "db": {
"name": "db", "name": "db",
"connector": "memory" "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" "./mixins"
] ]
}, },
"User": {
"dataSource": "auth"
},
"AccessToken": { "AccessToken": {
"dataSource": "db", "dataSource": "auth"
"public": false },
"ACL": {
"dataSource": "auth"
},
"RoleMapping": {
"dataSource": "auth"
},
"Role": {
"dataSource": "auth"
} }
} }