mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-12 00:19:48 +00:00
Feature: OAuth2 Gmail and Outlook email support (#7866)
This commit is contained in:
@@ -13,6 +13,16 @@
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editMailAccount()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }">
|
||||
<i-bs name="plus-circle"></i-bs> <ng-container i18n>Add Account</ng-container>
|
||||
</button>
|
||||
@if (gmailOAuthUrl) {
|
||||
<a class="btn btn-sm btn-outline-secondary ms-2" [href]="gmailOAuthUrl" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }">
|
||||
<i-bs name="google"></i-bs> <ng-container i18n>Connect Gmail Account</ng-container>
|
||||
</a>
|
||||
}
|
||||
@if (outlookOAuthUrl) {
|
||||
<a class="btn btn-sm btn-outline-secondary ms-2" [href]="outlookOAuthUrl" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }">
|
||||
<i-bs name="microsoft"></i-bs> <ng-container i18n>Connect Outlook Account</ng-container>
|
||||
</a>
|
||||
}
|
||||
</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
@@ -27,7 +37,15 @@
|
||||
@for (account of mailAccounts; track account) {
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount)">{{account.name}}</button></div>
|
||||
<div class="col d-flex align-items-center">
|
||||
<button class="btn btn-link p-0 text-start" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount)">
|
||||
{{account.name}}@switch (account.account_type) {
|
||||
@case (MailAccountType.IMAP) {<i-bs name="envelope-at-fill" class="ms-2"></i-bs>}
|
||||
@case (MailAccountType.Gmail_OAuth) {<i-bs name="google" class="ms-2"></i-bs>}
|
||||
@case (MailAccountType.Outlook_OAuth) {<i-bs name="microsoft" class="ms-2"></i-bs>}
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col d-flex align-items-center">{{account.imap_server}}</div>
|
||||
<div class="col d-flex align-items-center d-none d-sm-block">{{account.username}}</div>
|
||||
<div class="col">
|
||||
|
@@ -13,7 +13,7 @@ import {
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { MailAccount } from 'src/app/data/mail-account'
|
||||
import { MailAccount, MailAccountType } from 'src/app/data/mail-account'
|
||||
import { MailRule } from 'src/app/data/mail-rule'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||
@@ -44,10 +44,13 @@ import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { SwitchComponent } from '../../common/input/switch/switch.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { ActivatedRoute, convertToParamMap } from '@angular/router'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
|
||||
const mailAccounts = [
|
||||
{ id: 1, name: 'account1' },
|
||||
{ id: 2, name: 'account2' },
|
||||
{ id: 1, name: 'account1', account_type: MailAccountType.IMAP },
|
||||
{ id: 2, name: 'account2', account_type: MailAccountType.IMAP },
|
||||
{ id: 3, name: 'account3', accout_type: MailAccountType.Gmail_OAuth },
|
||||
]
|
||||
const mailRules = [
|
||||
{ id: 1, name: 'rule1', owner: 1, account: 1, enabled: true },
|
||||
@@ -62,6 +65,8 @@ describe('MailComponent', () => {
|
||||
let modalService: NgbModal
|
||||
let toastService: ToastService
|
||||
let permissionsService: PermissionsService
|
||||
let activatedRoute: ActivatedRoute
|
||||
let settingsService: SettingsService
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -110,6 +115,9 @@ describe('MailComponent', () => {
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
permissionsService = TestBed.inject(PermissionsService)
|
||||
activatedRoute = TestBed.inject(ActivatedRoute)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
settingsService.currentUser = { id: 1 }
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest
|
||||
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||
@@ -348,4 +356,36 @@ describe('MailComponent', () => {
|
||||
expect(patchSpy).toHaveBeenCalled()
|
||||
expect(toastInfoSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show success message when oauth account is connected', () => {
|
||||
const queryParams = { oauth_success: '1' }
|
||||
jest
|
||||
.spyOn(activatedRoute, 'queryParamMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap(queryParams)))
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
completeSetup()
|
||||
expect(toastInfoSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show error message when oauth account connect fails', () => {
|
||||
const queryParams = { oauth_success: '0' }
|
||||
jest
|
||||
.spyOn(activatedRoute, 'queryParamMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap(queryParams)))
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
completeSetup()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should open account edit dialog if oauth account is connected', () => {
|
||||
const queryParams = { oauth_success: '1', oauth_account: '3' }
|
||||
jest
|
||||
.spyOn(activatedRoute, 'queryParamMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap(queryParams)))
|
||||
completeSetup()
|
||||
component.oAuthAccountId = 3
|
||||
const editSpy = jest.spyOn(component, 'editMailAccount')
|
||||
component.ngOnInit()
|
||||
expect(editSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Subject, first, takeUntil } from 'rxjs'
|
||||
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
|
||||
import { MailAccount } from 'src/app/data/mail-account'
|
||||
import { MailAccount, MailAccountType } from 'src/app/data/mail-account'
|
||||
import { MailRule } from 'src/app/data/mail-rule'
|
||||
import {
|
||||
PermissionsService,
|
||||
@@ -18,6 +18,9 @@ import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-ac
|
||||
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-mail',
|
||||
@@ -28,17 +31,30 @@ export class MailComponent
|
||||
extends ComponentWithPermissions
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
public MailAccountType = MailAccountType
|
||||
|
||||
mailAccounts: MailAccount[] = []
|
||||
mailRules: MailRule[] = []
|
||||
|
||||
unsubscribeNotifier: Subject<any> = new Subject()
|
||||
oAuthAccountId: number
|
||||
|
||||
public get gmailOAuthUrl(): string {
|
||||
return this.settingsService.get(SETTINGS_KEYS.GMAIL_OAUTH_URL)
|
||||
}
|
||||
|
||||
public get outlookOAuthUrl(): string {
|
||||
return this.settingsService.get(SETTINGS_KEYS.OUTLOOK_OAUTH_URL)
|
||||
}
|
||||
|
||||
constructor(
|
||||
public mailAccountService: MailAccountService,
|
||||
public mailRuleService: MailRuleService,
|
||||
private toastService: ToastService,
|
||||
private modalService: NgbModal,
|
||||
public permissionsService: PermissionsService
|
||||
public permissionsService: PermissionsService,
|
||||
private settingsService: SettingsService,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -50,6 +66,15 @@ export class MailComponent
|
||||
.subscribe({
|
||||
next: (r) => {
|
||||
this.mailAccounts = r.results
|
||||
console.log(this.mailAccounts, this.oAuthAccountId)
|
||||
|
||||
if (this.oAuthAccountId) {
|
||||
this.editMailAccount(
|
||||
this.mailAccounts.find(
|
||||
(account) => account.id === this.oAuthAccountId
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
error: (e) => {
|
||||
this.toastService.showError(
|
||||
@@ -70,6 +95,27 @@ export class MailComponent
|
||||
this.toastService.showError($localize`Error retrieving mail rules`, e)
|
||||
},
|
||||
})
|
||||
|
||||
this.route.queryParamMap.subscribe((params) => {
|
||||
if (params.get('oauth_success')) {
|
||||
const success = params.get('oauth_success') === '1'
|
||||
if (success) {
|
||||
this.toastService.showInfo($localize`OAuth2 authentication success`)
|
||||
this.oAuthAccountId = parseInt(params.get('account_id'))
|
||||
if (this.mailAccounts.length > 0) {
|
||||
this.editMailAccount(
|
||||
this.mailAccounts.find(
|
||||
(account) => account.id === this.oAuthAccountId
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
this.toastService.showError(
|
||||
$localize`OAuth2 authentication failed, see logs for details`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
Reference in New Issue
Block a user