Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type { LoginService } from '@rocket.chat/ui-contexts';
import { AuthenticationContext, useSetting } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
import type { ContextType, ReactElement, ReactNode } from 'react';
import React, { useMemo } from 'react';

import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubscriptionFactory';
import { useLDAPAndCrowdCollisionWarning } from './hooks/useLDAPAndCrowdCollisionWarning';

const capitalize = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1);

const config: Record<string, Partial<LoginService>> = {
'apple': { title: 'Apple', icon: 'apple' },
'facebook': { title: 'Facebook', icon: 'facebook' },
'twitter': { title: 'Twitter', icon: 'twitter' },
'google': { title: 'Google', icon: 'google' },
'github': { title: 'Github', icon: 'github' },
'github_enterprise': { title: 'Github Enterprise', icon: 'github' },
'gitlab': { title: 'Gitlab', icon: 'gitlab' },
'dolphin': { title: 'Dolphin', icon: 'dophin' },
'drupal': { title: 'Drupal', icon: 'drupal' },
'nextcloud': { title: 'Nextcloud', icon: 'nextcloud' },
'tokenpass': { title: 'Tokenpass', icon: 'tokenpass' },
'meteor-developer': { title: 'Meteor', icon: 'meteor' },
'wordpress': { title: 'WordPress', icon: 'wordpress' },
'linkedin': { title: 'Linkedin', icon: 'linkedin' },
};

export type LoginMethods = keyof typeof Meteor extends infer T ? (T extends `loginWith${string}` ? T : never) : never;

type AuthenticationProviderProps = {
children: ReactNode;
};

const AuthenticationProvider = ({ children }: AuthenticationProviderProps): ReactElement => {
const isLdapEnabled = useSetting<boolean>('LDAP_Enable');
const isCrowdEnabled = useSetting<boolean>('CROWD_Enable');

const loginMethod: LoginMethods = (isLdapEnabled && 'loginWithLDAP') || (isCrowdEnabled && 'loginWithCrowd') || 'loginWithPassword';

useLDAPAndCrowdCollisionWarning();

const contextValue = useMemo(
(): ContextType<typeof AuthenticationContext> => ({
loginWithToken: (token: string): Promise<void> =>
new Promise((resolve, reject) =>
Meteor.loginWithToken(token, (err) => {
if (err) {
return reject(err);
}
resolve(undefined);
}),
),
loginWithPassword: (user: string | { username: string } | { email: string } | { id: string }, password: string): Promise<void> =>
new Promise((resolve, reject) => {
Meteor[loginMethod](user, password, (error) => {
if (error) {
reject(error);
return;
}

resolve();
});
}),
loginWithService: <T extends LoginService>({ service, clientConfig = {} }: T): (() => Promise<true>) => {
const loginMethods = {
'meteor-developer': 'MeteorDeveloperAccount',
} as const;

const loginWithService = `loginWith${loginMethods[service] || capitalize(String(service || ''))}`;

const method: (config: unknown, cb: (error: any) => void) => Promise<true> = (Meteor as any)[loginWithService] as any;

if (!method) {
return () => Promise.reject(new Error('Login method not found'));
}

return () =>
new Promise((resolve, reject) => {
method(clientConfig, (error: any): void => {
if (!error) {
resolve(true);
return;
}
reject(error);
});
});
},
queryAllServices: createReactiveSubscriptionFactory(() =>
ServiceConfiguration.configurations
.find(
{
showButton: { $ne: false },
},
{
sort: {
service: 1,
},
},
)
.fetch()
.map(
({ appId: _, ...service }) =>
({
title: capitalize(String((service as any).service || '')),
...service,
...(config[(service as any).service] ?? {}),
} as any),
),
),
}),
[loginMethod],
);

return <AuthenticationContext.Provider children={children} value={contextValue} />;
};

export default AuthenticationProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useSetting } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
import { useEffect } from 'react';

import type { LoginMethods } from '../UserProvider';
import type { LoginMethods } from '../AuthenticationProvider';

export function useLDAPAndCrowdCollisionWarning() {
const isLdapEnabled = useSetting<boolean>('LDAP_Enable');
Expand Down
45 changes: 24 additions & 21 deletions apps/meteor/client/providers/MeteorProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';

import { OmnichannelRoomIconProvider } from '../components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider';
import ActionManagerProvider from './ActionManagerProvider';
import AuthenticationProvider from './AuthenticationProvider/AuthenticationProvider';
import AuthorizationProvider from './AuthorizationProvider';
import AvatarUrlProvider from './AvatarUrlProvider';
import { CallProvider } from './CallProvider';
Expand Down Expand Up @@ -36,27 +37,29 @@ const MeteorProvider: FC = ({ children }) => (
<LayoutProvider>
<AvatarUrlProvider>
<UserProvider>
<CustomSoundProvider>
<DeviceProvider>
<ModalProvider>
<AuthorizationProvider>
<EmojiPickerProvider>
<OmnichannelRoomIconProvider>
<UserPresenceProvider>
<ActionManagerProvider>
<VideoConfProvider>
<CallProvider>
<OmnichannelProvider>{children}</OmnichannelProvider>
</CallProvider>
</VideoConfProvider>
</ActionManagerProvider>
</UserPresenceProvider>
</OmnichannelRoomIconProvider>
</EmojiPickerProvider>
</AuthorizationProvider>
</ModalProvider>
</DeviceProvider>
</CustomSoundProvider>
<AuthenticationProvider>
<CustomSoundProvider>
<DeviceProvider>
<ModalProvider>
<AuthorizationProvider>
<EmojiPickerProvider>
<OmnichannelRoomIconProvider>
<UserPresenceProvider>
<ActionManagerProvider>
<VideoConfProvider>
<CallProvider>
<OmnichannelProvider>{children}</OmnichannelProvider>
</CallProvider>
</VideoConfProvider>
</ActionManagerProvider>
</UserPresenceProvider>
</OmnichannelRoomIconProvider>
</EmojiPickerProvider>
</AuthorizationProvider>
</ModalProvider>
</DeviceProvider>
</CustomSoundProvider>
</AuthenticationProvider>
</UserProvider>
</AvatarUrlProvider>
</LayoutProvider>
Expand Down
100 changes: 3 additions & 97 deletions apps/meteor/client/providers/UserProvider/UserProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings';
import { useLocalStorage } from '@rocket.chat/fuselage-hooks';
import type { LoginService, SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { UserContext, useEndpoint, useSetting } from '@rocket.chat/ui-contexts';
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { UserContext, useEndpoint } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
import type { ContextType, ReactElement, ReactNode } from 'react';
import React, { useEffect, useMemo } from 'react';
Expand All @@ -15,33 +15,13 @@ import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubsc
import { useCreateFontStyleElement } from '../../views/account/accessibility/hooks/useCreateFontStyleElement';
import { useDeleteUser } from './hooks/useDeleteUser';
import { useEmailVerificationWarning } from './hooks/useEmailVerificationWarning';
import { useLDAPAndCrowdCollisionWarning } from './hooks/useLDAPAndCrowdCollisionWarning';
import { useUpdateAvatar } from './hooks/useUpdateAvatar';
import { useUpdateCustomUserStatus } from './hooks/useUpdateCustomUserStatus';

const getUserId = (): string | null => Meteor.userId();

const getUser = (): IUser | null => Meteor.user() as IUser | null;

const capitalize = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1);

const config: Record<string, Partial<LoginService>> = {
'apple': { title: 'Apple', icon: 'apple' },
'facebook': { title: 'Facebook', icon: 'facebook' },
'twitter': { title: 'Twitter', icon: 'twitter' },
'google': { title: 'Google', icon: 'google' },
'github': { title: 'Github', icon: 'github' },
'github_enterprise': { title: 'Github Enterprise', icon: 'github' },
'gitlab': { title: 'Gitlab', icon: 'gitlab' },
'dolphin': { title: 'Dolphin', icon: 'dophin' },
'drupal': { title: 'Drupal', icon: 'drupal' },
'nextcloud': { title: 'Nextcloud', icon: 'nextcloud' },
'tokenpass': { title: 'Tokenpass', icon: 'tokenpass' },
'meteor-developer': { title: 'Meteor', icon: 'meteor' },
'wordpress': { title: 'WordPress', icon: 'wordpress' },
'linkedin': { title: 'Linkedin', icon: 'linkedin' },
};

const logout = (): Promise<void> =>
new Promise((resolve, reject) => {
const user = getUser();
Expand All @@ -56,16 +36,11 @@ const logout = (): Promise<void> =>
});
});

export type LoginMethods = keyof typeof Meteor extends infer T ? (T extends `loginWith${string}` ? T : never) : never;

type UserProviderProps = {
children: ReactNode;
};

const UserProvider = ({ children }: UserProviderProps): ReactElement => {
const isLdapEnabled = useSetting<boolean>('LDAP_Enable');
const isCrowdEnabled = useSetting<boolean>('CROWD_Enable');

const userId = useReactiveValue(getUserId);
const user = useReactiveValue(getUser);
const [userLanguage, setUserLanguage] = useLocalStorage('userLanguage', '');
Expand All @@ -76,9 +51,6 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => {
const createFontStyleElement = useCreateFontStyleElement();
createFontStyleElement(user?.settings?.preferences?.fontSize);

const loginMethod: LoginMethods = (isLdapEnabled && 'loginWithLDAP') || (isCrowdEnabled && 'loginWithCrowd') || 'loginWithPassword';

useLDAPAndCrowdCollisionWarning();
useEmailVerificationWarning(user ?? undefined);

useUpdateCustomUserStatus();
Expand All @@ -103,75 +75,9 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => {

return ChatRoom.find(query, options).fetch();
}),
loginWithToken: (token: string): Promise<void> =>
new Promise((resolve, reject) =>
Meteor.loginWithToken(token, (err) => {
if (err) {
return reject(err);
}
resolve(undefined);
}),
),
loginWithPassword: (user: string | { username: string } | { email: string } | { id: string }, password: string): Promise<void> =>
new Promise((resolve, reject) => {
Meteor[loginMethod](user, password, (error) => {
if (error) {
reject(error);
return;
}

resolve();
});
}),
logout,
loginWithService: <T extends LoginService>({ service, clientConfig = {} }: T): (() => Promise<true>) => {
const loginMethods = {
'meteor-developer': 'MeteorDeveloperAccount',
} as const;

const loginWithService = `loginWith${loginMethods[service] || capitalize(String(service || ''))}`;

const method: (config: unknown, cb: (error: any) => void) => Promise<true> = (Meteor as any)[loginWithService] as any;

if (!method) {
return () => Promise.reject(new Error('Login method not found'));
}

return () =>
new Promise((resolve, reject) => {
method(clientConfig, (error: any): void => {
if (!error) {
resolve(true);
return;
}
reject(error);
});
});
},
queryAllServices: createReactiveSubscriptionFactory(() =>
ServiceConfiguration.configurations
.find(
{
showButton: { $ne: false },
},
{
sort: {
service: 1,
},
},
)
.fetch()
.map(
({ appId: _, ...service }) =>
({
title: capitalize(String((service as any).service || '')),
...service,
...(config[(service as any).service] ?? {}),
} as any),
),
),
}),
[userId, user, loginMethod],
[userId, user],
);

useEffect(() => {
Expand Down
6 changes: 1 addition & 5 deletions apps/meteor/client/sidebar/Sidebar.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ISetting } from '@rocket.chat/core-typings';
import type { LoginService, SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import { UserContext, SettingsContext } from '@rocket.chat/ui-contexts';
import type { Meta, Story } from '@storybook/react';
import type { ObjectId } from 'mongodb';
Expand Down Expand Up @@ -98,10 +98,6 @@ const userContextValue: ContextType<typeof UserContext> = {
querySubscription: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => undefined],

queryAllServices: () => [() => (): void => undefined, (): LoginService[] => []],
loginWithService: () => () => Promise.reject('loginWithService not implemented'),
loginWithPassword: async () => Promise.reject('loginWithPassword not implemented'),
loginWithToken: async () => Promise.reject('loginWithToken not implemented'),
logout: () => Promise.resolve(),
};

Expand Down
4 changes: 0 additions & 4 deletions packages/mock-providers/src/MockedAppRootBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,7 @@ export class MockedAppRootBuilder {
};

private user: ContextType<typeof UserContext> = {
loginWithPassword: () => Promise.reject(new Error('not implemented')),
logout: () => Promise.reject(new Error('not implemented')),
loginWithService: () => () => Promise.reject(new Error('not implemented')),
loginWithToken: () => Promise.reject(new Error('not implemented')),
queryAllServices: () => [() => () => undefined, () => []],
queryPreference: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => undefined],
querySubscription: () => [() => () => undefined, () => undefined],
Expand Down
5 changes: 0 additions & 5 deletions packages/mock-providers/src/MockedUserContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { LoginService } from '@rocket.chat/ui-contexts';
import { UserContext } from '@rocket.chat/ui-contexts';
import React from 'react';
import type { ContextType } from 'react';
Expand All @@ -23,10 +22,6 @@ const userContextValue: ContextType<typeof UserContext> = {
querySubscription: () => [() => () => undefined, () => undefined],
queryRoom: () => [() => () => undefined, () => undefined],

queryAllServices: () => [() => (): void => undefined, (): LoginService[] => []],
loginWithService: () => () => Promise.reject('loginWithService not implemented'),
loginWithPassword: async () => Promise.reject('loginWithPassword not implemented'),
loginWithToken: async () => Promise.reject('loginWithToken not implemented'),
logout: () => Promise.resolve(),
};

Expand Down
Loading