fix: CB-792 CB-730 CB-906 authentication & administration

This commit is contained in:
Wroud
2021-03-31 15:22:24 +03:00
parent db03e7c4da
commit 9363dae717
29 changed files with 187 additions and 77 deletions
@@ -86,7 +86,7 @@ export class AdministrationScreenService {
this.autoSaveService.withAutoSave(this.itemState, ADMINISTRATION_ITEMS_STATE);
this.autoSaveService.withAutoSave(this.info, ADMINISTRATION_INFO);
this.permissionsResource.onDataUpdate.addHandler(() => {
this.permissionsResource.onDataUpdate.addPostHandler(() => {
this.checkPermissions(this.screenService.routerService.state);
});
}
@@ -6,10 +6,11 @@
* you may not use this file except in compliance with the License.
*/
import { ConnectionInfoResource, CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual, NavigatorViewSettings } from '@cloudbeaver/core-connections';
import { ConnectionInfoResource } from '@cloudbeaver/core-connections';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ContextMenuService, IMenuPanel } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { isNavigatorViewSettingsEqual, CONNECTION_NAVIGATOR_VIEW_SETTINGS, NavigatorViewSettings } from '@cloudbeaver/core-root';
import type { NavNode } from '../shared/NodesManager/EntityTypes';
import { EObjectFeature } from '../shared/NodesManager/EObjectFeature';
@@ -315,6 +315,10 @@ export class NavigationTabsService {
}
private async restoreTab(tab: ITab, removedTabs: string[]): Promise<void> {
if (tab.restored) {
return;
}
let restoreFail = false;
try {
const handler = this.handlers.get(tab.handlerId);
@@ -21,8 +21,8 @@ export class AuthInfoService {
return this.userInfoResource.data;
}
async login(provider: string, credentials: Record<string, string>): Promise<UserInfo | null> {
return this.userInfoResource.login(provider, credentials);
async login(provider: string, credentials: Record<string, string>, link?: boolean): Promise<UserInfo | null> {
return this.userInfoResource.login(provider, credentials, link);
}
async logout(): Promise<void> {
@@ -23,9 +23,14 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void>
super(null);
this.sync(this.sessionResource);
this.sessionDataResource.beforeLoad.addHandler(() => this.load());
this.onDataOutdated.addHandler(() => this.sessionDataResource.markOutdated());
}
isLinked(provideId: string): boolean {
return this.data?.linkedAuthProviders.includes(provideId) || false;
}
getId(): string {
return this.data?.userId || 'anonymous';
}
@@ -40,7 +45,7 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void>
|| this.data.authTokens.some(token => token.origin.type === type && token.origin.subType === subType);
}
async login(provider: string, credentials: Record<string, string>): Promise<UserInfo | null> {
async login(provider: string, credentials: Record<string, string>, link?: boolean): Promise<UserInfo | null> {
await this.performUpdate(undefined, undefined, async () => {
const processedCredentials = await this.authProviderService.processCredentials(provider, credentials);
@@ -48,10 +53,11 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void>
const { authToken } = await this.graphQLService.sdk.authLogin({
provider,
credentials: processedCredentials,
linkUser: link,
customIncludeOriginDetails: true,
});
if (this.data === null) {
if (this.data === null || link) {
this.data = await this.loader();
} else {
this.data.authTokens.push(authToken as UserAuthToken);
@@ -67,9 +73,9 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void>
if (this.data) {
await this.graphQLService.sdk.authLogout();
this.data = null;
this.sessionDataResource.refresh();
}
});
await this.sessionDataResource.refresh();
}
protected async loader(): Promise<UserInfo | null> {
@@ -10,7 +10,7 @@ import { action, makeObservable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor';
import { SessionDataResource } from '@cloudbeaver/core-root';
import { NavigatorViewSettings, SessionDataResource } from '@cloudbeaver/core-root';
import {
GraphQLService,
CachedMapResource,
@@ -26,7 +26,6 @@ import {
} from '@cloudbeaver/core-sdk';
import { ConnectionsResource, DatabaseConnection } from './Administration/ConnectionsResource';
import type { NavigatorViewSettings } from './ConnectionNavigatorViewSettings';
export type Connection = DatabaseConnection & { authProperties?: UserConnectionAuthPropertiesFragment[] };
export type ConnectionInitConfig = Omit<InitConnectionMutationVariables, 'includeOrigin' | 'customIncludeOriginDetails' | 'includeAuthProperties' | 'customIncludeNetworkHandlerCredentials'>;
@@ -32,4 +32,3 @@ export * from './NetworkHandlerResource';
export * from './useConnectionInfo';
export * from './useDBDriver';
export * from './getUniqueConnectionName';
export * from './ConnectionNavigatorViewSettings';
+3
View File
@@ -7,6 +7,7 @@
*/
import { Bootstrap } from './Bootstrap';
import { Dependency } from './Dependency';
import type { DIContainer } from './DIContainer';
import { RootContainerService } from './entities/RootContainerService';
import type { IServiceCollection, IServiceInjector } from './IApp';
@@ -62,6 +63,8 @@ export class App {
if ('register' in serviceInstance) {
await serviceInstance.register();
}
} else if (service.prototype instanceof Dependency) {
this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from './injectable';
@injectable()
export abstract class Dependency {
}
+1
View File
@@ -2,6 +2,7 @@ export * from './IApp';
export * from './App';
export * from './AppContext';
export * from './Bootstrap';
export * from './Dependency';
export * from './injectable';
export * from './PluginManifest';
export * from './useService';
@@ -40,7 +40,7 @@ export interface DialogInternal {
@injectable()
export class CommonDialogService {
dialogs: DialogInternal[] = observable([]);
dialogs: DialogInternal[] = observable([], { deep: false });
// note that if dialog is closed by user it will be resolved with DialogueStateResult.Rejected
async open<TPayload, TResult>(
@@ -12,7 +12,7 @@ export interface IContextGetter<TData> {
}
export interface IExecutionContextProvider<TData> {
hasContext: (token: IContextLoader<TData>) => boolean;
hasContext: (token: IContextLoader<any, TData>) => boolean;
getContext: IContextGetter<TData>;
}
@@ -17,6 +17,10 @@ export class TaskScheduler<TIdentifier> {
return this.queue.map(task => task.id);
}
get executing(): boolean {
return this.queue.length > 0;
}
private readonly queue: Array<ITask<TIdentifier>>;
private readonly isBlocked: BlockedExecution<TIdentifier> | null;
@@ -53,7 +57,6 @@ export class TaskScheduler<TIdentifier> {
await error?.(exception);
throw exception;
} finally {
this.queue.splice(this.queue.indexOf(task), 1);
await after?.();
}
}
@@ -72,18 +75,22 @@ export class TaskScheduler<TIdentifier> {
id: TIdentifier,
promise: () => Promise<T>,
) {
if (!this.isBlocked) {
return promise();
try {
if (!this.isBlocked) {
return await promise();
}
const queueList = this.queue.filter(active => this.isBlocked!(active.id, id));
for (const task of queueList) {
try {
await task.task;
} catch {}
}
return await promise();
} finally {
this.queue.splice(this.queue.findIndex(task => task.id === id), 1);
}
const queueList = this.queue.filter(active => this.isBlocked!(active.id, id));
for (const task of queueList) {
try {
await task.task;
} catch {}
}
return promise();
}
}
@@ -9,7 +9,9 @@
import { makeObservable, observable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { GraphQLService, CachedDataResource, ServerConfig, ServerConfigInput } from '@cloudbeaver/core-sdk';
import { GraphQLService, CachedDataResource, ServerConfig, ServerConfigInput, NavigatorSettingsInput } from '@cloudbeaver/core-sdk';
import { isNavigatorViewSettingsEqual } from './ConnectionNavigatorViewSettings';
@injectable()
export class ServerConfigResource extends CachedDataResource<ServerConfig | null, void> {
@@ -59,23 +61,73 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
return this.update.publicCredentialsSaveEnabled ?? this.data?.publicCredentialsSaveEnabled ?? false;
}
isChanged(): boolean {
if (!this.data) {
return false;
}
if (this.update.adminName || this.update.adminPassword) {
return true;
}
return (
this.update.serverName !== this.data.name
|| this.update.sessionExpireTime !== this.data.sessionExpireTime
|| this.update.anonymousAccessEnabled !== this.data.anonymousAccessEnabled
|| this.update.authenticationEnabled !== this.data.authenticationEnabled
|| this.update.adminCredentialsSaveEnabled !== this.data.adminCredentialsSaveEnabled
|| this.update.publicCredentialsSaveEnabled !== this.data.publicCredentialsSaveEnabled
|| this.update.customConnectionsEnabled !== this.data.supportsCustomConnections
|| this.update.enabledAuthProviders !== this.data.enabledAuthProviders
);
}
isNavigatorSettingsChanged(settings: NavigatorSettingsInput): boolean {
if (!this.data?.defaultNavigatorSettings) {
return false;
}
return !isNavigatorViewSettingsEqual(this.data.defaultNavigatorSettings, settings);
}
setDataUpdate(update: ServerConfigInput): void {
this.update = update;
}
async save(): Promise<void> {
async setDefaultNavigatorSettings(settings: NavigatorSettingsInput): Promise<void> {
await this.performUpdate(undefined, undefined, async () => {
await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings });
if (this.data) {
this.data.defaultNavigatorSettings = { ...settings };
} else {
this.data = await this.loader();
}
}, () => !this.isNavigatorSettingsChanged(settings));
}
async save(onlyRestart = false): Promise<void> {
await this.performUpdate(undefined, undefined, async () => {
await this.graphQLService.sdk.configureServer({
configuration: this.update,
configuration: onlyRestart ? {} : this.update,
});
this.data = await this.loader();
});
}, () => !this.isChanged() && !onlyRestart);
}
protected async loader(): Promise<ServerConfig> {
const { serverConfig } = await this.graphQLService.sdk.serverConfig();
this.syncUpdateData(serverConfig);
return serverConfig as ServerConfig;
}
private syncUpdateData(serverConfig: ServerConfig) {
this.update.serverName = serverConfig.name;
this.update.sessionExpireTime = serverConfig.sessionExpireTime;
@@ -90,7 +142,5 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
this.update.customConnectionsEnabled = serverConfig.supportsCustomConnections;
this.update.enabledAuthProviders = serverConfig.enabledAuthProviders;
return serverConfig as ServerConfig;
}
}
@@ -21,11 +21,11 @@ export type SessionState = SessionStateFragment;
export class SessionResource extends CachedDataResource<SessionState | null, void> {
constructor(
private graphQLService: GraphQLService,
private serverConfiguration: ServerConfigResource
serverConfiguration: ServerConfigResource
) {
super(null);
this.sync(this.serverConfiguration);
this.sync(serverConfiguration);
}
protected async loader(): Promise<SessionState> {
+1
View File
@@ -1,3 +1,4 @@
export * from './ConnectionNavigatorViewSettings';
export * from './PermissionsResource';
export * from './PermissionsService';
export * from './ServerConfigResource';
@@ -35,6 +35,7 @@ export abstract class CachedDataResource<
markUpdated(param: TParam): void {
const metadata = this.metadata.get(param as unknown as TKey);
metadata.outdated = false;
metadata.exception = null;
this.loaded = true;
}
@@ -6,8 +6,9 @@
* you may not use this file except in compliance with the License.
*/
import { observable, makeObservable } from 'mobx';
import { observable, makeObservable, action } from 'mobx';
import { Dependency } from '@cloudbeaver/core-di';
import { Executor, ExecutorInterrupter, IExecutor, TaskScheduler } from '@cloudbeaver/core-executor';
import { MetadataMap } from '@cloudbeaver/core-utils';
@@ -44,7 +45,7 @@ export abstract class CachedResource<
TParam,
TKey = TParam,
TContext = void
> {
> extends Dependency {
data: TData;
readonly onDataOutdated: IExecutor<TParam>;
@@ -54,15 +55,23 @@ export abstract class CachedResource<
protected metadata: MetadataMap<TKey, ICachedResourceMetadata>;
protected loading = false;
protected get loading(): boolean {
return this.scheduler.executing;
}
protected scheduler: TaskScheduler<TParam>;
protected paramAliases: Array<IParamAlias<TParam>>;
constructor(defaultValue: TData) {
makeObservable<CachedResource<TData, TParam, TKey, TContext>, 'loading'>(this, {
super();
makeObservable<CachedResource<TData, TParam, TKey, TContext>, 'loader'>(this, {
data: observable,
loading: observable,
loader: action,
markDataLoading: action,
markDataLoaded: action,
markDataError: action,
markOutdated: action,
markUpdated: action,
});
this.includes = this.includes.bind(this);
@@ -82,7 +91,7 @@ export abstract class CachedResource<
resource: CachedResource<any, TParam, TParam, void>,
context: TContext
): void {
resource.onDataOutdated.addHandler(param => this.markOutdated(param));
resource.onDataOutdated.addHandler(this.markOutdated.bind(this));
resource.onDataUpdate.addHandler(param => this.load(param, context));
this.beforeLoad.addHandler(param => resource.load(param));
}
@@ -207,22 +216,24 @@ export abstract class CachedResource<
}
param = this.transformParam(param);
const contexts = await this.beforeLoad.execute(param);
if (ExecutorInterrupter.isInterrupted(contexts)) {
return;
}
this.markDataLoading(param, context);
this.loading = true;
return this.scheduler.schedule(
param,
async () => {
// repeated because previous task maybe has been load requested data
// repeated because previous task maybe has been load requested data
if (exitCheck?.()) {
return;
}
return await this.taskWrapper(param, context, update);
},
() => {
this.markDataLoaded(param, context);
this.loading = false;
},
() => this.markDataLoaded(param, context),
() => this.onDataUpdate.execute(param),
exception => this.markDataError(exception, param, context));
}
@@ -240,21 +251,17 @@ export abstract class CachedResource<
}
this.markDataLoading(param, context);
this.loading = true;
await this.scheduler.schedule(
param,
async () => {
// repeated because previous task maybe has been load requested data
// repeated because previous task maybe has been load requested data
if (this.isLoaded(param, context) && !this.isOutdated(param) && !refresh) {
return;
}
await this.taskWrapper(param, context, this.loadingTask);
},
() => {
this.markDataLoaded(param, context);
this.loading = false;
},
() => this.markDataLoaded(param, context),
() => this.onDataUpdate.execute(param),
exception => this.markDataError(exception, param, context));
}
@@ -1,9 +1,10 @@
query authLogin(
$provider: ID!
$credentials: Object!
$linkUser: Boolean
$customIncludeOriginDetails: Boolean!
) {
authToken: authLogin(provider: $provider, credentials: $credentials) {
authToken: authLogin(provider: $provider, credentials: $credentials, linkUser: $linkUser) {
...AuthToken
}
}
@@ -2,6 +2,7 @@ query getActiveUser($customIncludeOriginDetails: Boolean!) {
user: activeUser {
userId
displayName
linkedAuthProviders
authTokens {
...AuthToken
}
+11 -3
View File
@@ -77,6 +77,7 @@ export interface QueryAllConnectionsArgs {
export interface QueryAuthLoginArgs {
provider: Scalars['ID'];
credentials: Scalars['Object'];
linkUser?: Maybe<Scalars['Boolean']>;
}
export interface QueryAuthLogoutArgs {
@@ -935,6 +936,7 @@ export interface UserInfo {
userId: Scalars['String'];
displayName?: Maybe<Scalars['String']>;
authTokens: UserAuthToken[];
linkedAuthProviders: Array<Scalars['String']>;
}
export interface DataTransferProcessorInfo {
@@ -967,6 +969,7 @@ export interface AsyncTaskCancelMutation { result: Mutation['asyncTaskCancel'] }
export type AuthLoginQueryVariables = Exact<{
provider: Scalars['ID'];
credentials: Scalars['Object'];
linkUser?: Maybe<Scalars['Boolean']>;
customIncludeOriginDetails: Scalars['Boolean'];
}>;
@@ -982,7 +985,7 @@ export type GetActiveUserQueryVariables = Exact<{
export interface GetActiveUserQuery {
user?: Maybe<(
Pick<UserInfo, 'userId' | 'displayName'>
Pick<UserInfo, 'userId' | 'displayName' | 'linkedAuthProviders'>
& { authTokens: AuthTokenFragment[] }
)>;
}
@@ -1819,8 +1822,12 @@ export const AsyncTaskCancelDocument = `
}
`;
export const AuthLoginDocument = `
query authLogin($provider: ID!, $credentials: Object!, $customIncludeOriginDetails: Boolean!) {
authToken: authLogin(provider: $provider, credentials: $credentials) {
query authLogin($provider: ID!, $credentials: Object!, $linkUser: Boolean, $customIncludeOriginDetails: Boolean!) {
authToken: authLogin(
provider: $provider
credentials: $credentials
linkUser: $linkUser
) {
...AuthToken
}
}
@@ -1835,6 +1842,7 @@ export const GetActiveUserDocument = `
user: activeUser {
userId
displayName
linkedAuthProviders
authTokens {
...AuthToken
}
@@ -11,8 +11,8 @@ import React, { useCallback } from 'react';
import styled from 'reshadow';
import { BASE_CONTAINERS_STYLES, SwitchNew } from '@cloudbeaver/core-blocks';
import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual } from '@cloudbeaver/core-connections';
import { useTranslate } from '@cloudbeaver/core-localization';
import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual } from '@cloudbeaver/core-root';
import { useStyles } from '@cloudbeaver/core-theming';
import type { IServerConfigurationPageState } from '../IServerConfigurationPageState';
@@ -13,6 +13,7 @@ import { AdministrationItemContentComponent, AdministrationTools, ADMINISTRATION
import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, GroupItem, GroupTitle, IconButton, Loader, Placeholder, SubmittingForm, useFocus, useFormValidator } from '@cloudbeaver/core-blocks';
import { useController, useService } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { useStyles } from '@cloudbeaver/core-theming';
import { ServerConfigurationConfigurationForm } from './Form/ServerConfigurationConfigurationForm';
@@ -42,16 +43,19 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser
const translate = useTranslate();
const style = useStyles(styles, ADMINISTRATION_TOOLS_STYLES, BASE_CONTAINERS_STYLES);
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const serverConfigResource = useService(ServerConfigResource);
const service = useService(ServerConfigurationService);
const controller = useController(ServerConfigurationPageController);
const changed = serverConfigResource.isChanged()
|| serverConfigResource.isNavigatorSettingsChanged(service.state.navigatorConfig);
useFormValidator(service.validationTask, focusedRef);
return styled(style)(
<SubmittingForm ref={focusedRef} name='server_config' onSubmit={controller.save} onChange={controller.change}>
{controller.editing && (
<AdministrationTools>
<IconButton name="admin-save" viewBox="0 0 28 28" onClick={controller.save} />
<IconButton name="admin-cancel" viewBox="0 0 28 28" onClick={controller.reset} />
<IconButton name="admin-save" viewBox="0 0 28 28" disabled={!changed} onClick={controller.save} />
<IconButton name="admin-cancel" viewBox="0 0 28 28" disabled={!changed} onClick={controller.reset} />
</AdministrationTools>
)}
<ColoredContainer wrap gap overflow parent>
@@ -167,7 +167,7 @@ export class ServerConfigurationService {
}
try {
await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: data.state.navigatorConfig });
await this.serverConfigResource.setDefaultNavigatorSettings(data.state.navigatorConfig);
if (!data.configurationWizard || data.finish) {
await this.serverConfigResource.save();
}
@@ -11,7 +11,7 @@ import { AppScreenService } from '@cloudbeaver/core-app';
import { AppAuthService, AuthProviderContext, AuthProviderService, AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID, UserInfoResource } from '@cloudbeaver/core-authentication';
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/core-executor';
import type { IExecutorHandler } from '@cloudbeaver/core-executor';
import { SessionDataResource } from '@cloudbeaver/core-root';
import { ScreenService } from '@cloudbeaver/core-routing';
import type { ObjectOrigin } from '@cloudbeaver/core-sdk';
@@ -37,8 +37,8 @@ export class AuthenticationService extends Bootstrap {
this.authPromise = null;
}
async authUser(provider: string | null = null): Promise<void> {
await this.auth(false, provider);
async authUser(provider: string | null = null, link?: boolean): Promise<void> {
await this.auth(false, provider, link);
}
async logout(): Promise<void> {
@@ -53,11 +53,11 @@ export class AuthenticationService extends Bootstrap {
}
}
private async auth(persistent: boolean, provider: string | null = null) {
private async auth(persistent: boolean, provider: string | null = null, link?: boolean) {
if (this.authPromise) {
return this.authPromise;
}
this.authPromise = this.authDialogService.showLoginForm(persistent, provider);
this.authPromise = this.authDialogService.showLoginForm(persistent, provider, link);
try {
await this.authPromise;
} finally {
@@ -75,9 +75,6 @@ export class AuthenticationService extends Bootstrap {
}
register(): void {
this.sessionDataResource.beforeLoad.addHandler(
ExecutorInterrupter.interrupter(() => this.appAuthService.isAuthNeeded())
);
this.sessionDataResource.beforeLoad.addPostHandler(() => { this.requireAuthentication(); });
this.appScreenService.activation.addHandler(() => this.requireAuthentication());
@@ -85,20 +85,25 @@ const styles = composes(
`
);
export const AuthDialog: DialogComponent<string | null, null> = observer(function AuthDialog({
payload,
interface IAuthPayload {
provider: string | null;
link?: boolean;
}
export const AuthDialog: DialogComponent<IAuthPayload, null> = observer(function AuthDialog({
payload: { provider, link },
options,
rejectDialog,
}) {
const userInfo = useService(UserInfoResource);
const controller = useController(AuthDialogController, rejectDialog);
const controller = useController(AuthDialogController, link || false, rejectDialog);
const translate = useTranslate();
if (payload) {
controller.selectProvider(payload);
if (provider) {
controller.selectProvider(provider);
}
const showTabs = !payload && controller.providers.length > 1;
const showTabs = !provider && controller.providers.length > 1;
const additional = userInfo.data !== null && controller.provider?.id && !userInfo.hasToken(controller.provider?.id);
@@ -33,6 +33,7 @@ export class AuthDialogController implements IInitializableController, IDestruct
readonly error = new GQLErrorCatcher();
private isDistructed = false;
private link!: boolean;
private close!: () => void;
constructor(
@@ -49,7 +50,8 @@ export class AuthDialogController implements IInitializableController, IDestruct
});
}
init(onClose: () => void) {
init(link: boolean, onClose: () => void) {
this.link = link;
this.close = onClose;
this.loadProviders();
}
@@ -65,7 +67,7 @@ export class AuthDialogController implements IInitializableController, IDestruct
this.isAuthenticating = true;
try {
await this.authInfoService.login(this.provider.id, this.credentials);
await this.authInfoService.login(this.provider.id, this.credentials, this.link);
this.close();
} catch (exception) {
if (!this.error.catch(exception) || this.isDistructed) {
@@ -17,7 +17,7 @@ export class AuthDialogService {
private commonDialogService: CommonDialogService
) { }
async showLoginForm(persistent = false, provider: string | null = null): Promise<void> {
await this.commonDialogService.open(AuthDialog, provider, { persistent });
async showLoginForm(persistent = false, provider: string | null = null, link?: boolean): Promise<void> {
await this.commonDialogService.open(AuthDialog, { provider, link }, { persistent });
}
}