Open georve opened 4 years ago
Issue type
I'm submitting an issue when I made login (check one with "x")
- [x ] bug report
- [ ] feature request
- [ ] question about the decisions made in the repository
Issue description
when I try to make login using the authentication component. The screen is keeping freezer and no any request is made to the backend. when I try many times in the console appear the following error Maximum call stack size exceeded"
Current behavior:
i have the configuration of authentication using jwt token
Expected behavior:
the ngx-admin must be working using jwt token sending the authorization bearer header to the backend and even update the token when it is revoked.
Steps to reproduce:
- I used
Related code:
app.module.ts
`/**
- @license
- Copyright Akveo. All Rights Reserved.
- Licensed under the MIT License. See License.txt in the project root for license information. */ import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule, ErrorHandler } from '@angular/core'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { CoreModule } from './@core/core.module'; import { ThemeModule } from './@theme/theme.module'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { NbPasswordAuthStrategy, NbAuthModule, NbAuthJWTToken, NbAuthJWTInterceptor, NbTokenStorage, NbTokenLocalStorage, NB_AUTH_TOKEN_INTERCEPTOR_FILTER } from '@nebular/auth'; import {AuthModule} from './auth/auth.module'; import { NbSecurityModule, NbRoleProvider } from '@nebular/security'; import { environment } from '../environments/environment'; import { NbChatModule, NbDatepickerModule, NbDialogModule, NbMenuModule, NbSidebarModule, NbToastrModule, NbWindowModule, } from '@nebular/theme'; import { AuthGuard } from './auth-guard.service'; import {RoleProvider} from './role.provider'; import { GlobalErrorHandler } from './global-error-handler'; import { ServerErrorInterceptor } from './server-error.interceptor'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, BrowserAnimationsModule, HttpClientModule, AppRoutingModule, ThemeModule.forRoot(), NbSidebarModule.forRoot(), NbMenuModule.forRoot(), NbDatepickerModule.forRoot(), NbDialogModule.forRoot(), NbWindowModule.forRoot(), NbToastrModule.forRoot(), NbSecurityModule.forRoot(), NbChatModule.forRoot({ messageGoogleMapKey: 'AIzaSyA_wNuCzia92MAmdLRzmqitRGvCF7wCZPY', }), CoreModule.forRoot(), AuthModule, NbAuthModule.forRoot({ strategies: [ NbPasswordAuthStrategy.setup({ name: 'email', baseEndpoint: environment.baseUrlApi, token: { class: NbAuthJWTToken, key: 'token', // this parameter tells where to look for the token }, login: { endpoint: '/users/login', method: 'post', redirect: { success: '/page/', failure: null, // stay on the same page }, }, register: { endpoint: '/users/sign-in', method: 'post', }, }), ], forms: {}, }),
], providers: [ AuthGuard, { provide: NbRoleProvider, useClass: RoleProvider }, { provide: ErrorHandler, useClass: GlobalErrorHandler }, { provide: NbTokenStorage, useClass: NbTokenLocalStorage }, { provide: HTTP_INTERCEPTORS, useClass: NbAuthJWTInterceptor, multi: true }, { provide: NB_AUTH_TOKEN_INTERCEPTOR_FILTER, useValue: (req) => { return false }}, { provide: HTTP_INTERCEPTORS, useClass: ServerErrorInterceptor, multi: true }, ], bootstrap: [AppComponent], }) export class AppModule { } `
app-routing.module.ts
`import { ExtraOptions, RouterModule, Routes } from '@angular/router'; import { NgModule } from '@angular/core'; import {LoginComponent} from './auth/components/login/login.component'; import { AuthGuard } from './auth-guard.service'; import { NbAuthComponent, NbLoginComponent, NbLogoutComponent, NbRegisterComponent, NbRequestPasswordComponent, NbResetPasswordComponent, } from '@nebular/auth';
export const routes: Routes = [ { path: 'pages', canActivate: [AuthGuard], // here we tell Angular to check the access with our AuthGuard loadChildren: () => import('./pages/pages.module') .then(m => m.PagesModule), }, { path: 'auth', component: NbAuthComponent, children: [ { path: '', component: LoginComponent, }, { path: 'login', component: LoginComponent, }, { path: 'register', component: NbRegisterComponent, }, { path: 'logout', component: NbLogoutComponent, }, { path: 'request-password', component: NbRequestPasswordComponent, }, { path: 'reset-password', component: NbResetPasswordComponent, }, ], }, { path: '', redirectTo: 'pages', pathMatch: 'full' }, { path: '**', redirectTo: 'pages' }, ];
const config: ExtraOptions = { useHash: false, };
@NgModule({ imports: [RouterModule.forRoot(routes, config)], exports: [RouterModule], }) export class AppRoutingModule { } `
auth-guard.service.ts `import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { NbAuthService } from '@nebular/auth'; import { tap } from 'rxjs/operators';
@Injectable() export class AuthGuard implements CanActivate {
constructor(private authService: NbAuthService, private router: Router) { }
canActivate() { return this.authService.isAuthenticated() .pipe( tap(authenticated => { if (!authenticated) { this.router.navigate(['auth/login']); } }), ); } }`
global-error-handler.ts `import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { NbAuthService } from '@nebular/auth'; import { tap } from 'rxjs/operators';
@Injectable() export class AuthGuard implements CanActivate {
constructor(private authService: NbAuthService, private router: Router) { }
canActivate() { return this.authService.isAuthenticated() .pipe( tap(authenticated => { if (!authenticated) { this.router.navigate(['auth/login']); } }), ); } }`
global-error-handler.ts ` import { ErrorHandler, Injectable, Injector } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { ErrorService } from './@core/services/error.service'; import { LoggingService } from './@core/services/logging.service'; import { NotificationService } from './@core/services/notification.service';
@Injectable() export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error: Error | HttpErrorResponse) { const errorService = this.injector.get(ErrorService); const logger = this.injector.get(LoggingService); const notifier = this.injector.get(NotificationService); let message; let stackTrace; if (error instanceof HttpErrorResponse) { // Server error message = errorService.getServerErrorMessage(error); stackTrace = errorService.getServerErrorStackTrace(error); notifier.showError(message); } else { // Client Error message = errorService.getClientErrorMessage(error); notifier.showError(message); } // Always log errors logger.logError(message, stackTrace); console.error(error); } }`
server-error.interceptor.ts `import { Injectable, Injector } from '@angular/core'; import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse, } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { retry, catchError } from 'rxjs/operators'; import { Router } from '@angular/router'; import { NotificationService } from './@core/services/notification.service';
@Injectable() export class ServerErrorInterceptor implements HttpInterceptor { constructor(private injector: Injector) {}
intercept(request: HttpRequest, next: HttpHandler): Observable
{ return next.handle(request).pipe( retry(1), catchError((error: HttpErrorResponse) => { let errorMsg; if (error.error instanceof Error) { // A client-side or network error occurred. Handle it accordingly. errorMsg = 'An error occurred: ${error.error.message}'; } else { // The backend returned an unsuccessful response code. // The response body may contain clues as to what went wrong, errorMsg = 'Backend returned code ${error.status}, body was: ${error.error}'; } if (error.status === 401 || error.status === 403) { this.injector.get(NotificationService).showUnathorized(errorMsg); this.injector.get(Router).navigateByUrl('/auth/login'); } else { return throwError(error); } }) );
} }`
insert short code snippets here
Other information:
npm, node, OS, Browser
<!-- Node, npm: `node --version` and `npm --version` OS: Windows (7/8/10). Linux (incl. distribution). macOS (El Capitan? Sierra?) Browser: Chrome/Safari/Firefox/etc? -->
Angular, Nebular
<!-- Check your `package-lock.json` or locate a `package.json` in the `node_modules` folder. --> `{ "name": "crm-app", "version": "4.0.1", "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/akveo/ngx-admin.git" }, "bugs": { "url": "https://github.com/akveo/ngx-admin/issues" }, "scripts": { "ng": "ng", "conventional-changelog": "conventional-changelog", "start": "ng serve --host 0.0.0.0", "build": "ng build", "build:prod": "npm run build -- --prod --aot", "test": "ng test", "test:coverage": "rimraf coverage && npm run test -- --code-coverage", "lint": "ng lint", "lint:fix": "ng lint crm-app --fix", "lint:styles": "stylelint ./src/**/*.scss", "lint:ci": "npm run lint && npm run lint:styles", "pree2e": "webdriver-manager update --standalone false --gecko false", "e2e": "ng e2e", "docs": "compodoc -p src/tsconfig.app.json -d docs", "docs:serve": "compodoc -p src/tsconfig.app.json -d docs -s", "prepush": "npm run lint:ci", "release:changelog": "npm run conventional-changelog -- -p angular -i CHANGELOG.md -s", "postinstall": "ngcc --properties es2015 es5 browser module main --first-only --create-ivy-entry-points" }, "dependencies": { "@akveo/ng2-completer": "^9.0.1", "@angular/animations": "^9.0.4", "@angular/cdk": "^9.1.2", "@angular/common": "^9.0.4", "@angular/compiler": "^9.0.4", "@angular/core": "^9.0.4", "@angular/forms": "^9.0.4", "@angular/material": "^9.1.0", "@angular/platform-browser": "^9.0.4", "@angular/platform-browser-dynamic": "^9.0.4", "@angular/router": "^9.0.4", "@asymmetrik/ngx-leaflet": "3.0.1", "@nebular/auth": "5.0.0", "@nebular/eva-icons": "5.0.0", "@nebular/security": "^5.0.0", "@nebular/theme": "5.0.0", "@swimlane/ngx-charts": "^13.0.2", "angular2-chartjs": "0.4.1", "bootstrap": "4.3.1", "chart.js": "^2.7.1", "ckeditor": "4.7.3", "classlist.js": "1.1.20150312", "core-js": "2.5.1", "echarts": "^4.0.2", "eva-icons": "^1.1.3", "intl": "1.2.5", "ionicons": "2.0.1", "leaflet": "1.2.0", "nebular-icons": "1.1.0", "ng2-charts": "^2.3.2", "ng2-ckeditor": "^1.2.2", "ng2-smart-table": "^1.6.0", "ngx-echarts": "^4.2.2", "node-sass": "^4.12.0", "normalize.css": "6.0.0", "pace-js": "1.0.2", "roboto-fontface": "0.8.0", "rxjs": "6.5.4", "rxjs-compat": "6.3.0", "socicon": "3.0.5", "style-loader": "^1.1.3", "tinymce": "4.5.7", "tslib": "^1.10.0", "typeface-exo": "0.0.22", "web-animations-js": "^2.3.2", "zone.js": "~0.10.2" }, "devDependencies": { "@angular-devkit/build-angular": "~0.900.4", "@angular/cli": "^9.0.4", "@angular/compiler-cli": "^9.0.4", "@angular/language-service": "9.0.4", "@compodoc/compodoc": "1.0.1", "@fortawesome/fontawesome-free": "^5.2.0", "@types/d3-color": "1.0.5", "@types/jasmine": "2.5.54", "@types/jasminewd2": "2.0.3", "@types/leaflet": "1.2.3", "@types/node": "^12.11.1", "codelyzer": "^5.1.2", "conventional-changelog-cli": "1.3.4", "jasmine-core": "2.6.4", "jasmine-spec-reporter": "4.1.1", "karma": "1.7.1", "karma-chrome-launcher": "2.1.1", "karma-cli": "1.0.1", "karma-coverage-istanbul-reporter": "1.3.0", "karma-jasmine": "1.1.0", "karma-jasmine-html-reporter": "0.2.2", "npm-run-all": "4.0.2", "protractor": "5.1.2", "rimraf": "2.6.1", "stylelint": "7.13.0", "ts-node": "3.2.2", "tslint": "^5.7.0", "tslint-language-service": "^0.9.9", "typescript": "3.7.5", "yargs": "^15.4.0" } } `
same here on FireFox
I have the same issue!
I have same issue, the strange thing is it work fine in incognito mode but in normal browser mode it gives me same error.
core.module.ts
strategies: [ NbOAuth2AuthStrategy.setup({ name: 'oauth', clientId: environment.client_id, clientSecret: environment.client_secret, baseEndpoint: environment.api_url, redirect: { success: '/', failure: null, }, token: { grantType: NbOAuth2GrantType.PASSWORD, class: NbAuthOAuth2Token, endpoint: '/oauth/token', }, clientAuthMethod: NbOAuth2ClientAuthMethod.REQUEST_BODY, refresh: { grantType: NbOAuth2GrantType.REFRESH_TOKEN, endpoint: '/oauth/token', }, }), ], }).providers,
auth.component.ts -> custom component
export class AuthComponent extends NbLoginComponent { alive: boolean; user: User = new UserModel();
login(): void { this.errors = this.messages = []; this.submitted = true; debugger; this.service.authenticate("oauth", { email: this.user.email, password: this.user.password }).subscribe((result: NbAuthResult) => { debugger; this.submitted = false;
if (result.isSuccess()) {
this.messages = result.getMessages();
} else {
this.errors = result.getErrors();
}
const redirect = result.getRedirect();
if (redirect) {
setTimeout(() => {
return this.router.navigateByUrl(redirect);
}, this.redirectDelay);
}
this.cd.detectChanges();
});
}
}
@aalisher5 I have the same here. Have noticed across different browsers too.
Hello there, any updates on this issue? facing the same problem
A temporary solution is to clear the site data in Developer Tools > Application
I read somewhere about disabling autocomplete on the input as a workaround also. Hasn't happened for me since
@pkenny1 , @Ashwin-op Thank you very much for taking the time to answer, I will check those options
In my case it was the token interceptor not getting filtered out during refresh-token action. Modify your provider as following:
{ provide: NB_AUTH_TOKEN_INTERCEPTOR_FILTER, useValue: function (req: HttpRequest<any>) {
return req.url.endsWith('refresh-token');
} },
I found a possible solution, apparently if you takes too long the log again on the login strategy that you are using, and tries to login without already been properly logged out the nebular will somehow get in a infinite loop, for exemple, when the token still present in the browser local storage if you are using the localStorageStrategy for token storage and tries to login.
that's explain why the login works in other browsers (that you haven't logged before) and also works in incognito mode.
The solution cab be achieved by extending the NbLoginComponent, then getting access to the NbTokenStorage by dependency injection and clearing the token before the login happens
e.g:
@Component({
selector: 'ngx-login',
templateUrl: './login.component.html',
})
export class NgxLoginComponent extends NbLoginComponent {
constructor(
service: NbAuthService,
@Inject(NB_AUTH_OPTIONS) protected options: {},
cd: ChangeDetectorRef, router: Router,
private tokenStorageService: NbTokenStorage
) {
super(service, options, cd, router);
}
login(): void {
this.tokenStorageService.clear(); // this clear the token from the storage accordingly with the storage strategy
super.login();
}
}
@luuisAfonso Can confirm that we need to clear the token in the storage.
I think the simplest solution I have found to work is adding the this.tokenStorageService.clear() line to the auth-guard service as shown below.
canActivate() {
return this.authService.isAuthenticated()
.pipe(
tap(authenticated => {
// console.log(this.authService.isAuthenticated());
if (!authenticated) {
this.tokenStorageService.clear();
this.router.navigate(['auth/login']);
}
}),
);
}
}
Issue type
I'm submitting an issue when I made login (check one with "x")
Issue description
when I try to make login using the authentication component. The screen is keeping freezer and no any request is made to the backend. when I try many times in the console appear the following error Maximum call stack size exceeded"
Current behavior:
i have the configuration of authentication using jwt token
Expected behavior:
the ngx-admin must be working using jwt token sending the authorization bearer header to the backend and even update the token when it is revoked.
Steps to reproduce:
Related code:
app.module.ts
`/**
Licensed under the MIT License. See License.txt in the project root for license information. */ import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule, ErrorHandler } from '@angular/core'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { CoreModule } from './@core/core.module'; import { ThemeModule } from './@theme/theme.module'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { NbPasswordAuthStrategy, NbAuthModule, NbAuthJWTToken, NbAuthJWTInterceptor, NbTokenStorage, NbTokenLocalStorage, NB_AUTH_TOKEN_INTERCEPTOR_FILTER } from '@nebular/auth'; import {AuthModule} from './auth/auth.module'; import { NbSecurityModule, NbRoleProvider } from '@nebular/security'; import { environment } from '../environments/environment'; import { NbChatModule, NbDatepickerModule, NbDialogModule, NbMenuModule, NbSidebarModule, NbToastrModule, NbWindowModule, } from '@nebular/theme'; import { AuthGuard } from './auth-guard.service'; import {RoleProvider} from './role.provider'; import { GlobalErrorHandler } from './global-error-handler'; import { ServerErrorInterceptor } from './server-error.interceptor'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, BrowserAnimationsModule, HttpClientModule, AppRoutingModule, ThemeModule.forRoot(), NbSidebarModule.forRoot(), NbMenuModule.forRoot(), NbDatepickerModule.forRoot(), NbDialogModule.forRoot(), NbWindowModule.forRoot(), NbToastrModule.forRoot(), NbSecurityModule.forRoot(), NbChatModule.forRoot({ messageGoogleMapKey: 'AIzaSyA_wNuCzia92MAmdLRzmqitRGvCF7wCZPY', }), CoreModule.forRoot(), AuthModule, NbAuthModule.forRoot({ strategies: [ NbPasswordAuthStrategy.setup({ name: 'email', baseEndpoint: environment.baseUrlApi, token: { class: NbAuthJWTToken, key: 'token', // this parameter tells where to look for the token }, login: { endpoint: '/users/login', method: 'post', redirect: { success: '/page/', failure: null, // stay on the same page }, }, register: { endpoint: '/users/sign-in', method: 'post', }, }), ], forms: {}, }),
], providers: [ AuthGuard, { provide: NbRoleProvider, useClass: RoleProvider }, { provide: ErrorHandler, useClass: GlobalErrorHandler }, { provide: NbTokenStorage, useClass: NbTokenLocalStorage }, { provide: HTTP_INTERCEPTORS, useClass: NbAuthJWTInterceptor, multi: true }, { provide: NB_AUTH_TOKEN_INTERCEPTOR_FILTER, useValue: (req) => { return false }}, { provide: HTTP_INTERCEPTORS, useClass: ServerErrorInterceptor, multi: true }, ], bootstrap: [AppComponent], }) export class AppModule { } `
app-routing.module.ts
`import { ExtraOptions, RouterModule, Routes } from '@angular/router'; import { NgModule } from '@angular/core'; import {LoginComponent} from './auth/components/login/login.component'; import { AuthGuard } from './auth-guard.service'; import { NbAuthComponent, NbLoginComponent, NbLogoutComponent, NbRegisterComponent, NbRequestPasswordComponent, NbResetPasswordComponent, } from '@nebular/auth';
export const routes: Routes = [ { path: 'pages', canActivate: [AuthGuard], // here we tell Angular to check the access with our AuthGuard loadChildren: () => import('./pages/pages.module') .then(m => m.PagesModule), }, { path: 'auth', component: NbAuthComponent, children: [ { path: '', component: LoginComponent, }, { path: 'login', component: LoginComponent, }, { path: 'register', component: NbRegisterComponent, }, { path: 'logout', component: NbLogoutComponent, }, { path: 'request-password', component: NbRequestPasswordComponent, }, { path: 'reset-password', component: NbResetPasswordComponent, }, ], }, { path: '', redirectTo: 'pages', pathMatch: 'full' }, { path: '**', redirectTo: 'pages' }, ];
const config: ExtraOptions = { useHash: false, };
@NgModule({ imports: [RouterModule.forRoot(routes, config)], exports: [RouterModule], }) export class AppRoutingModule { } `
auth-guard.service.ts `import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { NbAuthService } from '@nebular/auth'; import { tap } from 'rxjs/operators';
@Injectable() export class AuthGuard implements CanActivate {
constructor(private authService: NbAuthService, private router: Router) { }
canActivate() { return this.authService.isAuthenticated() .pipe( tap(authenticated => { if (!authenticated) { this.router.navigate(['auth/login']); } }), ); } }`
global-error-handler.ts `import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { NbAuthService } from '@nebular/auth'; import { tap } from 'rxjs/operators';
@Injectable() export class AuthGuard implements CanActivate {
constructor(private authService: NbAuthService, private router: Router) { }
canActivate() { return this.authService.isAuthenticated() .pipe( tap(authenticated => { if (!authenticated) { this.router.navigate(['auth/login']); } }), ); } }`
global-error-handler.ts ` import { ErrorHandler, Injectable, Injector } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { ErrorService } from './@core/services/error.service'; import { LoggingService } from './@core/services/logging.service'; import { NotificationService } from './@core/services/notification.service';
@Injectable() export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error: Error | HttpErrorResponse) { const errorService = this.injector.get(ErrorService); const logger = this.injector.get(LoggingService); const notifier = this.injector.get(NotificationService); let message; let stackTrace; if (error instanceof HttpErrorResponse) { // Server error message = errorService.getServerErrorMessage(error); stackTrace = errorService.getServerErrorStackTrace(error); notifier.showError(message); } else { // Client Error message = errorService.getClientErrorMessage(error); notifier.showError(message); } // Always log errors logger.logError(message, stackTrace); console.error(error); } }`
server-error.interceptor.ts `import { Injectable, Injector } from '@angular/core'; import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse, } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { retry, catchError } from 'rxjs/operators'; import { Router } from '@angular/router'; import { NotificationService } from './@core/services/notification.service';
@Injectable() export class ServerErrorInterceptor implements HttpInterceptor { constructor(private injector: Injector) {}
intercept(request: HttpRequest, next: HttpHandler): Observable<HttpEvent> {
} }`
Other information:
npm, node, OS, Browser
Angular, Nebular