Using the Aurelia Redirect class - aurelia

I've added an authorize pipeline step to my router. Everything works fine, but when I use the Redirect class to point the user to the login page, it takes a URL as its argument. I'd prefer to pass in the route name as I would if I were using Router.navigateToRoute(). Is this possible?
#inject(AuthService)
class AuthorizeStep {
constructor (authService) {
this.authService = authService;
}
run (navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.auth)) {
if (!this.authService.isLoggedIn) {
return next.cancel(new Redirect('url-to-login-page')); // Would prefer to use the name of route; 'login', instead.
}
}
return next();
}
}

After some Googling I found the Router.generate() method which takes a router name (and optional params) and returns the URL. I've now updated my authorize step to the following:
#inject(Router, AuthService)
class AuthorizeStep {
constructor (router, authService) {
this.router = router;
this.authService = authService;
}
run (navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.auth)) {
if (!this.authService.isLoggedIn) {
return next.cancel(new Redirect(this.router.generate('login')));
}
}
return next();
}
}
Edit: After some more googling I found the RedirectToRoute class;
import { RedirectToRoute } from 'aurelia-router';
...
return next.cancel(new RedirectToRoute('login'));

Related

How to use gaurds on module level in nest.js?

I want to use the gaurds on module level instead of controller routes and global module.
How can I do that?
I have this gaurd on my code:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly verifyOptions?: VerifySessionOptions) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = context.switchToHttp();
let err = undefined;
const resp = ctx.getResponse();
await verifySession(this.verifyOptions)(ctx.getRequest(), resp, (res) => {
err = res;
});
if (resp.headersSent) {
throw new STError({
message: 'RESPONSE_SENT',
type: 'RESPONSE_SENT',
});
}
if (err) {
throw err;
}
return true;
}
}
I have many modules in my app like applications, chat, share etc and I want to protect the routes. For that protected routes I want to use this AuthGaurd.
But I don't want to use the gaurd on each and every controller endpoint like this:
#Post()
UseGaurds(new AuthGaurd())
async createApplication(
#Body() createAppData: CreateApplicationDto,
#Session() session: SessionContainer
) {
try {
const newDoc = await this.applicationService.create(createAppData);
return utils.sendSuccess(SUCCESS.S200.DEFAULT, newDoc);
} catch (error) {
throw new BadRequestException();
}
}
I want to use the gaurd on module level. Not on the global module but the modules like application, chat, share etc.
This is what my applicationModule looks like:
#Module({
imports: [SequelizeModule.forFeature([Application])],
providers: [ApplicationServices, ...applicationProvider, ...shareProvider],
exports: [ApplicationServices],
controllers: [ApplicationController],
})
export class ApplicationModule {}

Logout redirectTo not redirecting correctly

I have a custom AuthProvider in react-admin
I am using the checkAuth function and the logic seems to be running correctly:
checkAuth: () => {
if(localStorage.getItem("userprofile")) {
return Promise.resolve()
} else {
return Promise.reject({ redirectTo: '/login', message: 'login.required' })
}
}
The issue is that the resulting routed url seems to be defaulting to a specific resource. After the redirectTo call the browser redirects to: http://localhost:3001/login#/clients
instead of the expected http://localhost:3001/login
Is there some default route setting of some logic reason for this?
Thanks for you help.
I use react-admin 3.8.5 and to change this behavior I had to use customSagas:
https://marmelab.com/react-admin/Admin.html#customsagas
logoutSaga.js
import { UNREGISTER_RESOURCE } from 'react-admin'
import { replace } from 'connected-react-router'
import { put, takeEvery } from 'redux-saga/effects'
function* logoutMonitor(action) {
try {
if (action.payload === 'your resource name') {
yield put(replace({pathname: '/login', state: {nextPathname: '/'}})) // Changing the route for the next entrance!
}
} catch (error) {
console.warn('logoutSaga:', error)
}
}
function* logoutSaga() {
yield takeEvery([UNREGISTER_RESOURCE], logoutMonitor)
}
export default logoutSaga

How to call a http post method from a service in a parent director

My http method returns results when it is contained in my component, but does not return any results when called from a service located one directory up.
I've checked the console and there are no errors. I have tried printing to the console, which works from within the service (returns the desired data), but does not when run from within the child component.
This is the service that I'm trying to build:
import { Injectable } from '#angular/core';
import { Resturant } from '../../models/resturant.model'
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class GetResturantsService {
fullListresturants: Resturant[];
constructor(private http:HttpClient) { }
fetchList(){
this.http.get('https://lunchlads.firebaseio.com/posts.json')
.pipe(map(responseData =>{
const postsArray: Resturant[] = [];
for (const key in responseData) {
if (responseData.hasOwnProperty(key)){
postsArray.push({ ...responseData[key], id:key })
}
}
return postsArray;
}))
.subscribe(posts => {
// this.fullListresturants = posts;
});
}
}
This is the component which is one file down in the directory:
import { Component, OnInit } from '#angular/core';
import { Resturant } from '../../../models/resturant.model'
import { GetResturantsService } from '../get-resturants.service'
import { HttpClient } from '#angular/common/http';
//import { map } from 'rxjs/operators';
#Component({
selector: 'app-list-all',
templateUrl: './list-all.component.html',
styleUrls: ['./list-all.component.css']
})
export class ListAllComponent implements OnInit {
fullListresturants: Resturant;
constructor(private http:HttpClient, private listAllResturants:GetResturantsService) { }
ngOnInit() {
this.onfullList();
}
onfullList(){
this.fullList();
}
private fullList(){
// this.http.get('https://lunchlads.firebaseio.com/posts.json')
// .pipe(map(responseData =>{
// const postsArray: Resturant[] = [];
// for (const key in responseData) {
// if (responseData.hasOwnProperty(key)){
// postsArray.push({ ...responseData[key], id:key })
// }
// }
// return postsArray;
// }))
// .subscribe(posts => {
// // this.fullListresturants = posts;
// });
this.listAllResturants.fetchList();
}
}
The firebase backend contains roughly 10 records with a name:string, votes:number, and selected:number fields. When run from the component, the html file simply returns the name values with an *ngFor loop.
When run from the service, nothing is returned and no errors are reported in the console.
I suspect the problem lies somewhere in how I am calling the fetchList method from the component, but google and me have not been able to suss out what I'm doing wrong.
Your service should return an observable to make it work. As per your current code, you are not returning anything from GetResturantsService.fetchList(). To make it work let change the service like this:
export class GetResturantsService {
fullListresturants: Resturant[];
constructor(private http:HttpClient) { }
fetchList(){
return this.http.get('https://lunchlads.firebaseio.com/posts.json')
.pipe(map(responseData =>{
const postsArray: Resturant[] = [];
for (const key in responseData) {
if (responseData.hasOwnProperty(key)){
postsArray.push({ ...responseData[key], id:key })
}
}
return postsArray;
}));
}
}
Now in component subscribe to the observable returned from fetchList method like this:
export class ListAllComponent implements OnInit {
fullListresturants: Resturant;
constructor(private http:HttpClient, private listAllResturants:GetResturantsService) { }
ngOnInit() {
this.onfullList();
}
onfullList(){
this.fullList();
}
private fullList(){
this.listAllResturants.fetchList()
.subscribe(posts => {
//DO whatever you want to do with posts
this.fullListresturants = posts;
});
}
}
Hope it helps.

How to return to view after authentication in Aurelia

I have a view that can be accessed by a direct link from an email.
Ex.
http://myServer:7747/#/pics/ClientId/YYYY-MM-DD
So this is set up using a route:
{ route: ['/pics', '/pics/:clientId/:sessionDate', 'pics'],
name: 'pics', moduleId: './views/pics', nav: false, title: 'Pictures',
auth: true, activationStrategy: activationStrategy.invokeLifecycle
},
So if a client clicks on this link and is not logged in, I want the view to redirect to a login screen (I am using aurelia-authentication plugin) and then when it succeeds, I want it to return to this page using the same urlParams.
I have the redirect to the login page working, but getting back to this view is proving difficult. If I just try to use history.back() the problem is that the authentication plugin has pushed another navigationInstruction (loginRedirect) onto the history before I can do anything. If I just try to hard-code a 'go back twice' navigation I run into a problem when a user simply tries to log in fresh from the main page and there is no history.
Seems like this should be easier than it is, what am I doing wrong?
I haven't used the aurelia-authentication plugin, but I can help with a basic technique you can use that makes this very easy. In your main.js file, set the root of your app to a "login" component. Within the login component, when the user has successfully authenticated, set the root of your app to a "shell" component (or any component you choose) that has a router view and configure the router in its view-model. Once this happens, the router will take the user to the proper component based on the url. If the user logs out, just set the app root back to the "login" component.
Here's some cursory code to attempt to convey the idea. I assume you're using the SpoonX plugin, but that's not really necessary. Just as long as you reset the root of your app when the user authenticates, it will work.
In main.js
.....
aurelia.start().then(() => aurelia.setRoot('login'));
.....
In login.js
import {AuthService} from 'aurelia-authentication';
import {Aurelia, inject} from 'aurelia-framework';
#inject(AuthService, Aurelia)
export class Login {
constructor(authService, aurelia) {
this.authService = authService;
this.aurelia = aurelia;
}
login(credentialsObject) {
return this.authService.login(credentialsObject)
.then(() => {
this.authenticated = this.authService.authenticated;
if (this.authenticated) {
this.aurelia.setRoot('shell');
}
});
}
.....
}
In shell.html
.....
<router-view></router-view>
.....
In shell.js
.....
configureRouter(config, router) {
this.router = router;
config.map(YOUR ROUTES HERE);
}
.....
I got this to work by replacing the plugin's authenticateStep with my own:
import { inject } from 'aurelia-dependency-injection';
import { Redirect } from 'aurelia-router';
import { AuthService } from "aurelia-authentication";
import { StateStore } from "./StateStore";
#inject(AuthService, StateStore)
export class SaveNavStep {
authService: AuthService;
commonState: StateStore;
constructor(authService: AuthService, commonState: StateStore) {
this.authService = authService;
this.commonState = commonState;
}
run(routingContext, next) {
const isLoggedIn = this.authService.authenticated;
const loginRoute = this.authService.config.loginRoute;
if (routingContext.getAllInstructions().some(route => route.config.auth === true)) {
if (!isLoggedIn) {
this.commonState.postLoginNavInstr = routingContext;
return next.cancel(new Redirect(loginRoute));
}
} else if (isLoggedIn && routingContext.getAllInstructions().some(route => route.fragment === loginRoute)) {
return next.cancel(new Redirect(this.authService.config.loginRedirect));
}
return next();
}
}
The only difference between mine and the stock one is that I inject a 'StateStore' object where I save the NavigationInstruction that requires authentication.
Then in my login viewModel, I inject this same StateStore (singleton) object and do something like this to log in:
login() {
var redirectUri = '#/defaultRedirectUri';
if (this.commonState.postLoginNavInstr) {
redirectUri = this.routing.router.generate(this.commonState.postLoginNavInstr.config.name,
this.commonState.postLoginNavInstr.params,
{ replace: true });
}
var credentials = {
username: this.userName,
password: this.password,
grant_type: "password"
};
this.routing.auth.login(credentials,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
redirectUri
).catch(e => {
this.dialogService.open({
viewModel: InfoDialog,
model: ExceptionHelpers.exceptionToString(e)
});
});
};
Hope this helps someone!

Preventing unauthed ajax requests from redux actions

I have a component defined like this. fetchBrands is a redux action.
class Brands extends Component {
componentWillMount() {
this.props.fetchBrands();
}
render() {
return (
// jsx omitted for brevity
);
}
}
function mapStateToProps(state) {
return { brands: state.brands.brands }
}
export default connect(mapStateToProps, { fetchBrands: fetchBrands })(Brands);
This component is wrapped in a Higher Order Component that looks like this:
export default function(ComposedComponent) {
class Authentication extends Component {
// kind if like dependency injection
static contextTypes = {
router: React.PropTypes.object
}
componentWillMount() {
if (!this.props.authenticated) {
this.context.router.push('/');
}
}
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) {
this.context.router.push('/');
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
function mapStateToProps(state) {
return { authenticated: state.auth.authenticated };
}
return connect(mapStateToProps)(Authentication);
}
Then, in my router config, I am doing the following:
<Route path="brands" component={requireAuth(Brands)} />
If the auth token doesn't exist in local storage, I redirect to a public page. However, the fetchBrands action is still being called which is firing off an ajax request. The server is rejecting it because of the lack of an auth token, but I don't want the call to even be made.
export function fetchBrands() {
return function(dispatch) {
// ajax request here
}
}
I could wrap the ajax request with an if/else check, but that isn't very DRY considering all the action functions I'd need to do this in. How can I implement something DRY to prevent the calls if auth fails at the browser level?
You should write a middleware for that. http://redux.js.org/docs/advanced/Middleware.html
Since you are using axios I would really recommend using something like https://github.com/svrcekmichal/redux-axios-middleware
Then you can use a middleware like
const tokenMiddleware = store => next => action => {
if (action.payload && action.payload.request && action.payload.request.secure) {
const { auth: { isAuthenticated } } = store.getState()
const secure = action.payload.request.secure
if (!isAuthenticated && secure) {
history.push({ pathname: 'login', query: { next: history.getCurrentLocation().pathname } })
return Promise.reject(next({ type: 'LOGIN_ERROR', ...omit(action, 'payload') }))
}
}
return next(action)
}
action.payload.request.secure is a custom prop I use to indicate the request needs authentication.
I this case I also redirect using history from react-router but you can handle this to dispatch another action (store.dispatch({ whatever })) and react as you need