YANG QIA be6b949f55
feat(auth): support signing in with username (#2376)
* feat(auth): support signing in with username

* chore: compatibility && add INIT_ROOT_USERNAME

* chore: add default username of root user

* chore: add notice

* fix: typo

* chore: change rule of username

* fix: sqlite add unique constraint

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
2023-08-19 22:02:26 +08:00

139 lines
3.7 KiB
TypeScript

import { AuthConfig, BaseAuth } from '@nocobase/auth';
import { PasswordField } from '@nocobase/database';
import crypto from 'crypto';
import { namespace } from '../preset';
export class BasicAuth extends BaseAuth {
constructor(config: AuthConfig) {
const userCollection = config.ctx.db.getCollection('users');
super({ ...config, userCollection });
}
async validate() {
const ctx = this.ctx;
const {
values: {
account, // Username or email
email, // Old parameter, compatible with old api
password,
},
} = ctx.action.params;
if (!account && !email) {
ctx.throw(400, ctx.t('Please enter your username or email', { ns: namespace }));
}
const user = await this.userRepository.findOne({
filter: {
$or: [{ username: account || null }, { email: account || email }],
},
});
if (!user) {
ctx.throw(401, ctx.t('The username or email is incorrect, please re-enter', { ns: namespace }));
}
const field = this.userCollection.getField<PasswordField>('password');
const valid = await field.verify(password, user.password);
if (!valid) {
ctx.throw(401, ctx.t('The password is incorrect, please re-enter', { ns: namespace }));
}
return user;
}
async signUp() {
const ctx = this.ctx;
const options = this.authenticator.options?.public || {};
if (!options.allowSignUp) {
ctx.throw(403, ctx.t('Not allowed to sign up', { ns: namespace }));
}
const User = ctx.db.getRepository('users');
const { values } = ctx.action.params;
const { username } = values;
if (!/^[^@.<>"'/]{2,16}$/.test(username)) {
ctx.throw(400, ctx.t('Please enter a valid username', { ns: namespace }));
}
const user = await User.create({ values });
return user;
}
async lostPassword() {
const ctx = this.ctx;
const {
values: { email },
} = ctx.action.params;
if (!email) {
ctx.throw(400, ctx.t('Please fill in your email address', { ns: namespace }));
}
const user = await this.userRepository.findOne({
where: {
email,
},
});
if (!user) {
ctx.throw(401, ctx.t('The email is incorrect, please re-enter', { ns: namespace }));
}
user.resetToken = crypto.randomBytes(20).toString('hex');
await user.save();
return user;
}
async resetPassword() {
const ctx = this.ctx;
const {
values: { email, password, resetToken },
} = ctx.action.params;
const user = await this.userRepository.findOne({
where: {
email,
resetToken,
},
});
if (!user) {
ctx.throw(404);
}
user.token = null;
user.resetToken = null;
user.password = password;
await user.save();
return user;
}
async getUserByResetToken() {
const ctx = this.ctx;
const { token } = ctx.action.params;
const user = await this.userRepository.findOne({
where: {
resetToken: token,
},
});
if (!user) {
ctx.throw(401);
}
return user;
}
async changePassword() {
const ctx = this.ctx;
const {
values: { oldPassword, newPassword },
} = ctx.action.params;
const currentUser = ctx.auth.user;
if (!currentUser) {
ctx.throw(401);
}
const user = await this.userRepository.findOne({
where: {
email: currentUser.email,
},
});
const pwd = this.userCollection.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;
await user.save();
return currentUser;
}
}