refactor: rewrite oauth setup for the new api

This commit is contained in:
Polianin Nikita 2024-12-28 07:44:41 +03:00
parent a8b1485b0e
commit a7542eaf32
6 changed files with 137 additions and 41 deletions

View File

@ -1,6 +1,6 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import ApiService, {AvailableVersion} from "@api/api.service"; import ApiService, {AvailableVersion} from "@api/api.service";
import {catchError, of, switchMap} from "rxjs"; import {catchError, of} from "rxjs";
import {DatabaseResponse} from "@api/v1/configuration/databaseResponse"; import {DatabaseResponse} from "@api/v1/configuration/databaseResponse";
import {DatabaseRequest} from "@api/v1/configuration/databaseRequest"; import {DatabaseRequest} from "@api/v1/configuration/databaseRequest";
import {CacheRequest} from "@api/v1/configuration/cacheRequest"; import {CacheRequest} from "@api/v1/configuration/cacheRequest";
@ -137,18 +137,21 @@ export default class SetupService extends ApiService {
public adminConfiguration() { public adminConfiguration() {
let request = this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('UpdateAdminConfiguration')
.setWithCredentials()
.build;
return this.get(request).pipe(switchMap(_ => {
request = this.createRequestBuilder()
.setEndpoint('AdminConfiguration') .setEndpoint('AdminConfiguration')
.setWithCredentials() .setWithCredentials()
.build; .build;
return this.get<UserResponse>(request); return this.get<UserResponse>(request);
})); }
public registerOAuth(token: string) {
let request = this.createRequestBuilder()
.setEndpoint('HandleToken')
.setQueryParams({token: token})
.setWithCredentials()
.build;
return this.get<null>(request);
} }
public setLogging(data: LoggingRequest | null = null) { public setLogging(data: LoggingRequest | null = null) {

View File

@ -1,4 +1,4 @@
@if (providers.length !== 0) { @if (!loading && providers.length !== 0) {
<hr/> <hr/>
<div> <div>
<p class="mat-body-2 secondary">{{ message }}</p> <p class="mat-body-2 secondary">{{ message }}</p>
@ -17,4 +17,7 @@
} }
</div> </div>
</div> </div>
} @else if (loading) {
<hr/>
<app-data-spinner style="display: flex; justify-content: center;"/>
} }

View File

@ -1,9 +1,10 @@
import {Component, EventEmitter, Inject, Input, Output} from '@angular/core'; import {Component, EventEmitter, Inject, Input, OnInit, Output} from '@angular/core';
import AuthApiService, {OAuthProviderData} from "@api/v1/authApiService"; import AuthApiService, {OAuthProviderData} from "@api/v1/authApiService";
import {OAuthProvider} from "@model/oAuthProvider"; import {OAuthProvider} from "@model/oAuthProvider";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
import { import {
MAT_DIALOG_DATA, MatDialog, MAT_DIALOG_DATA,
MatDialog,
MatDialogActions, MatDialogActions,
MatDialogContent, MatDialogContent,
MatDialogRef, MatDialogRef,
@ -11,6 +12,11 @@ import {
} from "@angular/material/dialog"; } from "@angular/material/dialog";
import {MatButton} from "@angular/material/button"; import {MatButton} from "@angular/material/button";
import {DataSpinnerComponent} from "@component/common/data-spinner/data-spinner.component"; import {DataSpinnerComponent} from "@component/common/data-spinner/data-spinner.component";
import {ActivatedRoute} from "@angular/router";
import {catchError, finalize, Observable, switchMap, tap} from "rxjs";
import {TwoFactorAuthentication} from "@model/twoFactorAuthentication";
import {OAuthAction} from "@model/oAuthAction";
import SetupService from "@api/v1/setup.service";
interface AvailableOAuthProviders extends OAuthProviderData { interface AvailableOAuthProviders extends OAuthProviderData {
disabled: boolean; disabled: boolean;
@ -60,12 +66,13 @@ export class DeleteConfirmDialog {
], ],
templateUrl: './OAuthProviders.html', templateUrl: './OAuthProviders.html',
styleUrl: './OAuthProviders.css', styleUrl: './OAuthProviders.css',
providers: [AuthApiService] providers: [SetupService, AuthApiService]
}) })
export class OAuthProviders { export class OAuthProviders implements OnInit {
protected providers: AvailableOAuthProviders[] = []; protected providers: AvailableOAuthProviders[] = [];
protected _activeProvidersId: OAuthProvider[] = []; protected _activeProvidersId: OAuthProvider[] = [];
protected _activeProviders: string[] = []; protected _activeProviders: string[] = [];
protected loading = true;
@Input() message: string = 'Вы можете войти в аккаунт через'; @Input() message: string = 'Вы можете войти в аккаунт через';
@ -80,11 +87,73 @@ export class OAuthProviders {
} }
@Input() canUnlink: boolean = false; @Input() canUnlink: boolean = false;
@Input() action: OAuthAction = OAuthAction.Login;
@Input() isSetup: boolean = false;
@Output() public oAuthUpdateProviders = new EventEmitter(); @Output() public oAuthUpdateProviders = new EventEmitter();
@Output() public oAuthLoginResult: EventEmitter<TwoFactorAuthentication> = new EventEmitter();
constructor(authApi: AuthApiService, private notify: ToastrService, private dialog: MatDialog) { constructor(private setupApi: SetupService,
authApi.availableProviders().subscribe(providers => this.updateDisabledProviders(providers)); private authApi: AuthApiService,
private notify: ToastrService,
private dialog: MatDialog,
private route: ActivatedRoute) {
}
ngOnInit(): void {
const fullUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
this.authApi.availableProviders(fullUrl).subscribe(providers => {
this.updateDisabledProviders(providers);
});
this.route.queryParamMap
.pipe(
switchMap(params => {
const result = params.get('result');
if (!result) {
this.loading = false; // Нет результата, завершение загрузки
return [];
}
return this.handleOAuthResult(result); // Обрабатываем результат
}),
catchError(_ => {
this.loading = false;
return [];
})
)
.subscribe();
}
private handleOAuthResult(result: string): Observable<any> {
switch (this.action) {
case OAuthAction.Login:
return this.authApi.loginOAuth(result).pipe(
tap(auth => {
this.oAuthLoginResult.emit(auth);
}),
finalize(() => {
this.loading = false;
})
);
case OAuthAction.Bind:
if (this.isSetup) {
return this.setupApi.registerOAuth(result).pipe(
tap(() => {
this.oAuthUpdateProviders.emit();
}),
finalize(() => {
this.loading = false;
})
);
} else
throw new Error('Action "Bind" requires setup mode to be enabled.');
break;
default:
throw new Error('Unknown action type for action ' + this.action);
}
} }
private updateDisabledProviders(data: OAuthProviderData[] | null = null) { private updateDisabledProviders(data: OAuthProviderData[] | null = null) {
@ -98,26 +167,15 @@ export class OAuthProviders {
} }
protected openOAuth(provider: AvailableOAuthProviders) { protected openOAuth(provider: AvailableOAuthProviders) {
console.log(provider.redirect);
const oauthWindow = window.open( const oauthWindow = window.open(
provider.redirect, provider.redirect,
'_blank', '_self'
); );
if (!oauthWindow) { if (!oauthWindow) {
this.notify.error('Не удалось открыть OAuth окно'); this.notify.error('Не удалось открыть OAuth окно');
return; return;
} }
provider.active = true;
const checkInterval = setInterval(() => {
if (oauthWindow.closed) {
clearInterval(checkInterval);
this.oAuthUpdateProviders.emit();
provider.active = false;
}
}, 1500);
} }
protected confirmDelete(provider: AvailableOAuthProviders) { protected confirmDelete(provider: AvailableOAuthProviders) {

View File

@ -76,7 +76,9 @@
} }
</mat-form-field> </mat-form-field>
<OAuthProviders [canUnlink]="true" [activeProvidersId]="activatedProviders" (oAuthUpdateProviders)="updateProviders()" <OAuthProviders [canUnlink]="true" [activeProvidersId]="activatedProviders"
[message]="'Или можете получить часть данных от сторонних сервисов'"/> (oAuthUpdateProviders)="updateProviders()"
[message]="'Или можете получить часть данных от сторонних сервисов'"
[action]="OAuthAction.Bind" [isSetup]="true"/>
</div> </div>
</form> </form>

View File

@ -1,4 +1,5 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {Location} from '@angular/common';
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
import {NavigationService} from "@service/navigation.service"; import {NavigationService} from "@service/navigation.service";
import {passwordMatchValidator} from '@service/password-match.validator'; import {passwordMatchValidator} from '@service/password-match.validator';
@ -13,6 +14,8 @@ import AuthApiService from "@api/v1/authApiService";
import {OAuthProviders} from "@component/OAuthProviders/OAuthProviders"; import {OAuthProviders} from "@component/OAuthProviders/OAuthProviders";
import {OAuthProvider} from "@model/oAuthProvider"; import {OAuthProvider} from "@model/oAuthProvider";
import {PasswordInputComponent} from "@component/common/password-input/password-input.component"; import {PasswordInputComponent} from "@component/common/password-input/password-input.component";
import {OAuthAction} from "@model/oAuthAction";
import {Router} from "@angular/router";
@Component({ @Component({
selector: 'app-create-admin', selector: 'app-create-admin',
@ -29,7 +32,7 @@ import {PasswordInputComponent} from "@component/common/password-input/password-
PasswordInputComponent PasswordInputComponent
], ],
templateUrl: './create-admin.component.html', templateUrl: './create-admin.component.html',
providers: [AuthApiService] providers: [AuthApiService, Location]
}) })
export class CreateAdminComponent { export class CreateAdminComponent {
@ -37,7 +40,8 @@ export class CreateAdminComponent {
protected hideRetypePass = true; protected hideRetypePass = true;
protected activatedProviders: OAuthProvider[] = []; protected activatedProviders: OAuthProvider[] = [];
constructor( constructor(private router: Router,
private location: Location,
private navigationService: NavigationService, private formBuilder: FormBuilder, private api: SetupService) { private navigationService: NavigationService, private formBuilder: FormBuilder, private api: SetupService) {
this.createAdminForm = this.formBuilder.group({ this.createAdminForm = this.formBuilder.group({
user: ['', Validators.pattern(/^([A-Za-z0-9]){4,}$/)], user: ['', Validators.pattern(/^([A-Za-z0-9]){4,}$/)],
@ -76,6 +80,9 @@ export class CreateAdminComponent {
this.activatedProviders = configuration.usedOAuthProviders; this.activatedProviders = configuration.usedOAuthProviders;
} }
const currentPath = this.router.url.split('?')[0];
this.location.replaceState(currentPath);
}); });
} }
@ -87,4 +94,6 @@ export class CreateAdminComponent {
protected updateProviders() { protected updateProviders() {
this.updateAdminData(); this.updateAdminData();
} }
protected readonly OAuthAction = OAuthAction;
} }

View File

@ -1,6 +1,6 @@
import {Component, ViewEncapsulation} from '@angular/core'; import {Component, ViewEncapsulation} from '@angular/core';
import {MatSidenavModule} from "@angular/material/sidenav"; import {MatSidenavModule} from "@angular/material/sidenav";
import {Router, RouterOutlet} from "@angular/router"; import {ActivatedRoute, Router, RouterOutlet} from "@angular/router";
import {MatCard} from "@angular/material/card"; import {MatCard} from "@angular/material/card";
import {MatButton} from "@angular/material/button"; import {MatButton} from "@angular/material/button";
import {NavigationService} from "@service/navigation.service"; import {NavigationService} from "@service/navigation.service";
@ -30,20 +30,29 @@ export class SetupComponent {
protected skipButtonDisabled: boolean = false; protected skipButtonDisabled: boolean = false;
protected loaderActive: boolean = false; protected loaderActive: boolean = false;
protected routes: Array<string> = ['', 'welcome', 'database', 'cache', 'password-policy', 'schedule', 'logging', 'create-admin', 'two-factor', 'summary']; protected routes: Array<string> = ['', 'welcome', 'create-admin', 'database', 'cache', 'password-policy', 'schedule', 'logging', 'two-factor', 'summary'];
private index: number = 1; private index: number = 1;
protected get getIndex() { protected get getIndex() {
return this.index; return this.index;
} }
constructor(private router: Router, private navigationService: NavigationService, api: SetupService, private notify: ToastrService) { constructor(private route: ActivatedRoute,
private router: Router,
private navigationService: NavigationService,
api: SetupService,
private notify: ToastrService) {
api.isConfigured().subscribe(x => { api.isConfigured().subscribe(x => {
if (x) this.router.navigate(['/']).then(); if (x) this.router.navigate(['/']).then();
}); });
if (!this.router.url.includes(this.routes[this.index])) { if (!this.router.url.includes(this.routes[this.index])) {
this.router.navigate(['setup/', this.routes[this.index]]).then(); const currentQueryParams = this.route.snapshot.queryParams;
this.router.navigate(
['setup/', this.routes[this.index]],
{queryParams: currentQueryParams}
).then();
} }
this.initializeButtonSubscriptions(); this.initializeButtonSubscriptions();
@ -104,13 +113,25 @@ export class SetupComponent {
this.moveToPreviousPage(); this.moveToPreviousPage();
} }
private moveToNextPage() { private moveToNextPage(): void {
if (this.index < this.routes.length - 1) { if (this.index < this.routes.length - 1) {
this.index++; this.index++;
this.router.navigate(['setup/', this.routes[this.index]]).then();
const currentQueryParams = this.route.snapshot.queryParams;
this.router.navigate(
['setup/', this.routes[this.index]],
{queryParams: currentQueryParams}
).then(() => {
this.initializePage(); this.initializePage();
});
} else { } else {
this.router.navigate(['/']).then(); const currentQueryParams = this.route.snapshot.queryParams;
this.router.navigate(
['/'],
{queryParams: currentQueryParams}
).then();
} }
} }