diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/acl.test.ts b/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/acl.test.ts index b6caefc01..bc03c786e 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/acl.test.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/acl.test.ts @@ -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({ diff --git a/packages/plugins/@nocobase/plugin-users/src/server/__tests__/actions.test.ts b/packages/plugins/@nocobase/plugin-users/src/server/__tests__/actions.test.ts index 1a1329f1b..df1251c1d 100644 --- a/packages/plugins/@nocobase/plugin-users/src/server/__tests__/actions.test.ts +++ b/packages/plugins/@nocobase/plugin-users/src/server/__tests__/actions.test.ts @@ -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, diff --git a/packages/plugins/@nocobase/plugin-users/src/server/__tests__/utils.ts b/packages/plugins/@nocobase/plugin-users/src/server/__tests__/utils.ts index baa1589e3..d856926c6 100644 --- a/packages/plugins/@nocobase/plugin-users/src/server/__tests__/utils.ts +++ b/packages/plugins/@nocobase/plugin-users/src/server/__tests__/utils.ts @@ -2,7 +2,4 @@ import { UserPluginConfig } from '../server'; export const userPluginConfig: UserPluginConfig = { name: 'users', - jwt: { - secret: '09f26e402586e2faa8da4c98a35f1b20d6b033c60', - }, }; diff --git a/packages/plugins/@nocobase/plugin-users/src/server/actions/users.ts b/packages/plugins/@nocobase/plugin-users/src/server/actions/users.ts index 6990bbdbc..b1503abf1 100644 --- a/packages/plugins/@nocobase/plugin-users/src/server/actions/users.ts +++ b/packages/plugins/@nocobase/plugin-users/src/server/actions/users.ts @@ -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({ - 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({ - 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({ - where: { - email: ctx.state.currentUser.email, - }, - }); - const pwd = User.getField('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(); -} diff --git a/packages/plugins/@nocobase/plugin-users/src/server/authenticators/index.ts b/packages/plugins/@nocobase/plugin-users/src/server/authenticators/index.ts deleted file mode 100644 index cb18b3cf0..000000000 --- a/packages/plugins/@nocobase/plugin-users/src/server/authenticators/index.ts +++ /dev/null @@ -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({ ...more, ...natives })) { - authenticators.register(name, authenticator); - } -} diff --git a/packages/plugins/@nocobase/plugin-users/src/server/authenticators/password.ts b/packages/plugins/@nocobase/plugin-users/src/server/authenticators/password.ts deleted file mode 100644 index f64783578..000000000 --- a/packages/plugins/@nocobase/plugin-users/src/server/authenticators/password.ts +++ /dev/null @@ -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({ - 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('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(); -} diff --git a/packages/plugins/@nocobase/plugin-users/src/server/jwt-service.ts b/packages/plugins/@nocobase/plugin-users/src/server/jwt-service.ts deleted file mode 100644 index cee942485..000000000 --- a/packages/plugins/@nocobase/plugin-users/src/server/jwt-service.ts +++ /dev/null @@ -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 { - return new Promise((resolve, reject) => { - jwt.verify(token, this.secret(), (err: any, decoded: any) => { - if (err) { - return reject(err); - } - - resolve(decoded); - }); - }); - } -} diff --git a/packages/plugins/@nocobase/plugin-users/src/server/middlewares/check.ts b/packages/plugins/@nocobase/plugin-users/src/server/middlewares/check.ts deleted file mode 100644 index 516b3f019..000000000 --- a/packages/plugins/@nocobase/plugin-users/src/server/middlewares/check.ts +++ /dev/null @@ -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(); - }; -} diff --git a/packages/plugins/@nocobase/plugin-users/src/server/middlewares/index.ts b/packages/plugins/@nocobase/plugin-users/src/server/middlewares/index.ts deleted file mode 100644 index 538d318f9..000000000 --- a/packages/plugins/@nocobase/plugin-users/src/server/middlewares/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { check } from './check'; -export { parseToken } from './parseToken'; diff --git a/packages/plugins/@nocobase/plugin-users/src/server/middlewares/parseToken.ts b/packages/plugins/@nocobase/plugin-users/src/server/middlewares/parseToken.ts deleted file mode 100644 index e0fbd609e..000000000 --- a/packages/plugins/@nocobase/plugin-users/src/server/middlewares/parseToken.ts +++ /dev/null @@ -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; - } -} diff --git a/packages/plugins/@nocobase/plugin-users/src/server/server.ts b/packages/plugins/@nocobase/plugin-users/src/server/server.ts index b1f5f811f..427502ad9 100644 --- a/packages/plugins/@nocobase/plugin-users/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-users/src/server/server.ts @@ -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 { - public jwtService: JwtService; - - public authenticators: Registry = 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 { 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 { }; }); - 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 { plugin: this, }, }); - - initAuthenticators(this); } getInstallingData(options: any = {}) { @@ -160,7 +142,7 @@ export default class UsersPlugin extends Plugin { 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 { 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(); - }), - ); - } }