Separate token refresh
This commit is contained in:
parent
ea3df01040
commit
b0f9ed2cf6
@ -24,6 +24,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
@if (object?.expiration) {
|
||||||
|
<button type="button" class="btn btn-outline-secondary me-2" (click)="refreshToken()" [disabled]="networkActive || refreshTokenActive">
|
||||||
|
@if (refreshTokenActive) {
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
<span class="visually-hidden mr-1" i18n>Loading...</span>
|
||||||
|
}
|
||||||
|
<ng-container i18n>Refresh Token</ng-container>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<div class="m-0 me-auto">
|
||||||
|
@if (refreshTokenResult) {
|
||||||
|
<ngb-alert #refreshTokenResultAlert [type]="refreshTokenResult" class="mb-0 py-2" (closed)="refreshTokenResult = null">{{refreshTokenResultMessage}}</ngb-alert>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<div class="m-0 me-2">
|
<div class="m-0 me-2">
|
||||||
@if (testResult) {
|
@if (testResult) {
|
||||||
<ngb-alert #testResultAlert [type]="testResult" class="mb-0 py-2" (closed)="testResult = null">{{testResultMessage}}</ngb-alert>
|
<ngb-alert #testResultAlert [type]="testResult" class="mb-0 py-2" (closed)="testResult = null">{{testResultMessage}}</ngb-alert>
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { IMAPSecurity } from 'src/app/data/mail-account'
|
import { IMAPSecurity, MailAccountType } from 'src/app/data/mail-account'
|
||||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
@ -82,6 +82,7 @@ describe('MailAccountEditDialogComponent', () => {
|
|||||||
imap_port: 443,
|
imap_port: 443,
|
||||||
imap_security: IMAPSecurity.SSL,
|
imap_security: IMAPSecurity.SSL,
|
||||||
is_token: false,
|
is_token: false,
|
||||||
|
account_type: MailAccountType.IMAP,
|
||||||
}
|
}
|
||||||
|
|
||||||
// success
|
// success
|
||||||
|
@ -21,9 +21,14 @@ const IMAP_SECURITY_OPTIONS = [
|
|||||||
export class MailAccountEditDialogComponent extends EditDialogComponent<MailAccount> {
|
export class MailAccountEditDialogComponent extends EditDialogComponent<MailAccount> {
|
||||||
testActive: boolean = false
|
testActive: boolean = false
|
||||||
testResult: string
|
testResult: string
|
||||||
alertTimeout
|
testAlertTimeout
|
||||||
|
refreshTokenActive: boolean = false
|
||||||
|
refreshTokenResult: string
|
||||||
|
refreshTokenAlertTimeout
|
||||||
|
|
||||||
@ViewChild('testResultAlert', { static: false }) testResultAlert: NgbAlert
|
@ViewChild('testResultAlert', { static: false }) testResultAlert: NgbAlert
|
||||||
|
@ViewChild('refreshTokenResultAlert', { static: false })
|
||||||
|
refreshTokenResultAlert: NgbAlert
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
service: MailAccountService,
|
service: MailAccountService,
|
||||||
@ -62,7 +67,7 @@ export class MailAccountEditDialogComponent extends EditDialogComponent<MailAcco
|
|||||||
test() {
|
test() {
|
||||||
this.testActive = true
|
this.testActive = true
|
||||||
this.testResult = null
|
this.testResult = null
|
||||||
clearTimeout(this.alertTimeout)
|
clearTimeout(this.testAlertTimeout)
|
||||||
const mailService = this.service as MailAccountService
|
const mailService = this.service as MailAccountService
|
||||||
const newObject = Object.assign(
|
const newObject = Object.assign(
|
||||||
Object.assign({}, this.object),
|
Object.assign({}, this.object),
|
||||||
@ -72,12 +77,18 @@ export class MailAccountEditDialogComponent extends EditDialogComponent<MailAcco
|
|||||||
next: (result: { success: boolean }) => {
|
next: (result: { success: boolean }) => {
|
||||||
this.testActive = false
|
this.testActive = false
|
||||||
this.testResult = result.success ? 'success' : 'danger'
|
this.testResult = result.success ? 'success' : 'danger'
|
||||||
this.alertTimeout = setTimeout(() => this.testResultAlert.close(), 5000)
|
this.testAlertTimeout = setTimeout(
|
||||||
|
() => this.testResultAlert.close(),
|
||||||
|
5000
|
||||||
|
)
|
||||||
},
|
},
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
this.testActive = false
|
this.testActive = false
|
||||||
this.testResult = 'danger'
|
this.testResult = 'danger'
|
||||||
this.alertTimeout = setTimeout(() => this.testResultAlert.close(), 5000)
|
this.testAlertTimeout = setTimeout(
|
||||||
|
() => this.testResultAlert.close(),
|
||||||
|
5000
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -87,4 +98,35 @@ export class MailAccountEditDialogComponent extends EditDialogComponent<MailAcco
|
|||||||
? $localize`Successfully connected to the mail server`
|
? $localize`Successfully connected to the mail server`
|
||||||
: $localize`Unable to connect to the mail server`
|
: $localize`Unable to connect to the mail server`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshToken() {
|
||||||
|
this.refreshTokenActive = true
|
||||||
|
this.refreshTokenResult = null
|
||||||
|
clearTimeout(this.refreshTokenAlertTimeout)
|
||||||
|
const mailService = this.service as MailAccountService
|
||||||
|
mailService.refreshOauthToken(this.object).subscribe({
|
||||||
|
next: (result: { success: boolean }) => {
|
||||||
|
this.refreshTokenActive = false
|
||||||
|
this.refreshTokenResult = result.success ? 'success' : 'danger'
|
||||||
|
this.refreshTokenAlertTimeout = setTimeout(
|
||||||
|
() => this.refreshTokenResultAlert.close(),
|
||||||
|
5000
|
||||||
|
)
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.refreshTokenActive = false
|
||||||
|
this.refreshTokenResult = 'danger'
|
||||||
|
this.refreshTokenAlertTimeout = setTimeout(
|
||||||
|
() => this.refreshTokenResultAlert.close(),
|
||||||
|
5000
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get refreshTokenResultMessage() {
|
||||||
|
return this.refreshTokenResult === 'success'
|
||||||
|
? $localize`Successfully refreshed the token`
|
||||||
|
: $localize`Unable to refresh the token`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ const mail_accounts = [
|
|||||||
username: 'user',
|
username: 'user',
|
||||||
password: 'pass',
|
password: 'pass',
|
||||||
is_token: false,
|
is_token: false,
|
||||||
|
account_type: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Mail Account 2',
|
name: 'Mail Account 2',
|
||||||
@ -30,6 +31,7 @@ const mail_accounts = [
|
|||||||
username: 'user',
|
username: 'user',
|
||||||
password: 'pass',
|
password: 'pass',
|
||||||
is_token: false,
|
is_token: false,
|
||||||
|
account_type: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Mail Account 3',
|
name: 'Mail Account 3',
|
||||||
@ -40,6 +42,7 @@ const mail_accounts = [
|
|||||||
username: 'user',
|
username: 'user',
|
||||||
password: 'pass',
|
password: 'pass',
|
||||||
is_token: false,
|
is_token: false,
|
||||||
|
account_type: 1,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -55,20 +58,6 @@ describe(`Additional service tests for MailAccountService`, () => {
|
|||||||
expect(req.request.method).toEqual('POST')
|
expect(req.request.method).toEqual('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support patchMany', () => {
|
|
||||||
subscription = service.patchMany(mail_accounts).subscribe()
|
|
||||||
mail_accounts.forEach((mail_account) => {
|
|
||||||
const req = httpTestingController.expectOne(
|
|
||||||
`${environment.apiBaseUrl}${endpoint}/${mail_account.id}/`
|
|
||||||
)
|
|
||||||
expect(req.request.method).toEqual('PATCH')
|
|
||||||
req.flush(mail_account)
|
|
||||||
})
|
|
||||||
httpTestingController.expectOne(
|
|
||||||
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support reload', () => {
|
it('should support reload', () => {
|
||||||
service['reload']()
|
service['reload']()
|
||||||
const req = httpTestingController.expectOne(
|
const req = httpTestingController.expectOne(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { HttpClient } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { combineLatest, Observable } from 'rxjs'
|
|
||||||
import { tap } from 'rxjs/operators'
|
import { tap } from 'rxjs/operators'
|
||||||
import { MailAccount } from 'src/app/data/mail-account'
|
import { MailAccount } from 'src/app/data/mail-account'
|
||||||
import { AbstractPaperlessService } from './abstract-paperless-service'
|
import { AbstractPaperlessService } from './abstract-paperless-service'
|
||||||
@ -34,15 +33,11 @@ export class MailAccountService extends AbstractPaperlessService<MailAccount> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(o: MailAccount) {
|
update(o: MailAccount) {
|
||||||
|
delete o.expiration
|
||||||
|
delete o.refresh_token
|
||||||
return super.update(o).pipe(tap(() => this.reload()))
|
return super.update(o).pipe(tap(() => this.reload()))
|
||||||
}
|
}
|
||||||
|
|
||||||
patchMany(objects: MailAccount[]): Observable<MailAccount[]> {
|
|
||||||
return combineLatest(objects.map((o) => super.patch(o))).pipe(
|
|
||||||
tap(() => this.reload())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(o: MailAccount) {
|
delete(o: MailAccount) {
|
||||||
return super.delete(o).pipe(tap(() => this.reload()))
|
return super.delete(o).pipe(tap(() => this.reload()))
|
||||||
}
|
}
|
||||||
@ -52,4 +47,8 @@ export class MailAccountService extends AbstractPaperlessService<MailAccount> {
|
|||||||
delete account['set_permissions']
|
delete account['set_permissions']
|
||||||
return this.http.post(this.getResourceUrl() + 'test/', account)
|
return this.http.post(this.getResourceUrl() + 'test/', account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshOauthToken(o: MailAccount) {
|
||||||
|
return this.http.post(this.getResourceUrl(o.id) + 'refresh_oauth_token/', o)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,21 +76,6 @@ const mail_rules = [
|
|||||||
commonAbstractPaperlessServiceTests(endpoint, MailRuleService)
|
commonAbstractPaperlessServiceTests(endpoint, MailRuleService)
|
||||||
|
|
||||||
describe(`Additional service tests for MailRuleService`, () => {
|
describe(`Additional service tests for MailRuleService`, () => {
|
||||||
it('should support patchMany', () => {
|
|
||||||
subscription = service.patchMany(mail_rules).subscribe()
|
|
||||||
mail_rules.forEach((mail_rule) => {
|
|
||||||
const req = httpTestingController.expectOne(
|
|
||||||
`${environment.apiBaseUrl}${endpoint}/${mail_rule.id}/`
|
|
||||||
)
|
|
||||||
expect(req.request.method).toEqual('PATCH')
|
|
||||||
req.flush(mail_rule)
|
|
||||||
})
|
|
||||||
const reloadReq = httpTestingController.expectOne(
|
|
||||||
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
|
|
||||||
)
|
|
||||||
reloadReq.flush({ results: mail_rules })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support reload', () => {
|
it('should support reload', () => {
|
||||||
service['reload']()
|
service['reload']()
|
||||||
const req = httpTestingController.expectOne(
|
const req = httpTestingController.expectOne(
|
||||||
|
@ -37,12 +37,6 @@ export class MailRuleService extends AbstractPaperlessService<MailRule> {
|
|||||||
return super.update(o).pipe(tap(() => this.reload()))
|
return super.update(o).pipe(tap(() => this.reload()))
|
||||||
}
|
}
|
||||||
|
|
||||||
patchMany(objects: MailRule[]): Observable<MailRule[]> {
|
|
||||||
return combineLatest(objects.map((o) => super.patch(o))).pipe(
|
|
||||||
tap(() => this.reload())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(o: MailRule) {
|
delete(o: MailRule) {
|
||||||
return super.delete(o).pipe(tap(() => this.reload()))
|
return super.delete(o).pipe(tap(() => this.reload()))
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ from django.conf import settings
|
|||||||
from django.http import HttpResponseBadRequest
|
from django.http import HttpResponseBadRequest
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from rest_framework.decorators import action
|
||||||
from rest_framework.generics import GenericAPIView
|
from rest_framework.generics import GenericAPIView
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -35,6 +36,14 @@ class MailAccountViewSet(ModelViewSet, PassUserMixin):
|
|||||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||||
filter_backends = (ObjectOwnedOrGrantedPermissionsFilter,)
|
filter_backends = (ObjectOwnedOrGrantedPermissionsFilter,)
|
||||||
|
|
||||||
|
@action(methods=["post"], detail=True)
|
||||||
|
def refresh_oauth_token(self, request, pk=None):
|
||||||
|
return (
|
||||||
|
Response({"success": True})
|
||||||
|
if refresh_oauth_token(MailAccount.objects.get(id=pk))
|
||||||
|
else HttpResponseBadRequest("Unable to refresh token")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MailRuleViewSet(ModelViewSet, PassUserMixin):
|
class MailRuleViewSet(ModelViewSet, PassUserMixin):
|
||||||
model = MailRule
|
model = MailRule
|
||||||
@ -56,16 +65,14 @@ class MailAccountTestView(GenericAPIView):
|
|||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
# account exists, use the password from there instead of *** and refresh_token / expiration
|
# account exists, use the password from there instead of ***
|
||||||
if (
|
if (
|
||||||
len(serializer.validated_data.get("password").replace("*", "")) == 0
|
len(serializer.validated_data.get("password").replace("*", "")) == 0
|
||||||
and request.data["id"] is not None
|
and request.data["id"] is not None
|
||||||
):
|
):
|
||||||
existing_account = MailAccount.objects.get(pk=request.data["id"])
|
serializer.validated_data["password"] = MailAccount.objects.get(
|
||||||
serializer.validated_data["password"] = existing_account.password
|
pk=request.data["id"],
|
||||||
serializer.validated_data["account_type"] = existing_account.account_type
|
).password
|
||||||
serializer.validated_data["refresh_token"] = existing_account.refresh_token
|
|
||||||
serializer.validated_data["expiration"] = existing_account.expiration
|
|
||||||
|
|
||||||
account = MailAccount(**serializer.validated_data)
|
account = MailAccount(**serializer.validated_data)
|
||||||
|
|
||||||
@ -75,16 +82,6 @@ class MailAccountTestView(GenericAPIView):
|
|||||||
account.imap_security,
|
account.imap_security,
|
||||||
) as M:
|
) as M:
|
||||||
try:
|
try:
|
||||||
if (
|
|
||||||
account.is_token
|
|
||||||
and account.expiration is not None
|
|
||||||
and account.expiration < timezone.now()
|
|
||||||
):
|
|
||||||
if refresh_oauth_token(account):
|
|
||||||
account.refresh_from_db()
|
|
||||||
else:
|
|
||||||
raise MailError("Unable to refresh oauth token")
|
|
||||||
|
|
||||||
mailbox_login(M, account)
|
mailbox_login(M, account)
|
||||||
return Response({"success": True})
|
return Response({"success": True})
|
||||||
except MailError:
|
except MailError:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user