Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ refactor: Move next-auth hooks to user store actions #2364

Merged
merged 24 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c4df8e2
:recycle: refactor: move next-auth hooks to store acitons
cy948 May 4, 2024
db9557f
Merge branch 'main' into refactor/next-auth-hooks
cy948 May 4, 2024
992e82c
:fire: refactor: remove redundant files
cy948 May 5, 2024
c48f100
Merge branch 'main' into refactor/next-auth-hooks
cy948 May 5, 2024
550bde4
Merge branch 'main' into refactor/next-auth-hooks
cy948 May 5, 2024
599c601
♻️ refactor: add next-auth hooks
cy948 May 5, 2024
da3600a
♻️ refactor: add env `NEXT_PUBLIC_ENABLE_NEXT_AUTH`
cy948 May 6, 2024
dcd2c31
:memo: docs: update docs for new NextAuth env
cy948 May 6, 2024
1a839ca
Merge branch 'main' into refactor/next-auth-hooks
cy948 May 6, 2024
5eac550
♻️ refactor: remove `login` in auth actions
cy948 May 6, 2024
8b692d7
🐛fix: login buttion not shown on settings
cy948 May 6, 2024
0eaf64b
:white_check_mark: test: hooks in auth actions
cy948 May 6, 2024
e1a5fec
♻️ refactor: change button show condition
cy948 May 6, 2024
ce7491c
♻️ refactor: use server config to show oauth button
cy948 May 8, 2024
f1b9977
Merge branch 'main' into refactor/next-auth-hooks
cy948 May 8, 2024
17cc3ee
🐛fix: context error in settings
cy948 May 8, 2024
259c075
Merge branch 'main' into refactor/next-auth-hooks
cy948 May 10, 2024
d8da7f8
♻️ refactor: use server config store to enable auth
cy948 May 10, 2024
dde83fc
:speech_balloon: refactor: remove tips
cy948 May 12, 2024
ed85d64
Merge branch 'main' into refactor/next-auth-hooks
cy948 May 12, 2024
23a3a13
♻️ refactor: remove env `NEXT_PUBLIC_ENABLE_NEXT_AUTH`
cy948 May 12, 2024
1184a8e
Merge branch 'main' into refactor/next-auth-hooks
cy948 May 13, 2024
d5a07d4
:fire: refactor: remove open profile in store auth actions
cy948 May 13, 2024
4006a59
:white_check_mark: test(useMenu): change states
cy948 May 13, 2024
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
9 changes: 9 additions & 0 deletions docs/self-hosting/advanced/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ By setting the environment variables NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK

## Next Auth

Before using NextAuth, please set the following variables in LobeChat's environment variables:

| Environment Variable | Type | Description |
| --- | --- | --- |
| `NEXT_AUTH_SECRET` | Required | The key used to encrypt Auth.js session tokens. You can use the following command: `openssl rand -base64 32`, or visit `https://generate-secret.vercel.app/32` to generate the key. |
| `ACCESS_CODE` | Required | Add a password to access this service. You can set a sufficiently long random password to "disable" access code authorization. |
| `NEXTAUTH_URL` | Optional | This URL specifies the callback address for Auth.js when performing OAuth verification. Set this only if the default generated redirect address is incorrect. `https://example.com/api/auth` |
arvinxx marked this conversation as resolved.
Show resolved Hide resolved
| `NEXT_AUTH_SSO_PROVIDERS` | Optional | This environment variable is used to enable multiple identity verification sources simultaneously, separated by commas, for example, `auth0,azure-ad,authentik`. |

Currently supported identity verification services include:

<Cards>
Expand Down
9 changes: 9 additions & 0 deletions docs/self-hosting/advanced/authentication.zh-CN.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ LobeChat 与 Clerk 做了深度集成,能够为用户提供一个更加安全

## Next Auth

在使用 NextAuth 之前,请先在 LobeChat 的环境变量中设置以下变量:

| 环境变量 | 类型 | 描述 |
| --- | --- | --- |
| `NEXT_AUTH_SECRET` | 必选 | 用于加密 Auth.js 会话令牌的密钥。您可以使用以下命令: `openssl rand -base64 32`,或者访问 `https://generate-secret.vercel.app/32` 生成秘钥。 |
| `ACCESS_CODE` | 必选 | 添加访问此服务的密码,你可以设置一个足够长的随机密码以 “禁用” 访问码授权 |
| `NEXTAUTH_URL` | 可选 | 该 URL 用于指定 Auth.js 在执行 OAuth 验证时的回调地址,当默认生成的重定向地址发生不正确时才需要设置。`https://example.com/api/auth` |
| `NEXT_AUTH_SSO_PROVIDERS` | 可选 | 该环境变量用于同时启用多个身份验证源,以逗号 `,` 分割,例如 `auth0,azure-ad,authentik`。 |

目前支持的身份验证服务有:

<Cards>
Expand Down
19 changes: 11 additions & 8 deletions src/app/(main)/settings/common/features/Common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { Form, type ItemGroup } from '@lobehub/ui';
import { App, Button, Input } from 'antd';
import isEqual from 'fast-deep-equal';
import { signIn, signOut } from 'next-auth/react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -12,23 +11,22 @@ import { FORM_STYLE } from '@/const/layoutTokens';
import { DEFAULT_SETTINGS } from '@/const/settings';
import { useChatStore } from '@/store/chat';
import { useFileStore } from '@/store/file';
import { useServerConfigStore } from '@/store/serverConfig';
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
import { useSessionStore } from '@/store/session';
import { useToolStore } from '@/store/tool';
import { useUserStore } from '@/store/user';
import { settingsSelectors, userProfileSelectors } from '@/store/user/selectors';

type SettingItemGroup = ItemGroup;

export interface SettingsCommonProps {
showAccessCodeConfig: boolean;
showOAuthLogin?: boolean;
}

const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin }) => {
const Common = memo(() => {
const { t } = useTranslation('setting');
const [form] = Form.useForm();

const isSignedIn = useUserStore((s) => s.isSignedIn);
const showAccessCodeConfig = useServerConfigStore(serverConfigSelectors.enabledAccessCode);
const showOAuthLogin = useServerConfigStore(serverConfigSelectors.enabledOAuthSSO);
const user = useUserStore(userProfileSelectors.userProfile, isEqual);

const [clearSessions, clearSessionGroups] = useSessionStore((s) => [
Expand All @@ -42,7 +40,12 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin
const [removeAllFiles] = useFileStore((s) => [s.removeAllFiles]);
const removeAllPlugins = useToolStore((s) => s.removeAllPlugins);
const settings = useUserStore(settingsSelectors.currentSettings, isEqual);
const [setSettings, resetSettings] = useUserStore((s) => [s.setSettings, s.resetSettings]);
const [setSettings, resetSettings, signIn, signOut] = useUserStore((s) => [
s.setSettings,
s.resetSettings,
s.openLogin,
s.logout,
]);

const { message, modal } = App.useApp();

Expand Down
10 changes: 1 addition & 9 deletions src/app/(main)/settings/common/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { authEnv } from '@/config/auth';
import { getServerConfig } from '@/config/server';

import Common from './features/Common';
import Theme from './features/Theme';

const Page = () => {
const { SHOW_ACCESS_CODE_CONFIG } = getServerConfig();

return (
<>
<Theme />
<Common
showAccessCodeConfig={SHOW_ACCESS_CODE_CONFIG}
showOAuthLogin={authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH}
/>
<Common />
</>
);
};
Expand Down
3 changes: 2 additions & 1 deletion src/config/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* @deprecated
*/
ENABLE_OAUTH_SSO?: string;
NEXT_PUBLIC_ENABLE_NEXT_AUTH?: string;
NEXT_AUTH_SECRET?: string;
/**
* @deprecated
Expand Down Expand Up @@ -52,7 +53,7 @@
export const getAuthConfig = () => {
if (process.env.ENABLE_OAUTH_SSO) {
console.warn(
'`ENABLE_OAUTH_SSO` is deprecated and will be removed in LobeChat 1.0. just set `NEXT_AUTH_SECRET` enough',
'`ENABLE_OAUTH_SSO` is deprecated and will be removed in LobeChat 1.0. Please set `NEXT_PUBLIC_ENABLE_NEXT_AUTH` instead.',

Check warning on line 56 in src/config/auth.ts

View check run for this annotation

Codecov / codecov/patch

src/config/auth.ts#L56

Added line #L56 was not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里也不需要 NEXT_PUBLIC_ENABLE_NEXT_AUTH 了吧?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

确实,我改一下。

);
}

Expand Down
10 changes: 6 additions & 4 deletions src/features/Conversation/Error/OAuthForm.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { Icon } from '@lobehub/ui';
import { App, Button } from 'antd';
import { ScanFace } from 'lucide-react';
import { signIn, signOut } from 'next-auth/react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Center, Flexbox } from 'react-layout-kit';

import { useOAuthSession } from '@/hooks/useOAuthSession';
arvinxx marked this conversation as resolved.
Show resolved Hide resolved
import { useChatStore } from '@/store/chat';
import { useUserStore } from '@/store/user';
import { authSelectors, userProfileSelectors } from '@/store/user/selectors';

import { FormAction } from './style';

const OAuthForm = memo<{ id: string }>(({ id }) => {
const { t } = useTranslation('error');

const { user, isOAuthLoggedIn } = useOAuthSession();
const [signIn, signOut] = useUserStore((s) => [s.openLogin, s.logout]);
const user = useUserStore(userProfileSelectors.userProfile);
const isOAuthLoggedIn = useUserStore(authSelectors.isLoginWithAuth);

const [resend, deleteMessage] = useChatStore((s) => [s.regenerateMessage, s.deleteMessage]);

Expand All @@ -38,7 +40,7 @@ const OAuthForm = memo<{ id: string }>(({ id }) => {
avatar={isOAuthLoggedIn ? '✅' : '🕵️‍♂️'}
description={
isOAuthLoggedIn
? `${t('unlock.oauth.welcome')} ${user?.name}`
? `${t('unlock.oauth.welcome')} ${user?.fullName || ''}`
: t('unlock.oauth.description')
}
title={isOAuthLoggedIn ? t('unlock.oauth.success') : t('unlock.oauth.title')}
Expand Down
11 changes: 8 additions & 3 deletions src/features/User/__tests__/UserAvatar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('UserAvatar', () => {

act(() => {
useUserStore.setState({
enableAuth: () => true,
isSignedIn: true,
user: { avatar: mockAvatar, id: 'abc', username: mockUsername },
});
Expand All @@ -45,7 +46,11 @@ describe('UserAvatar', () => {
const mockUsername = 'testuser';

act(() => {
useUserStore.setState({ isSignedIn: true, user: { id: 'bbb', username: mockUsername } });
useUserStore.setState({
enableAuth: () => true,
isSignedIn: true,
user: { id: 'bbb', username: mockUsername },
});
});

render(<UserAvatar />);
Expand All @@ -54,7 +59,7 @@ describe('UserAvatar', () => {

it('should show LobeChat and default avatar when the user is not logged in and enable auth', () => {
act(() => {
useUserStore.setState({ isSignedIn: false, user: undefined });
useUserStore.setState({ enableAuth: () => true, isSignedIn: false, user: undefined });
});

render(<UserAvatar />);
Expand All @@ -67,7 +72,7 @@ describe('UserAvatar', () => {
it('should show LobeChat and default avatar when the user is not logged in and disabled auth', () => {
enableAuth = false;
act(() => {
useUserStore.setState({ isSignedIn: false, user: undefined });
useUserStore.setState({ enableAuth: () => false, isSignedIn: false, user: undefined });
});

render(<UserAvatar />);
Expand Down
24 changes: 0 additions & 24 deletions src/hooks/useOAuthSession.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/libs/next-auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,3 @@ export const {
handlers: { GET, POST },
auth,
} = nextAuth;

declare module '@auth/core/jwt' {
// Returned by the `jwt` callback and `auth`, when using JWT sessions
interface JWT {
userId?: string;
}
}
2 changes: 2 additions & 0 deletions src/server/globalConfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { parseAgentConfig } from './parseDefaultAgent';

export const getServerGlobalConfig = () => {
const {
ACCESS_CODES,
ENABLE_LANGFUSE,

DEFAULT_AGENT_CONFIG,
Expand Down Expand Up @@ -48,6 +49,7 @@ export const getServerGlobalConfig = () => {
config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
},

enabledAccessCode: ACCESS_CODES?.length > 0,
enabledOAuthSSO: enableNextAuth,
languageModel: {
anthropic: {
Expand Down
1 change: 1 addition & 0 deletions src/store/serverConfig/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const featureFlagsSelectors = (s: ServerConfigStore) =>
mapFeatureFlagsEnvToState(s.featureFlags);

export const serverConfigSelectors = {
enabledAccessCode: (s: ServerConfigStore) => !!s.serverConfig?.enabledAccessCode,
enabledOAuthSSO: (s: ServerConfigStore) => s.serverConfig.enabledOAuthSSO,
enabledTelemetryChat: (s: ServerConfigStore) => s.serverConfig.telemetry.langfuse || false,
isMobile: (s: ServerConfigStore) => s.isMobile || false,
Expand Down
61 changes: 61 additions & 0 deletions src/store/user/slices/auth/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ afterEach(() => {
enableClerk = false;
});

/**
* Mock nextauth 库相关方法
*/
vi.mock('next-auth/react', async () => {
return {
signIn: vi.fn(),
signOut: vi.fn(),
};
});

describe('createAuthSlice', () => {
describe('refreshUserConfig', () => {
it('should refresh user config', async () => {
Expand Down Expand Up @@ -162,6 +172,32 @@ describe('createAuthSlice', () => {

expect(clerkSignOutMock).not.toHaveBeenCalled();
});

it('should call next-auth signOut when NextAuth is enabled', async () => {
useUserStore.setState({ enabledNextAuth: () => true });

const { result } = renderHook(() => useUserStore());

await act(async () => {
await result.current.logout();
});

const { signOut } = await import('next-auth/react');

expect(signOut).toHaveBeenCalled();
});

it('should not call next-auth signOut when NextAuth is disabled', async () => {
const { result } = renderHook(() => useUserStore());

await act(async () => {
await result.current.logout();
});

const { signOut } = await import('next-auth/react');

expect(signOut).not.toHaveBeenCalled();
});
});

describe('openLogin', () => {
Expand Down Expand Up @@ -190,6 +226,31 @@ describe('createAuthSlice', () => {

expect(clerkSignInMock).not.toHaveBeenCalled();
});

it('should call next-auth signIn when NextAuth is enabled', async () => {
useUserStore.setState({ enabledNextAuth: () => true });

const { result } = renderHook(() => useUserStore());

await act(async () => {
await result.current.openLogin();
});

const { signIn } = await import('next-auth/react');

expect(signIn).toHaveBeenCalled();
});
it('should not call next-auth signIn when NextAuth is disabled', async () => {
const { result } = renderHook(() => useUserStore());

await act(async () => {
await result.current.openLogin();
});

const { signIn } = await import('next-auth/react');

expect(signIn).not.toHaveBeenCalled();
});
});

describe('openUserProfile', () => {
Expand Down
Loading