diff --git a/packages/core/auth/src/auth-manager.ts b/packages/core/auth/src/auth-manager.ts index ec999df78..518d10eb4 100644 --- a/packages/core/auth/src/auth-manager.ts +++ b/packages/core/auth/src/auth-manager.ts @@ -1,12 +1,17 @@ import { Context, Next } from '@nocobase/actions'; -import { Model } from '@nocobase/database'; import { Registry } from '@nocobase/utils'; import { Auth, AuthExtend } from './auth'; import { JwtOptions, JwtService } from './base/jwt-service'; import { ITokenBlacklistService } from './base/token-blacklist-service'; +export interface Authenticator { + authType: string; + options: Record; + [key: string]: any; +} + export interface Storer { - get: (name: string) => Promise; + get: (name: string) => Promise; } export type AuthManagerOptions = { diff --git a/packages/core/auth/src/auth.ts b/packages/core/auth/src/auth.ts index 3f970ebc9..face37fd4 100644 --- a/packages/core/auth/src/auth.ts +++ b/packages/core/auth/src/auth.ts @@ -1,8 +1,9 @@ import { Context } from '@nocobase/actions'; import { Model } from '@nocobase/database'; +import { Authenticator } from './auth-manager'; export type AuthConfig = { - authenticator: Model; + authenticator: Authenticator; options: { [key: string]: any; }; @@ -22,7 +23,7 @@ interface IAuth { export abstract class Auth implements IAuth { abstract user: Model; - protected authenticator: Model; + protected authenticator: Authenticator; protected options: { [key: string]: any; }; @@ -36,7 +37,7 @@ export abstract class Auth implements IAuth { } // The abstract methods are required to be implemented by all authentications. - abstract check(); + abstract check(): Promise; // The following methods are mainly designed for user authentications. async signIn(): Promise {} async signUp(): Promise {} diff --git a/packages/core/client/src/auth/OptionsComponent.tsx b/packages/core/client/src/auth/OptionsComponent.tsx deleted file mode 100644 index b15f6b76f..000000000 --- a/packages/core/client/src/auth/OptionsComponent.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { FunctionComponent, createContext, useContext, createElement } from 'react'; -import { useTranslation } from 'react-i18next'; - -const OptionsComponentContext = createContext<{ - [authType: string]: FunctionComponent; -}>({}); - -export const OptionsComponentProvider: React.FC<{ authType: string; component: FunctionComponent }> = (props) => { - const components = useContext(OptionsComponentContext); - components[props.authType] = props.component; - return {props.children}; -}; - -export const useHasOptionsComponent = (authType: string) => { - const components = useContext(OptionsComponentContext); - return components[authType]; -}; - -export const useOptionsComponent = (authType: string) => { - const { t } = useTranslation(); - const component = useHasOptionsComponent(authType); - return component ? createElement(component) : <>; -}; diff --git a/packages/core/client/src/auth/SigninPage.tsx b/packages/core/client/src/auth/SigninPage.tsx deleted file mode 100644 index d7e938383..000000000 --- a/packages/core/client/src/auth/SigninPage.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { css } from '@emotion/css'; -import { useForm } from '@formily/react'; -import { Space, Tabs } from 'antd'; -import React, { - FunctionComponent, - FunctionComponentElement, - createContext, - createElement, - useCallback, - useContext, - useState, -} from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate, useSearchParams } from 'react-router-dom'; -import { useAPIClient, useApp, useCurrentDocumentTitle, useCurrentUserContext, useRequest, useViewport } from '..'; -import { useSigninPageExtension } from './SigninPageExtension'; - -const SigninPageContext = createContext<{ - [authType: string]: { - component: FunctionComponent; - tabTitle?: string; - }; -}>({}); - -export const SigninPageProvider: React.FC<{ - authType: string; - component: FunctionComponent<{ authenticator: Authenticator }>; - tabTitle?: string; -}> = (props) => { - const components = useContext(SigninPageContext); - components[props.authType] = { - component: props.component, - tabTitle: props.tabTitle, - }; - return {props.children}; -}; - -export type Authenticator = { - name: string; - authType: string; - title?: string; - options?: { - [key: string]: any; - }; - sort?: number; -}; - -export const AuthenticatorsContext = createContext([]); - -export function useRedirect(next = '/admin') { - const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - return useCallback(() => { - navigate(searchParams.get('redirect') || '/admin', { replace: true }); - }, [navigate, searchParams]); -} - -export const useSignIn = (authenticator) => { - const form = useForm(); - const api = useAPIClient(); - const redirect = useRedirect(); - const { refreshAsync } = useCurrentUserContext(); - return { - async run() { - await form.submit(); - await api.auth.signIn(form.values, authenticator); - await refreshAsync(); - redirect(); - }, - }; -}; - -export const SigninPage = () => { - const { t } = useTranslation(); - useCurrentDocumentTitle('Signin'); - useViewport(); - const signInPages = useContext(SigninPageContext); - const api = useAPIClient(); - const [authenticators, setAuthenticators] = useState([]); - const [tabs, setTabs] = useState< - (Authenticator & { - component: FunctionComponentElement<{ authenticator: Authenticator }>; - tabTitle: string; - })[] - >([]); - const signinExtension = useSigninPageExtension(authenticators); - - const handleAuthenticators = (authenticators: Authenticator[]) => { - const tabs = authenticators - .map((authenticator) => { - const page = signInPages[authenticator.authType]; - if (!page) { - return; - } - return { - component: createElement<{ - authenticator: Authenticator; - }>(page.component, { authenticator }), - tabTitle: authenticator.title || page.tabTitle || authenticator.name, - ...authenticator, - }; - }) - .filter((i) => i); - - setAuthenticators(authenticators); - setTabs(tabs); - }; - - const { error } = useRequest( - () => - api - .resource('authenticators') - .publicList() - .then((res) => { - return res?.data?.data || []; - }), - { - onSuccess: (data) => { - handleAuthenticators(data); - }, - }, - ); - - if (error) { - throw error; - } - - if (!authenticators.length) { - return
{t('Oops! No authentication methods available.')}
; - } - - return ( - - - {tabs.length > 1 ? ( - ({ label: t(tab.tabTitle), key: tab.name, children: tab.component }))} /> - ) : tabs.length ? ( -
{tabs[0].component}
- ) : ( - <> - )} - - {signinExtension} - -
-
- ); -}; diff --git a/packages/core/client/src/auth/SigninPageExtension.tsx b/packages/core/client/src/auth/SigninPageExtension.tsx deleted file mode 100644 index e5b453bc2..000000000 --- a/packages/core/client/src/auth/SigninPageExtension.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, { createContext, FunctionComponent, useContext } from 'react'; -import { Authenticator, AuthenticatorsContext } from './SigninPage'; - -export interface SigninPageExtensionContextValue { - components: { - [authType: string]: FunctionComponent<{ - authenticator: Authenticator; - [key: string]: any; - }>; - }; -} - -export const SigninPageExtensionContext = createContext({ components: {} }); - -export const useSigninPageExtension = (authenticators = []) => { - const { components } = useContext(SigninPageExtensionContext); - const types = Object.keys(components); - return authenticators - .filter((authenticator) => types.includes(authenticator.authType)) - .map((authenticator, index) => - React.createElement(components[authenticator.authType], { key: index, authenticator }), - ); -}; - -export const SigninPageExtensionProvider: React.FC<{ - authType: string; - component: FunctionComponent<{ - authenticator: Authenticator; - [key: string]: any; - }>; -}> = (props) => { - const { components } = useContext(SigninPageExtensionContext); - if (!components[props.authType]) { - components[props.authType] = props.component; - } - - return ( - {props.children} - ); -}; diff --git a/packages/core/client/src/auth/SignupPage.tsx b/packages/core/client/src/auth/SignupPage.tsx deleted file mode 100644 index 43c3827d2..000000000 --- a/packages/core/client/src/auth/SignupPage.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { message } from 'antd'; -import React, { useContext, createContext, FunctionComponent, createElement } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Link, useNavigate, useSearchParams } from 'react-router-dom'; -import { useAPIClient, useCurrentDocumentTitle, useViewport } from '..'; -import { useForm } from '@formily/react'; - -export const SignupPageContext = createContext<{ - [authType: string]: { - component: FunctionComponent<{ - name: string; - }>; - }; -}>({}); - -export const SignupPageProvider: React.FC<{ - authType: string; - component: FunctionComponent<{ - name: string; - }>; -}> = (props) => { - const components = useContext(SignupPageContext); - components[props.authType] = { - component: props.component, - }; - return {props.children}; -}; - -export interface UseSignupProps { - authenticator?: string; - message?: { - success?: string; - }; -} - -export const useSignup = (props?: UseSignupProps) => { - const navigate = useNavigate(); - const form = useForm(); - const api = useAPIClient(); - const { t } = useTranslation(); - return { - async run() { - await form.submit(); - await api.auth.signUp(form.values, props?.authenticator); - message.success(props?.message?.success || t('Sign up successfully, and automatically jump to the sign in page')); - setTimeout(() => { - navigate('/signin'); - }, 2000); - }, - }; -}; - -export const SignupPage = () => { - const { t } = useTranslation(); - useViewport(); - useCurrentDocumentTitle('Signup'); - const [searchParams] = useSearchParams(); - const authType = searchParams.get('authType'); - const name = searchParams.get('name'); - const signUpPages = useContext(SignupPageContext); - if (!signUpPages[authType]) { - return ( -
-
{t('Oops! The authentication type does not allow sign-up.')}
- {t('Return')} -
- ); - } - return createElement(signUpPages[authType].component, { name }); -}; diff --git a/packages/core/client/src/auth/index.ts b/packages/core/client/src/auth/index.ts deleted file mode 100644 index 76e445792..000000000 --- a/packages/core/client/src/auth/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Plugin } from '../application/Plugin'; -import { SigninPageExtensionProvider } from './SigninPageExtension'; - -export * from './OptionsComponent'; -export * from './SigninPage'; -export * from './SigninPageExtension'; -export * from './SignupPage'; - -export class SigninPageExtensionPlugin extends Plugin { - async load() { - this.app.use(SigninPageExtensionProvider, this.options); - } -} diff --git a/packages/core/client/src/index.ts b/packages/core/client/src/index.ts index 77e942de0..c2bf282cf 100644 --- a/packages/core/client/src/index.ts +++ b/packages/core/client/src/index.ts @@ -18,7 +18,6 @@ export * from './api-client'; export * from './appInfo'; export * from './application'; export * from './async-data-provider'; -export * from './auth'; export * from './block-provider'; export * from './china-region'; export * from './collection-manager'; diff --git a/packages/core/client/src/nocobase-buildin-plugin/index.tsx b/packages/core/client/src/nocobase-buildin-plugin/index.tsx index d6b41e820..c979d6c48 100644 --- a/packages/core/client/src/nocobase-buildin-plugin/index.tsx +++ b/packages/core/client/src/nocobase-buildin-plugin/index.tsx @@ -9,12 +9,11 @@ import { ACLPlugin } from '../acl'; import { useAPIClient } from '../api-client'; import { Application } from '../application'; import { Plugin } from '../application/Plugin'; -import { SigninPage, SigninPageExtensionPlugin, SignupPage } from '../auth'; import { BlockSchemaComponentPlugin } from '../block-provider'; import { RemoteDocumentTitlePlugin } from '../document-title'; import { PinnedListPlugin } from '../plugin-manager'; import { PMPlugin } from '../pm'; -import { AdminLayoutPlugin, AuthLayout, RouteSchemaComponent } from '../route-switch'; +import { AdminLayoutPlugin, RouteSchemaComponent } from '../route-switch'; import { AntdSchemaComponentPlugin, SchemaComponentPlugin, menuItemInitializer } from '../schema-component'; import { ErrorFallback } from '../schema-component/antd/error-fallback'; import { AssociationFilterPlugin, SchemaInitializerPlugin } from '../schema-initializer'; @@ -285,25 +284,10 @@ export class NocoBaseBuildInPlugin extends Plugin { path: '/admin/:name', Component: 'RouteSchemaComponent', }); - - this.router.add('auth', { - Component: 'AuthLayout', - }); - this.router.add('auth.signin', { - path: '/signin', - Component: 'SigninPage', - }); - this.router.add('auth.signup', { - path: '/signup', - Component: 'SignupPage', - }); } addComponents() { this.app.addComponents({ - AuthLayout, - SigninPage, - SignupPage, ErrorFallback, RouteSchemaComponent, BlockTemplatePage, @@ -329,7 +313,6 @@ export class NocoBaseBuildInPlugin extends Plugin { await this.app.pm.add(SchemaInitializerPlugin, { name: 'schema-initializer' }); await this.app.pm.add(BlockSchemaComponentPlugin, { name: 'block-schema-component' }); await this.app.pm.add(AntdSchemaComponentPlugin, { name: 'antd-schema-component' }); - await this.app.pm.add(SigninPageExtensionPlugin, { name: 'signin-page-extension' }); await this.app.pm.add(ACLPlugin, { name: 'builtin-acl' }); await this.app.pm.add(RemoteDocumentTitlePlugin, { name: 'remote-document-title' }); await this.app.pm.add(PMPlugin, { name: 'builtin-pm' }); diff --git a/packages/core/client/src/route-switch/antd/index.ts b/packages/core/client/src/route-switch/antd/index.ts index 82fe22c3c..313ae5965 100644 --- a/packages/core/client/src/route-switch/antd/index.ts +++ b/packages/core/client/src/route-switch/antd/index.ts @@ -1,3 +1,2 @@ export * from './admin-layout'; -export * from './auth-layout'; export * from './route-schema-component'; diff --git a/packages/core/client/src/user/SigninPage.tsx b/packages/core/client/src/user/SigninPage.tsx deleted file mode 100644 index 0586b123c..000000000 --- a/packages/core/client/src/user/SigninPage.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { css } from '@emotion/css'; -import { ISchema, useForm } from '@formily/react'; -import { Space, Tabs } from 'antd'; -import React, { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Link, useNavigate, useSearchParams } from 'react-router-dom'; -import { SchemaComponent, useAPIClient, useCurrentDocumentTitle, useSystemSettings } from '..'; -import { useSigninPageExtension } from './SigninPageExtension'; -import VerificationCode from './VerificationCode'; - -const passwordForm: ISchema = { - type: 'object', - name: 'passwordForm', - 'x-component': 'FormV2', - properties: { - email: { - type: 'string', - required: true, - 'x-component': 'Input', - 'x-validator': 'email', - 'x-decorator': 'FormItem', - 'x-component-props': { placeholder: '{{t("Email")}}', style: {} }, - }, - password: { - type: 'string', - 'x-component': 'Password', - required: true, - 'x-decorator': 'FormItem', - 'x-component-props': { placeholder: '{{t("Password")}}', style: {} }, - }, - actions: { - type: 'void', - 'x-component': 'div', - properties: { - submit: { - title: '{{t("Sign in")}}', - type: 'void', - 'x-component': 'Action', - 'x-component-props': { - htmlType: 'submit', - block: true, - type: 'primary', - useAction: '{{ usePasswordSignIn }}', - style: { width: '100%' }, - }, - }, - }, - }, - }, -}; - -export function useRedirect(next = '/admin') { - const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - return useCallback(() => { - navigate(searchParams.get('redirect') || '/admin', { replace: true }); - }, [navigate, searchParams]); -} - -export const usePasswordSignIn = () => { - const form = useForm(); - const api = useAPIClient(); - const redirect = useRedirect(); - return { - async run() { - await form.submit(); - await api.auth.signIn(form.values); - redirect(); - }, - }; -}; - -const phoneForm: ISchema = { - type: 'object', - name: 'phoneForm', - 'x-component': 'Form', - properties: { - phone: { - type: 'string', - required: true, - 'x-component': 'Input', - 'x-validator': 'phone', - 'x-decorator': 'FormItem', - 'x-component-props': { placeholder: '{{t("Phone")}}', style: {} }, - }, - code: { - type: 'string', - required: true, - 'x-component': 'VerificationCode', - 'x-component-props': { - actionType: 'users:signin', - targetFieldName: 'phone', - }, - 'x-decorator': 'FormItem', - }, - actions: { - title: '{{t("Sign in")}}', - type: 'void', - 'x-component': 'Action', - 'x-component-props': { - htmlType: 'submit', - block: true, - type: 'primary', - useAction: '{{ usePhoneSignIn }}', - style: { width: '100%' }, - }, - }, - }, -}; - -export function usePhoneSignIn() { - const form = useForm(); - const api = useAPIClient(); - const redirect = useRedirect(); - return { - async run() { - await form.submit(); - await api.auth.signIn(form.values, 'sms'); - redirect(); - }, - }; -} - -export interface SigninPageProps { - schema?: ISchema; - components?: any; - scope?: any; -} - -export const SigninPage = (props: SigninPageProps) => { - const { t } = useTranslation(); - useCurrentDocumentTitle('Signin'); - const ctx = useSystemSettings(); - const signinExtension = useSigninPageExtension(); - const { allowSignUp, smsAuthEnabled } = ctx?.data?.data || {}; - const { schema, components, scope } = props; - return ( - - {smsAuthEnabled ? ( - , - }, - { - label: t('Sign in via phone'), - key: 'phone', - children: ( - - ), - }, - ]} - /> - ) : ( - - )} -
{signinExtension}
- {allowSignUp && ( -
- {t('Create an account')} -
- )} -
- ); -}; diff --git a/packages/core/client/src/user/SigninPageExtension.tsx b/packages/core/client/src/user/SigninPageExtension.tsx deleted file mode 100644 index 7a444703c..000000000 --- a/packages/core/client/src/user/SigninPageExtension.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { createContext, FunctionComponent, useContext } from 'react'; - -export interface SigninPageExtensionContextValue { - components: FunctionComponent[]; -} - -export const SigninPageExtensionContext = createContext({ components: [] }); - -export const useSigninPageExtension = () => { - const { components } = useContext(SigninPageExtensionContext); - return components.map((component, index) => React.createElement(component, { key: index })); -}; - -export const SigninPageExtensionProvider = (props: { component: FunctionComponent; children: JSX.Element }) => { - const { components } = useContext(SigninPageExtensionContext); - - const list = props.component ? [...components, props.component] : components; - - return ( - - {props.children} - - ); -}; diff --git a/packages/core/client/src/user/SignupPage.tsx b/packages/core/client/src/user/SignupPage.tsx deleted file mode 100644 index 51b226716..000000000 --- a/packages/core/client/src/user/SignupPage.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { ISchema, useForm } from '@formily/react'; -import { uid } from '@formily/shared'; -import { message } from 'antd'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Navigate, useNavigate } from 'react-router-dom'; -import { SchemaComponent, useAPIClient, useCurrentDocumentTitle, useSystemSettings } from '..'; -import VerificationCode from './VerificationCode'; - -const signupPageSchema: ISchema = { - type: 'object', - name: uid(), - 'x-component': 'FormV2', - properties: { - email: { - type: 'string', - required: true, - 'x-component': 'Input', - 'x-validator': 'email', - 'x-decorator': 'FormItem', - 'x-component-props': { placeholder: '{{t("Email")}}', style: {} }, - }, - phone: { - type: 'string', - required: true, - 'x-component': 'Input', - 'x-validator': 'phone', - 'x-decorator': 'FormItem', - 'x-component-props': { placeholder: '{{t("Phone")}}', style: {} }, - 'x-visible': '{{smsAuthEnabled}}', - }, - code: { - type: 'string', - required: true, - 'x-component': 'VerificationCode', - 'x-component-props': { - actionType: 'users:signup', - targetFieldName: 'phone', - }, - 'x-decorator': 'FormItem', - 'x-visible': '{{smsAuthEnabled}}', - }, - password: { - type: 'string', - required: true, - 'x-component': 'Password', - 'x-decorator': 'FormItem', - 'x-component-props': { placeholder: '{{t("Password")}}', checkStrength: true, style: {} }, - 'x-reactions': [ - { - dependencies: ['.confirm_password'], - fulfill: { - state: { - selfErrors: '{{$deps[0] && $self.value && $self.value !== $deps[0] ? t("Password mismatch") : ""}}', - }, - }, - }, - ], - }, - confirm_password: { - type: 'string', - required: true, - 'x-component': 'Password', - 'x-decorator': 'FormItem', - 'x-component-props': { placeholder: '{{t("Confirm password")}}', style: {} }, - 'x-reactions': [ - { - dependencies: ['.password'], - fulfill: { - state: { - selfErrors: '{{$deps[0] && $self.value && $self.value !== $deps[0] ? t("Password mismatch") : ""}}', - }, - }, - }, - ], - }, - actions: { - type: 'void', - 'x-component': 'div', - properties: { - submit: { - title: '{{t("Sign up")}}', - type: 'void', - 'x-component': 'Action', - 'x-component-props': { - block: true, - type: 'primary', - htmlType: 'submit', - useAction: '{{ useSignup }}', - style: { width: '100%' }, - }, - }, - }, - }, - link: { - type: 'void', - 'x-component': 'div', - properties: { - link: { - type: 'void', - 'x-component': 'Link', - 'x-component-props': { to: '/signin' }, - 'x-content': '{{t("Log in with an existing account")}}', - }, - }, - }, - }, -}; - -export interface UseSignupProps { - message?: { - success?: string; - }; -} - -export const useSignup = (props?: UseSignupProps) => { - const navigate = useNavigate(); - const form = useForm(); - const api = useAPIClient(); - const { t } = useTranslation(); - return { - async run() { - await form.submit(); - await api.resource('users').signup({ - values: form.values, - }); - message.success(props?.message?.success || t('Sign up successfully, and automatically jump to the sign in page')); - setTimeout(() => { - navigate('/signin'); - }, 2000); - }, - }; -}; - -export interface SignupPageProps { - schema?: ISchema; - components?: any; - scope?: any; -} - -export const SignupPage = (props: SignupPageProps) => { - useCurrentDocumentTitle('Signup'); - const ctx = useSystemSettings(); - const { allowSignUp, smsAuthEnabled } = ctx?.data?.data || {}; - if (!allowSignUp) { - return ; - } - const { schema, components, scope } = props; - return ( - - ); -}; diff --git a/packages/core/client/src/user/index.ts b/packages/core/client/src/user/index.ts index 6f29f9917..c058b2b16 100644 --- a/packages/core/client/src/user/index.ts +++ b/packages/core/client/src/user/index.ts @@ -1,6 +1,3 @@ export * from './CurrentUser'; export * from './CurrentUserProvider'; export * from './CurrentUserSettingsMenuProvider'; -// export * from './SigninPage'; -// export * from './SignupPage'; -// export * from './SigninPageExtension'; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/AuthPluginProvider.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/AuthPluginProvider.tsx deleted file mode 100644 index be4fb0873..000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/client/AuthPluginProvider.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { OptionsComponentProvider, SigninPageProvider, SignupPageProvider } from '@nocobase/client'; -import React, { FC } from 'react'; -import { presetAuthType } from '../preset'; -import { Options } from './basic/Options'; -import SigninPage from './basic/SigninPage'; -import SignupPage from './basic/SignupPage'; -import { useAuthTranslation } from './locale'; - -export const AuthPluginProvider: FC = (props) => { - const { t } = useAuthTranslation(); - return ( - - - - {props.children} - - - - ); -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/authenticator.ts b/packages/plugins/@nocobase/plugin-auth/src/client/authenticator.ts new file mode 100644 index 000000000..353d6cc96 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/client/authenticator.ts @@ -0,0 +1,19 @@ +import { createContext, useContext } from 'react'; + +export type Authenticator = { + name: string; + authType: string; + authTypeTitle: string; + title?: string; + options?: { + [key: string]: any; + }; + sort?: number; +}; + +export const AuthenticatorsContext = createContext([]); + +export const useAuthenticator = (name: string) => { + const authenticators = useContext(AuthenticatorsContext); + return authenticators.find((a) => a.name === name); +}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/basic/Options.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/basic/Options.tsx index 6392104b2..93a99d0c6 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/basic/Options.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/basic/Options.tsx @@ -15,7 +15,7 @@ export const Options = () => { public: { type: 'object', properties: { - allowSignup: { + allowSignUp: { 'x-decorator': 'FormItem', type: 'boolean', title: '{{t("Allow to sign up")}}', diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/basic/SigninPage.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignInForm.tsx similarity index 60% rename from packages/plugins/@nocobase/plugin-auth/src/client/basic/SigninPage.tsx rename to packages/plugins/@nocobase/plugin-auth/src/client/basic/SignInForm.tsx index 5cdef680a..e0639ffae 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/basic/SigninPage.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignInForm.tsx @@ -1,7 +1,34 @@ import { ISchema } from '@formily/react'; -import { Authenticator, SchemaComponent, SignupPageContext, useSignIn } from '@nocobase/client'; -import React, { useContext } from 'react'; +import { SchemaComponent, useAPIClient, useCurrentUserContext } from '@nocobase/client'; +import React, { useCallback } from 'react'; import { useAuthTranslation } from '../locale'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useForm } from '@formily/react'; +import { useSignUpForms } from '../pages'; +import { Authenticator } from '../authenticator'; + +export function useRedirect(next = '/admin') { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + return useCallback(() => { + navigate(searchParams.get('redirect') || '/admin', { replace: true }); + }, [navigate, searchParams]); +} + +export const useSignIn = (authenticator: string) => { + const form = useForm(); + const api = useAPIClient(); + const redirect = useRedirect(); + const { refreshAsync } = useCurrentUserContext(); + return { + async run() { + await form.submit(); + await api.auth.signIn(form.values, authenticator); + await refreshAsync(); + redirect(); + }, + }; +}; const passwordForm: ISchema = { type: 'object', @@ -51,27 +78,27 @@ const passwordForm: ISchema = { }, }, }, - signup: { + signUp: { type: 'void', 'x-component': 'Link', 'x-component-props': { - to: '{{ signupLink }}', + to: '{{ signUpLink }}', }, 'x-content': '{{t("Create an account")}}', 'x-visible': '{{ allowSignUp }}', }, }, }; -export default (props: { authenticator: Authenticator }) => { +export const SignInForm = (props: { authenticator: Authenticator }) => { const { t } = useAuthTranslation(); const authenticator = props.authenticator; const { authType, name, options } = authenticator; - const signupPages = useContext(SignupPageContext); - const allowSignUp = !!signupPages[authType] && options?.allowSignup; - const signupLink = `/signup?authType=${authType}&name=${name}`; + const signUpPages = useSignUpForms(); + const allowSignUp = !!signUpPages[authType] && options?.allowSignUp; + const signUpLink = `/signup?name=${name}`; const useBasicSignIn = () => { return useSignIn(name); }; - return ; + return ; }; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignupPage.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignUpForm.tsx similarity index 63% rename from packages/plugins/@nocobase/plugin-auth/src/client/basic/SignupPage.tsx rename to packages/plugins/@nocobase/plugin-auth/src/client/basic/SignUpForm.tsx index aa4fe54df..dfbce92c8 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignupPage.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignUpForm.tsx @@ -1,8 +1,38 @@ -import { SchemaComponent, useSignup } from '@nocobase/client'; +import { SchemaComponent } from '@nocobase/client'; import { ISchema } from '@formily/react'; import React from 'react'; import { uid } from '@formily/shared'; import { useAuthTranslation } from '../locale'; +import { useAPIClient } from '@nocobase/client'; +import { useForm } from '@formily/react'; +import { useNavigate, Navigate } from 'react-router-dom'; +import { message } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { useAuthenticator } from '../authenticator'; + +export interface UseSignupProps { + authenticator?: string; + message?: { + success?: string; + }; +} + +export const useSignUp = (props?: UseSignupProps) => { + const navigate = useNavigate(); + const form = useForm(); + const api = useAPIClient(); + const { t } = useTranslation(); + return { + async run() { + await form.submit(); + await api.auth.signUp(form.values, props?.authenticator); + message.success(props?.message?.success || t('Sign up successfully, and automatically jump to the sign in page')); + setTimeout(() => { + navigate('/signin'); + }, 2000); + }, + }; +}; const signupPageSchema: ISchema = { type: 'object', @@ -63,7 +93,7 @@ const signupPageSchema: ISchema = { block: true, type: 'primary', htmlType: 'submit', - useAction: '{{ useBasicSignup }}', + useAction: '{{ useBasicSignUp }}', style: { width: '100%' }, }, }, @@ -84,10 +114,15 @@ const signupPageSchema: ISchema = { }, }; -export default (props: { name: string }) => { +export const SignUpForm = ({ authenticatorName: name }: { authenticatorName: string }) => { const { t } = useAuthTranslation(); - const useBasicSignup = () => { - return useSignup({ authenticator: props.name }); + const useBasicSignUp = () => { + return useSignUp({ authenticator: name }); }; - return ; + const authenticator = useAuthenticator(name); + const { options } = authenticator; + if (!options?.allowSignUp) { + return ; + } + return ; }; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/basic/index.ts b/packages/plugins/@nocobase/plugin-auth/src/client/basic/index.ts new file mode 100644 index 000000000..a31cbddc7 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/client/basic/index.ts @@ -0,0 +1,3 @@ +export * from './SignInForm'; +export * from './SignUpForm'; +export * from './Options'; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/index.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/index.tsx index c4989454e..50f5e1203 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/index.tsx @@ -1,10 +1,30 @@ import { Plugin } from '@nocobase/client'; -import { AuthPluginProvider } from './AuthPluginProvider'; import { AuthProvider } from './AuthProvider'; import { NAMESPACE } from './locale'; import { Authenticator } from './settings/Authenticator'; +import { AuthLayout, SignInPage, SignUpPage } from './pages'; +import { ComponentType } from 'react'; +import { Registry } from '@nocobase/utils/client'; +import { presetAuthType } from '../preset'; +import { SignInForm, SignUpForm, Options } from './basic'; +import { Authenticator as AuthenticatorType } from './authenticator'; + +export type AuthOptions = { + components: Partial<{ + SignInForm: ComponentType<{ authenticator: AuthenticatorType }>; + SignInButton: ComponentType<{ authenticator: AuthenticatorType }>; + SignUpForm: ComponentType<{ authenticatorName: string }>; + AdminSettingsForm: ComponentType; + }>; +}; export class AuthPlugin extends Plugin { + authTypes = new Registry(); + + registerType(authType: string, options: AuthOptions) { + this.authTypes.register(authType, options); + } + async load() { this.app.pluginSettingsManager.add(NAMESPACE, { icon: 'LoginOutlined', @@ -12,9 +32,38 @@ export class AuthPlugin extends Plugin { Component: Authenticator, aclSnippet: 'pm.auth.authenticators', }); + + this.router.add('auth', { + Component: 'AuthLayout', + }); + this.router.add('auth.signin', { + path: '/signin', + Component: 'SignInPage', + }); + this.router.add('auth.signup', { + path: '/signup', + Component: 'SignUpPage', + }); + + this.app.addComponents({ + AuthLayout, + SignInPage, + SignUpPage, + }); + this.app.providers.unshift([AuthProvider, {}]); - this.app.use(AuthPluginProvider); + + this.registerType(presetAuthType, { + components: { + SignInForm: SignInForm, + SignUpForm: SignUpForm, + AdminSettingsForm: Options, + }, + }); } } export default AuthPlugin; +export { useSignIn } from './basic'; +export { useAuthenticator, AuthenticatorsContext } from './authenticator'; +export type { Authenticator } from './authenticator'; diff --git a/packages/core/client/src/route-switch/antd/auth-layout/index.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx similarity index 51% rename from packages/core/client/src/route-switch/antd/auth-layout/index.tsx rename to packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx index 812f5a6ce..d887ca402 100644 --- a/packages/core/client/src/route-switch/antd/auth-layout/index.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/pages/AuthLayout.tsx @@ -1,11 +1,25 @@ import { css } from '@emotion/css'; import React from 'react'; import { Outlet } from 'react-router-dom'; -import { PoweredBy } from '../../../powered-by'; -import { useSystemSettings } from '../../../system-settings'; +import { useSystemSettings, PoweredBy, useRequest, useAPIClient } from '@nocobase/client'; +import { AuthenticatorsContext } from '../authenticator'; export function AuthLayout(props: any) { const { data } = useSystemSettings(); + const api = useAPIClient(); + const { data: authenticators = [], error } = useRequest(() => + api + .resource('authenticators') + .publicList() + .then((res) => { + return res?.data?.data || []; + }), + ); + + if (error) { + throw error; + } + return (

{data?.data?.title}

- + + +
{ + const plugin = usePlugin(AuthPlugin); + const authTypes = plugin.authTypes.getEntities(); + const signInForms = {}; + for (const [authType, options] of authTypes) { + if (options.components?.SignInForm) { + signInForms[authType] = options.components.SignInForm; + } + } + return signInForms; +}; + +export const useSignInButtons = (authenticators = []) => { + const plugin = usePlugin(AuthPlugin); + const authTypes = plugin.authTypes.getEntities(); + const customs = {}; + for (const [authType, options] of authTypes) { + if (options.components?.SignInButton) { + customs[authType] = options.components.SignInButton; + } + } + + const types = Object.keys(customs); + return authenticators + .filter((authenticator) => types.includes(authenticator.authType)) + .map((authenticator, index) => React.createElement(customs[authenticator.authType], { key: index, authenticator })); +}; + +export const SignInPage = () => { + const { t } = useAuthTranslation(); + useCurrentDocumentTitle('Signin'); + useViewport(); + const signInForms = useSignInForms(); + const authenticators = useContext(AuthenticatorsContext); + const signInButtons = useSignInButtons(authenticators); + + if (!authenticators.length) { + return
{t('No authentication methods available.')}
; + } + + const tabs = authenticators + .map((authenticator) => { + const C = signInForms[authenticator.authType]; + if (!C) { + return; + } + const defaultTabTitle = `${t('Sign-in')} (${t(authenticator.authTypeTitle || authenticator.authType)})`; + return { + component: createElement<{ + authenticator: Authenticator; + }>(C, { authenticator }), + tabTitle: authenticator.title || defaultTabTitle, + ...authenticator, + }; + }) + .filter((i) => i); + + return ( + + {tabs.length > 1 ? ( + ({ label: tab.tabTitle, key: tab.name, children: tab.component }))} /> + ) : tabs.length ? ( +
{tabs[0].component}
+ ) : ( + <> + )} + + {signInButtons} + +
+ ); +}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/pages/SignUpPage.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/pages/SignUpPage.tsx new file mode 100644 index 000000000..fd786a380 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/client/pages/SignUpPage.tsx @@ -0,0 +1,54 @@ +import { useCurrentDocumentTitle, usePlugin, useViewport } from '@nocobase/client'; +import React, { useContext, createContext, FunctionComponent, createElement } from 'react'; +import { Navigate, useSearchParams } from 'react-router-dom'; +import AuthPlugin, { AuthOptions } from '..'; +import { useAuthenticator } from '../authenticator'; + +export const SignupPageContext = createContext<{ + [authType: string]: { + component: FunctionComponent<{ + name: string; + }>; + }; +}>({}); + +export const SignupPageProvider: React.FC<{ + authType: string; + component: FunctionComponent<{ + name: string; + }>; +}> = (props) => { + const components = useContext(SignupPageContext); + components[props.authType] = { + component: props.component, + }; + return {props.children}; +}; + +export const useSignUpForms = (): { + [authType: string]: AuthOptions['components']['SignUpForm']; +} => { + const plugin = usePlugin(AuthPlugin); + const authTypes = plugin.authTypes.getEntities(); + const signUpForms = {}; + for (const [authType, options] of authTypes) { + if (options.components?.SignUpForm) { + signUpForms[authType] = options.components.SignUpForm; + } + } + return signUpForms; +}; + +export const SignUpPage = () => { + useViewport(); + useCurrentDocumentTitle('Signup'); + const signUpForms = useSignUpForms(); + const [searchParams] = useSearchParams(); + const name = searchParams.get('name'); + const authenticator = useAuthenticator(name); + const { authType } = authenticator || {}; + if (!signUpForms[authType]) { + return ; + } + return createElement(signUpForms[authType], { authenticatorName: name }); +}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/pages/index.ts b/packages/plugins/@nocobase/plugin-auth/src/client/pages/index.ts new file mode 100644 index 000000000..5b188796e --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/client/pages/index.ts @@ -0,0 +1,3 @@ +export * from './AuthLayout'; +export * from './SignInPage'; +export * from './SignUpPage'; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/settings/Options.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/settings/Options.tsx index 09e2e71d1..1ba46e5ac 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/settings/Options.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/settings/Options.tsx @@ -1,6 +1,8 @@ +import React from 'react'; import { observer, useForm } from '@formily/react'; -import { useActionContext, useOptionsComponent, useRecord, useRequest } from '@nocobase/client'; +import { useActionContext, usePlugin, useRecord, useRequest } from '@nocobase/client'; import { useEffect } from 'react'; +import AuthPlugin from '..'; export const useValuesFromOptions = (options) => { const record = useRecord(); @@ -26,9 +28,15 @@ export const useValuesFromOptions = (options) => { return result; }; +export const useAdminSettingsForm = (authType: string) => { + const plugin = usePlugin(AuthPlugin); + const auth = plugin.authTypes.get(authType); + return auth?.components?.AdminSettingsForm; +}; + export const Options = observer(() => { const form = useForm(); const record = useRecord(); - const component = useOptionsComponent(form.values.authType || record.authType); - return component; + const Component = useAdminSettingsForm(form.values.authType || record.authType); + return Component ? : null; }); diff --git a/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json index 7505a2236..3962ce6fd 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json +++ b/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json @@ -19,5 +19,8 @@ "SMS": "SMS", "Username/Email": "Username/Email", "Auth UID": "Auth UID", - "The authentication allows users to sign in via username or email.": "The authentication allows users to sign in via username or email." + "The authentication allows users to sign in via username or email.": "The authentication allows users to sign in via username or email.", + "No authentication methods available.": "No authentication methods available.", + "The password is inconsistent, please re-enter": "The password is inconsistent, please re-enter", + "Sign-in": "Sign-in" } diff --git a/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json index 45c27007c..ac0540f44 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json @@ -19,5 +19,8 @@ "SMS": "短信", "Username/Email": "用户名/邮箱", "Auth UID": "认证标识", - "The authentication allows users to sign in via username or email.": "该认证方式支持用户通过用户名或邮箱登录。" + "The authentication allows users to sign in via username or email.": "该认证方式支持用户通过用户名或邮箱登录。", + "No authentication methods available.": "没有可用的认证方式。", + "The password is inconsistent, please re-enter": "密码不一致,请重新输入", + "Sign-in": "登录" } diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts b/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts index 72000e665..60cb093be 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts @@ -84,7 +84,7 @@ describe('actions', () => { let db: Database; let agent; - beforeAll(async () => { + beforeEach(async () => { app = mockServer(); process.env.INIT_ROOT_EMAIL = 'test@nocobase.com'; process.env.INT_ROOT_USERNAME = 'test'; @@ -97,8 +97,8 @@ describe('actions', () => { agent = app.agent(); }); - afterAll(async () => { - await db.close(); + afterEach(async () => { + await app.destroy(); }); it('should sign in with password', async () => { @@ -125,6 +125,7 @@ describe('actions', () => { let res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({ username: 'new', password: 'new', + confirm_password: 'new', }); expect(res.statusCode).toEqual(200); @@ -199,5 +200,14 @@ describe('actions', () => { }); expect(res2.statusCode).toEqual(200); }); + + it('should check confirm password', async () => { + const res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({ + username: 'new', + password: 'new', + confirm_password: 'new1', + }); + expect(res.statusCode).toEqual(400); + }); }); }); diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/actions/authenticators.ts b/packages/plugins/@nocobase/plugin-auth/src/server/actions/authenticators.ts index 73d18b4e1..7510b8008 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/actions/authenticators.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/actions/authenticators.ts @@ -1,6 +1,7 @@ import { Context, Next } from '@nocobase/actions'; import { Model, Repository } from '@nocobase/database'; import { namespace } from '../../preset'; +import { AuthManager } from '@nocobase/auth'; async function checkCount(repository: Repository, id: number[]) { // TODO(yangqia): This is a temporary solution, may cause concurrency problem. @@ -24,6 +25,7 @@ export default { }, publicList: async (ctx: Context, next: Next) => { const repo = ctx.db.getRepository('authenticators'); + const authManager = ctx.app.authManager as AuthManager; const authenticators = await repo.find({ fields: ['name', 'authType', 'title', 'options', 'sort'], filter: { @@ -31,12 +33,16 @@ export default { }, sort: 'sort', }); - ctx.body = authenticators.map((authenticator: Model) => ({ - name: authenticator.name, - authType: authenticator.authType, - title: authenticator.title, - options: authenticator.options?.public || {}, - })); + ctx.body = authenticators.map((authenticator: Model) => { + const authType = authManager.getAuthConfig(authenticator.authType); + return { + name: authenticator.name, + authType: authenticator.authType, + authTypeTitle: authType?.title || '', + title: authenticator.title, + options: authenticator.options?.public || {}, + }; + }); await next(); }, destroy: async (ctx: Context, next: Next) => { diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts b/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts index 7ac3b2329..053e20561 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts @@ -51,11 +51,14 @@ export class BasicAuth extends BaseAuth { } const User = ctx.db.getRepository('users'); const { values } = ctx.action.params; - const { username } = values; + const { username, password, confirm_password } = values; if (!/^[^@.<>"'/]{2,16}$/.test(username)) { ctx.throw(400, ctx.t('Please enter a valid username', { ns: namespace })); } - const user = await User.create({ values }); + if (password !== confirm_password) { + ctx.throw(400, ctx.t('The password is inconsistent, please re-enter', { ns: namespace })); + } + const user = await User.create({ values: { username, password } }); return user; } diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/migrations/20231218132032-fix-allow-signup.ts b/packages/plugins/@nocobase/plugin-auth/src/server/migrations/20231218132032-fix-allow-signup.ts new file mode 100644 index 000000000..1861aef49 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/server/migrations/20231218132032-fix-allow-signup.ts @@ -0,0 +1,32 @@ +import { Migration } from '@nocobase/server'; +import { presetAuthType } from '../../preset'; + +export default class FixAllowSignUpMigration extends Migration { + async up() { + const repo = this.context.db.getRepository('authenticators'); + const authenticators = await repo.find({ + filter: { + authType: presetAuthType, + }, + }); + for (const authenticator of authenticators) { + const options = authenticator.get('options'); + const oldAllowSignUp = options?.public?.allowSignup; + if (oldAllowSignUp === undefined || oldAllowSignUp === null) { + continue; + } + options.public.allowSignUp = oldAllowSignUp; + delete options.public.allowSignup; + await repo.update({ + values: { + options, + }, + filter: { + name: authenticator.name, + }, + }); + } + } + + async down() {} +} diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/model/authenticator.ts b/packages/plugins/@nocobase/plugin-auth/src/server/model/authenticator.ts index cba25805b..5c2c2ed1f 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/model/authenticator.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/model/authenticator.ts @@ -1,6 +1,10 @@ +import { Authenticator } from '@nocobase/auth'; import { Database, Model } from '@nocobase/database'; -export class AuthModel extends Model { +export class AuthModel extends Model implements Authenticator { + declare authType: string; + declare options: any; + async findUser(uuid: string) { let user: Model; const users = await this.getUsers({ @@ -14,13 +18,13 @@ export class AuthModel extends Model { } } - async newUser(uuid: string, values?: any) { + async newUser(uuid: string, userValues?: any) { let user: Model; const db: Database = (this.constructor as any).database; await this.sequelize.transaction(async (transaction) => { // Create a new user if not exists user = await this.createUser( - values || { + userValues || { nickname: uuid, }, { diff --git a/packages/plugins/@nocobase/plugin-cas/package.json b/packages/plugins/@nocobase/plugin-cas/package.json index adc7cfa87..ad00fbb42 100644 --- a/packages/plugins/@nocobase/plugin-cas/package.json +++ b/packages/plugins/@nocobase/plugin-cas/package.json @@ -13,7 +13,8 @@ "@nocobase/database": "0.x", "@nocobase/sdk": "0.x", "@nocobase/server": "0.x", - "@nocobase/test": "0.x" + "@nocobase/test": "0.x", + "@nocobase/plugin-auth": ">=0.17.0-alpha.7" }, "devDependencies": { "@ant-design/icons": "5.x", diff --git a/packages/plugins/@nocobase/plugin-cas/src/client/SigninPage.tsx b/packages/plugins/@nocobase/plugin-cas/src/client/SigninPage.tsx index 9e3192d4c..1956ac699 100644 --- a/packages/plugins/@nocobase/plugin-cas/src/client/SigninPage.tsx +++ b/packages/plugins/@nocobase/plugin-cas/src/client/SigninPage.tsx @@ -1,9 +1,9 @@ -import { Authenticator } from '@nocobase/client'; import React, { useEffect } from 'react'; import { LoginOutlined } from '@ant-design/icons'; import { Button, Space, message } from 'antd'; import { useLocation } from 'react-router-dom'; import { getSubAppName } from '@nocobase/sdk'; +import { Authenticator } from '@nocobase/plugin-auth/client'; export const SigninPage = (props: { authenticator: Authenticator }) => { const location = useLocation(); diff --git a/packages/plugins/@nocobase/plugin-cas/src/client/index.tsx b/packages/plugins/@nocobase/plugin-cas/src/client/index.tsx index 84d0f9a69..51bb1df36 100644 --- a/packages/plugins/@nocobase/plugin-cas/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-cas/src/client/index.tsx @@ -1,24 +1,18 @@ -import { OptionsComponentProvider, SigninPageExtensionProvider, SignupPageProvider } from '@nocobase/client'; -import React from 'react'; import { Plugin } from '@nocobase/client'; - import { SigninPage } from './SigninPage'; import { Options } from './Options'; import { authType } from '../constants'; - -export function CASProvider(props) { - return ( - - - {props.children} - - - ); -} +import AuthPlugin from '@nocobase/plugin-auth/client'; export class SamlPlugin extends Plugin { async load() { - this.app.use(CASProvider); + const auth = this.app.pm.get(AuthPlugin); + auth.registerType(authType, { + components: { + SignInForm: SigninPage, + AdminSettingsForm: Options, + }, + }); } } diff --git a/packages/plugins/@nocobase/plugin-oidc/package.json b/packages/plugins/@nocobase/plugin-oidc/package.json index 0f06c1091..409b97f94 100644 --- a/packages/plugins/@nocobase/plugin-oidc/package.json +++ b/packages/plugins/@nocobase/plugin-oidc/package.json @@ -24,6 +24,7 @@ "@nocobase/client": "0.x", "@nocobase/database": "0.x", "@nocobase/server": "0.x", - "@nocobase/test": "0.x" + "@nocobase/test": "0.x", + "@nocobase/plugin-auth": ">=0.17.0-alpha.7" } } diff --git a/packages/plugins/@nocobase/plugin-oidc/src/client/OIDCButton.tsx b/packages/plugins/@nocobase/plugin-oidc/src/client/OIDCButton.tsx index 0ba8cb9da..9e393ffc7 100644 --- a/packages/plugins/@nocobase/plugin-oidc/src/client/OIDCButton.tsx +++ b/packages/plugins/@nocobase/plugin-oidc/src/client/OIDCButton.tsx @@ -1,9 +1,10 @@ import { LoginOutlined } from '@ant-design/icons'; -import { Authenticator, css, useAPIClient } from '@nocobase/client'; +import { css, useAPIClient } from '@nocobase/client'; import { Button, Space, message } from 'antd'; import React, { useEffect } from 'react'; import { useOidcTranslation } from './locale'; import { useLocation } from 'react-router-dom'; +import { Authenticator } from '@nocobase/plugin-auth/client'; export interface OIDCProvider { clientId: string; diff --git a/packages/plugins/@nocobase/plugin-oidc/src/client/OidcProvider.tsx b/packages/plugins/@nocobase/plugin-oidc/src/client/OidcProvider.tsx deleted file mode 100644 index 2559a8a08..000000000 --- a/packages/plugins/@nocobase/plugin-oidc/src/client/OidcProvider.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { OptionsComponentProvider, SigninPageExtensionProvider } from '@nocobase/client'; -import React, { FC } from 'react'; -import { OIDCButton } from './OIDCButton'; -import { authType } from '../constants'; -import { Options } from './Options'; - -export const OidcProvider: FC = (props) => { - return ( - - - {props.children} - - - ); -}; diff --git a/packages/plugins/@nocobase/plugin-oidc/src/client/Options.tsx b/packages/plugins/@nocobase/plugin-oidc/src/client/Options.tsx index d662365d1..391615f19 100644 --- a/packages/plugins/@nocobase/plugin-oidc/src/client/Options.tsx +++ b/packages/plugins/@nocobase/plugin-oidc/src/client/Options.tsx @@ -76,7 +76,7 @@ const schema = { 'x-component-props': { style: { width: '15%', - 'min-width': '100px', + minWidth: '100px', }, }, }, diff --git a/packages/plugins/@nocobase/plugin-oidc/src/client/index.tsx b/packages/plugins/@nocobase/plugin-oidc/src/client/index.tsx index de7161487..ec108556b 100644 --- a/packages/plugins/@nocobase/plugin-oidc/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-oidc/src/client/index.tsx @@ -1,9 +1,18 @@ import { Plugin } from '@nocobase/client'; -import { OidcProvider } from './OidcProvider'; +import AuthPlugin from '@nocobase/plugin-auth/client'; +import { authType } from '../constants'; +import { OIDCButton } from './OIDCButton'; +import { Options } from './Options'; export class OidcPlugin extends Plugin { async load() { - this.app.use(OidcProvider); + const auth = this.app.pm.get(AuthPlugin); + auth.registerType(authType, { + components: { + SignInButton: OIDCButton, + AdminSettingsForm: Options, + }, + }); } } diff --git a/packages/plugins/@nocobase/plugin-saml/package.json b/packages/plugins/@nocobase/plugin-saml/package.json index 19b30cf70..179540d17 100644 --- a/packages/plugins/@nocobase/plugin-saml/package.json +++ b/packages/plugins/@nocobase/plugin-saml/package.json @@ -22,6 +22,7 @@ "@nocobase/database": "0.x", "@nocobase/sdk": "0.x", "@nocobase/server": "0.x", - "@nocobase/test": "0.x" + "@nocobase/test": "0.x", + "@nocobase/plugin-auth": ">=0.17.0-alpha.7" } } diff --git a/packages/plugins/@nocobase/plugin-saml/src/client/SAMLButton.tsx b/packages/plugins/@nocobase/plugin-saml/src/client/SAMLButton.tsx index eedcefb7b..21653c09c 100644 --- a/packages/plugins/@nocobase/plugin-saml/src/client/SAMLButton.tsx +++ b/packages/plugins/@nocobase/plugin-saml/src/client/SAMLButton.tsx @@ -1,9 +1,10 @@ import { LoginOutlined } from '@ant-design/icons'; -import { Authenticator, css, useAPIClient } from '@nocobase/client'; +import { css, useAPIClient } from '@nocobase/client'; import { Button, Space, message } from 'antd'; import React, { useEffect } from 'react'; import { useSamlTranslation } from './locale'; import { useLocation } from 'react-router-dom'; +import { Authenticator } from '@nocobase/plugin-auth/client'; export const SAMLButton = ({ authenticator }: { authenticator: Authenticator }) => { const { t } = useSamlTranslation(); diff --git a/packages/plugins/@nocobase/plugin-saml/src/client/SamlProvider.tsx b/packages/plugins/@nocobase/plugin-saml/src/client/SamlProvider.tsx deleted file mode 100644 index afe19d153..000000000 --- a/packages/plugins/@nocobase/plugin-saml/src/client/SamlProvider.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { OptionsComponentProvider, SigninPageExtensionProvider } from '@nocobase/client'; -import React, { FC } from 'react'; -import { SAMLButton } from './SAMLButton'; -import { Options } from './Options'; -import { authType } from '../constants'; - -export const SamlProvider: FC = (props) => { - return ( - - - {props.children} - - - ); -}; diff --git a/packages/plugins/@nocobase/plugin-saml/src/client/index.tsx b/packages/plugins/@nocobase/plugin-saml/src/client/index.tsx index d8076616f..7aed83a9b 100644 --- a/packages/plugins/@nocobase/plugin-saml/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-saml/src/client/index.tsx @@ -1,9 +1,18 @@ import { Plugin } from '@nocobase/client'; -import { SamlProvider } from './SamlProvider'; +import AuthPlugin from '@nocobase/plugin-auth/client'; +import { authType } from '../constants'; +import { SAMLButton } from './SAMLButton'; +import { Options } from './Options'; export class SamlPlugin extends Plugin { async load() { - this.app.use(SamlProvider); + const auth = this.app.pm.get(AuthPlugin); + auth.registerType(authType, { + components: { + SignInButton: SAMLButton, + AdminSettingsForm: Options, + }, + }); } } diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/.npmignore b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/.npmignore deleted file mode 100644 index c593fe9df..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules -/src \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/README.md b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/README.md deleted file mode 100644 index 789181796..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Custom sign up page - -## Register - -```ts -yarn pm add sample-custom-signup-page -``` - -## Activate - -```bash -yarn pm enable sample-custom-signup-page -``` - -## Launch the app - -```bash -# for development -yarn dev - -# for production -yarn build -yarn start -``` - -## Visit the custom sign up page - -Open [http://localhost:13000/signup](http://localhost:13000/signup) in a web browser. diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/client.d.ts b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/client.d.ts deleted file mode 100644 index 6c459cbac..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/client.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './dist/client'; -export { default } from './dist/client'; diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/client.js b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/client.js deleted file mode 100644 index b6e3be70e..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/client.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/client/index.js'); diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/package.json b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/package.json deleted file mode 100644 index acdd9166a..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@nocobase/plugin-sample-custom-signup-page", - "version": "0.17.0-alpha.7", - "main": "./dist/server/index.js", - "devDependencies": { - "@formily/react": "2.x", - "react": "^18.2.0" - }, - "peerDependencies": { - "@nocobase/client": "0.x", - "@nocobase/server": "0.x", - "@nocobase/test": "0.x" - }, - "gitHead": "ce588eefb0bfc50f7d5bbee575e0b5e843bf6644" -} diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/server.d.ts b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/server.d.ts deleted file mode 100644 index c41081ddc..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/server.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './dist/server'; -export { default } from './dist/server'; diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/server.js b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/server.js deleted file mode 100644 index 972842039..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/server.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/server/index.js'); diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/client/index.tsx b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/client/index.tsx deleted file mode 100644 index af087f7cc..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/client/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useForm } from '@formily/react'; -import { Plugin, SignupPage, useSignup } from '@nocobase/client'; -import React from 'react'; - -const useCustomSignup = () => { - const { run } = useSignup(); - const form = useForm(); - return { - async run() { - console.log('useCustomSignup'); - form.setValuesIn('url', 'bb'); - await run(); - }, - }; -}; - -const CustomSignupPage = (props) => { - return ( -
-
Custom sign up page
- -
- ); -}; - -class CustomSignupPagePlugin extends Plugin { - async load() { - this.app.addComponents({ - SignupPage: CustomSignupPage, - }); - } -} - -export default CustomSignupPagePlugin; diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/index.ts b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/index.ts deleted file mode 100644 index 7e74612df..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './server'; -export { default } from './server'; diff --git a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/server/index.ts b/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/server/index.ts deleted file mode 100644 index 9ef95e139..000000000 --- a/packages/plugins/@nocobase/plugin-sample-custom-signup-page/src/server/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { InstallOptions, Plugin } from '@nocobase/server'; - -export class CustomSchemaScopePlugin extends Plugin { - beforeLoad() { - // TODO - } - - async load() {} - - async install(options: InstallOptions) { - // TODO - } -} - -export default CustomSchemaScopePlugin; diff --git a/packages/plugins/@nocobase/plugin-sms-auth/package.json b/packages/plugins/@nocobase/plugin-sms-auth/package.json index b4703cb38..d47ab1f82 100644 --- a/packages/plugins/@nocobase/plugin-sms-auth/package.json +++ b/packages/plugins/@nocobase/plugin-sms-auth/package.json @@ -17,7 +17,7 @@ "@nocobase/auth": "0.x", "@nocobase/client": "0.x", "@nocobase/database": "0.x", - "@nocobase/plugin-auth": "0.x", + "@nocobase/plugin-auth": ">=0.17.0-alpha.7", "@nocobase/plugin-verification": "0.x", "@nocobase/server": "0.x", "@nocobase/test": "0.x" diff --git a/packages/plugins/@nocobase/plugin-sms-auth/src/client/SigninPage.tsx b/packages/plugins/@nocobase/plugin-sms-auth/src/client/SigninPage.tsx index 6788e312d..53e9e777f 100644 --- a/packages/plugins/@nocobase/plugin-sms-auth/src/client/SigninPage.tsx +++ b/packages/plugins/@nocobase/plugin-sms-auth/src/client/SigninPage.tsx @@ -1,7 +1,8 @@ -import { Authenticator, SchemaComponent, useSignIn } from '@nocobase/client'; +import { SchemaComponent } from '@nocobase/client'; import { ISchema } from '@formily/react'; import React from 'react'; import VerificationCode from './VerificationCode'; +import { Authenticator, useSignIn } from '@nocobase/plugin-auth/client'; const phoneForm: ISchema = { type: 'object', diff --git a/packages/plugins/@nocobase/plugin-sms-auth/src/client/SmsAuthProvider.tsx b/packages/plugins/@nocobase/plugin-sms-auth/src/client/SmsAuthProvider.tsx deleted file mode 100644 index eeab82d36..000000000 --- a/packages/plugins/@nocobase/plugin-sms-auth/src/client/SmsAuthProvider.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { OptionsComponentProvider, SigninPageProvider } from '@nocobase/client'; -import React from 'react'; -import { SigninPage } from './SigninPage'; -import { useAuthTranslation } from './locale'; -import { authType } from '../constants'; -import { Options } from './Options'; - -export const SmsAuthProvider = (props) => { - const { t } = useAuthTranslation(); - return ( - - - {props.children} - - - ); -}; diff --git a/packages/plugins/@nocobase/plugin-sms-auth/src/client/index.tsx b/packages/plugins/@nocobase/plugin-sms-auth/src/client/index.tsx index 3c676c0ee..351b380fb 100644 --- a/packages/plugins/@nocobase/plugin-sms-auth/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-sms-auth/src/client/index.tsx @@ -1,9 +1,18 @@ import { Plugin } from '@nocobase/client'; -import { SmsAuthProvider } from './SmsAuthProvider'; +import AuthPlugin from '@nocobase/plugin-auth/client'; +import { SigninPage } from './SigninPage'; +import { Options } from './Options'; +import { authType } from '../constants'; export class SmsAuthPlugin extends Plugin { async load() { - this.app.use(SmsAuthProvider); + const auth = this.app.pm.get(AuthPlugin); + auth.registerType(authType, { + components: { + SignInForm: SigninPage, + AdminSettingsForm: Options, + }, + }); } }