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;
|
||||
|
||||
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({
|
||||
|
@ -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,
|
||||
|
@ -2,7 +2,4 @@ import { UserPluginConfig } from '../server';
|
||||
|
||||
export const userPluginConfig: UserPluginConfig = {
|
||||
name: 'users',
|
||||
jwt: {
|
||||
secret: '09f26e402586e2faa8da4c98a35f1b20d6b033c60',
|
||||
},
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 { 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();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user