mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-14 00:26:21 +00:00
Feature: two-factor authentication (#8012)
This commit is contained in:
@@ -439,4 +439,25 @@ describe('PermissionsService', () => {
|
||||
|
||||
expect(permissionsService.isAdmin()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('correctly checks superuser status', () => {
|
||||
permissionsService.initialize([], {
|
||||
username: 'testuser',
|
||||
last_name: 'User',
|
||||
first_name: 'Test',
|
||||
id: 1,
|
||||
is_superuser: true,
|
||||
})
|
||||
|
||||
expect(permissionsService.isSuperUser()).toBeTruthy()
|
||||
|
||||
permissionsService.initialize([], {
|
||||
username: 'testuser',
|
||||
last_name: 'User',
|
||||
first_name: 'Test',
|
||||
id: 1,
|
||||
})
|
||||
|
||||
expect(permissionsService.isSuperUser()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
@@ -56,6 +56,10 @@ export class PermissionsService {
|
||||
return this.currentUser?.is_staff
|
||||
}
|
||||
|
||||
public isSuperUser(): boolean {
|
||||
return this.currentUser?.is_superuser
|
||||
}
|
||||
|
||||
public currentUserOwnsObject(object: ObjectWithPermissions): boolean {
|
||||
return (
|
||||
!object ||
|
||||
|
@@ -72,4 +72,32 @@ describe('ProfileService', () => {
|
||||
)
|
||||
expect(req.request.method).toEqual('GET')
|
||||
})
|
||||
|
||||
it('calls get totp settings endpoint', () => {
|
||||
service.getTotpSettings().subscribe()
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}profile/totp/`
|
||||
)
|
||||
expect(req.request.method).toEqual('GET')
|
||||
})
|
||||
|
||||
it('calls activate totp endpoint', () => {
|
||||
service.activateTotp('secret', 'code').subscribe()
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}profile/totp/`
|
||||
)
|
||||
expect(req.request.method).toEqual('POST')
|
||||
expect(req.request.body).toEqual({
|
||||
secret: 'secret',
|
||||
code: 'code',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls deactivate totp endpoint', () => {
|
||||
service.deactivateTotp().subscribe()
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}profile/totp/`
|
||||
)
|
||||
expect(req.request.method).toEqual('DELETE')
|
||||
})
|
||||
})
|
||||
|
@@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import {
|
||||
TotpSettings,
|
||||
PaperlessUserProfile,
|
||||
SocialAccountProvider,
|
||||
} from '../data/user-profile'
|
||||
@@ -47,4 +48,30 @@ export class ProfileService {
|
||||
`${environment.apiBaseUrl}${this.endpoint}/social_account_providers/`
|
||||
)
|
||||
}
|
||||
|
||||
getTotpSettings(): Observable<TotpSettings> {
|
||||
return this.http.get<TotpSettings>(
|
||||
`${environment.apiBaseUrl}${this.endpoint}/totp/`
|
||||
)
|
||||
}
|
||||
|
||||
activateTotp(
|
||||
totpSecret: string,
|
||||
totpCode: string
|
||||
): Observable<{ success: boolean; recovery_codes: string[] }> {
|
||||
return this.http.post<{ success: boolean; recovery_codes: string[] }>(
|
||||
`${environment.apiBaseUrl}${this.endpoint}/totp/`,
|
||||
{
|
||||
secret: totpSecret,
|
||||
code: totpCode,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
deactivateTotp(): Observable<boolean> {
|
||||
return this.http.delete<boolean>(
|
||||
`${environment.apiBaseUrl}${this.endpoint}/totp/`,
|
||||
{}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -160,6 +160,18 @@ const user = {
|
||||
commonAbstractNameFilterPaperlessServiceTests(endpoint, UserService)
|
||||
|
||||
describe('Additional service tests for UserService', () => {
|
||||
beforeEach(() => {
|
||||
// Dont need to setup again
|
||||
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
service = TestBed.inject(UserService)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
subscription?.unsubscribe()
|
||||
httpTestingController.verify()
|
||||
})
|
||||
|
||||
it('should retain permissions on update', () => {
|
||||
subscription = service.listAll().subscribe()
|
||||
let req = httpTestingController.expectOne(
|
||||
@@ -179,15 +191,11 @@ describe('Additional service tests for UserService', () => {
|
||||
)
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Dont need to setup again
|
||||
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
service = TestBed.inject(UserService)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
subscription?.unsubscribe()
|
||||
httpTestingController.verify()
|
||||
it('should deactivate totp', () => {
|
||||
subscription = service.deactivateTotp(user).subscribe()
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}${endpoint}/${user.id}/deactivate_totp/`
|
||||
)
|
||||
expect(req.request.method).toEqual('POST')
|
||||
})
|
||||
})
|
||||
|
@@ -5,6 +5,7 @@ import { User } from 'src/app/data/user'
|
||||
import { PermissionsService } from '../permissions.service'
|
||||
import { AbstractNameFilterService } from './abstract-name-filter-service'
|
||||
|
||||
const endpoint = 'users'
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@@ -13,7 +14,7 @@ export class UserService extends AbstractNameFilterService<User> {
|
||||
http: HttpClient,
|
||||
private permissionService: PermissionsService
|
||||
) {
|
||||
super(http, 'users')
|
||||
super(http, endpoint)
|
||||
}
|
||||
|
||||
update(o: User): Observable<User> {
|
||||
@@ -31,4 +32,11 @@ export class UserService extends AbstractNameFilterService<User> {
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
deactivateTotp(u: User): Observable<boolean> {
|
||||
return this.http.post<boolean>(
|
||||
`${this.getResourceUrl(u.id, 'deactivate_totp')}`,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user