Skip to content

Commit

Permalink
feat(auth): support signing in with username (nocobase#2376)
Browse files Browse the repository at this point in the history
* feat(auth): support signing in with username

* chore: compatibility && add INIT_ROOT_USERNAME

* chore: add default username of root user

* chore: add notice

* fix: typo

* chore: change rule of username

* fix: sqlite add unique constraint

---------

Co-authored-by: chenos <[email protected]>
  • Loading branch information
2013xile and chenos authored Aug 19, 2023
1 parent 972b2be commit be6b949
Show file tree
Hide file tree
Showing 28 changed files with 289 additions and 47 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ DB_TABLE_PREFIX=
INIT_ROOT_EMAIL=[email protected]
INIT_ROOT_PASSWORD=admin123
INIT_ROOT_NICKNAME=Super Admin
INIT_ROOT_USERNAME=nocobase

# local or ali-oss
DEFAULT_STORAGE_TYPE=local
Expand Down
1 change: 1 addition & 0 deletions .env.test.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ DB_STORAGE=storage/db/nocobase-test.sqlite
[email protected]
INIT_ROOT_PASSWORD=admin123
INIT_ROOT_NICKNAME=Super Admin
INIT_ROOT_USERNAME=nocobase

# local or ali-oss
DEFAULT_STORAGE_TYPE=local
Expand Down
6 changes: 5 additions & 1 deletion packages/core/auth/src/auth-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type AuthManagerOptions = {

type AuthConfig = {
auth: AuthExtend<Auth>; // The authentication class.
title?: string; // The display name of the authentication type.
};

export class AuthManager {
Expand Down Expand Up @@ -52,7 +53,10 @@ export class AuthManager {
}

listTypes() {
return Array.from(this.authTypes.getKeys());
return Array.from(this.authTypes.getEntities()).map(([authType, authConfig]) => ({
name: authType,
title: authConfig.title,
}));
}

getAuthConfig(authType: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export * from './time';
export * from './updatedAt';
export * from './updatedBy';
export * from './url';
export * from './username';
40 changes: 40 additions & 0 deletions packages/core/client/src/collection-manager/interfaces/username.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { i18n } from '../../i18n';
import { defaultProps, operators, unique } from './properties';
import { IField } from './types';
import { registerValidateRules } from '@formily/core';

registerValidateRules({
username(value) {
return /^[^@.<>"'/]{2,16}$/.test(value) || i18n.t('2 to 16 characters (excluding @.<>"\'/)');
},
});

export const username: IField = {
name: 'username',
type: 'object',
group: 'basic',
order: 1,
title: '{{t("Username")}}',
sortable: true,
default: {
type: 'string',
// name,
uiSchema: {
type: 'string',
// title,
'x-component': 'Input',
'x-validator': { username: true },
required: true,
},
},
availableTypes: ['string'],
hasDefaultValue: false,
properties: {
...defaultProps,
unique,
},
filterable: {
operators: operators.string,
},
titleUsable: true,
};
2 changes: 2 additions & 0 deletions packages/core/client/src/locale/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ export default {
"Long text": "多行文本",
"Phone": "手机号码",
"Email": "电子邮箱",
"Username": "用户名",
'Null': '空值',
"Boolean": "逻辑值",
"Number": "数字",
Expand Down Expand Up @@ -472,6 +473,7 @@ export default {
"Verification code": "验证码",
"Send code": "发送验证码",
"Retry after {{count}} seconds": "{{count}} 秒后重试",
'2 to 16 characters (excluding @.<>"\'/)': '2到16个字符(不包含@.<>"\'/)',

"Roles": "角色",
"Add role": "添加角色",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/client/src/user/CurrentUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export const CurrentUser = () => {
`}
style={{ cursor: 'pointer', border: 0, padding: '16px', color: 'rgba(255, 255, 255, 0.65)' }}
>
{data?.data?.nickname || data?.data?.email}
{data?.data?.nickname || data?.data?.username || data?.data?.email}
</span>
</Dropdown>
</DropdownVisibleContext.Provider>
Expand Down
9 changes: 7 additions & 2 deletions packages/core/client/src/user/EditProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ const schema: ISchema = {
title: "{{t('Nickname')}}",
'x-decorator': 'FormItem',
'x-component': 'Input',
},
username: {
type: 'string',
title: '{{t("Username")}}',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': { username: true },
required: true,
},
email: {
Expand All @@ -78,15 +85,13 @@ const schema: ISchema = {
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': 'email',
required: true,
},
phone: {
type: 'string',
title: '{{t("Phone")}}',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': 'phone',
required: true,
},
footer: {
'x-component': 'Action.Drawer.Footer',
Expand Down
1 change: 1 addition & 0 deletions packages/core/create-nocobase-app/templates/app/.env.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ DB_DIALECT={{{env.DB_DIALECT}}}
[email protected]
INIT_ROOT_PASSWORD=admin123
INIT_ROOT_NICKNAME=Super Admin
INIT_ROOT_USERNAME=nocobase
2 changes: 1 addition & 1 deletion packages/plugins/auth/src/client/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const AuthProvider: FC = (props) => {
}}
>
<OptionsComponentProvider authType={presetAuthType} component={Options}>
<SigninPageProvider authType={presetAuthType} tabTitle={t('Sign in via email')} component={SigninPage}>
<SigninPageProvider authType={presetAuthType} tabTitle={t('Sign in via password')} component={SigninPage}>
<SignupPageProvider authType={presetAuthType} component={SignupPage}>
{props.children}
</SignupPageProvider>
Expand Down
10 changes: 10 additions & 0 deletions packages/plugins/auth/src/client/basic/Options.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { SchemaComponent } from '@nocobase/client';
import React from 'react';
import { useAuthTranslation } from '../locale';
import { Alert } from 'antd';

export const Options = () => {
const { t } = useAuthTranslation();
return (
<SchemaComponent
scope={{ t }}
components={{ Alert }}
schema={{
type: 'object',
properties: {
Expand All @@ -24,6 +26,14 @@ export const Options = () => {
},
},
},
notice: {
type: 'void',
'x-component': 'Alert',
'x-component-props': {
showIcon: true,
message: '{{t("The authentication allows users to sign in via username or email.")}}',
},
},
},
}}
/>
Expand Down
22 changes: 17 additions & 5 deletions packages/plugins/auth/src/client/basic/SigninPage.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { ISchema } from '@formily/react';
import { Authenticator, SchemaComponent, SignupPageContext, useSignIn } from '@nocobase/client';
import React, { useContext } from 'react';
import { useAuthTranslation } from '../locale';

const passwordForm: ISchema = {
type: 'object',
name: 'passwordForm',
'x-component': 'FormV2',
properties: {
email: {
account: {
type: 'string',
required: true,
'x-component': 'Input',
'x-validator': 'email',
'x-validator': `{{(value) => {
if (!value) {
return t("Please enter your username or email");
}
if (value.includes('@')) {
if (!/^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$/.test(value)) {
return t("Please enter a valid email");
}
} else {
return /^[^@.<>"'/]{2,16}$/.test(value) || t("Please enter a valid username");
}
}}}`,
'x-decorator': 'FormItem',
'x-component-props': { placeholder: '{{t("Email")}}', style: {} },
'x-component-props': { placeholder: '{{t("Username/Email")}}', style: {} },
},
password: {
type: 'string',
Expand Down Expand Up @@ -52,6 +63,7 @@ const passwordForm: ISchema = {
},
};
export default (props: { authenticator: Authenticator }) => {
const { t } = useAuthTranslation();
const authenticator = props.authenticator;
const { authType, name, options } = authenticator;
const signupPages = useContext(SignupPageContext);
Expand All @@ -61,5 +73,5 @@ export default (props: { authenticator: Authenticator }) => {
const useBasicSignIn = () => {
return useSignIn(name);
};
return <SchemaComponent schema={passwordForm} scope={{ useBasicSignIn, allowSignUp, signupLink }} />;
return <SchemaComponent schema={passwordForm} scope={{ useBasicSignIn, allowSignUp, signupLink, t }} />;
};
10 changes: 6 additions & 4 deletions packages/plugins/auth/src/client/basic/SignupPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { SchemaComponent, useSignup } from '@nocobase/client';
import { ISchema } from '@formily/react';
import React from 'react';
import { uid } from '@formily/shared';
import { useAuthTranslation } from '../locale';

const signupPageSchema: ISchema = {
type: 'object',
name: uid(),
'x-component': 'FormV2',
properties: {
email: {
username: {
type: 'string',
required: true,
'x-component': 'Input',
'x-validator': 'email',
'x-validator': { username: true },
'x-decorator': 'FormItem',
'x-component-props': { placeholder: '{{t("Email")}}', style: {} },
'x-component-props': { placeholder: '{{t("Username")}}', style: {} },
},
password: {
type: 'string',
Expand Down Expand Up @@ -84,8 +85,9 @@ const signupPageSchema: ISchema = {
};

export default (props: { name: string }) => {
const { t } = useAuthTranslation();
const useBasicSignup = () => {
return useSignup({ authenticator: props.name });
};
return <SchemaComponent schema={signupPageSchema} scope={{ useBasicSignup }} />;
return <SchemaComponent schema={signupPageSchema} scope={{ useBasicSignup, t }} />;
};
12 changes: 7 additions & 5 deletions packages/plugins/auth/src/client/settings/Authenticator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PlusOutlined, DownOutlined } from '@ant-design/icons';
import { AuthTypeContext, AuthTypesContext, useAuthTypes } from './authType';
import { useValuesFromOptions, Options } from './Options';
import { useTranslation } from 'react-i18next';
import { useAuthTranslation } from '../locale';

const useCloseAction = () => {
const { setVisible } = useActionContext();
Expand Down Expand Up @@ -59,6 +60,7 @@ const useCanNotDelete = () => {
};

export const Authenticator = () => {
const { t } = useAuthTranslation();
const [types, setTypes] = useState([]);
const api = useAPIClient();
useRequest(
Expand All @@ -68,10 +70,10 @@ export const Authenticator = () => {
.listTypes()
.then((res) => {
const types = res?.data?.data || [];
return types.map((type: string) => ({
key: type,
label: type,
value: type,
return types.map((type: { name: string; title?: string }) => ({
key: type.name,
label: t(type.title || type.name),
value: type.name,
}));
}),
{
Expand All @@ -87,7 +89,7 @@ export const Authenticator = () => {
<SchemaComponent
schema={authenticatorsSchema}
components={{ AddNew, Options }}
scope={{ types, useValuesFromOptions, useCanNotDelete }}
scope={{ types, useValuesFromOptions, useCanNotDelete, t }}
/>
</AuthTypesContext.Provider>
</Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const collection = {
name: 'name',
uiSchema: {
type: 'string',
title: '{{t("Name")}}',
title: '{{t("Auth UID")}}',
'x-component': 'Input',
required: true,
},
Expand All @@ -32,8 +32,9 @@ const collection = {
name: 'authType',
uiSchema: {
type: 'string',
title: '{{t("Auth Type", {ns: "auth"})}}',
title: '{{t("Auth Type")}}',
'x-component': 'Select',
dataSource: '{{ types }}',
required: true,
},
},
Expand Down Expand Up @@ -251,14 +252,16 @@ export const authenticatorsSchema: ISchema = {
},
},
authType: {
title: '{{t("Auth Type")}}',
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
authType: {
type: 'string',
'x-component': 'CollectionField',
'x-component': 'Select',
'x-read-pretty': true,
enum: '{{ types }}',
},
},
},
Expand Down
12 changes: 10 additions & 2 deletions packages/plugins/auth/src/locale/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@ const locale = {
Authenticators: '认证器',
Authentication: '用户认证',
'Sign in via email': '邮箱登录',
'Sign in via password': '密码登录',
'Not allowed to sign up': '禁止注册',
'Allow to sign up': '允许注册',
'The email is incorrect, please re-enter': '邮箱有误,请重新输入',
'Please fill in your email address': '请填写邮箱',
'The username or email is incorrect, please re-enter': '用户名或邮箱有误,请重新输入',
'The password is incorrect, please re-enter': '密码有误,请重新输入',
'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入',
'The phone number has been registered, please login directly': '手机号已注册,请直接登录',
'The phone number is not registered, please register first': '手机号未注册,请先注册',
'Please keep and enable at least one authenticator': '请至少保留并启用一个认证器',
'Allow to sign in with': '允许使用以下方式登录',
'Please enter a valid username': '请输入有效的用户名',
'Please enter a valid email': '请输入有效的邮箱',
'Please enter your username or email': '请输入用户名或邮箱',
SMS: '短信',
'Username/Email': '用户名/邮箱',
'Auth UID': '认证标识',
'The authentication allows users to sign in via username or email.': '该认证方式支持用户通过用户名或邮箱登录。',
};

export default locale;
Loading

0 comments on commit be6b949

Please sign in to comment.