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:
		| @@ -65,7 +65,7 @@ COPY scripts/docker-entrypoint.sh /sbin/docker-entrypoint.sh | ||||
|  | ||||
| # copy app | ||||
| COPY src/ ./src/ | ||||
| COPY --from=frontend /usr/src/paperless/src-ui/dist/paperless-ui/ ./src/documents/static/ | ||||
| COPY --from=frontend /usr/src/paperless/src-ui/dist/paperless-ui/ ./src/documents/static/frontend/ | ||||
|  | ||||
| # add users, setup scripts | ||||
| RUN addgroup --gid 1000 paperless \ | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
|           "builder": "@angular-devkit/build-angular:browser", | ||||
|           "options": { | ||||
|             "outputPath": "dist/paperless-ui", | ||||
|             "outputHashing": "none", | ||||
|             "index": "src/index.html", | ||||
|             "main": "src/main.ts", | ||||
|             "polyfills": "src/polyfills.ts", | ||||
| @@ -38,7 +39,7 @@ | ||||
|                 } | ||||
|               ], | ||||
|               "optimization": true, | ||||
|               "outputHashing": "all", | ||||
|               "outputHashing": "none", | ||||
|               "sourceMap": false, | ||||
|               "extractCss": true, | ||||
|               "namedChunks": false, | ||||
|   | ||||
| @@ -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,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) { | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/documents/static/bootstrap.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/documents/static/bootstrap.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1,23 +1,23 @@ | ||||
| .form-signin-container { | ||||
|   top: 0; | ||||
|   bottom: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   position: fixed; | ||||
| html, | ||||
| body { | ||||
|   height: 100%; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|   display: -ms-flexbox; | ||||
|   display: flex; | ||||
|   -ms-flex-align: center; | ||||
|   align-items: center; | ||||
|   padding-top: 40px; | ||||
|   padding-bottom: 40px; | ||||
|   background-color: #f5f5f5; | ||||
| } | ||||
| 
 | ||||
| .form-signin { | ||||
|   width: 100%; | ||||
|   max-width: 330px; | ||||
|   height: auto; | ||||
|   position: fixed; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   top: 0; | ||||
|   bottom: 0; | ||||
| 
 | ||||
|   padding: 15px; | ||||
|   margin: auto; | ||||
|   text-align: center; | ||||
| } | ||||
| .form-signin .checkbox { | ||||
|   font-weight: 400; | ||||
| @@ -9,11 +9,11 @@ | ||||
|   <base href="/"> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|   <link rel="icon" type="image/x-icon" href="favicon.ico"> | ||||
| <link rel="stylesheet" href="{% static 'styles.css' %}"></head> | ||||
| <link rel="stylesheet" href="{% static 'frontend/styles.css' %}"></head> | ||||
| <body> | ||||
|   <app-root>Loading...</app-root> | ||||
| 	<script src="{% static 'runtime.js' %}" defer></script> | ||||
| 	<script src="{% static 'polyfills.js' %}" defer></script> | ||||
| 	<script src="{% static 'main.js' %}" defer></script> | ||||
| 	<script src="{% static 'frontend/runtime.js' %}" defer></script> | ||||
| 	<script src="{% static 'frontend/polyfills.js' %}" defer></script> | ||||
| 	<script src="{% static 'frontend/main.js' %}" defer></script> | ||||
| </body> | ||||
| </html> | ||||
|   | ||||
							
								
								
									
										44
									
								
								src/documents/templates/registration/logged_out.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/documents/templates/registration/logged_out.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| <!doctype html> | ||||
|  | ||||
| {% load static %} | ||||
|  | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||||
|     <meta name="description" content=""> | ||||
|     <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> | ||||
|     <meta name="generator" content="Jekyll v4.1.1"> | ||||
|     <title>Paperless Sign In</title> | ||||
|  | ||||
|     <!-- Bootstrap core CSS --> | ||||
| 		<link href="{% static 'bootstrap.min.css' %}" rel="stylesheet"> | ||||
|  | ||||
|     <style> | ||||
|       .bd-placeholder-img { | ||||
|         font-size: 1.125rem; | ||||
|         text-anchor: middle; | ||||
|         -webkit-user-select: none; | ||||
|         -moz-user-select: none; | ||||
|         -ms-user-select: none; | ||||
|         user-select: none; | ||||
|       } | ||||
|  | ||||
|       @media (min-width: 768px) { | ||||
|         .bd-placeholder-img-lg { | ||||
|           font-size: 3.5rem; | ||||
|         } | ||||
|       } | ||||
|     </style> | ||||
|     <!-- Custom styles for this template --> | ||||
|     <link href="{% static 'signin.css' %}" rel="stylesheet"> | ||||
|   </head> | ||||
|  | ||||
|   <body class="text-center"> | ||||
|     <div class="form-signin"> | ||||
| 			<img class="mb-4" src="{% static 'frontend/assets/logo.svg' %}" alt="" width="300"> | ||||
| 			<p>You have been successfully logged out. Bye!</p> | ||||
| 			<a href="/">Sign in again</a> | ||||
| 		</div> | ||||
| 	</body> | ||||
| </html> | ||||
							
								
								
									
										54
									
								
								src/documents/templates/registration/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/documents/templates/registration/login.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| <!doctype html> | ||||
|  | ||||
| {% load static %} | ||||
|  | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||||
|     <meta name="description" content=""> | ||||
|     <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> | ||||
|     <meta name="generator" content="Jekyll v4.1.1"> | ||||
|     <title>Paperless Sign In</title> | ||||
|  | ||||
|     <!-- Bootstrap core CSS --> | ||||
| 		<link href="{% static 'bootstrap.min.css' %}" rel="stylesheet"> | ||||
|  | ||||
|     <style> | ||||
|       .bd-placeholder-img { | ||||
|         font-size: 1.125rem; | ||||
|         text-anchor: middle; | ||||
|         -webkit-user-select: none; | ||||
|         -moz-user-select: none; | ||||
|         -ms-user-select: none; | ||||
|         user-select: none; | ||||
|       } | ||||
|  | ||||
|       @media (min-width: 768px) { | ||||
|         .bd-placeholder-img-lg { | ||||
|           font-size: 3.5rem; | ||||
|         } | ||||
|       } | ||||
|     </style> | ||||
|     <!-- Custom styles for this template --> | ||||
|     <link href="{% static 'signin.css' %}" rel="stylesheet"> | ||||
|   </head> | ||||
|  | ||||
|   <body class="text-center"> | ||||
|     <form class="form-signin" method="post"> | ||||
| 			{% csrf_token %} | ||||
| 			<img class="mb-4" src="{% static 'frontend/assets/logo.svg' %}" alt="" width="300"> | ||||
| 			<p>Please sign in.</p> | ||||
| 			{% if form.errors %} | ||||
| 				<div class="alert alert-danger" role="alert"> | ||||
| 					Your username and password didn't match. Please try again. | ||||
| 				</div> | ||||
| 			{% endif %} | ||||
| 			<label for="inputUsername" class="sr-only">Username</label> | ||||
| 			<input type="text" name="username" id="inputUsername" class="form-control" placeholder="Username" required autofocus> | ||||
| 			<label for="inputPassword" class="sr-only">Password</label> | ||||
| 			<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required> | ||||
| 			<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> | ||||
| 		</form> | ||||
| 	</body> | ||||
| </html> | ||||
| @@ -1,11 +1,17 @@ | ||||
| from rest_framework.authentication import TokenAuthentication | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.models import User | ||||
| from rest_framework import authentication | ||||
|  | ||||
|  | ||||
| class AngularApiAuthenticationOverride(authentication.BaseAuthentication): | ||||
|     """ This class is here to provide authentication to the angular dev server | ||||
|         during development. This is disabled in production. | ||||
|     """ | ||||
|  | ||||
| # This authentication method is required to serve documents and thumbnails for the front end. | ||||
| # https://stackoverflow.com/questions/29433416/token-in-query-string-with-django-rest-frameworks-tokenauthentication | ||||
| class QueryTokenAuthentication(TokenAuthentication): | ||||
|     def authenticate(self, request): | ||||
|         # Check if 'token_auth' is in the request query params. | ||||
|         if 'auth_token' in request.query_params and 'HTTP_AUTHORIZATION' not in request.META: | ||||
|             return self.authenticate_credentials(request.query_params.get('auth_token')) | ||||
|         if settings.DEBUG and 'Origin' in request.headers and request.headers['Origin'] == 'http://localhost:4200': | ||||
|             user = User.objects.filter(is_staff=True).first() | ||||
|             print("Auto-Login with user {}".format(user)) | ||||
|             return (user, None) | ||||
|         else: | ||||
|             return None | ||||
|   | ||||
| @@ -21,6 +21,9 @@ def __get_boolean(key, default="NO"): | ||||
|     """ | ||||
|     return bool(os.getenv(key, default).lower() in ("yes", "y", "1", "t", "true")) | ||||
|  | ||||
| # NEVER RUN WITH DEBUG IN PRODUCTION. | ||||
| DEBUG = __get_boolean("PAPERLESS_DEBUG", "NO") | ||||
|  | ||||
| ############################################################################### | ||||
| # Directories                                                                 # | ||||
| ############################################################################### | ||||
| @@ -66,7 +69,6 @@ INSTALLED_APPS = [ | ||||
|     "django.contrib.admin", | ||||
|  | ||||
|     "rest_framework", | ||||
|     "rest_framework.authtoken", | ||||
|     "django_filters", | ||||
|  | ||||
| ] | ||||
| @@ -74,11 +76,15 @@ INSTALLED_APPS = [ | ||||
| REST_FRAMEWORK = { | ||||
|     'DEFAULT_AUTHENTICATION_CLASSES': [ | ||||
|         'rest_framework.authentication.BasicAuthentication', | ||||
|         'rest_framework.authentication.TokenAuthentication', | ||||
|         'paperless.auth.QueryTokenAuthentication' | ||||
|         'rest_framework.authentication.SessionAuthentication' | ||||
|     ] | ||||
| } | ||||
|  | ||||
| if DEBUG: | ||||
|     REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append( | ||||
|         'paperless.auth.AngularApiAuthenticationOverride' | ||||
|     ) | ||||
|  | ||||
| MIDDLEWARE = [ | ||||
|     'django.middleware.security.SecurityMiddleware', | ||||
|     'whitenoise.middleware.WhiteNoiseMiddleware', | ||||
| @@ -93,8 +99,6 @@ MIDDLEWARE = [ | ||||
|  | ||||
| ROOT_URLCONF = 'paperless.urls' | ||||
|  | ||||
| LOGIN_URL = "admin:login" | ||||
|  | ||||
| FORCE_SCRIPT_NAME = os.getenv("PAPERLESS_FORCE_SCRIPT_NAME") | ||||
|  | ||||
| WSGI_APPLICATION = 'paperless.wsgi.application' | ||||
| @@ -122,9 +126,6 @@ TEMPLATES = [ | ||||
| # Security                                                                    # | ||||
| ############################################################################### | ||||
|  | ||||
| # NEVER RUN WITH DEBUG IN PRODUCTION. | ||||
| DEBUG = __get_boolean("PAPERLESS_DEBUG", "NO") | ||||
|  | ||||
| if DEBUG: | ||||
|     X_FRAME_OPTIONS = '' | ||||
|     # this should really be 'allow-from uri' but its not supported in any mayor | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| from django.conf.urls import include, url | ||||
| from django.contrib import admin | ||||
| from django.contrib.auth.decorators import login_required | ||||
| from django.urls import path | ||||
| from django.views.decorators.csrf import csrf_exempt | ||||
| from django.views.generic import RedirectView | ||||
| from rest_framework.authtoken import views | ||||
| from rest_framework.routers import DefaultRouter | ||||
|  | ||||
| from paperless.views import FaviconView | ||||
| @@ -34,7 +34,7 @@ urlpatterns = [ | ||||
|     url(r"^api/search/autocomplete/", SearchAutoCompleteView.as_view(), name="autocomplete"), | ||||
|     url(r"^api/search/", SearchView.as_view(), name="search"), | ||||
|     url(r"^api/statistics/", StatisticsView.as_view(), name="statistics"), | ||||
|     url(r"^api/token/", views.obtain_auth_token), url(r"^api/", include((api_router.urls, 'drf'), namespace="drf")), | ||||
|     url(r"^api/", include((api_router.urls, 'drf'), namespace="drf")), | ||||
|  | ||||
|     # Favicon | ||||
|     url(r"^favicon.ico$", FaviconView.as_view(), name="favicon"), | ||||
| @@ -58,10 +58,12 @@ urlpatterns = [ | ||||
|     url(r"^push$", csrf_exempt(RedirectView.as_view(url='/api/documents/post_document/'))), | ||||
|  | ||||
|     # Frontend assets TODO: this is pretty bad. | ||||
|     path('assets/<path:path>', RedirectView.as_view(url='/static/assets/%(path)s')), | ||||
|     path('assets/<path:path>', RedirectView.as_view(url='/static/frontend/assets/%(path)s')), | ||||
|  | ||||
|     path('accounts/', include('django.contrib.auth.urls')), | ||||
|  | ||||
|     # Root of the Frontent | ||||
|     url(r".*", IndexView.as_view()), | ||||
|     url(r".*", login_required(IndexView.as_view())), | ||||
|  | ||||
| ] | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonas Winkler
					Jonas Winkler