I see in Aurelia site, one of the article uses run() {}. What does this method in general do? it is a lifecycle hook or it is a new Javascript 2016 method?
http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/cheat-sheet/7
import {Redirect} from 'aurelia-router';
export class App {
configureRouter(config) {
config.title = 'Aurelia';
config.addPipelineStep('authorize', AuthorizeStep);
config.map([
{ route: ['welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'flickr', name: 'flickr', moduleId: 'flickr', nav: true, auth: true },
{ route: 'child-router', name: 'childRouter', moduleId: 'child-router', nav: true, title:'Child Router' },
{ route: '', redirect: 'welcome' }
]);
}
}
class AuthorizeStep {
run(navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.auth)) {
var isLoggedIn = /* insert magic here */false;
if (!isLoggedIn) {
return next.cancel(new Redirect('login'));
}
}
return next();
}
}
You can add multiple pipeline steps to your router config. Each of the pipelines must implement PipelineStep interface:
interface PipelineStep {
/**
* Execute the pipeline step. The step should invoke next(), next.complete(),
* next.cancel(), or next.reject() to allow the pipeline to continue.
*
* #param instruction The navigation instruction.
* #param next The next step in the pipeline.
*/
run(instruction: NavigationInstruction, next: Next): void;
}
(source code)
As you can see there must be run method. At some point later run methods from all the steps will be executed.
So the answer to your question: no, it's not something ES2015 introduces, but rather a convention pipeline steps must follow.
Related
I have created a NUXT.JS content static site served with .md files. Now i want to add authentication to it. I want to redirect a user form my main site which is built in VUE.JS
User have to login to my main site and then clicking on a link -> redirect the user to nuxt site
Here are my nuxt configs:
import theme from '#nuxt/content-theme-docs'
export default theme({
docs: {
primaryColor: '#E24F55'
},
content: {
liveEdit: false
},
buildModules: [
'#nuxtjs/color-mode'
],
colorMode: {
preference: '', // default value of $colorMode.preference
fallback: 'light', // fallback value if not system preference found
hid: 'nuxt-color-mode-script',
globalName: '__NUXT_COLOR_MODE__',
componentName: 'ColorScheme',
classPrefix: '',
classSuffix: '-mode',
storageKey: 'nuxt-color-mode'
},
})
-------->>>>>>>>
In middleware>stats.js
export default function ({ route, redirect }) {
console.log('route', route)
// api call to check further
}
nuxt.config.js
import theme from '#nuxt/content-theme-docs'
export default theme({
docs: {
primaryColor: '#E24F55'
},
modules: ['#nuxtjs/axios'],
router: {
middleware: 'stats'
}
})
Here is a local/jwt example of how to use nuxt-auth in #nuxt/docs theme.
The file structure:
├───components
│ └───global
auth.vue
├───content
│ └───en
playground.md
├───node_modules
├───nuxt.config
├───package.json
├───static
// nuxt.config.js
import theme from "#nuxt/content-theme-docs";
export default theme({
docs: {
primaryColor: "#E24F55",
},
content: {
liveEdit: false,
},
buildModules: ["#nuxtjs/color-mode"],
colorMode: {
preference: "", // default value of $colorMode.preference
fallback: "light", // fallback value if not system preference found
hid: "nuxt-color-mode-script",
globalName: "__NUXT_COLOR_MODE__",
componentName: "ColorScheme",
classPrefix: "",
classSuffix: "-mode",
storageKey: "nuxt-color-mode",
},
// ---->
auth: {
strategies: {
local: {
token: {
property: "token",
// required: true,
// type: 'Bearer'
},
user: {
property: "user",
// autoFetch: true
},
endpoints: {
login: { url: "/api/auth/login", method: "post" },
logout: { url: "/api/auth/logout", method: "post" },
user: { url: "/api/auth/user", method: "get" },
},
},
},
},
// <----
});
// components/global/auth.vue
<template>
<div>
<form #submit.prevent="userLogin">
<div>
<label>Username</label>
<input type="text" v-model="login.username" />
</div>
<div>
<label>Password</label>
<input type="text" v-model="login.password" />
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
login: {
username: '',
password: ''
}
}
},
methods: {
async userLogin() {
try {
let response = await this.$auth.loginWith('local', { data: this.login })
console.log(response)
} catch (err) {
console.log(err)
}
}
}
}
</script>
and in your *.md file use the auth component:
---
title: Playground
description: ''
position: 1
category: Playground
---
<auth></auth>
This example is quite simple. It is only meant to show how to use nuxt auth in the nuxt docs theme.
oh ok, you're right, he can't register the middleware.
But you can create a plugin with beforeEach.
// plugins/guard.js
export default ({ app }) => {
app.router.beforeEach((to,from, next) => {
console.log(to, from)
next()
})
}
// nuxt.config.js
// ...
plugins: [__dirname + '/plugins/guard.js'],
// ...
I've spent some time redacting how to do it. Unfortunately I could not make proper edited and annotated screenshots of the Auth0 (too cumbersome with my current setup to make something clean) but here is my github repo with all the explanations on how to make this work.
https://github.com/kissu/so-nuxt-docs-theme-auth-auth0
I have an angular application, i need to implement e2e test in this project.
angular pack: 4.6.6
protractor: 5.3.0
Also i have a multi-layer router in my project, that wrap the router-outlet int o an other component in each layer.
When i need to navigate to one of the low-layer routes for example /notification/groups in the test environment, the program can't get the response from the page and return error:
Failed: Timed out waiting for asynchronous Angular tasks to finish after 11 seconds.
This may be because the current page is not an Angular application. Please see the FAQ for more details: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular
Also when i navigate to the login page in test environment, it will be pass without any problem just because it's on the highest layer in the router.
So the problem is about that angular can't detect the components that wrapped into other components with router-outlet.
how i can solve this problem
this is my router configuration:
[
{
path: "",
component: ThemeComponent,
canActivate: [AuthGuard],
children: [
{
path: "",
component: DefaultComponent,
children: [
{
path: "modbus-devices",
component: DeviceListComponent,
data: { deviceType: 'modbus' }
},
{
path: "snmp-devices",
component: DeviceListComponent,
data: { deviceType: 'snmp' }
},
{
path: "add-modbus-device",
component: ModbusAddDeviceComponent
},
{
path: "edit-modbus-device/:id",
component: ModbusEditDeviceComponent
},
{
path: "add-snmp-device",
component: SnmpAddDeviceComponent
},
{
path: "edit-snmp-device/:id",
component: SnmpEditDeviceComponent
},
{
path: "notification/groups",
component: NotificationGroupListComponent
},
{
path: "notification/setting",
component: NotificationPrioritySettingComponent
},
{
path: "notification/groups/add",
component: NotificationGroupAddComponent,
data: { edit: false }
},
{
path: "notification/groups/edit/:id",
component: NotificationGroupAddComponent,
data: { edit: true }
}
]
}
],
}, {
path: 'login',
component: AuthComponent
},
{
path: 'logout',
component: LogoutComponent
}
]
The error you received:
Failed: Timed out waiting for asynchronous Angular tasks to finish
after 11 seconds. This may be because the current page is not an
Angular application. Please see the FAQ for more details:
https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular
...happens because you have long-living, asynchronous code running inside of an Angular zone.
For instance, this line of code will cause the error above if you try to run an e2e test on it:
export class MyComponent implements OnInit {
ngOnInit() {
// Do something in a while
setTimeout(() => {}, 1000000);
}
}
To fix the error on the above code, you need to run that setTimeout outside of Angular so that it does not make everything hang.
export class MyComponent implements OnInit {
constructor(private zone: NgZone) {}
ngOnInit() {
this.zone.runOutsideAngular(() => {
// Do something in a while
setTimeout(() => {}, 1000000);
});
}
}
So many sure either your code or code of third party libraries you are using do not have any long-living asynchronous code, and if they do then you should run them outside of Angular and your problems should disappear. If you don't run them outside of Angular, Angular will wait for them to complete, and if it reaches a timeout (11 seconds in this case), it will give you the error you received.
We have a large Marionette app, with sub apps/modules.
Each of these registers its own router within the App.addInitializer.
What is the best way to flag certain routes as public and others as requiring authentication?
I have a way in the app to check if the user is authenticated or not, but I'm trying to avoid having to implement that check in every route handler.
PrivateModuleRouter.Router = Marionette.AppRouter.extend({
appRoutes: {
"privateRoute(/)" : "handlePrivateRoute",
}
});
var API = {
handlePrivateRoute: function() {
//I don't want to repeat this everywhere..
if(!Auth.isAuthenticated()) {
App.navigate('/login', {trigger:true});
} else {
PrivateRouteController.showForm();
}
};
App.addInitializer(function(){
new PrivateModuleRouter.Router({
controller: API
});
});
Is there way in the route definition to flag it as private, and then a top level route handler performs this check?
If it's on a Router event though, this may not trigger if the route handler was triggered directly (not passing trigger:true, and calling API.handlePrivateRoute() directly.
Disclaimer: as I don't personally use Marionette, this answer is based on Backbone only.
The execute function
Backbone provides the execute function in the router as a way to handle that kind of logic. Even the example has authentication logic in it:
var Router = Backbone.Router.extend({
execute: function(callback, args, name) {
if (!loggedIn) {
goToLogin();
return false;
}
args.push(parseQueryString(args.pop()));
if (callback) callback.apply(this, args);
}
});
The authentication router
One way to avoid repeating the execute in each router would be to make a base router for your app.
var BaseRouter = Backbone.Router.extend({
constructor: function(prefix, opt) {
// get the hash
this.auth = _.result(this, "auth", {});
BaseRouter.__super__.constructor.apply(this, arguments);
},
// requires auth by default?
authDefault: false,
/**
* Check the `auth` hash for a callback. Returns `authDefault` if
* the callback is not specified.
* #param {String} callbackName name of the function.
* #return {Boolean} true if the callback is private.
*/
hasAuth: function(callbackName) {
return _.result(this.auth, callbackName, this.authDefault);
},
// To easily override the auth logic in a specific router
checkAuth: function(){
return Auth.isAuthenticated();
},
execute: function(callback, args, name) {
if (this.hasAuth(name) && !this.checkAuth()) {
this.navigate('/login', { trigger: true });
return false;
}
}
});
Defining the specific routers
Then for each of your router, extend BaseRouter.
var SpecificRouter = BaseRouter.extend({
routes: {
'*otherwise': 'home', // notice the catch all
'public': 'publicRoute',
'private': 'privateRoute',
'unspecified': 'defaultAccessRoute'
},
/**
* The auth hash works like this:
* "functionName": [boolean, true if needs auth]
*
* home and publicRoute could be left out as it's the default here.
*/
auth: {
home: false, // public
publicRoute: false, // public
privateRoute: true, // needs authentication
// defaultAccessRoute will be public because BaseRouter
// defines `authDefault: false`.
},
home: function() {},
publicRoute: function() {},
privateRoute: function() {},
defaultAccessRoute: function() {},
});
And for a router which all routes are private by default:
var PrivateRouter = BaseRouter.extend({
authDefault: true,
routes: {
'*otherwise': 'home', // private
'only-private': 'onlyPrivate', // private
},
// ...snip...
/**
* Optional example on how to override the default auth behavior.
*/
checkAuth: function() {
var defaultAuthResult = PrivateRouter.__super__.checkAuth.call(this);
return this.specificProperty && defaultAuthResult;
}
});
In github you can find many solution for calling some methods before router's execution. For marionette you can use ideas from marionette-lite extension based in filters system.
You should define filter, for example RequresAuthFilter as:
import { Filter } from 'marionette-lite';
const RequresAuthFilter = Filter.extend({
name: 'requresAuth', // name is used in controller for detect filter
async: true, // async mode
execution: Filter.Before,
handler(fragment, args, next) {
// Requesting server to check if user is authorised
$.ajax({
url: '/auth',
success: () => {
this.isSignedIn = true;
next();
},
error: () => {
Backbone.navigate('login', true);
}
});
},
});
or short sync way:
import { Filter } from 'marionette-lite';
const RequresAuthFilter = Filter.extend({
name: 'requresAuth',
handler(fragment, args) {
if (!window.isSignedIn) {
Backbone.navigate('login', true);
}
},
});
And add this filter to Controller as:
const AppController = Marionette.Object.extend({
// Add available filters map
filtersMap: [
new RequresAuthFilter()
],
filters: {
// e.g. Action that need authentication and if user isn't
// authenticated gets redirect to login page
requresAuth: ['logout', 'private'],
},
logout() { /* ... */ },
private() { /* ... */ }
});
I want when user coming check if he login before - if not -> redirect to login page -> if his email & password are okay -> redirect back to welcome page.
What problem I have -> service work properly with login class but welcome class totally ignore any changes in service.
Code
users.js
export class Users {
users = [
{ name: 'Lucian', email: 'lucian1992#zalando.de', password: 'lucian' },
{ name: 'Corki', email: 'corki2010#supplier.de', password: 'corki' },
{ name: 'Vayne', email: 'vaynii#zalando.de', password: 'vayne' }];
securedUser = {};
error = {};
usedOnce = 0;
check(checkUser) {
this.usedOnce = 1;
this.securedUser = this.users.filter((user) => user.email === checkUser.email
&& user.password === checkUser.password)[0];
if (typeof this.securedUser === 'undefined'){
this.error = { message: "No such kind of user or wrong password" };
}
}
}
login.js
import {inject} from 'aurelia-framework';
import {Users} from 'users-list';
import {Router} from 'aurelia-router';
#inject(Users, Router)
export class Login {
constructor(userData, router) {
this.router = router;
this.users = userData.users;
this.check = userData.check;
this.securedUser = userData.securedUser; // Changed after check function
this.userError = userData.error;
}
checkUser = {};
login() {
this.check(this.checkUser);
if (typeof this.userError === 'undefined') {
alert(this.error.message);
} else {
this.router.navigate("")
}
}
}
welcome.js
import {inject} from 'aurelia-framework';
import {Users} from 'users-list';
import {Redirect} from 'aurelia-router';
#inject(Users)
export class Overview {
constructor(userData) {
this.usedOnce = userData.usedOnce;
this.securedUser = userData.securedUser; // This object changed in login.js, but totally ignores in this class
}
heading = 'Welcome to the Article Manager Prototype!';
canActivate() {
if (this.securedUser) {
return new Redirect('/login')
}
}
}
So, tech issue that securedUser changed in login.js, but totally ignored in welcome.js. Is this because of canActivate method? Because I also use activate() - the same problem.
Will appreciate any help with understanding the issue.
There is a solution for your problem in the official documentation:
import {Redirect} from 'aurelia-router';
export class App {
configureRouter(config) {
config.title = 'Aurelia';
config.addPipelineStep('authorize', AuthorizeStep);
config.map([
{ route: ['welcome'], name: 'welcome', moduleId: 'welcome', nav: true, title:'Welcome' },
{ route: 'flickr', name: 'flickr', moduleId: 'flickr', nav: true, auth: true },
{ route: 'child-router', name: 'childRouter', moduleId: 'child-router', nav: true, title:'Child Router' },
{ route: '', redirect: 'welcome' }
]);
}
}
class AuthorizeStep {
run(navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.auth)) {
var isLoggedIn = /* insert magic here */false;
if (!isLoggedIn) {
return next.cancel(new Redirect('login'));
}
}
return next();
}
}
Notice the addition of config.addPipelineStep('authorize', AuthorizeStep); compared to the regular router config logic.
Source: http://aurelia.io/docs.html#/aurelia/framework/1.0.0-beta.1.2.2/doc/article/cheat-sheet/7
(you need to scroll down a bit to section "Customizing the Navigation Pipeline")
I tried an own example with this and maybe i found your Problem.
You are checking if your securedUser is null. If it is NOT null you will reditect to login
canActivate() {
if (this.securedUser) { // change to !this.securedUser
return new Redirect('/login')
}
}
And you are setting your securedUser initial to an empty object:
securedUser = {};
Because of that your first redirect was working even your if condition was wrong.
Don´t set a initial value, set undefined or change your if condition and check on sercuredUser properties.
Your solution might work, but if you have 50 routes that must be authorised you would have to use canActivate hook 50 times! That is not an elegant solution for an authorising workflow.
This is a more elegant solution:
Instead of having a "route" to login, create an isolated root component and then update your main.js file:
//for example, imagine that your root component is located at src/app/app.js
//and your login component at src/login/login.js
//app.isLoggedIn() checks if the credentials/tokens are OK
aurelia.start().then(a => {
let rootComponent = '' app.isLoggedIn() ? 'app/app' : 'login/login';
a.setRoot(rootComponent, document.body);
});
Now, in your login/login.js root component you have to configure the router:
configureRouter(config, router) {
config.title = 'YourTitle';
config.map([
{ route: '', name: 'user-password', moduleId: './user-password', title: 'Sign In' }, //route for "username/password" screen
{ route: 'forgot-password', name: 'forgot-pwd', moduleId: './forgot-pwd', title: 'Forgot Password?' } //example of "forgot password" screen
]);
config.mapUnknownRoutes(instruction => {
//if an unknown route has been found,
//a route from the app component, 'users/add' for example
//redirect to sign-in component
return './user-password';
});
this.router = router;
}
Create appropriated methods to authenticate the user and then redirect it to the app/app component:
//import and inject { Aurelia } from 'aurelia-framework';
this.aurelia.setRoot('app/app');
Now, if an unauthorised user access 'users/add', it will be redirected to the login page. If the login succeeds, the 'users/add' page will be shown.
Hope this helps!
Here's my child router and the view code:
router.createChildRouter().makeRelative({
moduleId: 'products',
fromParent: true,
dynamicHash: ':id'
}).map([
{ route: 'specs', title: 'Specs', moduleId: 'specs', nav: true },
{ route: 'reviews', title: 'Reviews', moduleId: 'reviews', nav: true }
]).buildNavigationModel();
<ul class="tabs" data-bind="foreach: router.navigationModel()">
<li data-bind="css: { selected: isActive }">{{title}}</li>
</ul>
which will produce two tabs with the following URLs:
example.com/#products/200/specs
example.com/#products/200/reviews
I have a toolbar on my reviews page that sorts the content by date or by rating, so the URL becomes:
example.com/#products/200/reviews?date=asc&rating=desc
But when I switch tabs and come back, I lose the query strings. Is there way to keep them in the URL until I clear the filters?
Without knowing your exact implementation is there any reason to not just set this inside of a modules whose responsibility is to manage state?
products/(id)/specs view model / w/e -
define (['config.queryparams'], function (queryParams) {
var ctor = function () {
var self = this;
// Assume the queryParms module has an observable called parameters
self.queryparameters = queryParams.parameters;
}
ctor.prototype.active(id, querystring) {
var self = this;
// Set the queryParams.parameters observable to the query string
self.queryparameters(querystring);
}
return ctor;
});
Then in your config.queryparams module -
define([], function () {
var parameters = ko.observable();
var module = {
parameters: parameters
}
});
Now just reference config.queryparams from your other module and it is shared. Make sense?