use django authentication instead of auth tokens.

This commit is contained in:
Jonas Winkler
2020-11-09 15:28:12 +01:00
parent 7ec4e29966
commit 3b0e794b3d
24 changed files with 172 additions and 328 deletions

View File

@@ -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,

View File

@@ -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'}
];

View File

@@ -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]
})

View File

@@ -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>

View File

@@ -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()
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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."))
}
)
}
}

View File

@@ -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();
});
});

View File

@@ -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;
}
}

View File

@@ -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();
});
});

View File

@@ -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)
})
);
}
}

View File

@@ -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();
});
});

View File

@@ -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
}
}

View File

@@ -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) {