diff --git a/Api/Program.cs b/Api/Program.cs index 9bfe4f6..0670cae 100644 --- a/Api/Program.cs +++ b/Api/Program.cs @@ -17,26 +17,35 @@ namespace Api // Add services to the container. builder.Services.AddAuthentication(options => { - options.DefaultScheme = "Cookies"; - options.DefaultChallengeScheme = "oidc"; + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) - .AddCookie("Cookies") - .AddOpenIdConnect("oidc", options => + .AddJwtBearer(options => { - var pocketId = builder.Configuration.GetSection("Authentication:PocketId"); - options.Authority = pocketId["Authority"]; - options.ClientId = pocketId["ClientId"]; - options.ClientSecret = pocketId["ClientSecret"]; - options.CallbackPath = pocketId["CallbackPath"]; - options.ResponseType = "code"; - options.SaveTokens = true; - options.Scope.Clear(); - var scopes = pocketId["Scopes"] ?? "openid"; - foreach (var scope in scopes.Split(' ')) + options.Events = new JwtBearerEvents { - options.Scope.Add(scope); - } + OnTokenValidated = context => Task.CompletedTask, + OnChallenge = context => Task.CompletedTask + }; + + options.Authority = builder.Configuration.GetConnectionString("Authentication:PocketId:Authority"); + options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() + { + // ValidAudiences = builder.Configuration.GetSection("Authentication:PocketId:Audiences").Get(), + ValidIssuers = builder.Configuration.GetSection("Authentication:PocketId:Authority").Get() + }; }); + + builder.Services.AddCors(options => + { + options.AddPolicy("AllowAll", policy => + { + policy.AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); + }); + builder.Services.AddControllers(); // Add DbContext with SQL Server // Allow connection string to be set via environment variable (e.g., in Docker) @@ -56,39 +65,25 @@ namespace Api app.UseSwagger(); app.UseSwaggerUI(); + app.Use(async (context, next) => + { + if (context.Request.Method == HttpMethods.Options) + { + context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); + context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type"); + context.Response.StatusCode = StatusCodes.Status204NoContent; + return; + } + + await next(); + }); if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } - if (app.Environment.IsDevelopment()) - { - var staticFilePath = "/workspaces/centrum/Web/dist/Web/browser"; - app.UseDefaultFiles(new DefaultFilesOptions - { - FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(staticFilePath), - DefaultFileNames = new List { "index.html" } - }); - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(staticFilePath), - RequestPath = "" - }); - // Angular routing fallback - app.Use(async (context, next) => - { - await next(); - var path = context.Request.Path.Value ?? string.Empty; - if (context.Response.StatusCode == 404 && - !System.IO.Path.HasExtension(path) && - !path.StartsWith("/api")) - { - context.Request.Path = "/index.html"; - await next(); - } - }); - } - else + if (!app.Environment.IsDevelopment()) { app.UseDefaultFiles(); // Uses wwwroot by default app.UseStaticFiles(); @@ -106,6 +101,8 @@ namespace Api } }); } + + app.UseCors("AllowAll"); app.UseAuthentication(); app.UseAuthorization(); diff --git a/Api/appsettings.Development.json b/Api/appsettings.Development.json index d17ed84..4f4f107 100644 --- a/Api/appsettings.Development.json +++ b/Api/appsettings.Development.json @@ -1,8 +1,8 @@ { "Logging": { "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Default": "Trace", + "Microsoft.AspNetCore": "Trace" } }, "ConnectionStrings": { diff --git a/Api/appsettings.json b/Api/appsettings.json index 9925c2d..ae27eab 100644 --- a/Api/appsettings.json +++ b/Api/appsettings.json @@ -14,5 +14,5 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "http://localhost:4200" } \ No newline at end of file diff --git a/Web/.vscode/launch.json b/Web/.vscode/launch.json index 925af83..5afdf10 100644 --- a/Web/.vscode/launch.json +++ b/Web/.vscode/launch.json @@ -7,7 +7,8 @@ "type": "chrome", "request": "launch", "preLaunchTask": "npm: start", - "url": "http://localhost:4200/" + "url": "http://localhost:4200/", + "webRoot": "${workspaceFolder}/Web", }, { "name": "ng test", diff --git a/Web/package-lock.json b/Web/package-lock.json index a095a60..c108fa3 100644 --- a/Web/package-lock.json +++ b/Web/package-lock.json @@ -14,6 +14,7 @@ "@angular/forms": "^20.0.0", "@angular/platform-browser": "^20.0.0", "@angular/router": "^20.0.0", + "angular-oauth2-oidc": "^20.0.2", "rxjs": "~7.8.0", "tslib": "^2.3.0" }, @@ -3374,6 +3375,19 @@ } } }, + "node_modules/angular-oauth2-oidc": { + "version": "20.0.2", + "resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-20.0.2.tgz", + "integrity": "sha512-bMSXEQIuvgq8yqnsIatZggAvCJvY+pm7G8MK0tWCHR93UpFuNN+L5B6pY9CzRg8Ys+VVhkLIBx4zEHbJnv9icg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.5.2" + }, + "peerDependencies": { + "@angular/common": ">=20.0.0", + "@angular/core": ">=20.0.0" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", diff --git a/Web/package.json b/Web/package.json index 1665fb2..8668306 100644 --- a/Web/package.json +++ b/Web/package.json @@ -26,6 +26,7 @@ "@angular/forms": "^20.0.0", "@angular/platform-browser": "^20.0.0", "@angular/router": "^20.0.0", + "angular-oauth2-oidc": "^20.0.2", "rxjs": "~7.8.0", "tslib": "^2.3.0" }, diff --git a/Web/src/app/app.config.ts b/Web/src/app/app.config.ts index 2e06ce8..d65a489 100644 --- a/Web/src/app/app.config.ts +++ b/Web/src/app/app.config.ts @@ -2,11 +2,20 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessC import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; +import { provideHttpClient } from '@angular/common/http'; +import { provideOAuthClient } from 'angular-oauth2-oidc'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideZonelessChangeDetection(), - provideRouter(routes) + provideRouter(routes), + provideHttpClient(), + provideOAuthClient({ + resourceServer: { + allowedUrls: ['http://localhost:5000'], + sendAccessToken: true, + }, + }) ] }; diff --git a/Web/src/app/app.html b/Web/src/app/app.html index cfb20b1..67e7bd4 100644 --- a/Web/src/app/app.html +++ b/Web/src/app/app.html @@ -1,341 +1 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'}, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - diff --git a/Web/src/app/app.routes.ts b/Web/src/app/app.routes.ts index dc39edb..20f679e 100644 --- a/Web/src/app/app.routes.ts +++ b/Web/src/app/app.routes.ts @@ -1,3 +1,9 @@ import { Routes } from '@angular/router'; +import { Login } from './login/login'; -export const routes: Routes = []; +export const routes: Routes = [ + { + path: 'login', + component: Login + } +]; diff --git a/Web/src/app/app.ts b/Web/src/app/app.ts index 82ab9f2..163d5b3 100644 --- a/Web/src/app/app.ts +++ b/Web/src/app/app.ts @@ -1,12 +1,33 @@ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { Router, RouterOutlet } from '@angular/router'; +import { DefaultOAuthInterceptor, OAuthService } from 'angular-oauth2-oidc'; @Component({ selector: 'app-root', imports: [RouterOutlet], + providers: [OAuthService, { + provide: HTTP_INTERCEPTORS, + useClass: DefaultOAuthInterceptor, + multi: true, + }], templateUrl: './app.html', styleUrl: './app.scss' }) -export class App { +export class App implements OnInit { protected title = 'Web'; + constructor(private readonly as: OAuthService, private readonly router: Router) { + this.as.configure({ + issuer: 'https://identity.lesko.me', + redirectUri: 'http://localhost:4200/login', + clientId: '21131567-fea1-42a2-8907-21abd874eff8', + scope: 'openid profile email', + responseType: 'code', + showDebugInformation: true, + timeoutFactor: 0.01, + }); + } + ngOnInit(): void { + this.as.loadDiscoveryDocumentAndLogin(); + } } diff --git a/Web/src/app/login/login.html b/Web/src/app/login/login.html new file mode 100644 index 0000000..147cfc4 --- /dev/null +++ b/Web/src/app/login/login.html @@ -0,0 +1 @@ +

login works!

diff --git a/Web/src/app/login/login.scss b/Web/src/app/login/login.scss new file mode 100644 index 0000000..e69de29 diff --git a/Web/src/app/login/login.spec.ts b/Web/src/app/login/login.spec.ts new file mode 100644 index 0000000..dd8bbb3 --- /dev/null +++ b/Web/src/app/login/login.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Login } from './login'; + +describe('Login', () => { + let component: Login; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Login] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Login); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Web/src/app/login/login.ts b/Web/src/app/login/login.ts new file mode 100644 index 0000000..23d3d29 --- /dev/null +++ b/Web/src/app/login/login.ts @@ -0,0 +1,20 @@ +import { HttpClient } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { OAuthService } from 'angular-oauth2-oidc'; + +@Component({ + selector: 'app-login', + imports: [], + templateUrl: './login.html', + styleUrl: './login.scss' +}) +export class Login implements OnInit { + constructor(private readonly as: OAuthService, private readonly httpClient: HttpClient) { + + } + ngOnInit(): void { + this.httpClient.get('http://localhost:5000/api/product' + // { headers: { Authorization: `Bearer ${this.as.getAccessToken()}` } } + ).subscribe(console.warn); + } +} diff --git a/Web/themes/modernize/src/app/config.ts b/Web/themes/modernize/src/app/config.ts index f78393a..c23711e 100644 --- a/Web/themes/modernize/src/app/config.ts +++ b/Web/themes/modernize/src/app/config.ts @@ -6,6 +6,6 @@ export interface AppSettings { } export const defaults: AppSettings = { - sidenavOpened: false, - sidenavCollapsed: false, + sidenavOpened: true, + sidenavCollapsed: true, };