mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-07 22:49:26 +08:00
Merge branch 'next' into develop
This commit is contained in:
commit
049ddc95e9
@ -32,6 +32,7 @@ export type AuthManagerOptions = {
|
|||||||
type AuthConfig = {
|
type AuthConfig = {
|
||||||
auth: AuthExtend<Auth>; // The authentication class.
|
auth: AuthExtend<Auth>; // The authentication class.
|
||||||
title?: string; // The display name of the authentication type.
|
title?: string; // The display name of the authentication type.
|
||||||
|
getPublicOptions?: (options: Record<string, any>) => Record<string, any>; // Get the public options.
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AuthManager {
|
export class AuthManager {
|
||||||
|
@ -7,18 +7,151 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SchemaComponent } from '@nocobase/client';
|
import { SchemaComponent, useCollectionManager, useRecord } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { lang, useAuthTranslation } from '../locale';
|
import { lang, useAuthTranslation } from '../locale';
|
||||||
import { FormTab, ArrayTable } from '@formily/antd-v5';
|
import { FormTab, ArrayTable } from '@formily/antd-v5';
|
||||||
import { Alert } from 'antd';
|
import { Alert } from 'antd';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
|
||||||
|
const SignupFormSettings = () => {
|
||||||
|
const record = useRecord();
|
||||||
|
const cm = useCollectionManager();
|
||||||
|
const userCollection = cm.getCollection('users');
|
||||||
|
const fields = userCollection.fields.filter(
|
||||||
|
(field) => !field.hidden && !field.target && field.interface && !field.uiSchema?.['x-read-pretty'],
|
||||||
|
);
|
||||||
|
const enumArr = fields.map((field) => ({ value: field.name, label: field.uiSchema?.title }));
|
||||||
|
const value = useMemo(() => {
|
||||||
|
const fieldValue = record.options?.public?.signupForm || [];
|
||||||
|
const newValue = fieldValue.filter((item: any) => fields.find((field) => field.name === item.field));
|
||||||
|
for (const field of fields) {
|
||||||
|
const exist = newValue.find((item: any) => item.field === field.name);
|
||||||
|
if (!exist) {
|
||||||
|
newValue.push({
|
||||||
|
field: field.name,
|
||||||
|
show: field.name === 'username',
|
||||||
|
required: field.name === 'username',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newValue;
|
||||||
|
}, [fields, record]);
|
||||||
|
useEffect(() => {
|
||||||
|
record.options = {
|
||||||
|
...record.options,
|
||||||
|
public: {
|
||||||
|
...record.options?.public,
|
||||||
|
signupForm: value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [record, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SchemaComponent
|
||||||
|
components={{ ArrayTable }}
|
||||||
|
schema={{
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
signupForm: {
|
||||||
|
title: '{{t("Sign up form")}}',
|
||||||
|
type: 'array',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayTable',
|
||||||
|
'x-component-props': {
|
||||||
|
bordered: false,
|
||||||
|
},
|
||||||
|
'x-validator': `{{ (value) => {
|
||||||
|
const check = value?.some((item) => ['username', 'email'].includes(item.field) && item.show && item.required);
|
||||||
|
if (!check) {
|
||||||
|
return t('At least one of the username or email fields is required');
|
||||||
|
}
|
||||||
|
} }}`,
|
||||||
|
default: value,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
'x-decorator': 'ArrayItems.Item',
|
||||||
|
properties: {
|
||||||
|
column0: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayTable.Column',
|
||||||
|
'x-component-props': { width: 20, align: 'center' },
|
||||||
|
properties: {
|
||||||
|
sort: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayTable.SortHandle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayTable.Column',
|
||||||
|
'x-component-props': { width: 100, title: lang('Field') },
|
||||||
|
properties: {
|
||||||
|
field: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: enumArr,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column2: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayTable.Column',
|
||||||
|
'x-component-props': { width: 80, title: lang('Show') },
|
||||||
|
properties: {
|
||||||
|
show: {
|
||||||
|
type: 'boolean',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
'x-reactions': {
|
||||||
|
dependencies: ['.required'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{ $deps[0] || $self.value }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column3: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ArrayTable.Column',
|
||||||
|
'x-component-props': { width: 80, title: lang('Required') },
|
||||||
|
properties: {
|
||||||
|
required: {
|
||||||
|
type: 'boolean',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Checkbox',
|
||||||
|
'x-reactions': {
|
||||||
|
dependencies: ['.show'],
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{ !$deps[0] ? false : $self.value }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Options = () => {
|
export const Options = () => {
|
||||||
const { t } = useAuthTranslation();
|
const { t } = useAuthTranslation();
|
||||||
return (
|
return (
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
scope={{ t }}
|
scope={{ t }}
|
||||||
components={{ Alert, FormTab, ArrayTable }}
|
components={{ Alert, SignupFormSettings, FormTab }}
|
||||||
schema={{
|
schema={{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@ -52,96 +185,9 @@ export const Options = () => {
|
|||||||
'x-component': 'Checkbox',
|
'x-component': 'Checkbox',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
signupForm: {
|
[uid()]: {
|
||||||
title: '{{t("Sign up form")}}',
|
type: 'void',
|
||||||
type: 'array',
|
'x-component': 'SignupFormSettings',
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'ArrayTable',
|
|
||||||
'x-component-props': {
|
|
||||||
bordered: false,
|
|
||||||
},
|
|
||||||
'x-validator': `{{ (value) => {
|
|
||||||
const field = value?.filter((item) => item.show && item.required);
|
|
||||||
if (!field?.length) {
|
|
||||||
return t('At least one field is required');
|
|
||||||
}
|
|
||||||
} }}`,
|
|
||||||
default: [
|
|
||||||
{
|
|
||||||
field: 'username',
|
|
||||||
show: true,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'email',
|
|
||||||
show: false,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
'x-decorator': 'ArrayItems.Item',
|
|
||||||
properties: {
|
|
||||||
column0: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ArrayTable.Column',
|
|
||||||
'x-component-props': { width: 20, align: 'center' },
|
|
||||||
properties: {
|
|
||||||
sort: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ArrayTable.SortHandle',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
column1: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ArrayTable.Column',
|
|
||||||
'x-component-props': { width: 100, title: lang('Field') },
|
|
||||||
properties: {
|
|
||||||
field: {
|
|
||||||
type: 'string',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Select',
|
|
||||||
enum: [
|
|
||||||
{
|
|
||||||
label: lang('Username'),
|
|
||||||
value: 'username',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: lang('Email'),
|
|
||||||
value: 'email',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'x-read-pretty': true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
column2: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ArrayTable.Column',
|
|
||||||
'x-component-props': { width: 80, title: lang('Show') },
|
|
||||||
properties: {
|
|
||||||
show: {
|
|
||||||
type: 'boolean',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Checkbox',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
column3: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'ArrayTable.Column',
|
|
||||||
'x-component-props': { width: 80, title: lang('Required') },
|
|
||||||
properties: {
|
|
||||||
required: {
|
|
||||||
type: 'boolean',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component': 'Checkbox',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -26,23 +26,6 @@ export interface UseSignupProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemas = {
|
|
||||||
username: {
|
|
||||||
type: 'string',
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-validator': { username: true },
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component-props': { placeholder: '{{t("Username")}}', style: {} },
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: 'string',
|
|
||||||
'x-component': 'Input',
|
|
||||||
'x-validator': 'email',
|
|
||||||
'x-decorator': 'FormItem',
|
|
||||||
'x-component-props': { placeholder: '{{t("Email")}}', style: {} },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSignUp = (props?: UseSignupProps) => {
|
export const useSignUp = (props?: UseSignupProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
@ -69,9 +52,10 @@ const getSignupPageSchema = (fieldSchemas: any): ISchema => ({
|
|||||||
password: {
|
password: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
|
title: '{{t("Password")}}',
|
||||||
'x-component': 'Password',
|
'x-component': 'Password',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component-props': { placeholder: '{{t("Password")}}', checkStrength: true, style: {} },
|
'x-component-props': { checkStrength: true, style: {} },
|
||||||
'x-reactions': [
|
'x-reactions': [
|
||||||
{
|
{
|
||||||
dependencies: ['.confirm_password'],
|
dependencies: ['.confirm_password'],
|
||||||
@ -88,7 +72,8 @@ const getSignupPageSchema = (fieldSchemas: any): ISchema => ({
|
|||||||
required: true,
|
required: true,
|
||||||
'x-component': 'Password',
|
'x-component': 'Password',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component-props': { placeholder: '{{t("Confirm password")}}', style: {} },
|
title: '{{t("Confirm password")}}',
|
||||||
|
'x-component-props': { style: {} },
|
||||||
'x-reactions': [
|
'x-reactions': [
|
||||||
{
|
{
|
||||||
dependencies: ['.password'],
|
dependencies: ['.password'],
|
||||||
@ -140,22 +125,17 @@ export const SignUpForm = ({ authenticatorName: name }: { authenticatorName: str
|
|||||||
};
|
};
|
||||||
const authenticator = useAuthenticator(name);
|
const authenticator = useAuthenticator(name);
|
||||||
const { options } = authenticator;
|
const { options } = authenticator;
|
||||||
let { signupForm } = options;
|
const { signupForm } = options;
|
||||||
if (!(signupForm?.length && signupForm.some((item: any) => item.show && item.required))) {
|
|
||||||
signupForm = [{ field: 'username', show: true, required: true }];
|
|
||||||
}
|
|
||||||
const fieldSchemas = useMemo(() => {
|
const fieldSchemas = useMemo(() => {
|
||||||
return signupForm
|
return signupForm
|
||||||
.filter((field: { show: boolean }) => field.show)
|
.filter((field: { show: boolean }) => field.show)
|
||||||
.reduce((prev: any, item: { field: string; required: boolean }) => {
|
.reduce((prev: any, item: { field: string; required: boolean; uiSchema: any }) => {
|
||||||
const field = item.field;
|
prev[item.field] = {
|
||||||
if (schemas[field]) {
|
...item.uiSchema,
|
||||||
prev[field] = schemas[field];
|
required: item.required,
|
||||||
if (item.required) {
|
'x-decorator': 'FormItem',
|
||||||
prev[field].required = true;
|
};
|
||||||
}
|
return prev;
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
}, {});
|
}, {});
|
||||||
}, [signupForm]);
|
}, [signupForm]);
|
||||||
if (!options?.allowSignUp) {
|
if (!options?.allowSignUp) {
|
||||||
|
@ -28,5 +28,5 @@
|
|||||||
"Show": "Show",
|
"Show": "Show",
|
||||||
"Sign up settings": "Sign up settings",
|
"Sign up settings": "Sign up settings",
|
||||||
"Sign up form": "Sign up form",
|
"Sign up form": "Sign up form",
|
||||||
"At least one field is required": "At least one field is required"
|
"At least one of the username or email fields is required": "At least one of the username or email fields is required"
|
||||||
}
|
}
|
||||||
|
@ -28,5 +28,5 @@
|
|||||||
"Show": "显示",
|
"Show": "显示",
|
||||||
"Sign up settings": "注册设置",
|
"Sign up settings": "注册设置",
|
||||||
"Sign up form": "注册表单",
|
"Sign up form": "注册表单",
|
||||||
"At least one field is required": "至少需要设置一个必填字段"
|
"At least one of the username or email fields is required": "至少需要设置用户名或邮箱中的一个字段为必填字段"
|
||||||
}
|
}
|
||||||
|
@ -46,18 +46,30 @@ describe('actions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return enabled authenticators with public options', async () => {
|
it('should return enabled authenticators with public options', async () => {
|
||||||
|
app.authManager.registerTypes('testType1', {
|
||||||
|
auth: {} as any,
|
||||||
|
getPublicOptions: (options) => {
|
||||||
|
return {
|
||||||
|
text: 'custom public options',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
await repo.destroy({
|
await repo.destroy({
|
||||||
truncate: true,
|
truncate: true,
|
||||||
});
|
});
|
||||||
await repo.createMany({
|
await repo.createMany({
|
||||||
records: [
|
records: [
|
||||||
{ name: 'test', authType: 'testType', enabled: true, options: { public: { test: 1 }, private: { test: 2 } } },
|
{ name: 'test', authType: 'testType', enabled: true, options: { public: { test: 1 }, private: { test: 2 } } },
|
||||||
|
{ name: 'test1', authType: 'testType1', enabled: true },
|
||||||
{ name: 'test2', authType: 'testType' },
|
{ name: 'test2', authType: 'testType' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const res = await agent.resource('authenticators').publicList();
|
const res = await agent.resource('authenticators').publicList();
|
||||||
expect(res.body.data.length).toBe(1);
|
expect(res.body.data.length).toBe(2);
|
||||||
expect(res.body.data[0].name).toBe('test');
|
expect(res.body.data[0].name).toBe('test');
|
||||||
|
expect(res.body.data[0].options).toMatchObject({ test: 1 });
|
||||||
|
expect(res.body.data[1].name).toBe('test1');
|
||||||
|
expect(res.body.data[1].options).toMatchObject({ text: 'custom public options' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should keep at least one authenticator', async () => {
|
it('should keep at least one authenticator', async () => {
|
||||||
@ -260,16 +272,37 @@ describe('actions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should check username when signing up', async () => {
|
it('should check username when signing up', async () => {
|
||||||
const res1 = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
|
||||||
username: '',
|
|
||||||
});
|
|
||||||
expect(res1.statusCode).toEqual(400);
|
|
||||||
expect(res1.error.text).toBe('Please enter a valid username');
|
|
||||||
const res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
const res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
||||||
username: '@@',
|
username: '',
|
||||||
});
|
});
|
||||||
expect(res.statusCode).toEqual(400);
|
expect(res.statusCode).toEqual(400);
|
||||||
expect(res.error.text).toBe('Please enter a valid username');
|
expect(res.error.text).toBe('Please enter a valid username');
|
||||||
|
const res1 = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
||||||
|
username: '@@',
|
||||||
|
});
|
||||||
|
expect(res1.statusCode).toEqual(400);
|
||||||
|
expect(res1.error.text).toBe('Please enter a valid username');
|
||||||
|
|
||||||
|
const repo = db.getRepository('authenticators');
|
||||||
|
await repo.update({
|
||||||
|
filter: {
|
||||||
|
name: 'basic',
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
options: {
|
||||||
|
public: {
|
||||||
|
allowSignUp: true,
|
||||||
|
signupForm: [{ field: 'nickname', show: true }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res2 = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
||||||
|
nickname: 'test',
|
||||||
|
});
|
||||||
|
expect(res2.statusCode).toEqual(400);
|
||||||
|
expect(res2.error.text).toBe('Please enter a valid username');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check email when signing up', async () => {
|
it('should check email when signing up', async () => {
|
||||||
@ -305,6 +338,31 @@ describe('actions', () => {
|
|||||||
expect(res3.statusCode).toEqual(200);
|
expect(res3.statusCode).toEqual(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should check a required field when signing up', async () => {
|
||||||
|
const repo = db.getRepository('authenticators');
|
||||||
|
await repo.update({
|
||||||
|
filter: {
|
||||||
|
name: 'basic',
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
options: {
|
||||||
|
public: {
|
||||||
|
allowSignUp: true,
|
||||||
|
signupForm: [
|
||||||
|
{ field: 'username', show: true, required: true },
|
||||||
|
{ field: 'nickname', show: true, required: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const res1 = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
||||||
|
username: 'test',
|
||||||
|
});
|
||||||
|
expect(res1.statusCode).toEqual(400);
|
||||||
|
expect(res1.error.text).toBe('Please enter nickname');
|
||||||
|
});
|
||||||
|
|
||||||
it('should check password when signing up', async () => {
|
it('should check password when signing up', async () => {
|
||||||
const res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
const res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
||||||
username: 'new',
|
username: 'new',
|
||||||
@ -313,6 +371,41 @@ describe('actions', () => {
|
|||||||
expect(res.error.text).toBe('Please enter a password');
|
expect(res.error.text).toBe('Please enter a password');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should write correct user data when signing up', async () => {
|
||||||
|
const repo = db.getRepository('authenticators');
|
||||||
|
await repo.update({
|
||||||
|
filter: {
|
||||||
|
name: 'basic',
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
options: {
|
||||||
|
public: {
|
||||||
|
allowSignUp: true,
|
||||||
|
signupForm: [
|
||||||
|
{ field: 'username', show: true, required: true },
|
||||||
|
{ field: 'nickname', show: true, required: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({
|
||||||
|
username: 'test',
|
||||||
|
nickname: 'Test',
|
||||||
|
phone: '12345678901',
|
||||||
|
password: '123456',
|
||||||
|
confirm_password: '123456',
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toEqual(200);
|
||||||
|
const user = await db.getRepository('users').findOne({
|
||||||
|
filter: {
|
||||||
|
username: 'test',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(user.nickname).toBe('Test');
|
||||||
|
expect(user.phone).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should sign user out when changing password', async () => {
|
it('should sign user out when changing password', async () => {
|
||||||
const userRepo = db.getRepository('users');
|
const userRepo = db.getRepository('users');
|
||||||
const user = await userRepo.create({
|
const user = await userRepo.create({
|
||||||
|
@ -49,7 +49,7 @@ export default {
|
|||||||
authType: authenticator.authType,
|
authType: authenticator.authType,
|
||||||
authTypeTitle: authType?.title || '',
|
authTypeTitle: authType?.title || '',
|
||||||
title: authenticator.title,
|
title: authenticator.title,
|
||||||
options: authenticator.options?.public || {},
|
options: authType?.getPublicOptions?.(authenticator.options) || authenticator.options?.public || {},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
await next();
|
await next();
|
||||||
|
@ -11,6 +11,7 @@ import { AuthConfig, BaseAuth } from '@nocobase/auth';
|
|||||||
import { PasswordField } from '@nocobase/database';
|
import { PasswordField } from '@nocobase/database';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { namespace } from '../preset';
|
import { namespace } from '../preset';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export class BasicAuth extends BaseAuth {
|
export class BasicAuth extends BaseAuth {
|
||||||
constructor(config: AuthConfig) {
|
constructor(config: AuthConfig) {
|
||||||
@ -50,20 +51,41 @@ export class BasicAuth extends BaseAuth {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
private verfiySignupParams(values: any) {
|
private getSignupFormSettings() {
|
||||||
const options = this.authenticator.options?.public || {};
|
const options = this.authenticator.options?.public || {};
|
||||||
let { signupForm } = options;
|
let { signupForm = [] } = options;
|
||||||
if (!(signupForm?.length && signupForm.some((item: any) => item.show && item.required))) {
|
signupForm = signupForm.filter((item: { show: boolean }) => item.show);
|
||||||
signupForm = [{ field: 'username', show: true, required: true }];
|
if (
|
||||||
|
!(
|
||||||
|
signupForm.length &&
|
||||||
|
signupForm.some(
|
||||||
|
(item: { field: string; show: boolean; required: boolean }) =>
|
||||||
|
['username', 'email'].includes(item.field) && item.show && item.required,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// At least one of the username or email fields is required
|
||||||
|
signupForm.push({ field: 'username', show: true, required: true });
|
||||||
}
|
}
|
||||||
|
return signupForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private verfiySignupParams(
|
||||||
|
signupFormSettings: {
|
||||||
|
field: string;
|
||||||
|
show: boolean;
|
||||||
|
required: boolean;
|
||||||
|
}[],
|
||||||
|
values: any,
|
||||||
|
) {
|
||||||
const { username, email } = values;
|
const { username, email } = values;
|
||||||
const usernameSetting = signupForm.find((item: any) => item.field === 'username');
|
const usernameSetting = signupFormSettings.find((item: any) => item.field === 'username');
|
||||||
if (usernameSetting && usernameSetting.show) {
|
if (usernameSetting && usernameSetting.show) {
|
||||||
if ((username && !this.validateUsername(username)) || (usernameSetting.required && !username)) {
|
if ((username && !this.validateUsername(username)) || (usernameSetting.required && !username)) {
|
||||||
throw new Error('Please enter a valid username');
|
throw new Error('Please enter a valid username');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const emailSetting = signupForm.find((item: any) => item.field === 'email');
|
const emailSetting = signupFormSettings.find((item: any) => item.field === 'email');
|
||||||
if (emailSetting && emailSetting.show) {
|
if (emailSetting && emailSetting.show) {
|
||||||
if (email && !/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(email)) {
|
if (email && !/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(email)) {
|
||||||
throw new Error('Please enter a valid email address');
|
throw new Error('Please enter a valid email address');
|
||||||
@ -72,6 +94,13 @@ export class BasicAuth extends BaseAuth {
|
|||||||
throw new Error('Please enter a valid email address');
|
throw new Error('Please enter a valid email address');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const requiredFields = signupFormSettings.filter((item: any) => item.show && item.required);
|
||||||
|
requiredFields.forEach((item: { field: string }) => {
|
||||||
|
if (!values[item.field]) {
|
||||||
|
throw new Error(`Please enter ${item.field}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async signUp() {
|
async signUp() {
|
||||||
@ -82,9 +111,10 @@ export class BasicAuth extends BaseAuth {
|
|||||||
}
|
}
|
||||||
const User = ctx.db.getRepository('users');
|
const User = ctx.db.getRepository('users');
|
||||||
const { values } = ctx.action.params;
|
const { values } = ctx.action.params;
|
||||||
const { username, email, password, confirm_password } = values;
|
const { password, confirm_password } = values;
|
||||||
|
const signupFormSettings = this.getSignupFormSettings();
|
||||||
try {
|
try {
|
||||||
this.verfiySignupParams(values);
|
this.verfiySignupParams(signupFormSettings, values);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ctx.throw(400, this.ctx.t(error.message, { ns: namespace }));
|
ctx.throw(400, this.ctx.t(error.message, { ns: namespace }));
|
||||||
}
|
}
|
||||||
@ -94,7 +124,9 @@ export class BasicAuth extends BaseAuth {
|
|||||||
if (password !== confirm_password) {
|
if (password !== confirm_password) {
|
||||||
ctx.throw(400, ctx.t('The password is inconsistent, please re-enter', { ns: namespace }));
|
ctx.throw(400, ctx.t('The password is inconsistent, please re-enter', { ns: namespace }));
|
||||||
}
|
}
|
||||||
const user = await User.create({ values: { username, email, password } });
|
const fields = signupFormSettings.map((item: { field: string }) => item.field);
|
||||||
|
const userValues = _.pick(values, fields);
|
||||||
|
const user = await User.create({ values: { ...userValues, password } });
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,13 +50,46 @@ export class PluginAuthServer extends Plugin {
|
|||||||
this.app.authManager.registerTypes(presetAuthType, {
|
this.app.authManager.registerTypes(presetAuthType, {
|
||||||
auth: BasicAuth,
|
auth: BasicAuth,
|
||||||
title: tval('Password', { ns: namespace }),
|
title: tval('Password', { ns: namespace }),
|
||||||
|
getPublicOptions: (options) => {
|
||||||
|
const usersCollection = this.db.getCollection('users');
|
||||||
|
let signupForm = options?.public?.signupForm || [];
|
||||||
|
signupForm = signupForm.filter((item: { show: boolean }) => item.show);
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
signupForm.length &&
|
||||||
|
signupForm.some(
|
||||||
|
(item: { field: string; show: boolean; required: boolean }) =>
|
||||||
|
['username', 'email'].includes(item.field) && item.show && item.required,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// At least one of the username or email fields is required
|
||||||
|
signupForm.unshift({ field: 'username', show: true, required: true });
|
||||||
|
}
|
||||||
|
signupForm = signupForm
|
||||||
|
.filter((field: { show: boolean }) => field.show)
|
||||||
|
.map((item: { field: string; required: boolean }) => {
|
||||||
|
const field = usersCollection.getField(item.field);
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
uiSchema: {
|
||||||
|
...field.options?.uiSchema,
|
||||||
|
required: item.required,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...options?.public,
|
||||||
|
signupForm,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
// Register actions
|
// Register actions
|
||||||
Object.entries(authActions).forEach(
|
Object.entries(authActions).forEach(
|
||||||
([action, handler]) => this.app.resourcer.getResource('auth')?.addAction(action, handler),
|
([action, handler]) => this.app.resourceManager.getResource('auth')?.addAction(action, handler),
|
||||||
);
|
);
|
||||||
Object.entries(authenticatorsActions).forEach(([action, handler]) =>
|
Object.entries(authenticatorsActions).forEach(([action, handler]) =>
|
||||||
this.app.resourcer.registerAction(`authenticators:${action}`, handler),
|
this.app.resourceManager.registerActionHandler(`authenticators:${action}`, handler),
|
||||||
);
|
);
|
||||||
// Set up ACL
|
// Set up ACL
|
||||||
['check', 'signIn', 'signUp'].forEach((action) => this.app.acl.allow('auth', action));
|
['check', 'signIn', 'signUp'].forEach((action) => this.app.acl.allow('auth', action));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user