mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	use django authentication instead of auth tokens.
This commit is contained in:
		| @@ -4,7 +4,6 @@ import { AppFrameComponent } from './components/app-frame/app-frame.component'; | ||||
| import { DashboardComponent } from './components/dashboard/dashboard.component'; | ||||
| import { DocumentDetailComponent } from './components/document-detail/document-detail.component'; | ||||
| import { DocumentListComponent } from './components/document-list/document-list.component'; | ||||
| import { LoginComponent } from './components/login/login.component'; | ||||
| import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'; | ||||
| import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'; | ||||
| import { LogsComponent } from './components/manage/logs/logs.component'; | ||||
| @@ -12,25 +11,23 @@ import { SettingsComponent } from './components/manage/settings/settings.compone | ||||
| import { TagListComponent } from './components/manage/tag-list/tag-list.component'; | ||||
| import { NotFoundComponent } from './components/not-found/not-found.component'; | ||||
| import { SearchComponent } from './components/search/search.component'; | ||||
| import { AuthGuardService } from './services/auth-guard.service'; | ||||
|  | ||||
| const routes: Routes = [ | ||||
|   {path: '', redirectTo: 'dashboard', pathMatch: 'full'}, | ||||
|   {path: '', component: AppFrameComponent, children: [ | ||||
|     {path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'documents', component: DocumentListComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'view/:id', component: DocumentListComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'search', component: SearchComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'documents/:id', component: DocumentDetailComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'dashboard', component: DashboardComponent }, | ||||
|     {path: 'documents', component: DocumentListComponent }, | ||||
|     {path: 'view/:id', component: DocumentListComponent }, | ||||
|     {path: 'search', component: SearchComponent }, | ||||
|     {path: 'documents/:id', component: DocumentDetailComponent }, | ||||
|    | ||||
|     {path: 'tags', component: TagListComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'documenttypes', component: DocumentTypeListComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'correspondents', component: CorrespondentListComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'logs', component: LogsComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'settings', component: SettingsComponent, canActivate: [AuthGuardService] }, | ||||
|     {path: 'tags', component: TagListComponent }, | ||||
|     {path: 'documenttypes', component: DocumentTypeListComponent }, | ||||
|     {path: 'correspondents', component: CorrespondentListComponent }, | ||||
|     {path: 'logs', component: LogsComponent }, | ||||
|     {path: 'settings', component: SettingsComponent }, | ||||
|   ]},  | ||||
|  | ||||
|   {path: 'login', component: LoginComponent }, | ||||
|   {path: '404', component: NotFoundComponent}, | ||||
|   {path: '**', redirectTo: '/404', pathMatch: 'full'} | ||||
| ]; | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import { TagListComponent } from './components/manage/tag-list/tag-list.componen | ||||
| import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'; | ||||
| import { LogsComponent } from './components/manage/logs/logs.component'; | ||||
| import { SettingsComponent } from './components/manage/settings/settings.component'; | ||||
| import { LoginComponent } from './components/login/login.component'; | ||||
| import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||||
| import { DatePipe } from '@angular/common'; | ||||
| import { SafePipe } from './pipes/safe.pipe'; | ||||
| @@ -29,7 +28,6 @@ import { PageHeaderComponent } from './components/common/page-header/page-header | ||||
| import { AppFrameComponent } from './components/app-frame/app-frame.component'; | ||||
| import { ToastsComponent } from './components/common/toasts/toasts.component'; | ||||
| import { FilterEditorComponent } from './components/filter-editor/filter-editor.component'; | ||||
| import { AuthInterceptor } from './services/auth.interceptor'; | ||||
| import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'; | ||||
| import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'; | ||||
| import { NgxFileDropModule } from 'ngx-file-drop'; | ||||
| @@ -53,7 +51,6 @@ import { SortableDirective } from './directives/sortable.directive'; | ||||
|     DocumentTypeListComponent, | ||||
|     LogsComponent, | ||||
|     SettingsComponent, | ||||
|     LoginComponent, | ||||
|     SafePipe, | ||||
|     NotFoundComponent, | ||||
|     CorrespondentEditDialogComponent, | ||||
| @@ -88,12 +85,7 @@ import { SortableDirective } from './directives/sortable.directive'; | ||||
|     InfiniteScrollModule | ||||
|   ], | ||||
|   providers: [ | ||||
|     DatePipe, | ||||
|     { | ||||
|       provide: HTTP_INTERCEPTORS, | ||||
|       useClass: AuthInterceptor, | ||||
|       multi: true | ||||
|     } | ||||
|     DatePipe | ||||
|   ], | ||||
|   bootstrap: [AppComponent] | ||||
| }) | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|   </form> | ||||
|   <ul class="navbar-nav px-3"> | ||||
|     <li class="nav-item text-nowrap"> | ||||
|       <a class="nav-link" (click)="logout()" style="cursor: pointer;"> | ||||
|       <a class="nav-link" href="accounts/logout/"> | ||||
|         <svg class="buttonicon" fill="currentColor"> | ||||
|           <use xlink:href="assets/bootstrap-icons.svg#door-closed"/> | ||||
|         </svg> | ||||
|   | ||||
| @@ -1,10 +1,9 @@ | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { FormControl } from '@angular/forms'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { from, Observable, of, scheduled, Subscription } from 'rxjs'; | ||||
| import { from, Observable, Subscription } from 'rxjs'; | ||||
| import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { AuthService } from 'src/app/services/auth.service'; | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service'; | ||||
| import { SearchService } from 'src/app/services/rest/search.service'; | ||||
| import { SavedViewConfigService } from 'src/app/services/saved-view-config.service'; | ||||
| @@ -19,7 +18,6 @@ export class AppFrameComponent implements OnInit, OnDestroy { | ||||
|   constructor ( | ||||
|     public router: Router, | ||||
|     private openDocumentsService: OpenDocumentsService, | ||||
|     private authService: AuthService, | ||||
|     private searchService: SearchService, | ||||
|     public viewConfigService: SavedViewConfigService | ||||
|     ) { | ||||
| @@ -64,10 +62,6 @@ export class AppFrameComponent implements OnInit, OnDestroy { | ||||
|     this.router.navigate(['search'], {queryParams: {query: this.searchField.value}}) | ||||
|   } | ||||
|  | ||||
|   logout() { | ||||
|     this.authService.logout() | ||||
|   } | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.openDocuments = this.openDocumentsService.getOpenDocuments() | ||||
|   } | ||||
|   | ||||
| @@ -1,44 +0,0 @@ | ||||
| .form-signin-container { | ||||
|   top: 0; | ||||
|   bottom: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   position: fixed; | ||||
|   background-color: #f5f5f5; | ||||
| } | ||||
|  | ||||
| .form-signin { | ||||
|   max-width: 330px; | ||||
|   height: auto; | ||||
|   position: fixed; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   top: 0; | ||||
|   bottom: 0; | ||||
|  | ||||
|   margin: auto; | ||||
|   text-align: center; | ||||
| } | ||||
| .form-signin .checkbox { | ||||
|   font-weight: 400; | ||||
| } | ||||
| .form-signin .form-control { | ||||
|   position: relative; | ||||
|   box-sizing: border-box; | ||||
|   height: auto; | ||||
|   padding: 10px; | ||||
|   font-size: 16px; | ||||
| } | ||||
| .form-signin .form-control:focus { | ||||
|   z-index: 2; | ||||
| } | ||||
| .form-signin input[type="text"] { | ||||
|   margin-bottom: -1px; | ||||
|   border-bottom-right-radius: 0; | ||||
|   border-bottom-left-radius: 0; | ||||
| } | ||||
| .form-signin input[type="password"] { | ||||
|   margin-bottom: 10px; | ||||
|   border-top-left-radius: 0; | ||||
|   border-top-right-radius: 0; | ||||
| } | ||||
| @@ -1,17 +0,0 @@ | ||||
| <div class="form-signin-container"> | ||||
|   <form class="form-signin mt-5" [formGroup]="loginForm" (ngSubmit)="loginClicked()"> | ||||
|     <img class="mb-4" src="assets/logo.svg" alt="" width="100%"> | ||||
|     <h1 class="h3 mb-3 font-weight-normal">Login</h1> | ||||
|     <label for="inputUsername" class="sr-only">Username</label> | ||||
|     <input type="text" id="inputUsername" class="form-control" placeholder="Username" required autofocus formControlName="username"> | ||||
|     <label for="inputPassword" class="sr-only">Password</label> | ||||
|     <input type="password" id="inputPassword" class="form-control" placeholder="Password" required formControlName="password"> | ||||
|     <div class="checkbox mb-3"> | ||||
|       <label> | ||||
|         <input type="checkbox" value="remember-me" formControlName="rememberMe"> Remember me | ||||
|       </label> | ||||
|     </div> | ||||
|     <button class="btn btn-lg btn-primary btn-block mb-4" type="submit">Login</button> | ||||
|     <p><a href="/admin/">Go to admin interface</a></p> | ||||
|   </form> | ||||
| </div> | ||||
| @@ -1,25 +0,0 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { LoginComponent } from './login.component'; | ||||
|  | ||||
| describe('LoginComponent', () => { | ||||
|   let component: LoginComponent; | ||||
|   let fixture: ComponentFixture<LoginComponent>; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ LoginComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(LoginComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @@ -1,34 +0,0 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { AuthService } from 'src/app/services/auth.service'; | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-login', | ||||
|   templateUrl: './login.component.html', | ||||
|   styleUrls: ['./login.component.css'] | ||||
| }) | ||||
| export class LoginComponent implements OnInit { | ||||
|  | ||||
|   constructor(private auth: AuthService, private router: Router, private toastService: ToastService) { } | ||||
|  | ||||
|   loginForm = new FormGroup({ | ||||
|     username: new FormControl(''), | ||||
|     password: new FormControl(''), | ||||
|     rememberMe: new FormControl(false) | ||||
|   }) | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|  | ||||
|   loginClicked() { | ||||
|     this.auth.login(this.loginForm.value.username, this.loginForm.value.password, this.loginForm.value.rememberMe).subscribe(result => { | ||||
|       this.router.navigate(['']) | ||||
|     }, (error) => { | ||||
|       this.toastService.showToast(Toast.makeError("Unable to log in with provided credentials.")) | ||||
|     } | ||||
|     ) | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| import { TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { AuthGuardService } from './auth-guard.service'; | ||||
|  | ||||
| describe('AuthGuardService', () => { | ||||
|   let service: AuthGuardService; | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({}); | ||||
|     service = TestBed.inject(AuthGuardService); | ||||
|   }); | ||||
|  | ||||
|   it('should be created', () => { | ||||
|     expect(service).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @@ -1,20 +0,0 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { AuthService } from './auth.service'; | ||||
|  | ||||
| @Injectable({ | ||||
|   providedIn: 'root' | ||||
| }) | ||||
| export class AuthGuardService { | ||||
|  | ||||
|   constructor(public auth: AuthService, public router: Router) { } | ||||
|  | ||||
|   canActivate(): boolean { | ||||
|     if (!this.auth.isAuthenticated()) { | ||||
|       this.router.navigate(['login']); | ||||
|       return false; | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| import { TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { AuthInterceptor } from './auth.interceptor'; | ||||
|  | ||||
| describe('AuthInterceptor', () => { | ||||
|   beforeEach(() => TestBed.configureTestingModule({ | ||||
|     providers: [ | ||||
|       AuthInterceptor | ||||
|       ] | ||||
|   })); | ||||
|  | ||||
|   it('should be created', () => { | ||||
|     const interceptor: AuthInterceptor = TestBed.inject(AuthInterceptor); | ||||
|     expect(interceptor).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @@ -1,37 +0,0 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { | ||||
|   HttpRequest, | ||||
|   HttpHandler, | ||||
|   HttpEvent, | ||||
|   HttpInterceptor, | ||||
|   HttpErrorResponse | ||||
| } from '@angular/common/http'; | ||||
| import { Observable, throwError } from 'rxjs'; | ||||
| import { AuthService } from './auth.service'; | ||||
| import { catchError } from 'rxjs/operators'; | ||||
| import { Toast, ToastService } from './toast.service'; | ||||
|  | ||||
| @Injectable() | ||||
| export class AuthInterceptor implements HttpInterceptor { | ||||
|  | ||||
|   constructor(private authService: AuthService, private toastService: ToastService) {} | ||||
|  | ||||
|   intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { | ||||
|     if (this.authService.isAuthenticated()) { | ||||
|       request = request.clone({ | ||||
|         setHeaders: { | ||||
|           Authorization: 'Token ' + this.authService.getToken() | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|     return next.handle(request).pipe( | ||||
|       catchError((error: HttpErrorResponse) => { | ||||
|         if (error.status == 401 && this.authService.isAuthenticated()) { | ||||
|           this.authService.logout() | ||||
|           this.toastService.showToast(Toast.makeError("Your session has expired. Please log in again.")) | ||||
|         } | ||||
|         return throwError(error) | ||||
|       }) | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| import { TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { AuthService } from './auth.service'; | ||||
|  | ||||
| describe('AuthService', () => { | ||||
|   let service: AuthService; | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({}); | ||||
|     service = TestBed.inject(AuthService); | ||||
|   }); | ||||
|  | ||||
|   it('should be created', () => { | ||||
|     expect(service).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @@ -1,72 +0,0 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Observable } from 'rxjs'; | ||||
|  | ||||
| import { map } from 'rxjs/operators'; | ||||
| import { HttpClient } from '@angular/common/http'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { environment } from 'src/environments/environment'; | ||||
|  | ||||
| interface TokenResponse { | ||||
|   token: string | ||||
| } | ||||
|  | ||||
| @Injectable({ | ||||
|   providedIn: 'root' | ||||
| }) | ||||
| export class AuthService { | ||||
|  | ||||
|   private currentUsername: string | ||||
|  | ||||
|   private token: string | ||||
|  | ||||
|   constructor(private http: HttpClient, private router: Router) {  | ||||
|     this.token = localStorage.getItem('auth-service:token') | ||||
|     if (this.token == null) { | ||||
|       this.token = sessionStorage.getItem('auth-service:token') | ||||
|     } | ||||
|     this.currentUsername = localStorage.getItem('auth-service:currentUsername') | ||||
|     if (this.currentUsername == null) { | ||||
|       this.currentUsername = sessionStorage.getItem('auth-service:currentUsername') | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|   private requestToken(username: string, password: string): Observable<TokenResponse> { | ||||
|     return this.http.post<TokenResponse>(`${environment.apiBaseUrl}token/`, {"username": username, "password": password}) | ||||
|   } | ||||
|  | ||||
|   isAuthenticated(): boolean { | ||||
|     return this.currentUsername != null | ||||
|   } | ||||
|  | ||||
|   logout() { | ||||
|     this.currentUsername = null | ||||
|     this.token = null | ||||
|     localStorage.removeItem('auth-service:token') | ||||
|     localStorage.removeItem('auth-service:currentUsername') | ||||
|     sessionStorage.removeItem('auth-service:token') | ||||
|     sessionStorage.removeItem('auth-service:currentUsername') | ||||
|     this.router.navigate(['login']) | ||||
|   } | ||||
|  | ||||
|   login(username: string, password: string, rememberMe: boolean): Observable<boolean> { | ||||
|     return this.requestToken(username,password).pipe( | ||||
|       map(tokenResponse => { | ||||
|         this.currentUsername = username | ||||
|         this.token = tokenResponse.token | ||||
|         let storage = rememberMe ? localStorage : sessionStorage | ||||
|         storage.setItem('auth-service:token', this.token) | ||||
|         storage.setItem('auth-service:currentUsername', this.currentUsername) | ||||
|         return true | ||||
|       }) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   getToken(): string { | ||||
|     return this.token | ||||
|   } | ||||
|  | ||||
|   getCurrentUsername(): string { | ||||
|     return this.currentUsername | ||||
|   } | ||||
| } | ||||
| @@ -2,7 +2,6 @@ import { Injectable } from '@angular/core'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { AbstractPaperlessService } from './abstract-paperless-service'; | ||||
| import { HttpClient } from '@angular/common/http'; | ||||
| import { AuthService } from '../auth.service'; | ||||
| import { Observable } from 'rxjs'; | ||||
| import { Results } from 'src/app/data/results'; | ||||
| import { FilterRule } from 'src/app/data/filter-rule'; | ||||
| @@ -27,7 +26,7 @@ export const SORT_DIRECTION_DESCENDING = "des" | ||||
| }) | ||||
| export class DocumentService extends AbstractPaperlessService<PaperlessDocument> { | ||||
|  | ||||
|   constructor(http: HttpClient, private auth: AuthService) { | ||||
|   constructor(http: HttpClient) { | ||||
|     super(http, 'documents') | ||||
|   } | ||||
|  | ||||
| @@ -52,15 +51,15 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument> | ||||
|   } | ||||
|  | ||||
|   getPreviewUrl(id: number): string { | ||||
|     return this.getResourceUrl(id, 'preview') + `?auth_token=${this.auth.getToken()}` | ||||
|     return this.getResourceUrl(id, 'preview') | ||||
|   } | ||||
|  | ||||
|   getThumbUrl(id: number): string { | ||||
|     return this.getResourceUrl(id, 'thumb') + `?auth_token=${this.auth.getToken()}` | ||||
|     return this.getResourceUrl(id, 'thumb') | ||||
|   } | ||||
|  | ||||
|   getDownloadUrl(id: number): string { | ||||
|     return this.getResourceUrl(id, 'download') + `?auth_token=${this.auth.getToken()}` | ||||
|     return this.getResourceUrl(id, 'download') | ||||
|   } | ||||
|  | ||||
|   uploadDocument(formData) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonas Winkler
					Jonas Winkler