Skip to content

Commit c6d11f7

Browse files
authored
Fix travelperk oauth (#433)
* Fix travelperk oauth * temp url changes * oauth changes * Allow call + logger
1 parent eaae8ea commit c6d11f7

16 files changed

+194
-47
lines changed

src/app/app-routing.module.ts

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ const routes: Routes = [
1212
path: 'auth',
1313
loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule)
1414
},
15+
{
16+
path: 'oauth',
17+
loadChildren: () => import('./oauth/oauth.module').then(m => m.OauthModule)
18+
},
1519
{
1620
path: 'integrations',
1721
loadChildren: () => import('./integrations/integrations.module').then(m => m.IntegrationsModule),

src/app/auth/redirect/redirect.component.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ export class RedirectComponent implements OnInit {
2121
}
2222

2323
private setupNavigation(): void {
24-
if (this.route.snapshot.queryParams?.state === 'travelperk_local_redirect') {
25-
const url = `http://localhost:4200/integrations/travelperk?code=${this.route.snapshot.queryParams.code}`;
24+
if (this.route.snapshot.queryParams?.state.includes('travelperk_local_redirect')) {
25+
const orgId = this.route.snapshot.queryParams.state.split('_')[0];
26+
const url = `http://localhost:4200/oauth/travelperk?code=${this.route.snapshot.queryParams.code}&state=${orgId}`;
2627
this.windowService.redirect(url);
2728
} else if (this.route.snapshot.queryParams?.state === 'qbo_local_redirect' || this.route.snapshot.queryParams?.state === 'business_central_local_redirect') {
2829
const url = `http://localhost:4200/integrations/oauth_callback?code=${this.route.snapshot.queryParams.code}&realmId=${this.route.snapshot.queryParams.realmId}`;

src/app/core/interceptor/jwt.interceptor.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@ import { globalCacheBusterNotifier } from 'ts-cacheable';
1313
import { JwtHelperService } from '@auth0/angular-jwt';
1414
import { Token } from '../models/misc/token.model';
1515
import { AuthService } from '../services/common/auth.service';
16-
import { SiAuthService } from '../services/si/si-core/si-auth.service';
1716

1817
@Injectable()
1918
export class JwtInterceptor implements HttpInterceptor {
2019

2120
constructor(
2221
private authService: AuthService,
23-
private jwtHelpter: JwtHelperService,
24-
private siAuthService: SiAuthService
22+
private jwtHelpter: JwtHelperService
2523
) { }
2624

2725
private refreshTokenInProgress = false;
@@ -46,7 +44,7 @@ export class JwtInterceptor implements HttpInterceptor {
4644

4745
// Certain api's do not require token in headers.
4846
private isTokenMandatory(url: string): boolean {
49-
const endpointWithoutToken = url.includes('/api/auth/');
47+
const endpointWithoutToken = url.includes('/api/auth/') || url.includes('/travelperk/connect');
5048
return !endpointWithoutToken;
5149
}
5250

src/app/core/services/common/api.service.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,21 @@ export class ApiService {
2626
API_BASE_URL = url;
2727
}
2828

29-
private handleError(error: HttpErrorResponse, httpMethod: string) {
29+
private handleError(error: HttpErrorResponse, httpMethod: string, url: string) {
3030
if (error.error instanceof ErrorEvent) {
3131
console.error('An error occurred:', error.error.message);
3232
} else {
3333
console.error(
34-
`Backend returned code ${error.status}, method was: ${httpMethod}, body was: ${JSON.stringify(error.error)}`
34+
`Backend returned code ${error.status}, url was: ${url} method was: ${httpMethod}, body was: ${JSON.stringify(error.error)}`
3535
);
3636
}
3737
return throwError(error);
3838
}
3939

4040
post(endpoint: string, body: {}): Observable<any> {
41-
return this.http.post(API_BASE_URL + endpoint, body, httpOptions).pipe(catchError(error => {
42-
return this.handleError(error, 'POST');
41+
const url = API_BASE_URL + endpoint;
42+
return this.http.post(url, body, httpOptions).pipe(catchError(error => {
43+
return this.handleError(error, 'POST', url);
4344
}));
4445
}
4546

@@ -48,27 +49,31 @@ export class ApiService {
4849
Object.keys(apiParams).forEach(key => {
4950
params = params.set(key, apiParams[key]);
5051
});
51-
return this.http.get(API_BASE_URL + endpoint, { params }).pipe(catchError(error => {
52-
return this.handleError(error, 'GET');
52+
const url = API_BASE_URL + endpoint;
53+
return this.http.get(url, { params }).pipe(catchError(error => {
54+
return this.handleError(error, 'GET', url);
5355
}));
5456
}
5557

5658
patch(endpoint: string, body: {}): Observable<any> {
57-
return this.http.patch(API_BASE_URL + endpoint, body, httpOptions).pipe(catchError(error => {
58-
return this.handleError(error, 'PATCH');
59+
const url = API_BASE_URL + endpoint;
60+
return this.http.patch(url, body, httpOptions).pipe(catchError(error => {
61+
return this.handleError(error, 'PATCH', url);
5962
}));
6063
}
6164

6265
put(endpoint: string, body: {}): Observable<any> {
63-
return this.http.put(API_BASE_URL + endpoint, body, httpOptions).pipe(catchError(error => {
64-
return this.handleError(error, 'PUT');
66+
const url = API_BASE_URL + endpoint;
67+
return this.http.put(url, body, httpOptions).pipe(catchError(error => {
68+
return this.handleError(error, 'PUT', url);
6569
}));
6670
}
6771

6872
delete(endpoint: string, body: {}): Observable<any> {
6973
httpOptions.body = body;
70-
return this.http.delete(API_BASE_URL + endpoint, httpOptions).pipe(catchError(error => {
71-
return this.handleError(error, 'DELETE');
74+
const url = API_BASE_URL + endpoint;
75+
return this.http.delete(url, httpOptions).pipe(catchError(error => {
76+
return this.handleError(error, 'DELETE', url);
7277
}));
7378
}
7479
}

src/app/core/services/travelperk/travelperk.service.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export class TravelperkService {
6767
});
6868
}
6969

70-
connect(code: string): Observable<{}> {
71-
return this.apiService.post(`/orgs/${this.orgId}/travelperk/connect/`, { code });
70+
connect(code: string, orgId: string): Observable<{}> {
71+
return this.apiService.post(`/orgs/${orgId}/travelperk/connect/`, { code });
7272
}
7373

7474
disconnect(): Observable<{}> {

src/app/integrations/travelperk/travelperk.component.ts

+26-27
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
1818
templateUrl: './travelperk.component.html',
1919
styleUrls: ['./travelperk.component.scss']
2020
})
21-
export class TravelperkComponent implements OnInit, OnDestroy {
21+
export class TravelperkComponent implements OnInit {
2222
brandingKbArticles = brandingKbArticles;
2323

2424
AppName = AppName;
@@ -37,8 +37,6 @@ export class TravelperkComponent implements OnInit, OnDestroy {
3737

3838
org: Org = this.orgService.getCachedOrg();
3939

40-
private routeWatcher$: Subscription;
41-
4240
readonly brandingConfig = brandingConfig;
4341

4442
constructor(
@@ -56,6 +54,8 @@ export class TravelperkComponent implements OnInit, OnDestroy {
5654
this.travelperkData = travelperkData;
5755
this.isIntegrationConnected = travelperkData.is_travelperk_connected;
5856
this.isLoading = false;
57+
}, () => {
58+
this.isLoading = false;
5959
});
6060
}
6161

@@ -70,35 +70,34 @@ export class TravelperkComponent implements OnInit, OnDestroy {
7070

7171
connectTravelperk(): void {
7272
this.isConnectionInProgress = true;
73-
const url = `${environment.travelperk_base_url}/oauth2/authorize?client_id=${environment.travelperk_client_id}&redirect_uri=${environment.travelperk_redirect_uri}&scope=expenses:read&response_type=code&state=${environment.production ? 'none' : 'travelperk_local_redirect'}`;
74-
75-
this.windowService.redirect(url);
76-
}
77-
78-
removeQueryParams() {
79-
// Use Location to replace the state of the history with the same path but without query parameters
80-
this.location.replaceState(this.router.url.split('?')[0]);
81-
}
82-
83-
ngOnInit(): void {
84-
this.routeWatcher$ = this.route.queryParams.subscribe(params => {
85-
if (params.code) {
86-
this.travelperkService.connect(params.code).subscribe(() => {
87-
this.toastService.displayToastMessage(ToastSeverity.SUCCESS, 'Connected Travelperk successfully');
88-
this.removeQueryParams();
73+
const url = `${environment.travelperk_base_url}/oauth2/authorize?client_id=${environment.travelperk_client_id}&redirect_uri=${environment.travelperk_redirect_uri}&scope=expenses:read&response_type=code&state=${environment.production ? this.org.id : `${this.org.id}_travelperk_local_redirect`}`;
74+
75+
const popup = window.open(url, 'popup', 'popup=true, width=500, height=800, left=500');
76+
77+
const activePopup = setInterval(() => {
78+
try {
79+
if (popup?.location?.href?.includes('code')) {
80+
popup.close();
81+
} else if (!popup || !popup.closed) {
82+
return;
83+
}
84+
85+
clearInterval(activePopup);
86+
} catch (error) {
87+
if (error instanceof DOMException && error.message.includes('An attempt was made to break through the security policy of the user agent')) {
88+
this.travelperkService.getTravelperkData().subscribe(() => {
8989
this.isIntegrationConnected = true;
9090
this.isConnectionInProgress = false;
91-
this.isLoading = false;
91+
this.toastService.displayToastMessage(ToastSeverity.SUCCESS, 'Connected Travelperk successfully');
92+
popup?.close();
93+
clearInterval(activePopup);
9294
});
93-
} else {
94-
this.setupPage();
95+
}
9596
}
96-
});
97+
}, 2000);
9798
}
9899

99-
ngOnDestroy(): void {
100-
if (this.routeWatcher$) {
101-
this.routeWatcher$.unsubscribe();
102-
}
100+
ngOnInit(): void {
101+
this.setupPage();
103102
}
104103
}

src/app/oauth/oauth-routing.module.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { NgModule } from '@angular/core';
2+
import { RouterModule, Routes } from '@angular/router';
3+
import { OauthComponent } from './oauth.component';
4+
import { OauthTravelperkComponent } from './oauth-travelperk/oauth-travelperk.component';
5+
6+
const routes: Routes = [
7+
{
8+
path: '',
9+
component: OauthComponent,
10+
children: [
11+
{
12+
path: 'travelperk',
13+
component: OauthTravelperkComponent
14+
}
15+
]
16+
}
17+
];
18+
19+
@NgModule({
20+
imports: [RouterModule.forChild(routes)],
21+
exports: [RouterModule]
22+
})
23+
export class OauthRoutingModule { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="tw-flex tw-flex-col tw-justify-center tw-items-center tw-h-[100%]">
2+
<app-loader></app-loader>
3+
</div>

src/app/oauth/oauth-travelperk/oauth-travelperk.component.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { OauthTravelperkComponent } from './oauth-travelperk.component';
4+
5+
describe('TravelperkComponent', () => {
6+
let component: OauthTravelperkComponent;
7+
let fixture: ComponentFixture<OauthTravelperkComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [ OauthTravelperkComponent ]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(OauthTravelperkComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { ActivatedRoute } from '@angular/router';
3+
import { TravelperkService } from 'src/app/core/services/travelperk/travelperk.service';
4+
5+
@Component({
6+
selector: 'app-oauth-travelperk',
7+
templateUrl: './oauth-travelperk.component.html',
8+
styleUrls: ['./oauth-travelperk.component.scss']
9+
})
10+
export class OauthTravelperkComponent implements OnInit {
11+
12+
constructor(
13+
private route: ActivatedRoute,
14+
private travelPerkService: TravelperkService
15+
) { }
16+
17+
private connectTravelperk(): void {
18+
this.route.queryParams.subscribe(params => {
19+
if (params.code && params.state) {
20+
this.travelPerkService.connect(params.code, params.state).subscribe(() => {
21+
window.close();
22+
});
23+
}
24+
});
25+
}
26+
27+
ngOnInit(): void {
28+
this.connectTravelperk();
29+
}
30+
31+
}

src/app/oauth/oauth.component.html

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<router-outlet></router-outlet>

src/app/oauth/oauth.component.scss

Whitespace-only changes.

src/app/oauth/oauth.component.spec.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { OauthComponent } from './oauth.component';
4+
5+
describe('OauthComponent', () => {
6+
let component: OauthComponent;
7+
let fixture: ComponentFixture<OauthComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [ OauthComponent ]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(OauthComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});

src/app/oauth/oauth.component.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Component, OnInit } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-oauth',
5+
templateUrl: './oauth.component.html',
6+
styleUrls: ['./oauth.component.scss']
7+
})
8+
export class OauthComponent implements OnInit {
9+
10+
constructor() { }
11+
12+
ngOnInit(): void {
13+
}
14+
15+
}

src/app/oauth/oauth.module.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { NgModule } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
4+
import { OauthRoutingModule } from './oauth-routing.module';
5+
import { OauthComponent } from './oauth.component';
6+
import { OauthTravelperkComponent } from './oauth-travelperk/oauth-travelperk.component';
7+
import { SharedModule } from '../shared/shared.module';
8+
9+
10+
@NgModule({
11+
declarations: [
12+
OauthComponent,
13+
OauthTravelperkComponent
14+
],
15+
imports: [
16+
CommonModule,
17+
OauthRoutingModule,
18+
SharedModule
19+
]
20+
})
21+
export class OauthModule { }

0 commit comments

Comments
 (0)