Skip to content

Commit 37e7260

Browse files
authored
Merge pull request #669 from BIX-Digital/feature/GH-637-handle-basic-auth-in-spa
add form based auth in spa
2 parents 74e8e17 + 2916128 commit 37e7260

31 files changed

+565
-210
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Added
66

7+
- Handle form based auth in SPA ([#637](https://github.com/opendevstack/ods-provisioning-app/issues/637))
78
- Add default permissions to project groups on project creation ([#636](https://github.com/opendevstack/ods-provisioning-app/pull/636))
89
- Add support for odsbox for local development([#579](https://github.com/opendevstack/ods-provisioning-app/issues/579))
910
- Setup webhook in jira projects on project creation event ([#452](https://github.com/opendevstack/ods-provisioning-app/issues/452))
@@ -13,6 +14,7 @@
1314

1415
### Fixed
1516

17+
- Handle basic auth in SPA ([#637](https://github.com/opendevstack/ods-provisioning-app/issues/637))
1618
- Fix SPA searchbar layout ([#664](https://github.com/opendevstack/ods-provisioning-app/issues/664))
1719
- Wrong exception logging of bitbucket project key pre flight check ([#655](https://github.com/opendevstack/ods-provisioning-app/issues/655))
1820
- Show an error message when the component id does not match the regex expression ([#624](https://github.com/opendevstack/ods-provisioning-app/issues/624))

client/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ Thumbs.db
4747

4848
# app
4949
proxy.conf.json
50+
sslcert/
51+
/local.package.json

client/README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
## Status
44

5-
This first version of the new frontend stack was introduced as an experimental feature within OpenDevStack 3.x ([original issue](https://github.com/opendevstack/ods-provisioning-app/issues/518)). It can be activated for users with a `frontend.spa.enabled` feature flag in Spring Boot.
5+
This first version of the new frontend stack was introduced as an experimental feature within OpenDevStack 3.x
6+
([original issue](https://github.com/opendevstack/ods-provisioning-app/issues/518)). It can be activated for users with a
7+
`frontend.spa.enabled` feature flag in Spring Boot.
68

79
OpenDevStack NEXT will include this new frontend activated by default (without featue flag).
810

@@ -35,14 +37,27 @@ Doing this there's no need to run Spring Boot locally.
3537

3638
### 3. Test the setup
3739

38-
Run `yarn start:dev` and when it succeeded open `http://localhost:4200` in your browser. You should see the Angular app starting. The app will automatically reload if you change any of the source files.
40+
Run `yarn start:dev` and when it succeeded open `http://localhost:4200` in your browser. You should see the Angular app starting. The app
41+
will automatically reload if you change any of the source files.
3942

4043
## Development
4144

4245
### Dev Server
4346

4447
`yarn start:dev`: Starts a live reload dev server on `http://localhost:4200/`
4548

49+
### Dev Server with SSL localhost
50+
51+
Depending on the authentication strategy configured in Spring Boot it might be necessary to serve localhost over https so that the app
52+
behaves correctly. Although not ideal, we discovered this as the most effortless solution in the time of writing.
53+
54+
1. Create a self-signed certificate and add it to your OS - we found
55+
[this article](https://medium.com/@richardr39/using-angular-cli-to-serve-over-https-locally-70dab07417c8) quite helpful to set it up.
56+
2. We recommend creating `sslcert` folder - the `.gitignore` file already includes this naming.
57+
3. As mentioned in the article create a new script entry in `package.json`. We recommend naming it `start:dev:ssl`, like this:
58+
59+
`"start:dev:ssl": "ng serve --proxy-config proxy.conf.json --ssl --ssl-key ./sslcert/localhost.key --ssl-cert ./sslcert/localhost.crt"`
60+
4661
### Build
4762

4863
`yarn build`: The build artifacts will be stored in the `dist/` directory. `yarn build:prod` will be used for the pipeline.
@@ -58,11 +73,14 @@ Run `yarn start:dev` and when it succeeded open `http://localhost:4200` in your
5873

5974
### Prettier
6075

61-
Prettier is used as a pre-commit hook to format js, json, md, ts files. Formatting of html files has been temporarily removed due to inflexibility in context of Angular component markup.
76+
Prettier is used as a pre-commit hook to format js, json, md, ts files. Formatting of html files has been temporarily removed due to
77+
inflexibility in context of Angular component markup.
6278

6379
## Contributions welcome!
6480

65-
We're happy if you'd like to contribute to the further development of the new frontend client. Feel free to check the [existing issues tagged with "frontend-spa"](https://github.com/opendevstack/ods-provisioning-app/labels/frontend-spa) or add a new issue there.
81+
We're happy if you'd like to contribute to the further development of the new frontend client. Feel free to check the
82+
[existing issues tagged with "frontend-spa"](https://github.com/opendevstack/ods-provisioning-app/labels/frontend-spa) or add a new issue
83+
there.
6684

6785
## Usage of Angular CLI
6886

client/proxy.conf.template.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44
"host": "<Your Host>",
55
"protocol": "https:"
66
},
7-
"secure": false,
7+
"secure": true,
88
"changeOrigin": true,
9-
"logLevel": "info",
10-
"headers": {
11-
"Authorization": "Basic <Your Authorization Key>"
12-
}
9+
"logLevel": "info"
1310
}
1411
}

client/src/app/app.component.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<div class="logo">
55
<h1>ODS Provisioning</h1>
66
</div>
7-
<div class="user-area" [hidden]="router.url.indexOf('/logout') !== -1">
7+
<div class="user-area" hidden>
88
<div class="user-area__logout">
99
<a
1010
mat-mini-fab
@@ -34,8 +34,7 @@ <h1>ODS Provisioning</h1>
3434

3535
<ng-container *ngIf="!isError; else globalError">
3636
<div class="container">
37-
38-
<div class="sidebar">
37+
<div class="sidebar" *ngIf="!hideSidebar">
3938
<app-sidebar
4039
[projects]="projects"
4140
[isNewProjectFormActive]="isNewProjectFormActive"

client/src/app/app.component.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { Component, OnInit, Renderer2 } from '@angular/core';
22
import { MatIconRegistry } from '@angular/material/icon';
33
import { DomSanitizer } from '@angular/platform-browser';
44
import { EditModeService } from './modules/edit-mode/services/edit-mode.service';
5-
import { catchError, filter } from 'rxjs/operators';
5+
import { catchError } from 'rxjs/operators';
66
import { EMPTY } from 'rxjs';
77
import { EditModeFlag } from './modules/edit-mode/domain/edit-mode';
88
import { StorageService } from './modules/storage/services/storage.service';
9-
import { NavigationStart, Router } from '@angular/router';
9+
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
1010
import { ProjectService } from './modules/project/services/project.service';
1111
import { ProjectData, ProjectStorage } from './domain/project';
12+
import { AuthenticationService } from './modules/authentication/services/authentication.service';
1213

1314
@Component({
1415
selector: 'app-root',
@@ -19,25 +20,53 @@ export class AppComponent implements OnInit {
1920
isLoading = true;
2021
isError: boolean;
2122
isNewProjectFormActive = false;
23+
hideSidebar = false;
2224

2325
projects: ProjectData[] = [];
2426

2527
constructor(
2628
public editMode: EditModeService,
2729
public router: Router,
30+
private activatedRoute: ActivatedRoute,
2831
private matIconRegistry: MatIconRegistry,
2932
private domSanitizer: DomSanitizer,
3033
private renderer: Renderer2,
3134
private projectService: ProjectService,
32-
private storageService: StorageService
35+
private storageService: StorageService,
36+
private authenticationService: AuthenticationService
3337
) {
3438
this.matIconRegistry.addSvgIconSet(this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/mdi-custom-icons.svg'));
3539
this.matIconRegistry.addSvgIconSet(this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/tech-stack.svg'));
3640
}
3741

3842
ngOnInit() {
39-
this.checkRedirectToProjectDetail();
40-
this.loadAllProjects();
43+
this.activatedRoute.queryParams.subscribe(params => {
44+
if (params['sso']) {
45+
this.setSsoMode();
46+
}
47+
});
48+
49+
this.router.events.subscribe(event => {
50+
if (event instanceof NavigationStart) {
51+
if (event.url === '/') {
52+
this.loadAllProjects();
53+
}
54+
this.isLoading = false;
55+
}
56+
});
57+
}
58+
59+
private setSsoMode() {
60+
this.authenticationService.sso = true;
61+
this.router.navigate([], {
62+
queryParams: {
63+
sso: null
64+
}
65+
});
66+
}
67+
68+
private isSsoActive() {
69+
return this.authenticationService.sso;
4170
}
4271

4372
getEditModeStatus() {
@@ -55,12 +84,10 @@ export class AppComponent implements OnInit {
5584
}
5685

5786
private checkRedirectToProjectDetail() {
58-
this.router.events.pipe(filter(event => event instanceof NavigationStart && event.url === '/')).subscribe(() => {
59-
const projectKey = this.getProjectKeyFormStorage();
60-
if (projectKey) {
61-
this.router.navigateByUrl(`/project/${projectKey}`);
62-
}
63-
});
87+
const projectKey = this.getProjectKeyFormStorage();
88+
if (projectKey) {
89+
this.router.navigateByUrl(`/project/${projectKey}`);
90+
}
6491
}
6592

6693
private getProjectKeyFormStorage(): string | undefined {
@@ -73,15 +100,17 @@ export class AppComponent implements OnInit {
73100
.getAllProjects()
74101
.pipe(
75102
catchError(() => {
76-
this.isError = true;
77103
this.isLoading = false;
104+
this.isError = true;
78105
return EMPTY;
79106
})
80107
)
81108
.subscribe((response: ProjectData[]) => {
82109
this.projects = response;
83110
this.isLoading = false;
84111
this.isError = false;
112+
this.hideSidebar = false;
113+
this.checkRedirectToProjectDetail();
85114
});
86115
}
87116
}

client/src/app/app.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ import { MatButtonModule } from '@angular/material/button';
99
import { MatIconModule } from '@angular/material/icon';
1010
import { GeneralErrorPageModule } from './modules/general-error-page/general-error-page.module';
1111
import { LoadingIndicatorModule } from './modules/loading-indicator/loading-indicator.module';
12+
import { CustomErrorHandlerModule } from './modules/error-handler/custom-error-handler.module';
13+
import { AuthenticationModule } from './modules/authentication/authentication.module';
1214

1315
@NgModule({
1416
declarations: [AppComponent],
1517
imports: [
18+
CustomErrorHandlerModule,
19+
AuthenticationModule,
1620
BrowserModule,
1721
BrowserAnimationsModule,
1822
AppRoutingModule,

client/src/app/modules/app-routing/app-routing.module.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ProjectPageModule } from '../project-page/project-page.module';
66
import { StorageModule } from '../storage/storage.module';
77
import { NewProjectModule } from '../new-project/new-project.module';
88
import { ProjectModule } from '../project/project.module';
9+
import { AuthenticationModule } from '../authentication/authentication.module';
910

1011
const routes: Routes = [
1112
{
@@ -24,6 +25,10 @@ const routes: Routes = [
2425
path: 'about',
2526
loadChildren: () => import('../about-page/about-page.module').then(m => m.AboutPageModule)
2627
},
28+
{
29+
path: 'login',
30+
loadChildren: () => import('../login/login.module').then(m => m.LoginModule)
31+
},
2732
{
2833
path: 'logout',
2934
loadChildren: () => import('../logout/logout.module').then(m => m.LogoutModule)
@@ -62,9 +67,12 @@ const routes: Routes = [
6267
apiProjectTemplatesUrl: environment.apiProjectTemplatesUrl,
6368
apiGenerateProjectKeyUrl: environment.apiGenerateProjectKeyUrl
6469
}),
65-
NewProjectModule
70+
NewProjectModule,
71+
AuthenticationModule.withOptions({
72+
apiAuthUrl: environment.apiAuthUrl,
73+
apiLogoutUrl: environment.apiLogoutUrl
74+
})
6675
],
67-
exports: [RouterModule],
68-
providers: []
76+
exports: [RouterModule]
6977
})
7078
export class AppRoutingModule {}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ModuleWithProviders, NgModule } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
import { AuthenticationService } from './services/authentication.service';
4+
import { HttpClientModule } from '@angular/common/http';
5+
import { API_AUTH_URL, API_LOGOUT_URL } from '../../tokens';
6+
7+
@NgModule({
8+
declarations: [],
9+
imports: [CommonModule, HttpClientModule],
10+
providers: [AuthenticationService]
11+
})
12+
export class AuthenticationModule {
13+
static withOptions(options: { apiAuthUrl: string; apiLogoutUrl: string }): ModuleWithProviders<AuthenticationModule> {
14+
return {
15+
ngModule: AuthenticationModule,
16+
providers: [
17+
{
18+
provide: API_AUTH_URL,
19+
useValue: options.apiAuthUrl
20+
},
21+
{
22+
provide: API_LOGOUT_URL,
23+
useValue: options.apiLogoutUrl
24+
}
25+
]
26+
};
27+
}
28+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { AuthenticationService } from './authentication.service';
4+
5+
describe('AuthenticationService', () => {
6+
let service: AuthenticationService;
7+
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({});
10+
service = TestBed.inject(AuthenticationService);
11+
});
12+
13+
it('should be created', () => {
14+
expect(service).toBeTruthy();
15+
});
16+
});

0 commit comments

Comments
 (0)