chore(users): remove deprecated code (#3122)
* chore: remove deprecated code in user plugin * chore: update * fix: test * fix: test
This commit is contained in:
parent
1efedee68c
commit
cb7f1d7aa9
@ -114,12 +114,7 @@ describe('acl', () => {
|
|||||||
|
|
||||||
userPlugin = app.getPlugin('users') as UsersPlugin;
|
userPlugin = app.getPlugin('users') as UsersPlugin;
|
||||||
|
|
||||||
const testAgent = app.agent().auth(
|
const testAgent = app.agent().login(u1);
|
||||||
userPlugin.jwtService.sign({
|
|
||||||
userId: u1.get('id'),
|
|
||||||
}),
|
|
||||||
{ type: 'bearer' },
|
|
||||||
);
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const response1 = await testAgent.resource('repairs').list({
|
const response1 = await testAgent.resource('repairs').list({
|
||||||
|
@ -38,27 +38,6 @@ describe('actions', () => {
|
|||||||
await app.destroy();
|
await app.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
// it('should login user with password', async () => {
|
|
||||||
// const { INIT_ROOT_EMAIL, INIT_ROOT_PASSWORD } = process.env;
|
|
||||||
|
|
||||||
// let response = await agent.resource('users').check();
|
|
||||||
// expect(response.body.data.id).toBeUndefined();
|
|
||||||
|
|
||||||
// response = await agent.post('/users:signin').send({
|
|
||||||
// email: INIT_ROOT_EMAIL,
|
|
||||||
// password: INIT_ROOT_PASSWORD,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// expect(response.statusCode).toEqual(200);
|
|
||||||
|
|
||||||
// const data = response.body.data;
|
|
||||||
// const token = data.token;
|
|
||||||
// expect(token).toBeDefined();
|
|
||||||
|
|
||||||
// response = await agent.get('/users:check').set({ Authorization: 'Bearer ' + token });
|
|
||||||
// expect(response.body.data.id).toBeDefined();
|
|
||||||
// });
|
|
||||||
|
|
||||||
it('update profile', async () => {
|
it('update profile', async () => {
|
||||||
const res1 = await agent.resource('users').updateProfile({
|
const res1 = await agent.resource('users').updateProfile({
|
||||||
filterByTk: adminUser.id,
|
filterByTk: adminUser.id,
|
||||||
|
@ -2,7 +2,4 @@ import { UserPluginConfig } from '../server';
|
|||||||
|
|
||||||
export const userPluginConfig: UserPluginConfig = {
|
export const userPluginConfig: UserPluginConfig = {
|
||||||
name: 'users',
|
name: 'users',
|
||||||
jwt: {
|
|
||||||
secret: '09f26e402586e2faa8da4c98a35f1b20d6b033c60',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
@ -1,112 +1,4 @@
|
|||||||
import { Context, Next } from '@nocobase/actions';
|
import { Context, Next } from '@nocobase/actions';
|
||||||
import { PasswordField } from '@nocobase/database';
|
|
||||||
import { branch } from '@nocobase/resourcer';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { namespace } from '../';
|
|
||||||
|
|
||||||
export async function check(ctx: Context, next: Next) {
|
|
||||||
if (ctx.state.currentUser) {
|
|
||||||
const user = ctx.state.currentUser.toJSON();
|
|
||||||
ctx.body = user;
|
|
||||||
} else {
|
|
||||||
ctx.body = {};
|
|
||||||
}
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signin(ctx: Context, next: Next) {
|
|
||||||
const { authenticators, jwtService } = ctx.app.getPlugin('users');
|
|
||||||
const branches = {};
|
|
||||||
for (const [name, authenticator] of authenticators.getEntities()) {
|
|
||||||
branches[name] = authenticator;
|
|
||||||
}
|
|
||||||
|
|
||||||
return branch(branches, (context) => context.action.params.authenticator ?? 'password')(ctx, () => {
|
|
||||||
const user = ctx.state.currentUser.toJSON();
|
|
||||||
const token = jwtService.sign({ userId: user.id });
|
|
||||||
ctx.body = {
|
|
||||||
user,
|
|
||||||
token,
|
|
||||||
};
|
|
||||||
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signout(ctx: Context, next: Next) {
|
|
||||||
ctx.body = ctx.state.currentUser;
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signup(ctx: Context, next: Next) {
|
|
||||||
const User = ctx.db.getRepository('users');
|
|
||||||
const { values } = ctx.action.params;
|
|
||||||
const user = await User.create({ values });
|
|
||||||
ctx.body = user;
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function lostpassword(ctx: Context, next: Next) {
|
|
||||||
const {
|
|
||||||
values: { email },
|
|
||||||
} = ctx.action.params;
|
|
||||||
if (!email) {
|
|
||||||
ctx.throw(400, { code: 'InvalidUserData', message: ctx.t('Please fill in your email address', { ns: namespace }) });
|
|
||||||
}
|
|
||||||
const User = ctx.db.getCollection('users');
|
|
||||||
const user = await User.model.findOne<any>({
|
|
||||||
where: {
|
|
||||||
email,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!user) {
|
|
||||||
ctx.throw(404, {
|
|
||||||
code: 'InvalidUserData',
|
|
||||||
message: ctx.t('The email is incorrect, please re-enter', { ns: namespace }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
user.resetToken = crypto.randomBytes(20).toString('hex');
|
|
||||||
await user.save();
|
|
||||||
ctx.body = user;
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function resetpassword(ctx: Context, next: Next) {
|
|
||||||
const {
|
|
||||||
values: { email, password, resetToken },
|
|
||||||
} = ctx.action.params;
|
|
||||||
const User = ctx.db.getCollection('users');
|
|
||||||
const user = await User.model.findOne<any>({
|
|
||||||
where: {
|
|
||||||
email,
|
|
||||||
resetToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!user) {
|
|
||||||
ctx.throw(404);
|
|
||||||
}
|
|
||||||
user.token = null;
|
|
||||||
user.resetToken = null;
|
|
||||||
user.password = password;
|
|
||||||
await user.save();
|
|
||||||
ctx.body = user;
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getUserByResetToken(ctx: Context, next: Next) {
|
|
||||||
const { token } = ctx.action.params;
|
|
||||||
const User = ctx.db.getCollection('users');
|
|
||||||
const user = await User.model.findOne({
|
|
||||||
where: {
|
|
||||||
resetToken: token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!user) {
|
|
||||||
ctx.throw(401);
|
|
||||||
}
|
|
||||||
ctx.body = user;
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateProfile(ctx: Context, next: Next) {
|
export async function updateProfile(ctx: Context, next: Next) {
|
||||||
const { values } = ctx.action.params;
|
const { values } = ctx.action.params;
|
||||||
@ -122,27 +14,3 @@ export async function updateProfile(ctx: Context, next: Next) {
|
|||||||
ctx.body = result;
|
ctx.body = result;
|
||||||
await next();
|
await next();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function changePassword(ctx: Context, next: Next) {
|
|
||||||
const {
|
|
||||||
values: { oldPassword, newPassword },
|
|
||||||
} = ctx.action.params;
|
|
||||||
if (!ctx.state.currentUser) {
|
|
||||||
ctx.throw(401);
|
|
||||||
}
|
|
||||||
const User = ctx.db.getCollection('users');
|
|
||||||
const user = await User.model.findOne<any>({
|
|
||||||
where: {
|
|
||||||
email: ctx.state.currentUser.email,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const pwd = User.getField<PasswordField>('password');
|
|
||||||
const isValid = await pwd.verify(oldPassword, user.password);
|
|
||||||
if (!isValid) {
|
|
||||||
ctx.throw(401, ctx.t('The password is incorrect, please re-enter', { ns: namespace }));
|
|
||||||
}
|
|
||||||
user.password = newPassword;
|
|
||||||
user.save();
|
|
||||||
ctx.body = ctx.state.currentUser.toJSON();
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import path from 'path';
|
|
||||||
|
|
||||||
import { requireModule } from '@nocobase/utils';
|
|
||||||
import { HandlerType } from '@nocobase/resourcer';
|
|
||||||
|
|
||||||
import Plugin from '..';
|
|
||||||
|
|
||||||
interface Authenticators {
|
|
||||||
[key: string]: HandlerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (plugin: Plugin, more: Authenticators = {}) {
|
|
||||||
const { authenticators } = plugin;
|
|
||||||
|
|
||||||
const natives = ['password'].reduce(
|
|
||||||
(result, key) =>
|
|
||||||
Object.assign(result, {
|
|
||||||
[key]: requireModule(path.isAbsolute(key) ? key : path.join(__dirname, key)) as HandlerType,
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const [name, authenticator] of Object.entries(<Authenticators>{ ...more, ...natives })) {
|
|
||||||
authenticators.register(name, authenticator);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import { PasswordField } from '@nocobase/database';
|
|
||||||
import { Context, Next } from '@nocobase/actions';
|
|
||||||
import { namespace } from '..';
|
|
||||||
|
|
||||||
export default async function (ctx: Context, next: Next) {
|
|
||||||
const { uniqueField = 'email', values } = ctx.action.params;
|
|
||||||
|
|
||||||
if (!values[uniqueField]) {
|
|
||||||
return ctx.throw(400, {
|
|
||||||
code: 'InvalidUserData',
|
|
||||||
message: ctx.t('Please fill in your email address', { ns: namespace }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const User = ctx.db.getCollection('users');
|
|
||||||
const user = await User.model.findOne<any>({
|
|
||||||
where: {
|
|
||||||
[uniqueField]: values[uniqueField],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return ctx.throw(404, ctx.t('The email is incorrect, please re-enter', { ns: namespace }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const field = User.getField<PasswordField>('password');
|
|
||||||
const valid = await field.verify(values.password, user.password);
|
|
||||||
if (!valid) {
|
|
||||||
return ctx.throw(404, ctx.t('The password is incorrect, please re-enter', { ns: namespace }));
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.state.currentUser = user;
|
|
||||||
|
|
||||||
return next();
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
|
||||||
|
|
||||||
export interface JwtOptions {
|
|
||||||
secret: string;
|
|
||||||
expiresIn?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class JwtService {
|
|
||||||
constructor(protected options: JwtOptions) {}
|
|
||||||
|
|
||||||
private expiresIn() {
|
|
||||||
return this.options.expiresIn || process.env.JWT_EXPIRES_IN || '7d';
|
|
||||||
}
|
|
||||||
|
|
||||||
private secret() {
|
|
||||||
return this.options.secret || process.env.APP_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
sign(payload: any) {
|
|
||||||
return jwt.sign(payload, this.secret(), { expiresIn: this.expiresIn() });
|
|
||||||
}
|
|
||||||
|
|
||||||
decode(token: string): Promise<any> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
jwt.verify(token, this.secret(), (err: any, decoded: any) => {
|
|
||||||
if (err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(decoded);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
// TODO(usage): 拦截用户的处理暂时作为一个中间件导出,应用需要的时候可以直接使用这个中间件
|
|
||||||
export function check(options) {
|
|
||||||
return async function check(ctx, next) {
|
|
||||||
const { currentUser } = ctx.state;
|
|
||||||
|
|
||||||
if (!currentUser) {
|
|
||||||
return ctx.throw(401, 'Unauthorized');
|
|
||||||
}
|
|
||||||
return next();
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export { check } from './check';
|
|
||||||
export { parseToken } from './parseToken';
|
|
@ -1,41 +0,0 @@
|
|||||||
import { Context, Next } from '@nocobase/actions';
|
|
||||||
|
|
||||||
export async function parseToken(ctx: Context, next: Next) {
|
|
||||||
const user = await findUserByToken(ctx);
|
|
||||||
if (user) {
|
|
||||||
ctx.state.currentUser = user;
|
|
||||||
}
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findUserByToken(ctx: Context) {
|
|
||||||
const token = ctx.getBearerToken();
|
|
||||||
if (!token) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { jwtService } = ctx.app.getPlugin('users');
|
|
||||||
try {
|
|
||||||
const { userId } = await jwtService.decode(token);
|
|
||||||
const collection = ctx.db.getCollection('users');
|
|
||||||
ctx.state.currentUserAppends = ctx.state.currentUserAppends || [];
|
|
||||||
for (const [, field] of collection.fields) {
|
|
||||||
if (field.type === 'belongsTo') {
|
|
||||||
ctx.state.currentUserAppends.push(field.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await ctx.db.getRepository('users').findOne({
|
|
||||||
appends: ctx.state.currentUserAppends,
|
|
||||||
filter: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.logger.info(`Current user id: ${userId}`);
|
|
||||||
return user;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
ctx.logger.error(error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +1,19 @@
|
|||||||
import { Collection, Op } from '@nocobase/database';
|
import { Collection, Op } from '@nocobase/database';
|
||||||
import { HandlerType } from '@nocobase/resourcer';
|
|
||||||
import { Plugin } from '@nocobase/server';
|
import { Plugin } from '@nocobase/server';
|
||||||
import { Registry, parse } from '@nocobase/utils';
|
import { parse } from '@nocobase/utils';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
|
||||||
import { namespace } from './';
|
import { namespace } from './';
|
||||||
import * as actions from './actions/users';
|
import * as actions from './actions/users';
|
||||||
import initAuthenticators from './authenticators';
|
|
||||||
import { JwtOptions, JwtService } from './jwt-service';
|
|
||||||
import { enUS, zhCN } from './locale';
|
import { enUS, zhCN } from './locale';
|
||||||
import { parseToken } from './middlewares';
|
|
||||||
|
|
||||||
export interface UserPluginConfig {
|
export interface UserPluginConfig {
|
||||||
name?: string;
|
name?: string;
|
||||||
jwt: JwtOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
||||||
public jwtService: JwtService;
|
|
||||||
|
|
||||||
public authenticators: Registry<HandlerType> = new Registry();
|
|
||||||
|
|
||||||
constructor(app, options) {
|
constructor(app, options) {
|
||||||
super(app, options);
|
super(app, options);
|
||||||
this.jwtService = new JwtService(options?.jwt || {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
@ -94,8 +84,6 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
|||||||
this.app.resourcer.registerActionHandler(`users:${key}`, action);
|
this.app.resourcer.registerActionHandler(`users:${key}`, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.app.resourcer.use(parseToken, { tag: 'parseToken' });
|
|
||||||
|
|
||||||
this.app.acl.addFixedParams('users', 'destroy', () => {
|
this.app.acl.addFixedParams('users', 'destroy', () => {
|
||||||
return {
|
return {
|
||||||
filter: {
|
filter: {
|
||||||
@ -112,13 +100,9 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const publicActions = ['check', 'signin', 'signup', 'lostpassword', 'resetpassword', 'getUserByResetToken'];
|
const loggedInActions = ['updateProfile'];
|
||||||
const loggedInActions = ['signout', 'updateProfile', 'changePassword'];
|
|
||||||
|
|
||||||
publicActions.forEach((action) => this.app.acl.allow('users', action));
|
|
||||||
loggedInActions.forEach((action) => this.app.acl.allow('users', action, 'loggedIn'));
|
loggedInActions.forEach((action) => this.app.acl.allow('users', action, 'loggedIn'));
|
||||||
|
|
||||||
this.app.on('beforeStart', () => this.initVerification());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
@ -133,8 +117,6 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
|||||||
plugin: this,
|
plugin: this,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
initAuthenticators(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getInstallingData(options: any = {}) {
|
getInstallingData(options: any = {}) {
|
||||||
@ -160,7 +142,7 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.repository.create({
|
await User.repository.create({
|
||||||
values: {
|
values: {
|
||||||
email: rootEmail,
|
email: rootEmail,
|
||||||
password: rootPassword,
|
password: rootPassword,
|
||||||
@ -174,84 +156,4 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
|
|||||||
await repo.db2cm('users');
|
await repo.db2cm('users');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(module): should move to preset or dynamic configuration panel
|
|
||||||
async initVerification() {
|
|
||||||
const verificationPlugin = this.app.getPlugin('verification') as any;
|
|
||||||
if (!verificationPlugin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const systemSettingsRepo = this.db.getRepository('systemSettings');
|
|
||||||
const settings = await systemSettingsRepo.findOne();
|
|
||||||
if (!settings.smsAuthEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
verificationPlugin.interceptors.register('users:signin', {
|
|
||||||
manual: true,
|
|
||||||
getReceiver(ctx) {
|
|
||||||
return ctx.action.params.values.phone;
|
|
||||||
},
|
|
||||||
expiresIn: 120,
|
|
||||||
validate: async (ctx, phone) => {
|
|
||||||
if (!phone) {
|
|
||||||
throw new Error(ctx.t('Not a valid cellphone number, please re-enter'));
|
|
||||||
}
|
|
||||||
const User = this.db.getCollection('users');
|
|
||||||
const exists = await User.model.count({
|
|
||||||
where: {
|
|
||||||
phone,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!exists) {
|
|
||||||
throw new Error(ctx.t('The phone number is not registered, please register first', { ns: namespace }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
verificationPlugin.interceptors.register('users:signup', {
|
|
||||||
getReceiver(ctx) {
|
|
||||||
return ctx.action.params.values.phone;
|
|
||||||
},
|
|
||||||
expiresIn: 120,
|
|
||||||
validate: async (ctx, phone) => {
|
|
||||||
if (!phone) {
|
|
||||||
throw new Error(ctx.t('Not a valid cellphone number, please re-enter', { ns: namespace }));
|
|
||||||
}
|
|
||||||
const User = this.db.getCollection('users');
|
|
||||||
const exists = await User.model.count({
|
|
||||||
where: {
|
|
||||||
phone,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (exists) {
|
|
||||||
throw new Error(ctx.t('The phone number has been registered, please login directly', { ns: namespace }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.authenticators.register('sms', (ctx, next) =>
|
|
||||||
verificationPlugin.intercept(ctx, async () => {
|
|
||||||
const { values } = ctx.action.params;
|
|
||||||
|
|
||||||
const User = ctx.db.getCollection('users');
|
|
||||||
const user = await User.model.findOne({
|
|
||||||
where: {
|
|
||||||
phone: values.phone,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!user) {
|
|
||||||
return ctx.throw(404, ctx.t('The phone number is incorrect, please re-enter', { ns: namespace }));
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.state.currentUser = user;
|
|
||||||
|
|
||||||
return next();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user