Pārlūkot izejas kodu

Auth0 integration with login

Lukas Angerer 3 gadi atpakaļ
vecāks
revīzija
0b00da52d8

+ 6 - 0
.gitignore

@@ -446,3 +446,9 @@ $RECYCLE.BIN/
 ## Angular
 ##
 .angular/
+
+
+##
+## Configs & Secrets
+##
+auth_config.json

+ 7 - 1
src/RunnersMeet.Client/angular.json

@@ -48,7 +48,13 @@
 									"maximumError": "4kb"
 								}
 							],
-							"outputHashing": "all"
+							"outputHashing": "all",
+							"fileReplacements": [
+								{
+									"replace": "src/env/environment.ts",
+									"with": "src/env/environment.prod.ts"
+								}
+							]
 						},
 						"development": {
 							"buildOptimizer": false,

+ 18 - 0
src/RunnersMeet.Client/authConfig.ts

@@ -0,0 +1,18 @@
+
+export interface AuthConfig {
+	domain: string;
+	clientId: string;
+	audience: string;
+	apiUri: string;
+	appUri: string;
+	errorPath: string;
+};
+
+export const authConfig: AuthConfig = {
+	domain: "dev-2ls6voifhbt37usw.eu.auth0.com",
+	clientId: "51IAWRARoNhevdmXODwxOb6xV2KEu1MO",
+	audience: "https://runners.larcanum.net",
+	apiUri: "https://localhost:7247",
+	appUri: "http://localhost:4200",
+	errorPath: "/error"
+};

+ 136 - 4
src/RunnersMeet.Client/package-lock.json

@@ -16,6 +16,7 @@
 				"@angular/platform-browser": "^15.0.0",
 				"@angular/platform-browser-dynamic": "^15.0.0",
 				"@angular/router": "^15.0.0",
+				"@auth0/auth0-angular": "^1.11.1",
 				"rxjs": "~7.5.0",
 				"tslib": "^2.3.0",
 				"zone.js": "~0.12.0"
@@ -541,6 +542,34 @@
 			"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==",
 			"dev": true
 		},
+		"node_modules/@auth0/auth0-angular": {
+			"version": "1.11.1",
+			"resolved": "https://registry.npmjs.org/@auth0/auth0-angular/-/auth0-angular-1.11.1.tgz",
+			"integrity": "sha512-q02tvjbela8TpJEaHbaw1vNkORLTOcjr/mRnxzE1rvIUbSfUGKueR8BTcT+a9mX0KIfF3lArGSF0p01bDO/P3w==",
+			"dependencies": {
+				"@auth0/auth0-spa-js": "^1.22.0",
+				"tslib": "^2.0.0"
+			},
+			"peerDependencies": {
+				"@angular/common": ">=12 <=15",
+				"@angular/core": ">=12 <=15",
+				"@angular/router": ">=12 <=15"
+			}
+		},
+		"node_modules/@auth0/auth0-spa-js": {
+			"version": "1.22.5",
+			"resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.22.5.tgz",
+			"integrity": "sha512-6gaQcd+Eb8ZBcdQkrrm9undM7dY/rPvVdQN8s7rxxrviUCs7OopEygsfSkHf67IP4HtlCiE8dSW5/AipRUOw/A==",
+			"dependencies": {
+				"abortcontroller-polyfill": "^1.7.3",
+				"browser-tabs-lock": "^1.2.15",
+				"core-js": "^3.25.1",
+				"es-cookie": "~1.3.2",
+				"fast-text-encoding": "^1.0.6",
+				"promise-polyfill": "^8.2.3",
+				"unfetch": "^4.2.0"
+			}
+		},
 		"node_modules/@babel/code-frame": {
 			"version": "7.18.6",
 			"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@@ -3074,6 +3103,11 @@
 			"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
 			"dev": true
 		},
+		"node_modules/abortcontroller-polyfill": {
+			"version": "1.7.5",
+			"resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz",
+			"integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ=="
+		},
 		"node_modules/accepts": {
 			"version": "1.3.8",
 			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -3594,6 +3628,15 @@
 				"node": ">=8"
 			}
 		},
+		"node_modules/browser-tabs-lock": {
+			"version": "1.2.15",
+			"resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz",
+			"integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==",
+			"hasInstallScript": true,
+			"dependencies": {
+				"lodash": ">=4.17.21"
+			}
+		},
 		"node_modules/browserslist": {
 			"version": "4.21.4",
 			"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
@@ -4137,6 +4180,16 @@
 				"node": ">=10.13.0"
 			}
 		},
+		"node_modules/core-js": {
+			"version": "3.26.1",
+			"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz",
+			"integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==",
+			"hasInstallScript": true,
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/core-js"
+			}
+		},
 		"node_modules/core-js-compat": {
 			"version": "3.26.1",
 			"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz",
@@ -4726,6 +4779,11 @@
 				"is-arrayish": "^0.2.1"
 			}
 		},
+		"node_modules/es-cookie": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz",
+			"integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q=="
+		},
 		"node_modules/es-module-lexer": {
 			"version": "0.9.3",
 			"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
@@ -5391,6 +5449,11 @@
 			"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
 			"dev": true
 		},
+		"node_modules/fast-text-encoding": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz",
+			"integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w=="
+		},
 		"node_modules/fastq": {
 			"version": "1.14.0",
 			"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
@@ -7119,8 +7182,7 @@
 		"node_modules/lodash": {
 			"version": "4.17.21",
 			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-			"dev": true
+			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
 		},
 		"node_modules/lodash.debounce": {
 			"version": "4.0.8",
@@ -8973,6 +9035,11 @@
 			"integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
 			"dev": true
 		},
+		"node_modules/promise-polyfill": {
+			"version": "8.2.3",
+			"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz",
+			"integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg=="
+		},
 		"node_modules/promise-retry": {
 			"version": "2.0.1",
 			"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
@@ -10590,6 +10657,11 @@
 				"node": "*"
 			}
 		},
+		"node_modules/unfetch": {
+			"version": "4.2.0",
+			"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
+			"integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA=="
+		},
 		"node_modules/unicode-canonical-property-names-ecmascript": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@@ -11564,6 +11636,29 @@
 			"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==",
 			"dev": true
 		},
+		"@auth0/auth0-angular": {
+			"version": "1.11.1",
+			"resolved": "https://registry.npmjs.org/@auth0/auth0-angular/-/auth0-angular-1.11.1.tgz",
+			"integrity": "sha512-q02tvjbela8TpJEaHbaw1vNkORLTOcjr/mRnxzE1rvIUbSfUGKueR8BTcT+a9mX0KIfF3lArGSF0p01bDO/P3w==",
+			"requires": {
+				"@auth0/auth0-spa-js": "^1.22.0",
+				"tslib": "^2.0.0"
+			}
+		},
+		"@auth0/auth0-spa-js": {
+			"version": "1.22.5",
+			"resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.22.5.tgz",
+			"integrity": "sha512-6gaQcd+Eb8ZBcdQkrrm9undM7dY/rPvVdQN8s7rxxrviUCs7OopEygsfSkHf67IP4HtlCiE8dSW5/AipRUOw/A==",
+			"requires": {
+				"abortcontroller-polyfill": "^1.7.3",
+				"browser-tabs-lock": "^1.2.15",
+				"core-js": "^3.25.1",
+				"es-cookie": "~1.3.2",
+				"fast-text-encoding": "^1.0.6",
+				"promise-polyfill": "^8.2.3",
+				"unfetch": "^4.2.0"
+			}
+		},
 		"@babel/code-frame": {
 			"version": "7.18.6",
 			"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@@ -13474,6 +13569,11 @@
 			"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
 			"dev": true
 		},
+		"abortcontroller-polyfill": {
+			"version": "1.7.5",
+			"resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz",
+			"integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ=="
+		},
 		"accepts": {
 			"version": "1.3.8",
 			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -13860,6 +13960,14 @@
 				"fill-range": "^7.0.1"
 			}
 		},
+		"browser-tabs-lock": {
+			"version": "1.2.15",
+			"resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz",
+			"integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==",
+			"requires": {
+				"lodash": ">=4.17.21"
+			}
+		},
 		"browserslist": {
 			"version": "4.21.4",
 			"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
@@ -14266,6 +14374,11 @@
 				}
 			}
 		},
+		"core-js": {
+			"version": "3.26.1",
+			"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz",
+			"integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA=="
+		},
 		"core-js-compat": {
 			"version": "3.26.1",
 			"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz",
@@ -14715,6 +14828,11 @@
 				"is-arrayish": "^0.2.1"
 			}
 		},
+		"es-cookie": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz",
+			"integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q=="
+		},
 		"es-module-lexer": {
 			"version": "0.9.3",
 			"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
@@ -15133,6 +15251,11 @@
 			"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
 			"dev": true
 		},
+		"fast-text-encoding": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz",
+			"integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w=="
+		},
 		"fastq": {
 			"version": "1.14.0",
 			"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
@@ -16435,8 +16558,7 @@
 		"lodash": {
 			"version": "4.17.21",
 			"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-			"dev": true
+			"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
 		},
 		"lodash.debounce": {
 			"version": "4.0.8",
@@ -17829,6 +17951,11 @@
 			"integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
 			"dev": true
 		},
+		"promise-polyfill": {
+			"version": "8.2.3",
+			"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz",
+			"integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg=="
+		},
 		"promise-retry": {
 			"version": "2.0.1",
 			"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
@@ -19045,6 +19172,11 @@
 			"integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==",
 			"dev": true
 		},
+		"unfetch": {
+			"version": "4.2.0",
+			"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
+			"integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA=="
+		},
 		"unicode-canonical-property-names-ecmascript": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",

+ 1 - 0
src/RunnersMeet.Client/package.json

@@ -18,6 +18,7 @@
 		"@angular/platform-browser": "^15.0.0",
 		"@angular/platform-browser-dynamic": "^15.0.0",
 		"@angular/router": "^15.0.0",
+		"@auth0/auth0-angular": "^1.11.1",
 		"rxjs": "~7.5.0",
 		"tslib": "^2.3.0",
 		"zone.js": "~0.12.0"

+ 13 - 0
src/RunnersMeet.Client/src/app/app.component.html

@@ -1,2 +1,15 @@
 <h1>{{title}}</h1>
 <router-outlet></router-outlet>
+<button type="button" (click)="login()">Login</button>
+<dl>
+	<dt>isLoading</dt>
+	<dd>{{ authService.isLoading$ | async | json }}</dd>
+	<dt>isAuthenticated</dt>
+	<dd>{{ authService.isAuthenticated$ | async | json }}</dd>
+	<dt>user</dt>
+	<dd>{{ authService.user$ | async | json }}</dd>
+	<dt>User Display Name</dt>
+	<dd>{{ (authService.user$ | async)?.nickname }}</dd>
+	<dt>User ID</dt>
+	<dd>{{ (authService.user$ | async)?.sub }}</dd>
+</dl>

+ 10 - 0
src/RunnersMeet.Client/src/app/app.component.ts

@@ -1,4 +1,5 @@
 import { Component } from '@angular/core';
+import { AuthService } from '@auth0/auth0-angular';
 
 @Component({
 	selector: 'app-root',
@@ -7,4 +8,13 @@ import { Component } from '@angular/core';
 })
 export class AppComponent {
 	public title = 'RunnersMeet';
+
+	public constructor(
+		public readonly authService: AuthService
+	) {
+	}
+
+	public login(): void {
+		this.authService.loginWithRedirect();
+	}
 }

+ 9 - 1
src/RunnersMeet.Client/src/app/app.module.ts

@@ -1,8 +1,10 @@
 import { NgModule } from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
+import { AuthModule } from '@auth0/auth0-angular';
 
 import { AppRoutingModule } from './app-routing.module';
 import { AppComponent } from './app.component';
+import { environment } from '../env/environment';
 
 @NgModule({
 	declarations: [
@@ -10,7 +12,13 @@ import { AppComponent } from './app.component';
 	],
 	imports: [
 		BrowserModule,
-		AppRoutingModule
+		AppRoutingModule,
+		AuthModule.forRoot({
+			...environment.auth0,
+			httpInterceptor: {
+				...environment.httpInterceptor,
+			},
+		})
 	],
 	providers: [],
 	bootstrap: [AppComponent]

+ 15 - 0
src/RunnersMeet.Client/src/env/environment.prod.ts

@@ -0,0 +1,15 @@
+import {authConfig} from '../../authConfig';
+
+export const environment = {
+	production: true,
+	auth0: {
+		domain: authConfig.domain,
+		clientId: authConfig.clientId,
+		audience: authConfig.audience,
+		redirectUri: window.location.origin,
+		errorPath: authConfig.errorPath,
+	},
+	httpInterceptor: {
+		allowedList: [`${authConfig.apiUri}/*`],
+	},
+};

+ 22 - 0
src/RunnersMeet.Client/src/env/environment.ts

@@ -0,0 +1,22 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+import {authConfig} from '../../authConfig';
+
+export const environment = {
+	production: false,
+	auth0: {
+		domain: authConfig.domain,
+		clientId: authConfig.clientId,
+		audience: authConfig.audience,
+		redirectUri: window.location.origin,
+		errorPath: authConfig.errorPath,
+	},
+	httpInterceptor: {
+		// List of URI wildcard patterns for which the user's JWT will be added to requests
+		allowedList: [
+			`${authConfig.apiUri}/*`
+		],
+	},
+};

+ 1 - 1
src/RunnersMeet.Client/src/index.html

@@ -3,7 +3,7 @@
 
 <head>
 	<meta charset="utf-8">
-	<title>RunnersMeetClient</title>
+	<title>RunnersMeet</title>
 	<base href="/">
 	<meta name="viewport" content="width=device-width, initial-scale=1">
 	<link rel="icon" type="image/x-icon" href="favicon.ico">

+ 5 - 0
src/RunnersMeet.Client/src/main.ts

@@ -1,7 +1,12 @@
+import { enableProdMode } from '@angular/core';
 import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
 
 import { AppModule } from './app/app.module';
+import { environment } from './env/environment';
 
+if (environment.production) {
+	enableProdMode();
+}
 
 platformBrowserDynamic().bootstrapModule(AppModule)
 	.catch(err => console.error(err));

+ 1 - 0
src/RunnersMeet.Client/tsconfig.json

@@ -15,6 +15,7 @@
 		"downlevelIteration": true,
 		"experimentalDecorators": true,
 		"moduleResolution": "node",
+		"resolveJsonModule": true,
 		"importHelpers": true,
 		"target": "ES2022",
 		"module": "ES2022",