mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
* dbeaver/pro#6744 refactor: migration to `@wroud/di` * tests: enable settings tests * chore: update syntax * fix: dynamic theme detection * fix: skip not stable test * fix: skip whole test * fix: make theme optional * fix: page reload * tests: skip unstable test * fix: remove optional * fix: load system theme * fix: prevent the system theme from always loading despite not being selected --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
307 lines
8.3 KiB
TypeScript
307 lines
8.3 KiB
TypeScript
/*
|
|
* CloudBeaver - Cloud Database Manager
|
|
* Copyright (C) 2020-2025 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 { computed, makeObservable, runInAction } from 'mobx';
|
|
|
|
import { injectable } from '@cloudbeaver/core-di';
|
|
import { AutoRunningTask, type ISyncExecutor, type ITask, SyncExecutor, whileTask } from '@cloudbeaver/core-executor';
|
|
import { CachedDataResource, type ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource';
|
|
import { SessionResource } from '@cloudbeaver/core-root';
|
|
import { type FederatedAuthInfo, type AuthInfo, type AuthLogoutQuery, AuthStatus, GraphQLService, type UserInfo } from '@cloudbeaver/core-sdk';
|
|
|
|
import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID.js';
|
|
import { AuthProviderService } from './AuthProviderService.js';
|
|
import type { ELMRole } from './ELMRole.js';
|
|
import type { IAuthCredentials } from './IAuthCredentials.js';
|
|
|
|
export type UserLogoutInfo = AuthLogoutQuery['result'];
|
|
|
|
export interface ILoginOptions {
|
|
credentials?: IAuthCredentials;
|
|
configurationId?: string;
|
|
linkUser?: boolean;
|
|
forceSessionsLogout?: boolean;
|
|
}
|
|
|
|
export type IFederatedLoginOptions = Omit<ILoginOptions, 'credentials'>;
|
|
|
|
export const ANONYMOUS_USER_ID = 'anonymous';
|
|
export const UNAUTHORIZED_ID = 'unauthorized';
|
|
|
|
@injectable(() => [GraphQLService, AuthProviderService, SessionResource])
|
|
export class UserInfoResource extends CachedDataResource<UserInfo | null, void> {
|
|
readonly onUserChange: ISyncExecutor<string>;
|
|
readonly onException: ISyncExecutor<Error>;
|
|
|
|
get authRole(): ELMRole | undefined {
|
|
return this.data?.authRole as ELMRole | undefined;
|
|
}
|
|
|
|
get teams() {
|
|
return this.data?.teams || [];
|
|
}
|
|
|
|
get parametersAvailable() {
|
|
return this.data !== null;
|
|
}
|
|
|
|
constructor(
|
|
private readonly graphQLService: GraphQLService,
|
|
private readonly authProviderService: AuthProviderService,
|
|
private readonly sessionResource: SessionResource,
|
|
) {
|
|
super(() => null);
|
|
|
|
this.onUserChange = new SyncExecutor();
|
|
this.onException = new SyncExecutor();
|
|
|
|
this.sync(
|
|
sessionResource,
|
|
() => {},
|
|
() => {},
|
|
);
|
|
|
|
makeObservable(this, {
|
|
parametersAvailable: computed,
|
|
});
|
|
}
|
|
|
|
isAnonymous(): this is { data: UserInfo } {
|
|
return this.data?.isAnonymous === true;
|
|
}
|
|
|
|
isAuthenticated(): this is { data: UserInfo } {
|
|
return !!this.data && !this.isAnonymous();
|
|
}
|
|
|
|
hasAccess(): this is { data: UserInfo } {
|
|
return this.isAnonymous() || this.isAuthenticated();
|
|
}
|
|
|
|
isLinked(provideId: string): boolean {
|
|
return this.data?.linkedAuthProviders.includes(provideId) || false;
|
|
}
|
|
|
|
getId(): string {
|
|
return this.data?.userId || UNAUTHORIZED_ID;
|
|
}
|
|
|
|
hasToken(providerId: string): boolean {
|
|
if (providerId === AUTH_PROVIDER_LOCAL_ID) {
|
|
return true;
|
|
}
|
|
|
|
if (!this.data) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: will be changed due wrong origin in authTokens
|
|
return this.data.authTokens.some(token => token.authProvider === providerId);
|
|
}
|
|
|
|
async login(provider: string, { credentials, configurationId, linkUser, forceSessionsLogout }: ILoginOptions): Promise<AuthInfo> {
|
|
let processedCredentials: Record<string, any> | undefined;
|
|
|
|
if (credentials) {
|
|
const processed = await this.authProviderService.processCredentials(provider, credentials);
|
|
processedCredentials = processed.credentials;
|
|
}
|
|
|
|
const { authInfo } = await this.graphQLService.sdk.authLogin({
|
|
provider,
|
|
configuration: configurationId,
|
|
credentials: processedCredentials,
|
|
linkUser,
|
|
forceSessionsLogout,
|
|
});
|
|
|
|
if (authInfo.userTokens && authInfo.authStatus === AuthStatus.Success) {
|
|
await this.syncData();
|
|
}
|
|
|
|
return authInfo as AuthInfo;
|
|
}
|
|
|
|
async requestFederatedLogin(
|
|
provider: string,
|
|
{ configurationId, linkUser, forceSessionsLogout }: IFederatedLoginOptions,
|
|
): Promise<FederatedAuthInfo> {
|
|
const { result } = await this.graphQLService.sdk.federatedLogin({
|
|
provider,
|
|
configuration: configurationId,
|
|
linkUser,
|
|
forceSessionsLogout,
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
autoLogin(authId: string, linkUser?: boolean): ITask<UserInfo | null> {
|
|
let activeTask: ITask<AuthInfo> | undefined;
|
|
|
|
return new AutoRunningTask<UserInfo | null>(
|
|
async () => {
|
|
activeTask = whileTask<AuthInfo>(
|
|
authInfo => {
|
|
if (authInfo.authStatus === AuthStatus.Success) {
|
|
return true;
|
|
} else if (authInfo.authStatus === AuthStatus.Error) {
|
|
throw new Error('Authentication error');
|
|
}
|
|
|
|
return false;
|
|
},
|
|
async () => {
|
|
const { authInfo } = await this.graphQLService.sdk.getAuthStatus({
|
|
authId,
|
|
linkUser,
|
|
});
|
|
return authInfo as AuthInfo;
|
|
},
|
|
1000,
|
|
undefined,
|
|
5 * 60 * 1000,
|
|
);
|
|
|
|
const authInfo = await activeTask;
|
|
|
|
if (authInfo.userTokens && authInfo.authStatus === AuthStatus.Success) {
|
|
await this.syncData();
|
|
}
|
|
|
|
return this.data;
|
|
},
|
|
() => {
|
|
activeTask?.cancel();
|
|
},
|
|
);
|
|
}
|
|
|
|
async logout(provider?: string, configuration?: string): Promise<AuthLogoutQuery> {
|
|
const result = await this.graphQLService.sdk.authLogout({
|
|
provider,
|
|
configuration,
|
|
});
|
|
|
|
const data = await this.loader();
|
|
runInAction(() => {
|
|
this.resetIncludes();
|
|
this.setData(data);
|
|
this.sessionResource.markOutdated();
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
async updatePreferences(preferences: Record<string, any>): Promise<UserInfo | null> {
|
|
await this.performUpdate(undefined, [], async () => {
|
|
const { user } = await this.graphQLService.sdk.updateUserPreferences({
|
|
preferences,
|
|
});
|
|
|
|
this.setData(user as UserInfo | null);
|
|
});
|
|
|
|
return this.data;
|
|
}
|
|
|
|
async setConfigurationParameter(key: string, value: any): Promise<UserInfo | null> {
|
|
await this.load();
|
|
|
|
if (!this.parametersAvailable) {
|
|
return this.data;
|
|
}
|
|
|
|
await this.performUpdate(undefined, [], async () => {
|
|
await this.graphQLService.sdk.setUserConfigurationParameter({
|
|
name: key,
|
|
value,
|
|
});
|
|
|
|
if (this.data) {
|
|
this.data.configurationParameters[key] = value;
|
|
}
|
|
|
|
this.onDataOutdated.execute();
|
|
});
|
|
|
|
return this.data;
|
|
}
|
|
|
|
async updateLocalPassword(oldPassword: string, newPassword: string): Promise<void> {
|
|
await this.performUpdate(undefined, [], async () => {
|
|
await this.graphQLService.sdk.authChangeLocalPassword({
|
|
oldPassword: this.authProviderService.hashValue(oldPassword),
|
|
newPassword: this.authProviderService.hashValue(newPassword),
|
|
});
|
|
});
|
|
}
|
|
|
|
async deleteConfigurationParameter(key: ResourceKeySimple<string>): Promise<UserInfo | null> {
|
|
await this.load();
|
|
|
|
if (!this.parametersAvailable) {
|
|
return this.data;
|
|
}
|
|
|
|
const keyList: string[] = [];
|
|
await this.performUpdate(undefined, [], async () => {
|
|
await ResourceKeyUtils.forEachAsync(key, async name => {
|
|
await this.graphQLService.sdk.setUserConfigurationParameter({
|
|
name,
|
|
value: null,
|
|
});
|
|
|
|
keyList.push(name);
|
|
});
|
|
|
|
runInAction(() => {
|
|
for (const item of keyList) {
|
|
delete this.data?.configurationParameters[item];
|
|
}
|
|
});
|
|
|
|
this.onDataOutdated.execute();
|
|
});
|
|
return this.data;
|
|
}
|
|
|
|
getConfigurationParameter(key: string): any {
|
|
return this.data?.configurationParameters[key];
|
|
}
|
|
|
|
async syncData(): Promise<void> {
|
|
this.resetIncludes();
|
|
this.setData(await this.loader());
|
|
this.sessionResource.markOutdated();
|
|
}
|
|
|
|
protected async loader(key: void): Promise<UserInfo | null> {
|
|
try {
|
|
const { user } = await this.graphQLService.sdk.getActiveUser({});
|
|
|
|
return (user as UserInfo | null) || null;
|
|
} catch (exception: any) {
|
|
if (this.onException.isEmpty) {
|
|
throw exception;
|
|
}
|
|
this.onException.execute(exception);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected override setData(data: UserInfo | null): void {
|
|
const prevUserId = this.getId();
|
|
super.setData(data);
|
|
const currentUserId = this.getId();
|
|
|
|
if (prevUserId !== currentUserId) {
|
|
this.onUserChange.execute(currentUserId);
|
|
}
|
|
}
|
|
}
|