chore(users): remove deprecated code (#3122)

* chore: remove deprecated code in user plugin

* chore: update

* fix: test

* fix: test
This commit is contained in:
YANG QIA 2023-12-01 13:23:41 +08:00 committed by GitHub
parent 1efedee68c
commit cb7f1d7aa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 4 additions and 411 deletions

View File

@ -114,12 +114,7 @@ describe('acl', () => {
userPlugin = app.getPlugin('users') as UsersPlugin;
const testAgent = app.agent().auth(
userPlugin.jwtService.sign({
userId: u1.get('id'),
}),
{ type: 'bearer' },
);
const testAgent = app.agent().login(u1);
// @ts-ignore
const response1 = await testAgent.resource('repairs').list({

View File

@ -38,27 +38,6 @@ describe('actions', () => {
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 () => {
const res1 = await agent.resource('users').updateProfile({
filterByTk: adminUser.id,

View File

@ -2,7 +2,4 @@ import { UserPluginConfig } from '../server';
export const userPluginConfig: UserPluginConfig = {
name: 'users',
jwt: {
secret: '09f26e402586e2faa8da4c98a35f1b20d6b033c60',
},
};

View File

@ -1,112 +1,4 @@
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) {
const { values } = ctx.action.params;
@ -122,27 +14,3 @@ export async function updateProfile(ctx: Context, next: Next) {
ctx.body = result;
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();
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
});
});
}
}

View File

@ -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();
};
}

View File

@ -1,2 +0,0 @@
export { check } from './check';
export { parseToken } from './parseToken';

View File

@ -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;
}
}

View File

@ -1,29 +1,19 @@
import { Collection, Op } from '@nocobase/database';
import { HandlerType } from '@nocobase/resourcer';
import { Plugin } from '@nocobase/server';
import { Registry, parse } from '@nocobase/utils';
import { parse } from '@nocobase/utils';
import { resolve } from 'path';
import { namespace } from './';
import * as actions from './actions/users';
import initAuthenticators from './authenticators';
import { JwtOptions, JwtService } from './jwt-service';
import { enUS, zhCN } from './locale';
import { parseToken } from './middlewares';
export interface UserPluginConfig {
name?: string;
jwt: JwtOptions;
}
export default class UsersPlugin extends Plugin<UserPluginConfig> {
public jwtService: JwtService;
public authenticators: Registry<HandlerType> = new Registry();
constructor(app, options) {
super(app, options);
this.jwtService = new JwtService(options?.jwt || {});
}
async beforeLoad() {
@ -94,8 +84,6 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
this.app.resourcer.registerActionHandler(`users:${key}`, action);
}
// this.app.resourcer.use(parseToken, { tag: 'parseToken' });
this.app.acl.addFixedParams('users', 'destroy', () => {
return {
filter: {
@ -112,13 +100,9 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
};
});
const publicActions = ['check', 'signin', 'signup', 'lostpassword', 'resetpassword', 'getUserByResetToken'];
const loggedInActions = ['signout', 'updateProfile', 'changePassword'];
const loggedInActions = ['updateProfile'];
publicActions.forEach((action) => this.app.acl.allow('users', action));
loggedInActions.forEach((action) => this.app.acl.allow('users', action, 'loggedIn'));
this.app.on('beforeStart', () => this.initVerification());
}
async load() {
@ -133,8 +117,6 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
plugin: this,
},
});
initAuthenticators(this);
}
getInstallingData(options: any = {}) {
@ -160,7 +142,7 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
return;
}
const user = await User.repository.create({
await User.repository.create({
values: {
email: rootEmail,
password: rootPassword,
@ -174,84 +156,4 @@ export default class UsersPlugin extends Plugin<UserPluginConfig> {
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();
}),
);
}
}