I'm trying to add a new AsyncValidator to check whether user's email already exist in database.
Below is may validator:
export class UniqueEmailValidator implements AsyncValidator {
constructor(private webService: WebWrapperService) {}
validate(ctrl: AbstractControl): Promise < ValidationErrors | null > | Observable < ValidationErrors | null > {
return this.webService.isEmailExistEx(ctrl.value)
.pipe(
map(res => {
console.log("get response" + res);
if (res) {
return { 'uniqueEmail': true};
}
return null;
})
);
}
}
The function isEmailExistEx in service will send a post request to server.
isEmailExistEx(email: string): Observable<boolean> {
this.http.post(this.baseUrl + "auth/verify",
{
"email": email
})
.subscribe(
(val: any) => {
if (!val.result) {
return of(false);
} else {
return of(true);
}
},
response => {
return of(false);
},
() => {
return of(false);
});
}
It reports following error:
A function whose declared type is neither 'void' nor 'any' must return a value.
How should I modify this function?
You're subscribeing to the Observable which will consume the value wrapped in it.
Use map instead of subscribeing and return a boolean value from it::
isEmailExistEx(email: string): Observable<boolean> {
return this.http.post(this.baseUrl + "auth/verify", { email })
.pipe(
map((val: any) => val.result ? true : false)
);
}
Related
I have a service and it accepts id as paramter and I am requesting the route with the id but I received the error above on my service . Any idea?
#service
const apiBaseUrl = `${environment.apiUrl}/api/userprofile`;
deactivateUserProfileStatus(id: number) {
return this.httpRequestService.put(`${apiBaseUrl}/inactive/${id}`);
}
#ts
DeactivateUserProfileStatus(id: number) {
this.isInProgress = true;
this._userService
.deactivateUserProfileStatus(id)
.pipe(
finalize(() => {
this.isInProgress = false;
})
)
.subscribe({
next: (res) => {
this._notificationService.showSuccess(
'User status has been updated successfully.'
);
// this.generalForm.disable();
this.getUserGeneralDetails();
// this._router.navigate(['transactions']);
},
error: (err) => {
this._notificationService.showError(
'Something went wrong, Try again later.'
);
this.isInProgress = false;
},
complete: () => {
this.isInProgress = false;
},
});
}
}
You should add the body (or atleast empty body) for your PUT request.
(method) HttpClient.put(url: string, body: any, options: {
headers?: HttpHeaders | {
[header: string]: string | string[];
};
observe?: "body";
params?: HttpParams | {
[param: string]: string | string[];
};
reportProgress?: boolean;
responseType: "arraybuffer";
withCredentials?: boolean;
}): Observable<...> (+14 overloads)
the only GET requests call without body just by URL.
I'm trying to implement a role based system on my application and I have the following:
authorize.service.ts
public isAuthenticated(): Observable<boolean> {
return this.getUser().pipe(map(u => !!u));
}
public hasRole(roles: Array<string>): Observable<boolean> {
return this.getUser().pipe(map(u => {
if (!!!u) {
return false;
}
const role = u['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'];
return roles.some(r => r === role);
}));
}
app.routing.ts
...
{ path: 'list', component: ListComponent, canActivate: [AuthorizeGuard], data: { title: 'List', roles: [Role.Admin, Role.Developer, Role.Guest, Role.User]} }
...
And I would like to have the following logic on my AuthorizeGuard:
If isn't authenticated navigate to the login page.
If is logged in and has the required role navigate to the page otherwise navigate to a "forbidden" page.
I have this code on my canActivate:
canActivate(
_next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
this.authorize.isAuthenticated().subscribe(isAuthenticated => {
if (isAuthenticated) {
this.authorize.hasRole(roles).subscribe(hasRole => {
if (hasRole) {
return true;
} else {
this.router.navigate(['forbidden']);
}
});
} else {
this.router.navigate(ApplicationPaths.LoginPathComponents, {
queryParams: {
[QueryParameterNames.ReturnUrl]: state.url
}
});
}
});
return true;
}
But it doesn't work because of the subscribers.
Is there anyway to make wait for the result and return it instead of always return true?
Thanks
You can't mix 2 subscribe and return true in the end, cause it always is true(last return).
You need return Observable<boolean> from the start and chain it to have Observable of false || true at the end
This code is changed in this editor, so it maybe won't work from the start, but you would get the idea of chaining
return this.authorize.isAuthenticated().pipe(
concatMap(isAuth => isAuth ? this.authorize.hasRole(roles) : throwError('NO_AUTH')),
concatMap(hasRole => hasRole ? of(true) : throwError('NO_ROLE')),
catchError(error => {
const isAuthError = error === 'NO_AUTH';
if (isAuthError) {
this.router.navigate(ApplicationPaths.LoginPathComponents, {
queryParams: {
[QueryParameterNames.ReturnUrl]: state.url
}
});
} else {
this.router.navigate(['forbidden']);
}
return of(false);
}),
);
p.s approach with catchError could be reworked to some other
I have some code that goes to my server and returns some data. I have noticed, that i am unable to catch/handle a 403 response.
Code:
canActivate(route: ActivatedRouteRequest, state: RouterStateSnapshot): Observable <boolean> {
return this.myService.getEntitlements()
.pipe(
map(data => {
this.isEntitled = data.hasEntitlements;
if(this.isEntitled === true) {
return true;
}
else {
return false;
}
}),
catchError((err: any) => {
return of(false);
})
};
}
i'm working with a angular and i'm trying to apply some AuthGard on some Paths.
The problem is canActivate() renders the content before it checks with the SecurityContext, after a verification that no SecurityContext is applied then a redirection to the default page (login) page is applied.
This is the portion of code responsible for this.
app.routing.ts
{
path: 'admin',
canActivate: [AuthGard],
component: HomeComponent,
children : [
{
path: 'add-merchant-admin',
component : AddMerchantAdminComponent,
},
{
path: 'list-merchant-admin',
component : ListMerchantAdminComponent,
}
]
},
AuthGard.ts
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
this._authService.getRoles().subscribe(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
this.error = false;
}
}
} else {
this._router.navigate(['login']);
this.error = true;
}
}, err => {
this._router.navigate(['login']);
this.error = true;
}
);
return !this.error;
};
AuthService
getRoles() {
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers, withCredentials: true});
return this.http.get('http://10.0.0.239:8080/**/**/RolesResource/getRole', options)
.map((res) => res)
.catch((error: any) => Observable.throw(error.text() || 'Server error'));
}
All Services are correctly injected,
Normally a redirection to protected area or default page should be applied after the verification is made using getRole() method.
The problem you are having is that this._authService.getRoles() makes a network call which is asynchronous. return !this.error; is being fired before the network call is being returned so !this.error does not change and is therefore still truthy.
To solve this issue you should be able to return an observable as follows:
return this._authService.getRoles().map(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
return false;
}
}
} else {
this._router.navigate(['login']);
return true;
}
}).catch((err) => {
this._router.navigate(['login']);
return Observable.of(false);
}
);
Something like this should work
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this._authService.getRoles()
.map(response => JSON.parse(response.text())[0].authority)
.do(role => localStorage.setItem('role', role))
.map( role => role === 'ROLE_ADMIN')
.catch(() => this._router.navigate(['login']));
};
You can try with return observable, which can be updated either true or false.
instead of returning return !this.error; which is always true, try to return
canActivate(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this._authService.getRoles().map(
res => {
if (res.status == 200) {
this.roles = JSON.parse(res.text());
this.role = this.roles[0].authority;
localStorage.setItem('role', this.role);
if (this.role == 'ROLE_ADMIN') {
this._router.navigate(['admin']);
} else {
if (this.role == 'ROLE_ANONYMOUS') {
this._router.navigate(['login']);
return false;
}
}
} else {
this._router.navigate(['login']);
return true;
}
}, err => {
this._router.navigate(['login']);
return Observable.of(false);
}
);
};
Edited
I'm having issue using native promises with second angular and typescript:
export class LoginComponent implements OnInit {
public user = {};
constructor( private authService:AuthenticationService) { }
ngOnInit() {
}
login() {
console.log( 'Connecting to server' );
this.authService.login( this.user ).then(( response ) => {
// works
console.log( response['success'] );
// error
console.log( response.success );
}, ( error ) => {
console.log( error );
});
}
}
below is simple service, with fake connect to the server:
export class AuthenticationService {
// ... some code ...
login( loginData ) {
let self = this;
return new Promise(function(resolve, reject){
// fake delay - for now is no back end
setTimeout(function() {
if ( loginData.username === 'username' && loginData.password === 'password' ) {
resolve({
message: "Successfully logged in",
success: true,
errors: null
});
} else {
reject({
message: "Wrong user data reperesented",
success: false,
errors: {
"username": true,
"password": true
}
});
}
}, 100);
});
}
// ... some code ...
}
Tried to search what I have to do to solve Property 'success' does not exist on type '{}'. error but without success.
This happens because the code above is not typed.
To make this work it should be either
login( loginData ): Promise<any> { ... }
or
this.authService.login( this.user ).then(( response: any ) => { ... })
A better way is to make types work for you instead of ignoring them:
interface IAuthLoginResponse {
message: string;
success: boolean;
errors: any;
}
...
login( loginData ): Promise<IAuthLoginResponse> {
return new Promise<IAuthLoginResponse>(function (resolve, reject) { ... })
}