diff --git a/.dev.env b/.dev.env index 5db598c8d..c94782027 100644 Binary files a/.dev.env and b/.dev.env differ diff --git a/.github/workflows/CD-time-tracker-ui.yml b/.github/workflows/CD-time-tracker-ui.yml index f4c22fa2f..35f334620 100644 --- a/.github/workflows/CD-time-tracker-ui.yml +++ b/.github/workflows/CD-time-tracker-ui.yml @@ -34,6 +34,8 @@ jobs: CLIENT_ID: ${{ secrets.client_id }} CLIENT_URL: ${{ secrets.client_url }} STACK_EXCHANGE_ID: ${{ secrets.stack_exchange_id }} + AUTH_URL: ${{ secrets.AUTH_URL }} + AUTH_APP_NAME: ${{ secrets.AUTH_APP_NAME }} STACK_EXCHANGE_ACCESS_TOKEN: ${{ secrets.stack_exchange_access_token }} AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${{ secrets.azure_app_configuration_connection_string }} run: | diff --git a/.github/workflows/CI-time-tracker-ui.yml b/.github/workflows/CI-time-tracker-ui.yml index 6c971a73f..39e7b2087 100644 --- a/.github/workflows/CI-time-tracker-ui.yml +++ b/.github/workflows/CI-time-tracker-ui.yml @@ -46,6 +46,8 @@ jobs: SCOPES: ${{ secrets.SCOPES }} CLIENT_ID: ${{ secrets.CLIENT_ID }} CLIENT_URL : ${{ secrets.CLIENT_URL }} + AUTH_URL: ${{ secrets.AUTH_URL }} + AUTH_APP_NAME: ${{ secrets.AUTH_APP_NAME }} STACK_EXCHANGE_ID: ${{ secrets.STACK_EXCHANGE_ID }} STACK_EXCHANGE_ACCESS_TOKEN: ${{ secrets.STACK_EXCHANGE_ACCESS_TOKEN }} AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${{ secrets.AZURE_APP_CONFIGURATION_CONNECTION_STRING }} diff --git a/.github/workflows/time-tracker-ui-ci.yml b/.github/workflows/time-tracker-ui-ci.yml index d7992606d..9a461e930 100644 --- a/.github/workflows/time-tracker-ui-ci.yml +++ b/.github/workflows/time-tracker-ui-ci.yml @@ -29,12 +29,16 @@ jobs: with: ssh-private-key: ${{ secrets.INFRA_TERRAFORM_MODULES_SSH_PRIV_KEY }} + - name: Unlock DEV secrets + uses: sliteteam/github-action-git-crypt-unlock@1.2.0 + env: + GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY_DEFAULT }} + - name: build docker run: make build - name: Running tests run: | - chmod -R 777 ./$home make test - name: Generate coverage report env: diff --git a/.prod.env b/.prod.env index 141800a50..488981683 100644 Binary files a/.prod.env and b/.prod.env differ diff --git a/.stage.env b/.stage.env index 431814a43..430329d05 100644 Binary files a/.stage.env and b/.stage.env differ diff --git a/Makefile b/Makefile index 110822943..24d343edf 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ cleanup: ## Delete image timetracker_ui .PHONY: run run: ## Execute timetracker_ui dev docker containe. - docker-compose --env-file=.dev.env up -d timetracker_ui + docker-compose --env-file=.dev.env up -d timetracker_ui .PHONY: logs logs: ## Show logs of timetracker_ui. @@ -41,7 +41,7 @@ remove: ## Delete container timetracker_ui. .PHONY: test test: ## Run all tests on docker container timetracker_ui at the CLI. docker-compose build timetracker_ui_test - docker-compose up -d timetracker_ui_test + docker-compose --env-file=.dev.env up -d timetracker_ui_test docker logs -f timetracker_ui_test .PHONY: testdev diff --git a/README.md b/README.md index 94e90975a..ce0baa2f0 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,13 @@ In project path, open your favourite command line and run `npm install` in order # Prepare your environment +### **Local DNS Configuration** + +To test the application in a local environment please modify you `/etc/hosts` on Linux/Mac. In Windows `C:\Windows\System32\Drivers\etc\hosts` and add this line: +```text +127.0.0.1 timetracker-dev.ioet.com +``` + ### Set environment variables **1**. Using GPG create your key by running this command in your favourite command shell: `gpg --generate-key`. diff --git a/docker-compose.yml b/docker-compose.yml index da0835dd7..7d1f6e504 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,8 @@ services: timetracker_ui: container_name: timetracker_ui image: timetracker_ui + env_file: + - .dev.env build: context: . dockerfile: ./Docker/Dockerfile.dev @@ -14,6 +16,8 @@ services: API_URL: ${API_URL} CLIENT_ID: ${CLIENT_ID} CLIENT_URL: ${CLIENT_URL} + AUTH_URL: ${AUTH_URL} + AUTH_APP_NAME: ${AUTH_APP_NAME} SCOPES: ${SCOPES} STACK_EXCHANGE_ID: ${STACK_EXCHANGE_ID} STACK_EXCHANGE_ACCESS_TOKEN: ${STACK_EXCHANGE_ACCESS_TOKEN} diff --git a/scripts/populate-keys.sh b/scripts/populate-keys.sh index 7dd584214..0a86def5d 100644 --- a/scripts/populate-keys.sh +++ b/scripts/populate-keys.sh @@ -5,6 +5,8 @@ echo "API_URL='$API_URL'" >> .env echo "AUTHORITY='$AUTHORITY'" >> .env echo "CLIENT_ID='$CLIENT_ID'" >> .env echo "CLIENT_URL='$CLIENT_URL'" >> .env +echo "AUTH_URL='$AUTH_URL'" >> .env +echo "AUTH_APP_NAME='$AUTH_APP_NAME'" >> .env echo "SCOPES='$SCOPES'" >> .env echo "STACK_EXCHANGE_ID='$STACK_EXCHANGE_ID'" >> .env echo "STACK_EXCHANGE_ACCESS_TOKEN='$STACK_EXCHANGE_ACCESS_TOKEN'" >> .env diff --git a/src/app/modules/activities-management/services/activity.service.ts b/src/app/modules/activities-management/services/activity.service.ts index e17cb728e..d413e0c9a 100644 --- a/src/app/modules/activities-management/services/activity.service.ts +++ b/src/app/modules/activities-management/services/activity.service.ts @@ -14,7 +14,7 @@ export class ActivityService { constructor(private http: HttpClient) {} getActivities(): Observable { - return this.http.get(this.baseUrl); + return this.http.get(this.baseUrl, { withCredentials: true }); } createActivity(activityData): Observable { @@ -23,12 +23,12 @@ export class ActivityService { tenant_id: '4225ab1e-1033-4a5f-8650-0dd4950f38c8', }; - return this.http.post(this.baseUrl, body); + return this.http.post(this.baseUrl, body, { withCredentials: true }); } deleteActivity(acitivityId: string): Observable { const url = `${this.baseUrl}/${acitivityId}`; - return this.http.delete(url); + return this.http.delete(url, { withCredentials: true }); } updateActivity(activityData): Observable { @@ -38,6 +38,6 @@ export class ActivityService { ...activityData, }; - return this.http.put(url, body); + return this.http.put(url, body, { withCredentials: true }); } } diff --git a/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts b/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts index 6d9e672c2..1c54fbea0 100644 --- a/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts +++ b/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts @@ -15,20 +15,20 @@ export class ProjectTypeService { getProjectTypes(customerId: any): Observable { const params = new HttpParams().set('customer_id', customerId.customerId); - return this.http.get(this.baseUrl, { params }); + return this.http.get(this.baseUrl, { params, withCredentials: true }); } createProjectType(projectTypeData): Observable { - return this.http.post(this.baseUrl, projectTypeData); + return this.http.post(this.baseUrl, projectTypeData, { withCredentials: true }); } deleteProjectType(projectTypeId: string): Observable { const url = `${this.baseUrl}/${projectTypeId}`; - return this.http.delete(url); + return this.http.delete(url, { withCredentials: true }); } updateProjectType(projectTypeData): Observable { const url = `${this.baseUrl}/${projectTypeData.id}`; - return this.http.put(url, projectTypeData); + return this.http.put(url, projectTypeData, { withCredentials: true }); } } diff --git a/src/app/modules/customer-management/components/projects/components/services/project.service.ts b/src/app/modules/customer-management/components/projects/components/services/project.service.ts index c5b431b1a..4ae84c095 100644 --- a/src/app/modules/customer-management/components/projects/components/services/project.service.ts +++ b/src/app/modules/customer-management/components/projects/components/services/project.service.ts @@ -17,19 +17,19 @@ export class ProjectService { getProjects(customerId: any): Observable { const params = new HttpParams().set('customer_id', customerId.customerId); - return this.http.get(this.url, { params }); + return this.http.get(this.url, { params, withCredentials: true }); } getAllProjects(): Observable { - return this.http.get(this.url); + return this.http.get(this.url, { withCredentials: true }); } getRecentProjects(): Observable { - return this.http.get(`${this.url}/recent`); + return this.http.get(`${this.url}/recent`, { withCredentials: true }); } createProject(projectData): Observable { - return this.http.post(this.url, projectData); + return this.http.post(this.url, projectData, { withCredentials: true }); } updateProject(projectData): Observable { @@ -39,12 +39,12 @@ export class ProjectService { projectData.status = 1; } } - return this.http.put(`${this.url}/${id}`, projectData); + return this.http.put(`${this.url}/${id}`, projectData, { withCredentials: true }); } deleteProject(projectId: string): Observable { return this.isDevelopmentOrProd ? this.http.put(`${this.url}/${projectId}`, { status: 0 }) - : this.http.delete(`${this.url}/${projectId}`); + : this.http.delete(`${this.url}/${projectId}`, { withCredentials: true }); } } diff --git a/src/app/modules/customer-management/services/customer.service.ts b/src/app/modules/customer-management/services/customer.service.ts index cc930a54c..880e58fc2 100644 --- a/src/app/modules/customer-management/services/customer.service.ts +++ b/src/app/modules/customer-management/services/customer.service.ts @@ -13,20 +13,20 @@ export class CustomerService { constructor(private http: HttpClient) {} createCustomer(customerData): Observable { - return this.http.post(this.baseUrl, customerData); + return this.http.post(this.baseUrl, customerData, { withCredentials: true }); } getCustomers(): Observable { - return this.http.get(this.baseUrl); + return this.http.get(this.baseUrl, { withCredentials: true }); } deleteCustomer(customerId: string): Observable { const url = `${this.baseUrl}/${customerId}`; - return this.http.delete(url); + return this.http.delete(url, { withCredentials: true }); } updateCustomer(customerData): Observable { const url = `${this.baseUrl}/${customerData.id}`; - return this.http.put(url, customerData); + return this.http.put(url, customerData, { withCredentials: true }); } } diff --git a/src/app/modules/login/login.component.html b/src/app/modules/login/login.component.html index 9ad2d90d3..43e07d27d 100644 --- a/src/app/modules/login/login.component.html +++ b/src/app/modules/login/login.component.html @@ -11,15 +11,6 @@

Please log in

-
-
- +
diff --git a/src/app/modules/login/login.component.spec.ts b/src/app/modules/login/login.component.spec.ts index 9de359249..99cf8b6bf 100644 --- a/src/app/modules/login/login.component.spec.ts +++ b/src/app/modules/login/login.component.spec.ts @@ -99,6 +99,16 @@ describe('LoginComponent', () => { expect(component).toBeTruthy(); }); + it('should set local storage when the component is initialized and is not legacy production', inject([Router], (router: Router) => { + component.isProduction = false; + spyOn(loginService, 'getUser').and.returnValue(of(userTest)); + spyOn(loginService, 'setLocalStorage'); + + component.ngOnInit(); + + expect(loginService.setLocalStorage).toHaveBeenCalled(); + })); + it('should sign up or login with google if is not logged-in into the app on Production', inject([Router], (router: Router) => { spyOn(azureAdB2CService, 'isLogin').and.returnValue(false); spyOn(azureAdB2CService, 'setCookies').and.returnValue(); diff --git a/src/app/modules/login/login.component.ts b/src/app/modules/login/login.component.ts index 792fc56e9..070a2f38f 100644 --- a/src/app/modules/login/login.component.ts +++ b/src/app/modules/login/login.component.ts @@ -22,6 +22,8 @@ declare global { export class LoginComponent implements OnInit { isProduction = environment.production === EnvironmentType.TT_PROD_LEGACY; cliendId = CLIENT_URL; + authUrl = environment.authUrl; + authAppName = environment.authAppName; auth2: any; @@ -34,57 +36,16 @@ export class LoginComponent implements OnInit { private ngZone?: NgZone ) {} - - googleAuthSDK() { - const sdkLoaded = 'googleSDKLoaded'; - const gapi = 'gapi'; - - (window as any)[sdkLoaded] = () => { - (window as any)[gapi].load('auth2', () => { - this.auth2 = ( window as any)[gapi].auth2.init({ - client_id: this.cliendId, - plugin_name: 'login', - cookiepolicy: 'single_host_origin', - scope: 'profile email' - }); - }); - }; - - (async (d, s, id) => { - const keyGoogle = 'src'; - const gjs = d.getElementsByTagName(s)[1]; - let js = gjs; - if (d.getElementById(id)) { return; } - js = d.createElement(s); js.id = id; - js[keyGoogle] = 'https://accounts.google.com/gsi/client'; - gjs.parentNode.insertBefore(js, gjs); - })(document, 'script', 'async defer'); - } - ngOnInit() { - - this.googleAuthSDK(); - if (this.isProduction && this.azureAdB2CService.isLogin()) { - this.router.navigate(['']); - } else { - this.loginService.isLogin().subscribe(isLogin => { - if (isLogin) { - this.router.navigate(['']); - } + if (!this.isProduction) { + this.loginService.getUser(null).subscribe((resp) => { + this.loginService.setCookies(); + const tokenObject = JSON.stringify(resp); + const tokenJson = JSON.parse(tokenObject); + this.loginService.setLocalStorage('user', tokenJson.token); + this.ngZone.run(() => this.router.navigate([''])); }); } - window.handleCredentialResponse = (response) => { - const {credential = ''} = response; - this.featureToggleCookiesService.setCookies(); - this.loginService.setLocalStorage('idToken', credential); - this.loginService.getUser(credential).subscribe((resp) => { - this.loginService.setCookies(); - const tokenObject = JSON.stringify(resp); - const tokenJson = JSON.parse(tokenObject); - this.loginService.setLocalStorage('user', tokenJson.token); - this.ngZone.run(() => this.router.navigate([''])); - }); - }; } login(): void { @@ -106,4 +67,8 @@ export class LoginComponent implements OnInit { } } + loginAuth() { + window.location.href = `${this.authUrl}/authn/login/${this.authAppName}`; + } + } diff --git a/src/app/modules/login/services/login.service.spec.ts b/src/app/modules/login/services/login.service.spec.ts index 94e246cc6..67fe3498f 100644 --- a/src/app/modules/login/services/login.service.spec.ts +++ b/src/app/modules/login/services/login.service.spec.ts @@ -4,6 +4,7 @@ import { TestBed } from '@angular/core/testing'; import { JwtHelperService } from '@auth0/angular-jwt'; import { SocialAuthService } from 'angularx-social-login'; import { CookieService } from 'ngx-cookie-service'; +import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; import { LoginService } from './login.service'; @@ -23,11 +24,11 @@ describe('LoginService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], providers: [ { providers: CookieService, useValue: cookieStoreStub }, { provide: SocialAuthService, useValue: socialAuthServiceStub }, - { provide: HttpClient, useValue: httpClientSpy } + { provide: HttpClient, useValue: httpClientSpy }, ], }); service = TestBed.inject(LoginService); @@ -122,6 +123,7 @@ describe('LoginService', () => { it('should logout with social angularx-social-login', () => { spyOn(cookieService, 'deleteAll').and.returnValue(); + spyOn(service, 'invalidateSessionCookie').and.returnValue(of(true)); service.logout(); @@ -129,6 +131,11 @@ describe('LoginService', () => { expect(cookieService.deleteAll).toHaveBeenCalled(); }); + it('should return an http observable when call invalidateSessionCooke', () => { + const result = service.invalidateSessionCookie(); + expect(result).toBeDefined(); + }); + it('should call cookieService when app is isLegacyProd', () => { service.isLegacyProd = true; service.localStorageKey = 'user2'; diff --git a/src/app/modules/login/services/login.service.ts b/src/app/modules/login/services/login.service.ts index 8a0869829..061a98cb4 100644 --- a/src/app/modules/login/services/login.service.ts +++ b/src/app/modules/login/services/login.service.ts @@ -1,11 +1,12 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { CookieService } from 'ngx-cookie-service'; import { EnvironmentType, UserEnum } from 'src/environments/enum'; import { environment } from 'src/environments/environment'; import { JwtHelperService } from '@auth0/angular-jwt'; import { map } from 'rxjs/operators'; import { of } from 'rxjs'; +import { Router } from '@angular/router'; @Injectable({ providedIn: 'root' @@ -15,18 +16,29 @@ export class LoginService { helper: JwtHelperService; isLegacyProd: boolean = environment.production === EnvironmentType.TT_PROD_LEGACY; localStorageKey = this.isLegacyProd ? 'user2' : 'user'; + ngZone?: NgZone; + constructor( private http?: HttpClient, private cookieService?: CookieService, + private router?: Router, ) { this.baseUrl = `${environment.timeTrackerApiUrl}/users`; this.helper = new JwtHelperService(); + this.router = router; } logout() { - this.cookieService.deleteAll(); localStorage.clear(); + this.cookieService.deleteAll(); + this.invalidateSessionCookie().toPromise().then(() => { + this.router.navigate(['login']); + }); + } + + invalidateSessionCookie() { + return this.http.post(`${this.baseUrl}/logout`, null, { withCredentials: true }); } isLogin() { @@ -92,7 +104,7 @@ export class LoginService { token: tokenString, }; - return this.http.post(`${this.baseUrl}/login`, body); + return this.http.post(`${this.baseUrl}/login`, body, { withCredentials: true }); } setCookies() { @@ -109,7 +121,7 @@ export class LoginService { isValidToken(token: string) { const body = { token }; - return this.http.post(`${this.baseUrl}/validate-token`, body).pipe( + return this.http.post(`${this.baseUrl}/validate-token`, body, { withCredentials: true }).pipe( map((response) => { const responseString = JSON.stringify(response); const responseJson = JSON.parse(responseString); @@ -120,5 +132,4 @@ export class LoginService { }) ); } - } diff --git a/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts b/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts index e9e81b6ff..cf421cb9e 100644 --- a/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts +++ b/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts @@ -7,6 +7,7 @@ import { FeatureToggleGeneralService } from '../../feature-toggles/feature-toggl import { FeatureToggleModel } from '../../feature-toggles/feature-toggle.model'; import { FeatureFilterModel } from '../../feature-toggles/filters/feature-filter.model'; import { DarkModeComponent } from './dark-mode.component'; +import { RouterTestingModule } from '@angular/router/testing'; describe('DarkModeComponent', () => { let component: DarkModeComponent; @@ -17,7 +18,7 @@ describe('DarkModeComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DarkModeComponent], - imports: [HttpClientTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], providers: [{ provide: SocialAuthService, useValue: socialAuthServiceStub }] }).compileComponents(); }); diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.html b/src/app/modules/shared/components/sidebar/sidebar.component.html index 66d7881b5..d76e5da99 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.html +++ b/src/app/modules/shared/components/sidebar/sidebar.component.html @@ -17,10 +17,10 @@

{{ item.text }}

- +
@@ -40,10 +40,10 @@

{{ item.text }}
-
+
- \ No newline at end of file + diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.scss b/src/app/modules/shared/components/sidebar/sidebar.component.scss index 7b1bfa5f6..5b7ecb78c 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.scss +++ b/src/app/modules/shared/components/sidebar/sidebar.component.scss @@ -4,6 +4,10 @@ body { overflow-x: hidden; } +button.logout:active { + outline: none; +} + #sidebar-wrapper { min-height: 100vh; margin-left: -15rem; @@ -76,4 +80,4 @@ body { height: -webkit-calc(100vh - 1vh); height: -o-calc(100vh - 1vh); height: calc(100vh - 1vh); -} \ No newline at end of file +} diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.spec.ts b/src/app/modules/shared/components/sidebar/sidebar.component.spec.ts index 7603ca631..de989047d 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.spec.ts +++ b/src/app/modules/shared/components/sidebar/sidebar.component.spec.ts @@ -14,7 +14,7 @@ describe('SidebarComponent', () => { let azureAdB2CServiceStubInjected; let loginServiceStubInjected: LoginService; let userInfoService: UserInfoService; - let router; + let router: Router; const routes: Routes = [{ path: 'time-clock', component: TimeClockComponent }]; const azureAdB2CServiceStub = { diff --git a/src/app/modules/shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service.spec.ts b/src/app/modules/shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service.spec.ts index 1fe77a7f3..0d21d38b0 100644 --- a/src/app/modules/shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service.spec.ts +++ b/src/app/modules/shared/feature-toggles/feature-toggle-cookies/feature-toggle-cookies.service.spec.ts @@ -7,6 +7,7 @@ import { FeatureToggleGeneralService } from '../feature-toggle-general/feature-t import { FeatureToggleModel } from '../feature-toggle.model'; import { TargetingFeatureFilterModel } from '../filters/targeting/targeting-feature-filter.model'; import { FeatureToggleCookiesService } from './feature-toggle-cookies.service'; +import { RouterTestingModule } from '@angular/router/testing'; describe('FeatureToggleCookiesService', () => { let cookieService: CookieService; @@ -16,7 +17,7 @@ describe('FeatureToggleCookiesService', () => { const socialAuthServiceStub = jasmine.createSpyObj('SocialAuthService', ['authState']); beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], providers: [CookieService, FeatureToggleGeneralService, { provide: SocialAuthService, useValue: socialAuthServiceStub } ] diff --git a/src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts b/src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts index 60becca27..8a8e8eb38 100644 --- a/src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts +++ b/src/app/modules/shared/feature-toggles/feature-toggle-general/feature-toggle-general.service.spec.ts @@ -6,6 +6,7 @@ import { FeatureToggleModel } from '../feature-toggle.model'; import { TargetingFeatureFilterModel } from '../filters/targeting/targeting-feature-filter.model'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { SocialAuthService } from 'angularx-social-login'; +import { RouterTestingModule } from '@angular/router/testing'; describe('FeatureToggleGeneralService', () => { @@ -15,7 +16,7 @@ describe('FeatureToggleGeneralService', () => { const socialAuthServiceStub = jasmine.createSpyObj('SocialAuthService', ['authState']); beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])], providers: [ { provide: FeatureManagerService }, { provide: SocialAuthService, useValue: socialAuthServiceStub } diff --git a/src/app/modules/time-clock/pages/time-clock.component.spec.ts b/src/app/modules/time-clock/pages/time-clock.component.spec.ts index d5dd535fc..8f9b7075f 100644 --- a/src/app/modules/time-clock/pages/time-clock.component.spec.ts +++ b/src/app/modules/time-clock/pages/time-clock.component.spec.ts @@ -14,6 +14,7 @@ import { EntryFieldsComponent } from '../components/entry-fields/entry-fields.co import { ToastrService } from 'ngx-toastr'; import { LoginService } from '../../login/services/login.service'; import { SocialAuthService } from 'angularx-social-login'; +import { RouterTestingModule } from '@angular/router/testing'; describe('TimeClockComponent', () => { let component: TimeClockComponent; @@ -55,7 +56,7 @@ describe('TimeClockComponent', () => { beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], + imports: [HttpClientTestingModule, RouterTestingModule], declarations: [TimeClockComponent, ProjectListHoverComponent, FilterProjectPipe, EntryFieldsComponent], providers: [ FormBuilder, diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts index e8b9a1b63..76c970da1 100644 --- a/src/app/modules/time-clock/services/entry.service.ts +++ b/src/app/modules/time-clock/services/entry.service.ts @@ -23,49 +23,53 @@ export class EntryService { urlInProductionLegacy = environment.production === EnvironmentType.TT_PROD_LEGACY; loadActiveEntry(): Observable { - return this.http.get(`${this.baseUrl}/running`); + return this.http.get(`${this.baseUrl}/running`, { withCredentials: true }); } loadEntries(date): Observable { const timezoneOffset = new Date().getTimezoneOffset(); - return this.http.get(`${this.baseUrl}?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`); + return this.http.get( + `${this.baseUrl}?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`, + { withCredentials: true } + ); } createEntry(entryData): Observable { - return this.http.post(this.baseUrl, entryData); + return this.http.post(this.baseUrl, entryData, { withCredentials: true }); } updateEntry(entryData): Observable { const {id} = entryData; - return this.http.put(`${this.baseUrl}/${id}`, entryData); + return this.http.put(`${this.baseUrl}/${id}`, entryData, { withCredentials: true }); } deleteEntry(entryId: string): Observable { const url = `${this.baseUrl}/${entryId}`; - return this.http.delete(url); + return this.http.delete(url, { withCredentials: true }); } stopEntryRunning(idEntry: string): Observable { return (this.urlInProductionLegacy ? - this.http.post(`${this.baseUrl}/${idEntry}/stop`, null) : this.http.put(`${this.baseUrl}/stop`, null) ); + this.http.post(`${this.baseUrl}/${idEntry}/stop`, null, { withCredentials: true }) : + this.http.put(`${this.baseUrl}/stop`, null, { withCredentials: true }) ); } restartEntry(idEntry: string): Observable { const url = `${this.baseUrl}/${idEntry}/restart`; - return this.http.post(url, null); + return this.http.post(url, null, { withCredentials: true }); } summary(): Observable { const timeOffset = new Date().getTimezoneOffset(); const summaryUrl = `${this.baseUrl}/summary?time_offset=${timeOffset}`; - return this.http.get(summaryUrl); + return this.http.get(summaryUrl, { withCredentials: true }); } findEntriesByProjectId(projectId: string): Observable { const startDate = this.getDateLastMonth(); const endDate = this.getCurrentDate(); const findEntriesByProjectURL = `${this.baseUrl}?limit=2&project_id=${projectId}&start_date=${startDate}&end_date=${endDate}`; - return this.http.get(findEntriesByProjectURL); + return this.http.get(findEntriesByProjectURL, { withCredentials: true }); } loadEntriesByTimeRange(range: TimeEntriesTimeRange, userId: string): Observable { @@ -79,7 +83,8 @@ export class EntryService { user_id: userId, limit: `${MAX_NUMBER_OF_ENTRIES_FOR_REPORTS}`, timezone_offset : new Date().getTimezoneOffset().toString(), - } + }, + withCredentials: true } ); } diff --git a/src/app/modules/user/services/user.service.ts b/src/app/modules/user/services/user.service.ts index 0eeb6a9ea..fffa3268e 100644 --- a/src/app/modules/user/services/user.service.ts +++ b/src/app/modules/user/services/user.service.ts @@ -16,6 +16,6 @@ export class UserService { loadUser(userId: any): Observable { const url = `${this.baseUrl}/${userId}`; - return this.http.get(url); + return this.http.get(url, { withCredentials: true }); } } diff --git a/src/app/modules/users/components/users-list/users-list.component.spec.ts b/src/app/modules/users/components/users-list/users-list.component.spec.ts index ada0b01ef..87f239920 100644 --- a/src/app/modules/users/components/users-list/users-list.component.spec.ts +++ b/src/app/modules/users/components/users-list/users-list.component.spec.ts @@ -9,6 +9,7 @@ import { DataTablesModule } from 'angular-datatables'; import { GrantUserRole, RevokeUserRole } from '../../store/user.actions'; import { ROLES } from '../../../../../environments/environment'; import { LoginService } from '../../../login/services/login.service'; +import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; import { UserInfoService } from 'src/app/modules/user/services/user-info.service'; @@ -44,7 +45,7 @@ describe('UsersListComponent', () => { beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ - imports: [NgxPaginationModule, DataTablesModule, HttpClientTestingModule], + imports: [NgxPaginationModule, DataTablesModule, HttpClientTestingModule, RouterTestingModule.withRoutes([])], declarations: [UsersListComponent], providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }, diff --git a/src/app/modules/users/services/users.service.ts b/src/app/modules/users/services/users.service.ts index 5f6ba6ea6..6a36bd2a9 100644 --- a/src/app/modules/users/services/users.service.ts +++ b/src/app/modules/users/services/users.service.ts @@ -15,30 +15,30 @@ export class UsersService { baseUrl = `${environment.timeTrackerApiUrl}/users`; loadUsers(): Observable { - return this.http.get(this.baseUrl); + return this.http.get(this.baseUrl, { withCredentials: true }); } grantRole(userId: string, roleId: string): Observable { const url = this.isProductionLegacy ? `${this.baseUrl}/${userId}/roles/${roleId}/grant` : `${this.baseUrl}/${userId}/${roleId}/grant`; - return this.http.post(url, null); + return this.http.post(url, null, { withCredentials: true }); } revokeRole(userId: string, roleId: string): Observable { const url = this.isProductionLegacy ? `${this.baseUrl}/${userId}/roles/${roleId}/revoke` : `${this.baseUrl}/${userId}/${roleId}/revoke`; - return this.http.post(url, null); + return this.http.post(url, null, { withCredentials: true }); } addUserToGroup(userId: string, group: string): Observable { return this.http.post(`${this.baseUrl}/${userId}/groups/add`, { group_name: group, - }); + }, { withCredentials: true }); } removeUserFromGroup(userId: string, group: string): Observable { return this.http.post(`${this.baseUrl}/${userId}/groups/remove`, { group_name: group, - }); + }, { withCredentials: true }); } } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index cc90f444f..e679026f4 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -4,6 +4,8 @@ export const environment = { production: EnvironmentType.TT_PROD, timeTrackerApiUrl: process.env["API_URL"], stackexchangeApiUrl: 'https://api.stackexchange.com', + authUrl: process.env['AUTH_URL'], + authAppName: process.env['AUTH_APP_NAME'], }; export const AUTHORITY = process.env["AUTHORITY"]; diff --git a/src/environments/environment.prodlegacy.ts b/src/environments/environment.prodlegacy.ts index c372c3ae4..2ff198b90 100644 --- a/src/environments/environment.prodlegacy.ts +++ b/src/environments/environment.prodlegacy.ts @@ -2,7 +2,7 @@ import { EnvironmentType } from './enum'; export const environment = { production: EnvironmentType.TT_PROD_LEGACY, - timeTrackerApiUrl: process.env["API_URL"], + timeTrackerApiUrl: process.env["API_URL"], stackexchangeApiUrl: 'https://api.stackexchange.com', }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index f5e34a552..e724acd9c 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -8,11 +8,14 @@ export const environment = { production: EnvironmentType.TT_DEV, timeTrackerApiUrl: process.env['API_URL'], stackexchangeApiUrl: 'https://api.stackexchange.com', + authUrl: process.env['AUTH_URL'], + authAppName: process.env['AUTH_APP_NAME'], }; export const AUTHORITY = process.env['AUTHORITY']; export const CLIENT_ID = process.env['CLIENT_ID']; export const CLIENT_URL = process.env['CLIENT_URL']; +export const AUTH_URL = process.env['AUTH_URL']; export const SCOPES = process.env['SCOPES'].split(','); export const STACK_EXCHANGE_ID = process.env['STACK_EXCHANGE_ID']; export const STACK_EXCHANGE_ACCESS_TOKEN = process.env['STACK_EXCHANGE_ACCESS_TOKEN']; diff --git a/src/polyfills.ts b/src/polyfills.ts index 03711e5d9..1c2217734 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -61,3 +61,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ + +(window as any).process = { + env: { DEBUG: undefined }, +}; diff --git a/webpack.config.js b/webpack.config.js index 44c1ce497..357929357 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,6 +13,8 @@ module.exports = (config) => { 'process.env.AUTHORITY': JSON.stringify(process.env["AUTHORITY"]), 'process.env.API_URL':JSON.stringify(process.env["API_URL"]), 'process.env.CLIENT_ID':JSON.stringify(process.env["CLIENT_ID"]), + 'process.env.AUTH_URL':JSON.stringify(process.env["AUTH_URL"]), + 'process.env.AUTH_APP_NAME':JSON.stringify(process.env["AUTH_APP_NAME"]), 'process.env.CLIENT_URL':JSON.stringify(process.env["CLIENT_URL"]), 'process.env.SCOPES':JSON.stringify(process.env["SCOPES"]), 'process.env.STACK_EXCHANGE_ID':JSON.stringify(process.env["STACK_EXCHANGE_ID"]),