@abgov/nx-adsp 12.4.1 → 12.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generators.json +6 -0
- package/package.json +1 -1
- package/src/generators/angular-app/angular-app.js +22 -7
- package/src/generators/angular-app/angular-app.js.map +1 -1
- package/src/generators/angular-app/files/src/app/app.component.css__tmpl__ +10 -54
- package/src/generators/angular-app/files/src/app/app.component.html__tmpl__ +18 -49
- package/src/generators/angular-app/files/src/app/app.component.spec.ts__tmpl__ +22 -27
- package/src/generators/angular-app/files/src/app/app.component.ts__tmpl__ +60 -20
- package/src/generators/angular-app/files/src/app/app.config.ts__tmpl__ +27 -0
- package/src/generators/angular-app/files/src/app/app.routes.ts__tmpl__ +10 -31
- package/src/generators/angular-app/files/src/app/home/home.component.html__tmpl__ +1 -3
- package/src/generators/angular-app/files/src/app/home/home.component.ts__tmpl__ +9 -23
- package/src/generators/angular-app/files/src/app/protected/protected.component.html__tmpl__ +2 -2
- package/src/generators/angular-app/files/src/app/protected/protected.component.spec.ts__tmpl__ +27 -43
- package/src/generators/angular-app/files/src/app/protected/protected.component.ts__tmpl__ +15 -15
- package/src/generators/angular-app/files/src/app/services/auth-guard.service.ts__tmpl__ +12 -21
- package/src/generators/angular-app/files/src/environments/environment.ts__tmpl__ +2 -11
- package/src/generators/angular-app/files/src/index.html__tmpl__ +3 -9
- package/src/generators/angular-app/files/src/main.ts__tmpl__ +8 -12
- package/src/generators/angular-app/files/src/silent-check-sso.html__tmpl__ +5 -0
- package/src/generators/angular-app/files/src/styles.css__tmpl__ +2 -2
- package/src/generators/mean/mean.d.ts +3 -0
- package/src/generators/mean/mean.js +30 -0
- package/src/generators/mean/mean.js.map +1 -0
- package/src/generators/mean/mean.spec.ts +38 -0
- package/src/generators/mean/schema.d.ts +11 -0
- package/src/generators/mean/schema.json +26 -0
- package/src/generators/angular-app/files/src/app/app.module.ts__tmpl__ +0 -47
- package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.css__tmpl__ +0 -13
- package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.html__tmpl__ +0 -12
- package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.spec.ts__tmpl__ +0 -33
- package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.ts__tmpl__ +0 -48
- package/src/generators/angular-app/files/src/app/auth.interceptor.ts__tmpl__ +0 -24
- package/src/generators/angular-app/files/src/app/logout/logout.component.html__tmpl__ +0 -1
- package/src/generators/angular-app/files/src/app/logout/logout.component.ts__tmpl__ +0 -9
- package/src/generators/angular-app/files/src/app/services/auth.service.ts__tmpl__ +0 -57
- package/src/generators/angular-app/files/src/app/tenant.service.ts__tmpl__ +0 -19
- package/src/generators/angular-app/files/src/environments/config.ts__tmpl__ +0 -21
- /package/src/generators/angular-app/files/{src → public}/assets/banner.jpg +0 -0
- /package/src/generators/angular-app/files/{src → public}/assets/github-1.svg +0 -0
- /package/src/generators/angular-app/files/{src → public}/favicon.ico +0 -0
package/generators.json
CHANGED
|
@@ -43,6 +43,12 @@
|
|
|
43
43
|
"factory": "./src/generators/angular-app/angular-app",
|
|
44
44
|
"schema": "./src/generators/angular-app/schema.json",
|
|
45
45
|
"description": "Generator that creates a Angular based frontend application."
|
|
46
|
+
},
|
|
47
|
+
"mean": {
|
|
48
|
+
"factory": "./src/generators/mean/mean",
|
|
49
|
+
"schema": "./src/generators/mean/schema.json",
|
|
50
|
+
"description": "Generator that creates a MEAN fullstack solution.",
|
|
51
|
+
"hidden": true
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
54
|
}
|
package/package.json
CHANGED
|
@@ -56,6 +56,7 @@ function removeFiles(host, options) {
|
|
|
56
56
|
}
|
|
57
57
|
function default_1(host, options) {
|
|
58
58
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
var _a, _b;
|
|
59
60
|
const normalizedOptions = yield normalizeOptions(host, options);
|
|
60
61
|
const { applicationGenerator: initAngular } = yield Promise.resolve().then(() => require('@nx/angular/generators'));
|
|
61
62
|
yield initAngular(host, {
|
|
@@ -64,17 +65,31 @@ function default_1(host, options) {
|
|
|
64
65
|
linter: 'none',
|
|
65
66
|
directory: `apps/${options.name}`
|
|
66
67
|
});
|
|
67
|
-
(0, devkit_1.addDependenciesToPackageJson)(host, {
|
|
68
|
-
'@abgov/angular-components': '
|
|
69
|
-
'@abgov/
|
|
70
|
-
'
|
|
71
|
-
|
|
68
|
+
(0, devkit_1.addDependenciesToPackageJson)(host, {
|
|
69
|
+
'@abgov/angular-components': '5.2.1',
|
|
70
|
+
'@abgov/design-tokens': '1.8.0',
|
|
71
|
+
'@abgov/ui-components-common': '^2.0.0',
|
|
72
|
+
'@abgov/web-components': '1.39.3',
|
|
73
|
+
'keycloak-angular': '^19.0.2',
|
|
74
|
+
'keycloak-js': '^23.0.7',
|
|
75
|
+
'zone.js': '~0.15.0',
|
|
76
|
+
}, {});
|
|
72
77
|
const addedProxy = addFiles(host, normalizedOptions);
|
|
73
|
-
|
|
78
|
+
// @nx/angular generates app.ts/html/css/spec.ts (new naming) and nx-welcome.ts;
|
|
79
|
+
// our templates use app.component.* and don't use nx-welcome.
|
|
80
|
+
for (const file of ['app.ts', 'app.html', 'app.css', 'app.spec.ts', 'nx-welcome.ts']) {
|
|
81
|
+
host.delete(`${normalizedOptions.projectRoot}/src/app/${file}`);
|
|
82
|
+
}
|
|
74
83
|
const layout = (0, devkit_1.getWorkspaceLayout)(host);
|
|
75
84
|
const config = (0, devkit_1.readProjectConfiguration)(host, options.name);
|
|
76
|
-
|
|
85
|
+
// Remove the generated fileReplacements for production — single environment.ts
|
|
86
|
+
// is pre-populated from tenant config at generation time.
|
|
87
|
+
if ((_b = (_a = config.targets.build.configurations) === null || _a === void 0 ? void 0 : _a.production) === null || _b === void 0 ? void 0 : _b.fileReplacements) {
|
|
88
|
+
delete config.targets.build.configurations.production.fileReplacements;
|
|
89
|
+
}
|
|
90
|
+
config.targets.build.options = Object.assign(Object.assign({}, config.targets.build.options), { polyfills: ['zone.js'], assets: [
|
|
77
91
|
...config.targets.build.options.assets,
|
|
92
|
+
`${normalizedOptions.projectRoot}/src/silent-check-sso.html`,
|
|
78
93
|
{
|
|
79
94
|
glob: 'nginx.conf',
|
|
80
95
|
input: `${layout.appsDir}/${options.name}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"angular-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/angular-app/angular-app.ts"],"names":[],"mappings":";;AAkGA,
|
|
1
|
+
{"version":3,"file":"angular-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/angular-app/angular-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,36 +1,16 @@
|
|
|
1
|
-
.
|
|
2
|
-
|
|
3
|
-
margin: 0 auto;
|
|
4
|
-
padding: 0;
|
|
1
|
+
.app {
|
|
2
|
+
margin: 0;
|
|
5
3
|
}
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
padding: 3rem 1rem;
|
|
11
|
-
width: 640px;
|
|
12
|
-
}
|
|
5
|
+
main {
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-columns: repeat(6, auto);
|
|
13
8
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
@media (min-width: 1024px) {
|
|
22
|
-
.container {
|
|
23
|
-
padding: 3rem 1rem;
|
|
24
|
-
margin: 0 auto;
|
|
25
|
-
width: 1024px;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
@media (min-width: 1280px) {
|
|
29
|
-
.container {
|
|
30
|
-
padding: 3rem 1rem;
|
|
31
|
-
margin: 0 auto;
|
|
32
|
-
width: 1280px;
|
|
33
|
-
}
|
|
9
|
+
|
|
10
|
+
section {
|
|
11
|
+
grid-column: 2 / span 2;
|
|
12
|
+
margin-top: 40px;
|
|
13
|
+
margin-bottom: 40px;
|
|
34
14
|
}
|
|
35
15
|
|
|
36
16
|
.nextSteps {
|
|
@@ -42,27 +22,3 @@
|
|
|
42
22
|
margin-bottom: 0;
|
|
43
23
|
line-height: 50px;
|
|
44
24
|
}
|
|
45
|
-
|
|
46
|
-
.nextSteps li button {
|
|
47
|
-
margin: 20px 0 0 10px;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.footer {
|
|
52
|
-
background-color: #f1f1f1;
|
|
53
|
-
padding-top: 56px;
|
|
54
|
-
padding-bottom: 56px;
|
|
55
|
-
padding-left: 150px;
|
|
56
|
-
padding-right: 150px;
|
|
57
|
-
display: flex;
|
|
58
|
-
flex-direction: row;
|
|
59
|
-
flex-wrap: wrap;
|
|
60
|
-
border-bottom: 16px solid #0081a2;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
@media (max-width: 767px) {
|
|
64
|
-
/* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */
|
|
65
|
-
.body-content {
|
|
66
|
-
padding-top: 50px;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,58 +1,27 @@
|
|
|
1
1
|
<div class="app">
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
<goab-microsite-header type="alpha"></goab-microsite-header>
|
|
3
|
+
<goab-app-header url="/" heading="<%= projectName %>">
|
|
4
|
+
<goab-button-group alignment="end">
|
|
5
|
+
@if (userName) { <span>{{ userName }}</span> }
|
|
6
|
+
@if (!isLoggedIn) {
|
|
7
|
+
<goab-button type="tertiary" (onClick)="login()">Sign in</goab-button>
|
|
8
|
+
} @else {
|
|
9
|
+
<goab-button type="tertiary" (onClick)="logout()">Sign out</goab-button>
|
|
10
|
+
}
|
|
11
|
+
</goab-button-group>
|
|
12
|
+
</goab-app-header>
|
|
13
|
+
<goab-hero-banner #heroBanner heading="<%= projectName %>"></goab-hero-banner>
|
|
14
|
+
<main>
|
|
10
15
|
<section>
|
|
11
16
|
<h2>Welcome to {{ title }}!</h2>
|
|
12
|
-
<p>
|
|
13
|
-
Don't panic. Start editing the project to build your digital service.
|
|
14
|
-
</p>
|
|
17
|
+
<p>Don't panic. Start editing the project to build your digital service.</p>
|
|
15
18
|
<h3>A few things you might want to do next:</h3>
|
|
16
19
|
<ul class="nextSteps">
|
|
17
|
-
<li>
|
|
18
|
-
<li>
|
|
19
|
-
|
|
20
|
-
enabling CORS on the API.
|
|
21
|
-
</li>
|
|
22
|
-
<li>Add requests to public API resources:</li>
|
|
23
|
-
<li>Add requests to private API resources:</li>
|
|
20
|
+
<li>Register the '<%= projectName %>' client in your realm and set CLIENT_SECRET.</li>
|
|
21
|
+
<li>Add requests to public API resources: {{ publicResource }}</li>
|
|
22
|
+
<li>Add requests to private API resources: {{ privateResource }}</li>
|
|
24
23
|
</ul>
|
|
25
24
|
</section>
|
|
26
|
-
<
|
|
27
|
-
<div class="row">
|
|
28
|
-
<h3><a routerLink="/">Home</a> | <a routerLink="/protected">Admin Area</a></h3>
|
|
29
|
-
<div *ngIf="isLoggedIn(); else elseBlock">
|
|
30
|
-
<goa-button (click)="logout()">
|
|
31
|
-
Sign Out
|
|
32
|
-
</goa-button>
|
|
33
|
-
</div>
|
|
34
|
-
<ng-template #elseBlock>
|
|
35
|
-
<goa-button (click)="login()">
|
|
36
|
-
Sign In
|
|
37
|
-
</goa-button>
|
|
38
|
-
</ng-template>
|
|
39
|
-
<div class="col-sm-12 body-content">
|
|
40
|
-
<router-outlet></router-outlet>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
25
|
+
<router-outlet></router-outlet>
|
|
44
26
|
</main>
|
|
45
|
-
<footer class="footer">
|
|
46
|
-
<div class="goa-socialconnect">
|
|
47
|
-
<div class="goa-title">Connect with us on</div>
|
|
48
|
-
<ul>
|
|
49
|
-
<div>
|
|
50
|
-
<img src="./assets/github-1.svg" height="15px" />
|
|
51
|
-
<a href="https://github.com/abgov" rel="noreferrer" target="_blank">
|
|
52
|
-
GitHub
|
|
53
|
-
</a>
|
|
54
|
-
</div>
|
|
55
|
-
</ul>
|
|
56
|
-
</div>
|
|
57
|
-
</footer>
|
|
58
27
|
</div>
|
|
@@ -1,44 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ElementRef } from '@angular/core';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
import { provideRouter } from '@angular/router';
|
|
4
|
+
import { GoabAppHeader, GoabButton, GoabButtonGroup, GoabHeroBanner, GoabMicrositeHeader } from '@abgov/angular-components';
|
|
5
|
+
import Keycloak from 'keycloak-js';
|
|
2
6
|
import { AppComponent } from './app.component';
|
|
3
|
-
import { AngularComponentsModule } from '@abgov/angular-components';
|
|
4
|
-
import { RouterTestingModule } from '@angular/router/testing';
|
|
5
|
-
import {
|
|
6
|
-
BrowserDynamicTestingModule,
|
|
7
|
-
platformBrowserDynamicTesting
|
|
8
|
-
} from "@angular/platform-browser-dynamic/testing";
|
|
9
|
-
|
|
10
7
|
|
|
11
8
|
describe('AppComponent', () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const keycloakMock = {
|
|
10
|
+
authenticated: false,
|
|
11
|
+
tokenParsed: null,
|
|
12
|
+
login: jasmine.createSpy('login'),
|
|
13
|
+
logout: jasmine.createSpy('logout'),
|
|
14
|
+
};
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
beforeEach(async () => {
|
|
19
17
|
await TestBed.configureTestingModule({
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
imports: [AppComponent, GoabAppHeader, GoabButton, GoabButtonGroup, GoabHeroBanner, GoabMicrositeHeader],
|
|
19
|
+
providers: [
|
|
20
|
+
provideRouter([]),
|
|
21
|
+
{ provide: Keycloak, useValue: keycloakMock },
|
|
22
|
+
{ provide: ElementRef, useValue: { nativeElement: { querySelector: () => null } } },
|
|
23
|
+
],
|
|
22
24
|
}).compileComponents();
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
it('should create the app', () => {
|
|
26
28
|
const fixture = TestBed.createComponent(AppComponent);
|
|
27
|
-
|
|
28
|
-
expect(app).toBeTruthy();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it(`should have as title '<%= projectName %>'`, () => {
|
|
32
|
-
const fixture = TestBed.createComponent(AppComponent);
|
|
33
|
-
const app = fixture.componentInstance;
|
|
34
|
-
expect(app.title).toEqual('<%= projectName %>');
|
|
29
|
+
expect(fixture.componentInstance).toBeTruthy();
|
|
35
30
|
});
|
|
36
31
|
|
|
37
|
-
it('should
|
|
32
|
+
it('should have welcome heading', () => {
|
|
38
33
|
const fixture = TestBed.createComponent(AppComponent);
|
|
39
34
|
fixture.detectChanges();
|
|
40
|
-
const compiled = fixture.nativeElement;
|
|
41
|
-
expect(compiled.querySelector('h2')
|
|
35
|
+
const compiled = fixture.nativeElement as HTMLElement;
|
|
36
|
+
expect(compiled.querySelector('h2')?.textContent).toContain(
|
|
42
37
|
'Welcome to <%= projectName %>!'
|
|
43
38
|
);
|
|
44
39
|
});
|
|
@@ -1,36 +1,76 @@
|
|
|
1
|
-
import { Component, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { AfterViewInit, Component, ElementRef, inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
|
2
|
+
import { HttpClient } from '@angular/common/http';
|
|
3
|
+
import { RouterOutlet } from '@angular/router';
|
|
4
|
+
import {
|
|
5
|
+
GoabAppHeader,
|
|
6
|
+
GoabButton,
|
|
7
|
+
GoabButtonGroup,
|
|
8
|
+
GoabHeroBanner,
|
|
9
|
+
GoabMicrositeHeader,
|
|
10
|
+
} from '@abgov/angular-components';
|
|
11
|
+
import Keycloak from 'keycloak-js';
|
|
12
|
+
import { getAccessToken } from './user.slice';
|
|
5
13
|
|
|
6
14
|
@Component({
|
|
7
|
-
selector: '<%= projectName %>-
|
|
15
|
+
selector: '<%= projectName %>-root',
|
|
16
|
+
standalone: true,
|
|
17
|
+
imports: [RouterOutlet, GoabAppHeader, GoabButton, GoabButtonGroup, GoabHeroBanner, GoabMicrositeHeader],
|
|
8
18
|
templateUrl: './app.component.html',
|
|
9
|
-
|
|
19
|
+
styleUrl: './app.component.css',
|
|
10
20
|
})
|
|
21
|
+
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
22
|
+
title = '<%= projectName %>';
|
|
11
23
|
|
|
12
|
-
|
|
13
|
-
title = "<%= projectName %>";
|
|
24
|
+
@ViewChild('heroBanner', { read: ElementRef, static: false }) heroBannerRef!: ElementRef;
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
private http = inject(HttpClient);
|
|
27
|
+
private keycloak = inject(Keycloak);
|
|
28
|
+
private heroObserver?: MutationObserver;
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
publicResource = 'Not retrieved';
|
|
31
|
+
privateResource = 'Not retrieved';
|
|
32
|
+
|
|
33
|
+
get isLoggedIn(): boolean { return this.keycloak.authenticated ?? false; }
|
|
34
|
+
get userName(): string { return (this.keycloak.tokenParsed?.['name'] as string) ?? ''; }
|
|
20
35
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
36
|
+
ngOnInit() {
|
|
37
|
+
this.http.get<{ message: string }>('/api/v1/public').subscribe({
|
|
38
|
+
next: (data) => (this.publicResource = data.message),
|
|
39
|
+
error: () => (this.publicResource = 'Error loading data'),
|
|
40
|
+
});
|
|
41
|
+
if (this.isLoggedIn) {
|
|
42
|
+
getAccessToken().then((token) => {
|
|
43
|
+
this.http.get<{ message: string }>('/api/v1/private', {
|
|
44
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
45
|
+
}).subscribe({
|
|
46
|
+
next: (data) => (this.privateResource = data.message),
|
|
47
|
+
error: () => (this.privateResource = 'Error loading data'),
|
|
48
|
+
});
|
|
49
|
+
});
|
|
24
50
|
}
|
|
25
51
|
}
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
53
|
+
ngAfterViewInit() {
|
|
54
|
+
const host = this.heroBannerRef?.nativeElement;
|
|
55
|
+
if (!host) return;
|
|
56
|
+
const setBackground = (inner: Element) => {
|
|
57
|
+
(inner as any).backgroundurl = '/assets/banner.jpg';
|
|
58
|
+
this.heroObserver?.disconnect();
|
|
59
|
+
};
|
|
60
|
+
const existing = host.querySelector('goa-hero-banner');
|
|
61
|
+
if (existing) {
|
|
62
|
+
setBackground(existing);
|
|
63
|
+
} else {
|
|
64
|
+
this.heroObserver = new MutationObserver(() => {
|
|
65
|
+
const inner = host.querySelector('goa-hero-banner');
|
|
66
|
+
if (inner) setBackground(inner);
|
|
67
|
+
});
|
|
68
|
+
this.heroObserver.observe(host, { childList: true, subtree: true });
|
|
30
69
|
}
|
|
31
70
|
}
|
|
32
71
|
|
|
33
|
-
|
|
72
|
+
ngOnDestroy() { this.heroObserver?.disconnect(); }
|
|
34
73
|
|
|
35
|
-
|
|
74
|
+
login() { this.keycloak.login(); }
|
|
75
|
+
logout() { this.keycloak.logout({ redirectUri: window.location.origin }); }
|
|
36
76
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
|
2
|
+
import { provideRouter } from '@angular/router';
|
|
3
|
+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
4
|
+
import { includeBearerTokenInterceptor, provideKeycloak } from 'keycloak-angular';
|
|
5
|
+
|
|
6
|
+
import { routes } from './app.routes';
|
|
7
|
+
import { environment } from '../environments/environment';
|
|
8
|
+
|
|
9
|
+
export const appConfig: ApplicationConfig = {
|
|
10
|
+
providers: [
|
|
11
|
+
provideZoneChangeDetection({ eventCoalescing: true }),
|
|
12
|
+
provideRouter(routes),
|
|
13
|
+
provideHttpClient(withInterceptors([includeBearerTokenInterceptor])),
|
|
14
|
+
provideKeycloak({
|
|
15
|
+
config: {
|
|
16
|
+
url: environment.access.url,
|
|
17
|
+
realm: environment.access.realm,
|
|
18
|
+
clientId: environment.access.client_id,
|
|
19
|
+
},
|
|
20
|
+
initOptions: {
|
|
21
|
+
onLoad: 'check-sso',
|
|
22
|
+
pkceMethod: 'S256',
|
|
23
|
+
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
],
|
|
27
|
+
};
|
|
@@ -1,34 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Routes, RouterModule } from '@angular/router';
|
|
4
|
-
import { HomeComponent } from './home/home.component';
|
|
1
|
+
import { Routes } from '@angular/router';
|
|
2
|
+
import { createAuthGuard } from 'keycloak-angular';
|
|
5
3
|
import { ProtectedComponent } from './protected/protected.component';
|
|
6
|
-
import { AuthGuardService } from './services/auth-guard.service';
|
|
7
|
-
import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
|
|
8
4
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
path: 'protected',
|
|
17
|
-
component: ProtectedComponent,
|
|
18
|
-
canActivate: [AuthGuardService],
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
path: 'auth-callback',
|
|
22
|
-
component: AuthCallbackComponent,
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
path: 'signout/callback',
|
|
26
|
-
component: LogoutComponent,
|
|
27
|
-
},
|
|
28
|
-
];
|
|
5
|
+
const authGuard = createAuthGuard(async (_route, _state, { authenticated, keycloak }) => {
|
|
6
|
+
if (authenticated) return true;
|
|
7
|
+
await keycloak.login({ redirectUri: window.location.href });
|
|
8
|
+
return false;
|
|
9
|
+
});
|
|
29
10
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
})
|
|
34
|
-
export class AppRoutingModule {}
|
|
11
|
+
export const routes: Routes = [
|
|
12
|
+
{ path: 'protected', component: ProtectedComponent, canActivate: [authGuard] },
|
|
13
|
+
];
|
|
@@ -1,33 +1,19 @@
|
|
|
1
|
-
import { Component, OnInit } from '@angular/core';
|
|
1
|
+
import { Component, inject, OnInit, signal } from '@angular/core';
|
|
2
2
|
import { HttpClient } from '@angular/common/http';
|
|
3
3
|
|
|
4
4
|
@Component({
|
|
5
|
-
selector: '<%= projectName %>-
|
|
5
|
+
selector: '<%= projectName %>-home',
|
|
6
|
+
standalone: true,
|
|
6
7
|
templateUrl: 'home.component.html',
|
|
7
8
|
})
|
|
8
9
|
export class HomeComponent implements OnInit {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
private getHealthUrl = `${this.configData().tenantApi.host}/health`;
|
|
13
|
-
|
|
14
|
-
getHealth() {
|
|
15
|
-
const uptimePromise = this.http.get(this.getHealthUrl);
|
|
16
|
-
|
|
17
|
-
uptimePromise.subscribe(
|
|
18
|
-
(data: any) => {
|
|
19
|
-
this.uptime = data.uptime;
|
|
20
|
-
},
|
|
21
|
-
(err) => console.error(err),
|
|
22
|
-
() => console.log('done loading uptime')
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
configData () {
|
|
27
|
-
return JSON.parse(localStorage.getItem('envData') || "\"\"");
|
|
28
|
-
}
|
|
10
|
+
private http = inject(HttpClient);
|
|
11
|
+
publicMessage = signal('Not retrieved');
|
|
29
12
|
|
|
30
13
|
ngOnInit() {
|
|
31
|
-
this.
|
|
14
|
+
this.http.get<{ message: string }>('/api/v1/public').subscribe({
|
|
15
|
+
next: (data) => this.publicMessage.set(data.message),
|
|
16
|
+
error: () => this.publicMessage.set('Error loading data'),
|
|
17
|
+
});
|
|
32
18
|
}
|
|
33
19
|
}
|
package/src/generators/angular-app/files/src/app/protected/protected.component.spec.ts__tmpl__
CHANGED
|
@@ -1,52 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
3
|
+
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
|
4
|
+
import { Keycloak } from 'keycloak-angular';
|
|
2
5
|
import { ProtectedComponent } from './protected.component';
|
|
3
|
-
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
|
4
|
-
import TenantService from '../tenant.service';
|
|
5
|
-
import {
|
|
6
|
-
BrowserDynamicTestingModule,
|
|
7
|
-
platformBrowserDynamicTesting
|
|
8
|
-
} from "@angular/platform-browser-dynamic/testing";
|
|
9
6
|
|
|
10
7
|
describe('ProtectedComponent', () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
const keycloakMock = {
|
|
9
|
+
authenticated: true,
|
|
10
|
+
tokenParsed: { name: 'Test User' },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let httpTesting: HttpTestingController;
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
await TestBed.configureTestingModule({
|
|
17
|
+
imports: [ProtectedComponent],
|
|
18
|
+
providers: [
|
|
19
|
+
provideHttpClient(),
|
|
20
|
+
provideHttpClientTesting(),
|
|
21
|
+
{ provide: Keycloak, useValue: keycloakMock },
|
|
22
|
+
],
|
|
23
|
+
}).compileComponents();
|
|
24
|
+
|
|
25
|
+
httpTesting = TestBed.inject(HttpTestingController);
|
|
18
26
|
});
|
|
19
|
-
beforeEach(
|
|
20
|
-
waitForAsync(() => {
|
|
21
|
-
TestBed.configureTestingModule({
|
|
22
|
-
declarations: [ProtectedComponent],
|
|
23
|
-
providers: [ProtectedComponent, TenantService],
|
|
24
|
-
imports: [HttpClientTestingModule],
|
|
25
|
-
}).compileComponents();
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
})
|
|
29
|
-
);
|
|
28
|
+
afterEach(() => httpTesting.verify());
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
fixture = TestBed.createComponent(ProtectedComponent);
|
|
30
|
+
it('should create', () => {
|
|
31
|
+
const fixture = TestBed.createComponent(ProtectedComponent);
|
|
33
32
|
fixture.detectChanges();
|
|
33
|
+
httpTesting.expectOne('/api/v1/private').flush({ message: 'Hello' });
|
|
34
|
+
expect(fixture.componentInstance).toBeTruthy();
|
|
34
35
|
});
|
|
35
|
-
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
httpTestingController.verify();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should create', (inject([TenantService, HttpTestingController],
|
|
41
|
-
() => {
|
|
42
|
-
const mockTenant = { status: 200, statusText: 'OK'}
|
|
43
|
-
const data = { name: 'Child Services' }
|
|
44
|
-
const req = httpTestingController.expectOne(
|
|
45
|
-
'http://localhost:3333/api/tenant/v1/realm/123'
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
expect(req.request.method).toEqual('GET');
|
|
49
|
-
|
|
50
|
-
req.flush(data, mockTenant);
|
|
51
|
-
})));
|
|
52
36
|
});
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { Component, OnInit } from '@angular/core';
|
|
2
|
-
import
|
|
1
|
+
import { Component, inject, OnInit, signal } from '@angular/core';
|
|
2
|
+
import { HttpClient } from '@angular/common/http';
|
|
3
|
+
import Keycloak from 'keycloak-js';
|
|
3
4
|
|
|
4
5
|
@Component({
|
|
5
|
-
selector: '
|
|
6
|
+
selector: '<%= projectName %>-protected',
|
|
7
|
+
standalone: true,
|
|
6
8
|
templateUrl: './protected.component.html',
|
|
7
|
-
|
|
9
|
+
styleUrl: './protected.component.css',
|
|
8
10
|
})
|
|
9
11
|
export class ProtectedComponent implements OnInit {
|
|
10
|
-
|
|
12
|
+
private http = inject(HttpClient);
|
|
13
|
+
private keycloak = inject(Keycloak);
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
privateMessage = signal('Not retrieved');
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
(data: any) => {
|
|
17
|
-
this.tenant = data.tenant;
|
|
18
|
-
},
|
|
19
|
-
(err) => console.error(err),
|
|
20
|
-
() => console.log('done loading tenant')
|
|
21
|
-
);
|
|
17
|
+
get userName(): string {
|
|
18
|
+
return (this.keycloak.tokenParsed?.['name'] as string) ?? '';
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
ngOnInit() {
|
|
25
|
-
this.
|
|
22
|
+
this.http.get<{ message: string }>('/api/v1/private').subscribe({
|
|
23
|
+
next: (data) => this.privateMessage.set(data.message),
|
|
24
|
+
error: () => this.privateMessage.set('Error loading data'),
|
|
25
|
+
});
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (this.authService.isLoggedIn()) {
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
console.log('start authentication...');
|
|
18
|
-
this.authService.startAuthentication();
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
1
|
+
// Auth guard is configured in app.routes.ts using keycloak-angular's createAuthGuard.
|
|
2
|
+
// Add this file if you need a reusable, injectable guard:
|
|
3
|
+
//
|
|
4
|
+
// import { Injectable } from '@angular/core';
|
|
5
|
+
// import { Router } from '@angular/router';
|
|
6
|
+
// import { createAuthGuard } from 'keycloak-angular';
|
|
7
|
+
//
|
|
8
|
+
// export const authGuard = createAuthGuard(async ({ keycloak }) => {
|
|
9
|
+
// if (keycloak.authenticated) return true;
|
|
10
|
+
// await keycloak.login({ redirectUri: window.location.href });
|
|
11
|
+
// return false;
|
|
12
|
+
// });
|
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
// This file can be replaced during build by using the `fileReplacements` array.
|
|
2
|
-
// When building for production, this file is replaced with `environment.prod.ts`.
|
|
3
|
-
|
|
4
1
|
export const environment = {
|
|
5
2
|
production: false,
|
|
6
3
|
access: {
|
|
7
|
-
url: '<%= accessServiceUrl
|
|
4
|
+
url: '<%= accessServiceUrl %>/auth',
|
|
8
5
|
realm: '<%= tenantRealm %>',
|
|
9
|
-
client_id: 'urn:ads:<%= tenant %>:<%= projectName %>'
|
|
6
|
+
client_id: 'urn:ads:<%= tenant %>:<%= projectName %>',
|
|
10
7
|
},
|
|
11
|
-
tenantApi: {
|
|
12
|
-
host: "http://localhost:3333",
|
|
13
|
-
endpoints: {
|
|
14
|
-
tenantNameByRealm: "/api/tenant/v1/realm",
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
8
|
};
|
|
@@ -6,16 +6,10 @@
|
|
|
6
6
|
<base href="/" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
8
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
|
9
|
-
<script
|
|
10
|
-
|
|
11
|
-
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"
|
|
12
|
-
></script>
|
|
13
|
-
<script
|
|
14
|
-
nomodule
|
|
15
|
-
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"
|
|
16
|
-
></script>
|
|
9
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/ionicons@latest/dist/ionicons/ionicons.esm.js"></script>
|
|
10
|
+
<script nomodule src="https://cdn.jsdelivr.net/npm/ionicons@latest/dist/ionicons/ionicons.js"></script>
|
|
17
11
|
</head>
|
|
18
12
|
<body>
|
|
19
|
-
<<%= projectName %>-
|
|
13
|
+
<<%= projectName %>-root></<%= projectName %>-root>
|
|
20
14
|
</body>
|
|
21
15
|
</html>
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import 'zone.js';
|
|
2
|
+
import '@abgov/web-components';
|
|
3
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
4
|
+
import { AppComponent } from './app/app.component';
|
|
5
|
+
import { appConfig } from './app/app.config';
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (environment.production) {
|
|
8
|
-
enableProdMode();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
platformBrowserDynamic()
|
|
12
|
-
.bootstrapModule(AppModule)
|
|
13
|
-
.catch((err) => console.error(err));
|
|
7
|
+
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
|
8
|
+
console.error(err)
|
|
9
|
+
);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = default_1;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const nx_oc_1 = require("@abgov/nx-oc");
|
|
7
|
+
const angular_app_1 = require("../angular-app/angular-app");
|
|
8
|
+
const express_service_1 = require("../express-service/express-service");
|
|
9
|
+
function normalizeOptions(host, options) {
|
|
10
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
11
|
+
const adsp = yield (0, nx_oc_1.getAdspConfiguration)(host, options);
|
|
12
|
+
return Object.assign(Object.assign({}, options), { adsp });
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function default_1(host, options) {
|
|
16
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
const normalizedOptions = yield normalizeOptions(host, options);
|
|
18
|
+
const projectName = (0, devkit_1.names)(options.name).fileName;
|
|
19
|
+
yield (0, express_service_1.default)(host, Object.assign(Object.assign({}, normalizedOptions), { name: `${projectName}-service` }));
|
|
20
|
+
yield (0, angular_app_1.default)(host, Object.assign(Object.assign({}, normalizedOptions), { name: `${projectName}-app`, proxy: {
|
|
21
|
+
location: '/api/',
|
|
22
|
+
proxyPass: `http://${projectName}-service:3333/${projectName}-service/`,
|
|
23
|
+
} }));
|
|
24
|
+
yield (0, devkit_1.formatFiles)(host);
|
|
25
|
+
return () => {
|
|
26
|
+
(0, devkit_1.installPackagesTask)(host);
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=mean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mean.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/mean/mean.ts"],"names":[],"mappings":";;AAcA,4BAuBC;;AArCD,uCAA2E;AAC3E,wCAAoD;AACpD,4DAAwD;AACxD,wEAAoE;AAGpE,SAAe,gBAAgB,CAC7B,IAAU,EACV,OAAe;;QAEf,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvD,uCAAY,OAAO,KAAE,IAAI,IAAG;IAC9B,CAAC;CAAA;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChE,MAAM,WAAW,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;QAEjD,MAAM,IAAA,yBAAkB,EAAC,IAAI,kCACxB,iBAAiB,KACpB,IAAI,EAAE,GAAG,WAAW,UAAU,IAC9B,CAAC;QAEH,MAAM,IAAA,qBAAc,EAAC,IAAI,kCACpB,iBAAiB,KACpB,IAAI,EAAE,GAAG,WAAW,MAAM,EAC1B,KAAK,EAAE;gBACL,QAAQ,EAAE,OAAO;gBACjB,SAAS,EAAE,UAAU,WAAW,iBAAiB,WAAW,WAAW;aACxE,IACD,CAAC;QAEH,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;QAExB,OAAO,GAAG,EAAE;YACV,IAAA,4BAAmB,EAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readProjectConfiguration } from '@nx/devkit';
|
|
2
|
+
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
3
|
+
import * as utils from '@abgov/nx-oc';
|
|
4
|
+
import { environments } from '@abgov/nx-oc';
|
|
5
|
+
import { Schema } from './schema';
|
|
6
|
+
import generator from './mean';
|
|
7
|
+
|
|
8
|
+
jest.mock('@abgov/nx-oc');
|
|
9
|
+
const utilsMock = utils as jest.Mocked<typeof utils>;
|
|
10
|
+
utilsMock.getAdspConfiguration.mockResolvedValue({
|
|
11
|
+
tenant: 'test',
|
|
12
|
+
tenantRealm: 'test',
|
|
13
|
+
accessServiceUrl: environments.test.accessServiceUrl,
|
|
14
|
+
directoryServiceUrl: environments.test.directoryServiceUrl,
|
|
15
|
+
});
|
|
16
|
+
utilsMock.deploymentGenerator.mockResolvedValue(undefined);
|
|
17
|
+
|
|
18
|
+
describe('MEAN Generator', () => {
|
|
19
|
+
const options: Schema = {
|
|
20
|
+
name: 'test',
|
|
21
|
+
env: 'dev',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
it('can run', async () => {
|
|
25
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
26
|
+
await generator(host, options);
|
|
27
|
+
|
|
28
|
+
const appConfig = readProjectConfiguration(host, 'test-app');
|
|
29
|
+
expect(appConfig.root).toBe('apps/test-app');
|
|
30
|
+
|
|
31
|
+
const serviceConfig = readProjectConfiguration(host, 'test-service');
|
|
32
|
+
expect(serviceConfig.root).toBe('apps/test-service');
|
|
33
|
+
|
|
34
|
+
expect(host.exists('apps/test-app/nginx.conf')).toBeTruthy();
|
|
35
|
+
const nginxConf = host.read('apps/test-app/nginx.conf').toString();
|
|
36
|
+
expect(nginxConf).toContain('http://test-service:3333/');
|
|
37
|
+
}, 30000);
|
|
38
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"cli": "nx",
|
|
4
|
+
"$id": "mean",
|
|
5
|
+
"title": "MEAN fullstack generator",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Name of the project.",
|
|
11
|
+
"$default": { "$source": "argv", "index": 0 },
|
|
12
|
+
"x-prompt": "What name would you like to use for the project?"
|
|
13
|
+
},
|
|
14
|
+
"env": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "ADSP environment to initialize the project for.",
|
|
17
|
+
"enum": ["dev", "test", "prod"],
|
|
18
|
+
"default": "prod"
|
|
19
|
+
},
|
|
20
|
+
"accessToken": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Access token for ADSP tenant configuration (skips interactive login)."
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": ["name"]
|
|
26
|
+
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { NgModule, APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
2
|
-
import { BrowserModule } from '@angular/platform-browser';
|
|
3
|
-
import "@abgov/web-components";
|
|
4
|
-
import { AngularComponentsModule } from '@abgov/angular-components';
|
|
5
|
-
|
|
6
|
-
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
|
7
|
-
import { AuthInterceptor } from './auth.interceptor';
|
|
8
|
-
import { FormsModule } from '@angular/forms';
|
|
9
|
-
import { AppRoutingModule } from './app.routes';
|
|
10
|
-
import { ProtectedComponent } from './protected/protected.component';
|
|
11
|
-
import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
|
|
12
|
-
import { AuthGuardService } from './services/auth-guard.service';
|
|
13
|
-
import { AuthService } from './services/auth.service';
|
|
14
|
-
import { AppComponent } from './app.component';
|
|
15
|
-
import { Config } from '../environments/config';
|
|
16
|
-
|
|
17
|
-
@NgModule({
|
|
18
|
-
imports: [
|
|
19
|
-
AngularComponentsModule,
|
|
20
|
-
BrowserModule,
|
|
21
|
-
HttpClientModule,
|
|
22
|
-
FormsModule,
|
|
23
|
-
AppRoutingModule,
|
|
24
|
-
],
|
|
25
|
-
providers: [
|
|
26
|
-
AuthGuardService,
|
|
27
|
-
AuthService,
|
|
28
|
-
Config,
|
|
29
|
-
{
|
|
30
|
-
provide: APP_INITIALIZER,
|
|
31
|
-
useFactory: initializeApp,
|
|
32
|
-
deps: [Config],
|
|
33
|
-
multi: true,
|
|
34
|
-
},
|
|
35
|
-
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
|
36
|
-
],
|
|
37
|
-
declarations: [AppComponent, AuthCallbackComponent, ProtectedComponent],
|
|
38
|
-
bootstrap: [AppComponent],
|
|
39
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
40
|
-
})
|
|
41
|
-
export class AppModule {}
|
|
42
|
-
|
|
43
|
-
export function initializeApp(config: Config) {
|
|
44
|
-
return (): Promise<any> => {
|
|
45
|
-
return config.Init();
|
|
46
|
-
};
|
|
47
|
-
}
|
package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.html__tmpl__
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<h3>Redirected to auth-callback route!</h3>
|
|
2
|
-
<p>Now you are logged in and allowed to access the "protected" route...</p>
|
|
3
|
-
<div class="info">
|
|
4
|
-
<div>
|
|
5
|
-
<div>Token Type:</div>
|
|
6
|
-
<div class="token">{{ tokenType }}</div>
|
|
7
|
-
</div>
|
|
8
|
-
<div>
|
|
9
|
-
<div>Token:</div>
|
|
10
|
-
<div class="token">{{ accessToken }}</div>
|
|
11
|
-
</div>
|
|
12
|
-
</div>
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
import { AuthCallbackComponent } from './auth-callback.component';
|
|
3
|
-
import {
|
|
4
|
-
BrowserDynamicTestingModule,
|
|
5
|
-
platformBrowserDynamicTesting
|
|
6
|
-
} from "@angular/platform-browser-dynamic/testing";
|
|
7
|
-
|
|
8
|
-
describe('AuthCallbackComponent', () => {
|
|
9
|
-
let component: AuthCallbackComponent;
|
|
10
|
-
let fixture: ComponentFixture<AuthCallbackComponent>;
|
|
11
|
-
let envData = {"production":false,"access":{"url":"https://testurl.com","realm":"123","client_id":"urn:ads:platform:tenant-admin-app"},"tenantApi":{"host":"http://localhost:3333","endpoints":{"tenantNameByRealm":"/api/tenant/v1/realm"}}}
|
|
12
|
-
|
|
13
|
-
localStorage.setItem('envData', JSON.stringify(envData));
|
|
14
|
-
|
|
15
|
-
beforeEach((() => {
|
|
16
|
-
TestBed.initTestEnvironment(
|
|
17
|
-
BrowserDynamicTestingModule,
|
|
18
|
-
platformBrowserDynamicTesting()
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
TestBed.configureTestingModule({
|
|
22
|
-
declarations: [AuthCallbackComponent],
|
|
23
|
-
}).compileComponents();
|
|
24
|
-
|
|
25
|
-
fixture = TestBed.createComponent(AuthCallbackComponent);
|
|
26
|
-
component = fixture.componentInstance;
|
|
27
|
-
fixture.detectChanges();
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
it('should create', () => {
|
|
31
|
-
expect(component).toBeTruthy();
|
|
32
|
-
});
|
|
33
|
-
});
|
package/src/generators/angular-app/files/src/app/auth-callback/auth-callback.component.ts__tmpl__
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Injectable, Component, OnInit } from '@angular/core';
|
|
2
|
-
import { AuthService } from '../services/auth.service';
|
|
3
|
-
import { User } from 'oidc-client';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: '<%= projectName %>-app-auth-callback',
|
|
7
|
-
templateUrl: './auth-callback.component.html',
|
|
8
|
-
styleUrls: ['./auth-callback.component.css'],
|
|
9
|
-
})
|
|
10
|
-
@Injectable({
|
|
11
|
-
providedIn: 'root',
|
|
12
|
-
})
|
|
13
|
-
export class AuthCallbackComponent implements OnInit {
|
|
14
|
-
public tokenType = '';
|
|
15
|
-
public accessToken = '';
|
|
16
|
-
|
|
17
|
-
constructor(private authService: AuthService) {}
|
|
18
|
-
|
|
19
|
-
ngOnInit() {
|
|
20
|
-
this.getAuth();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
getAuth() {
|
|
24
|
-
if (!this.authService.isLoggedIn()) {
|
|
25
|
-
this.authService
|
|
26
|
-
.completeAuthentication()
|
|
27
|
-
.catch((error) => {
|
|
28
|
-
console.error(`could not complete authentication: ${error}`);
|
|
29
|
-
})
|
|
30
|
-
.then(() => {
|
|
31
|
-
this.setTokens();
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
setTokens() {
|
|
37
|
-
if (this.authService.isLoggedIn()) {
|
|
38
|
-
const user: User | null = this.authService.getUser();
|
|
39
|
-
if (user) {
|
|
40
|
-
this.accessToken = user.access_token;
|
|
41
|
-
this.tokenType = user.token_type;
|
|
42
|
-
}
|
|
43
|
-
return true;
|
|
44
|
-
} else {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
|
2
|
-
import { Injectable } from '@angular/core';
|
|
3
|
-
import { Observable } from 'rxjs';
|
|
4
|
-
import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
|
|
5
|
-
|
|
6
|
-
@Injectable({ providedIn: 'root' })
|
|
7
|
-
export class AuthInterceptor implements HttpInterceptor {
|
|
8
|
-
|
|
9
|
-
constructor(private authCallBackComponent: AuthCallbackComponent) { }
|
|
10
|
-
|
|
11
|
-
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
|
12
|
-
if (this.authCallBackComponent.setTokens()) {
|
|
13
|
-
req = req.clone({
|
|
14
|
-
setHeaders: {
|
|
15
|
-
'Content-Type' : 'application/json; charset=utf-8',
|
|
16
|
-
'Accept' : 'application/json',
|
|
17
|
-
'Authorization': `Bearer ${this.authCallBackComponent.accessToken}`,
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return next.handle(req);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<div>Logged Out !!</div>
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@angular/core';
|
|
2
|
-
import { UserManager, UserManagerSettings, User } from 'oidc-client';
|
|
3
|
-
|
|
4
|
-
@Injectable({
|
|
5
|
-
providedIn: 'root',
|
|
6
|
-
})
|
|
7
|
-
export class AuthService {
|
|
8
|
-
private manager = new UserManager(getClientSettings());
|
|
9
|
-
private user: User | null = null;
|
|
10
|
-
|
|
11
|
-
constructor() {
|
|
12
|
-
this.manager.getUser().then((user) => {
|
|
13
|
-
this.user = user;
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
isLoggedIn(): boolean {
|
|
18
|
-
return this.user != null && !this.user.expired;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getUser(): User | null {
|
|
22
|
-
return this.user;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
startAuthentication(): Promise<void> {
|
|
26
|
-
return this.manager.signinRedirect();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
completeAuthentication(): Promise<void> {
|
|
30
|
-
return this.manager.signinRedirectCallback().then((user) => {
|
|
31
|
-
this.user = user;
|
|
32
|
-
console.log(JSON.stringify(this.user));
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
logout() {
|
|
37
|
-
return this.manager.signoutRedirect();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function getClientSettings(): UserManagerSettings {
|
|
42
|
-
const data = JSON.parse(localStorage.getItem('envData') || "\"\"");
|
|
43
|
-
const appUrl = `${window.location.protocol}//${window.location.hostname}${
|
|
44
|
-
window.location.port ? `:${window.location.port}` : ''
|
|
45
|
-
}`;
|
|
46
|
-
|
|
47
|
-
const settings = {
|
|
48
|
-
client_id: data.access.client_id,
|
|
49
|
-
redirect_uri: `${appUrl}/auth-callback`,
|
|
50
|
-
post_logout_redirect_uri: `${appUrl}/signout/callback`,
|
|
51
|
-
silent_redirect_uri: `${appUrl}/auth-callback`,
|
|
52
|
-
response_type: 'code',
|
|
53
|
-
authority: `${data.access.url}/auth/realms/${data.access.realm}`,
|
|
54
|
-
automaticSilentRenew: true,
|
|
55
|
-
};
|
|
56
|
-
return settings;
|
|
57
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@angular/core';
|
|
2
|
-
import { HttpClient } from '@angular/common/http';
|
|
3
|
-
|
|
4
|
-
@Injectable({ providedIn: 'root' })
|
|
5
|
-
export default class TenantService {
|
|
6
|
-
|
|
7
|
-
private getTenantNameUrl = `${this.configData().tenantApi.host}${this.configData().tenantApi.endpoints.tenantNameByRealm}/${this.configData().access.realm}`;
|
|
8
|
-
|
|
9
|
-
constructor(private http: HttpClient) {}
|
|
10
|
-
|
|
11
|
-
/** GET tenants from the server */
|
|
12
|
-
getTenant() {
|
|
13
|
-
return this.http.get(this.getTenantNameUrl);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
configData () {
|
|
17
|
-
return JSON.parse(localStorage.getItem('envData') || "\"\"");
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { environment } from './environment';
|
|
2
|
-
import { Injectable } from '@angular/core';
|
|
3
|
-
|
|
4
|
-
@Injectable()
|
|
5
|
-
export class Config {
|
|
6
|
-
constructor() {}
|
|
7
|
-
|
|
8
|
-
Init() {
|
|
9
|
-
return new Promise<void>((resolve, reject) => {
|
|
10
|
-
console.log("AppInitService.init() called");
|
|
11
|
-
|
|
12
|
-
fetch('/config/config.json')
|
|
13
|
-
.then((res) => {
|
|
14
|
-
return (res.ok ? res.json() : environment)
|
|
15
|
-
}).then(( envData ) => {
|
|
16
|
-
localStorage.setItem('envData', JSON.stringify(envData));
|
|
17
|
-
resolve();
|
|
18
|
-
})
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|