Skip to content

Commit

Permalink
feat: 密码加盐&去除所有明文密码
Browse files Browse the repository at this point in the history
  • Loading branch information
Mereithhh committed Sep 6, 2022
1 parent 548bee7 commit 61ab13d
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ contact_links:

- name: 官方 QQ 群
url: https://jq.qq.com/?_wv=1027&k=5NRyK2Sw
about: '加入VanBlog 用户交流群: 743037540'
about: '加入 VanBlog 用户交流群: 743037540'

# - name: Telegram
# url: https://t.me/walinejs
Expand Down
1 change: 1 addition & 0 deletions packages/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"copy-to-clipboard": "^3.3.2",
"front-matter": "^4.0.2",
"github-markdown-css": "^5.1.0",
"js-sha256": "^0.9.0",
"lodash": "^4.17.0",
"moment": "^2.29.0",
"monaco-editor": "^0.34.0",
Expand Down
12 changes: 10 additions & 2 deletions packages/admin/src/components/CollaboratorModal/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createCollaborator, updateCollaborator } from '@/services/van-blog/api';
import { encryptPwd } from '@/services/van-blog/encryptPwd';
import { ModalForm, ProFormSelect, ProFormText } from '@ant-design/pro-components';
const permissionOptions = [
{
Expand Down Expand Up @@ -57,9 +58,16 @@ export default function (props) {
initialValues={initialValues || undefined}
onFinish={async (values) => {
if (id) {
await updateCollaborator({ id, ...values });
await updateCollaborator({
id,
...values,
password: encryptPwd(values.name, values.password),
});
} else {
await createCollaborator(values);
await createCollaborator({
...values,
password: encryptPwd(values.name, values.password),
});
}
if (onFinish) {
onFinish();
Expand Down
3 changes: 2 additions & 1 deletion packages/admin/src/pages/InitPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import styles from './index.less';
import { ProFormText, StepsForm } from '@ant-design/pro-components';

import SiteInfoForm from '@/components/SiteInfoForm';
import { encryptPwd } from '@/services/van-blog/encryptPwd';
import { useRef } from 'react';

const InitPage = () => {
Expand Down Expand Up @@ -41,7 +42,7 @@ const InitPage = () => {
const newData = {
user: {
username: name,
password,
password: encryptPwd(name, password),
},
siteInfo,
};
Expand Down
8 changes: 7 additions & 1 deletion packages/admin/src/pages/SystemConfig/tabs/User.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import CollaboratorModal, { getPermissionLabel } from '@/components/CollaboratorModal';
import Tags from '@/components/Tags';
import { deleteCollaborator, getAllCollaborators, updateUser } from '@/services/van-blog/api';
import { encryptPwd } from '@/services/van-blog/encryptPwd';
import { ProForm, ProFormText, ProTable } from '@ant-design/pro-components';
import { Button, Card, message, Modal, Space } from 'antd';
import { useRef } from 'react';
Expand Down Expand Up @@ -31,6 +32,7 @@ const columns = [
key="edit"
onFinish={() => {
action?.reload();
message.success('修改协作者成功!');
}}
trigger={<a>修改</a>}
/>,
Expand Down Expand Up @@ -72,7 +74,10 @@ export default function () {
}}
syncToInitialValues={true}
onFinish={async (data) => {
await updateUser(data);
await updateUser({
name: data.name,
password: encryptPwd(data.name, data.password),
});
window.localStorage.removeItem('token');
setInitialState((s) => ({ ...s, user: undefined }));
history.push('/');
Expand Down Expand Up @@ -114,6 +119,7 @@ export default function () {
<Space>
<CollaboratorModal
onFinish={() => {
message.success('新建协作者成功!');
actionRef.current?.reload();
}}
trigger={<Button type="primary">新建</Button>}
Expand Down
4 changes: 3 additions & 1 deletion packages/admin/src/pages/user/Login/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Footer from '@/components/Footer';
import { login } from '@/services/van-blog/api';
import { encryptPwd } from '@/services/van-blog/encryptPwd';
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { LoginForm, ProFormCheckbox, ProFormText } from '@ant-design/pro-form';
import { message } from 'antd';
Expand Down Expand Up @@ -58,7 +59,8 @@ const Login = () => {
autoLogin: true,
}}
onFinish={async (values) => {
await handleSubmit(values);
const {username,password} = values;
await handleSubmit({username,password: encryptPwd(username,password)});
}}
>
{type === 'account' && (
Expand Down
5 changes: 5 additions & 0 deletions packages/admin/src/services/van-blog/encryptPwd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sha256 } from 'js-sha256';
export function encryptPwd(username, password) {
username = username.toLowerCase();
return sha256(username + sha256(sha256(sha256(sha256(password))) + sha256(username)));
}
5 changes: 5 additions & 0 deletions packages/admin/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9979,6 +9979,11 @@ js-levenshtein@^1.1.3:
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==

js-sha256@^0.9.0:
version "0.9.0"
resolved "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==

"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
Expand Down
1 change: 1 addition & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"highlight.js": "^11.6.0",
"image-size": "^1.0.2",
"js-base64": "^3.7.2",
"js-sha256": "^0.9.0",
"line-reader": "^0.4.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ISRProvider } from './provider/isr/isr.provider';
import { WalineProvider } from './provider/waline/waline.provider';
import { InitProvider } from './provider/init/init.provider';
import { json } from 'express';
import { UserProvider } from './provider/user/user.provider';

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
Expand Down Expand Up @@ -45,6 +46,9 @@ async function bootstrap() {
const initProvider = app.get(InitProvider);
initProvider.initVersion();
if (await initProvider.checkHasInited()) {
const userProvider = app.get(UserProvider);
// 老版本没加盐的用户数据洗一下。
userProvider.washUserWithSalt();
const metaProvider = app.get(MetaProvider);
metaProvider.updateTotalWords('首次启动');
const walineProvider = app.get(WalineProvider);
Expand Down
5 changes: 4 additions & 1 deletion packages/server/src/provider/init/init.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { UserDocument } from 'src/scheme/user.schema';
import { WalineProvider } from '../waline/waline.provider';
import { SettingProvider } from '../setting/setting.provider';
import { version } from '../../utils/loadConfig';
import { encryptPassword, makeSalt } from 'src/utils/crypto';

@Injectable()
export class InitProvider {
Expand All @@ -25,12 +26,14 @@ export class InitProvider {
toUpdateDto = { ...siteInfo, since: new Date() };
}
try {
const salt = makeSalt();
await this.userModel.create({
id: 0,
name: user.username,
password: user.password,
password: encryptPassword(user.username, user.password, salt),
mickname: user?.nickname || user.username,
type: 'admin',
salt,
});
await this.metaModel.create({
siteInfo: toUpdateDto,
Expand Down
90 changes: 83 additions & 7 deletions packages/server/src/provider/user/user.provider.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {
ForbiddenException,
Injectable,
Logger,
NotFoundException,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { UpdateUserDto } from 'src/types/user.dto';
import { UserDocument } from 'src/scheme/user.schema';
import { User, UserDocument } from 'src/scheme/user.schema';
import { Collaborator } from 'src/types/collaborator';
import { encryptPassword, makeSalt, washPassword } from 'src/utils/crypto';

@Injectable()
export class UserProvider {
logger = new Logger(UserProvider.name);
constructor(@InjectModel('User') private userModel: Model<UserDocument>) {}
async getUser(isList?: boolean) {
if (isList) {
Expand All @@ -21,17 +24,75 @@ export class UserProvider {
}
return await this.userModel.findOne({ id: 0 }).exec();
}
async washUserWithSalt() {
// 如果没加盐的老版本,给改成带加盐的。
const users = await this.userModel.find({
$or: [
{
salt: '',
},
{
salt: { $exists: false },
},
],
});
if (users && users.length > 0) {
this.logger.log(`老版本清洗密码未加盐用户 ${users.length} 人`);
for (const user of users) {
const salt = makeSalt();
const newPassword = washPassword(user.name, user.password, salt);
await this.userModel.updateOne(
{ id: user.id },
{ password: newPassword, salt },
);
}
}
}

async validateUser(name: string, password: string) {
return await this.userModel.findOne({ name, password }).exec();
const user = await this.userModel.findOne({ name });
if (!user) {
return null;
} else {
const result = await this.userModel
.findOne({ name, password: encryptPassword(name, password, user.salt) })
.exec();
if (result) {
this.updateSalt(result, password);
}
return result;
}
}

async updateSalt(user: User, passwordInput: string) {
const newSalt = makeSalt();
await this.userModel.updateOne(
{ id: user.id },
{
salt: newSalt,
password: encryptPassword(user.name, passwordInput, newSalt),
},
);
}

async updateUser(updateUserDto: UpdateUserDto) {
const currUser = await this.getUser();

if (!currUser) {
throw new NotFoundException();
} else {
return this.userModel
.updateOne({ id: currUser.id }, updateUserDto)
.updateOne(
{ id: currUser.id },
{
...updateUserDto,
password: encryptPassword(
updateUserDto.name,
updateUserDto.password,
currUser.salt,
),
},
)
.exec();
}
}
Expand All @@ -53,10 +114,13 @@ export class UserProvider {
if (isList) {
return await this.userModel.find(
{ type: 'collaborator' },
{ id: 1, name: 1, nickname: 1 },
{ id: 1, name: 1, nickname: 1, __v: 0, _id: 0 },
);
}
return await this.userModel.find({ type: 'collaborator' });
return await this.userModel.find(
{ type: 'collaborator' },
{ salt: 0, password: 0, __v: 0, _id: 0 },
);
}

async createCollaborator(collaboratorDto: Collaborator) {
Expand All @@ -65,11 +129,17 @@ export class UserProvider {
if (oldData) {
throw new ForbiddenException('已有为该用户名的协作者,不可重复创建!');
}

const salt = makeSalt();
return await this.userModel.create({
id: await this.getNewId(),
type: 'collaborator',
...collaboratorDto,
password: encryptPassword(
collaboratorDto.name,
collaboratorDto.password,
salt,
),
salt,
});
}
async updateCollaborator(collaboratorDto: Collaborator) {
Expand All @@ -78,14 +148,20 @@ export class UserProvider {
if (!oldData) {
throw new ForbiddenException('没有此协作者!无法更新!');
}

const salt = makeSalt();
return await this.userModel.updateOne(
{
id: oldData.id,
type: 'collaborator',
},
{
...collaboratorDto,
password: encryptPassword(
collaboratorDto.name,
collaboratorDto.password,
salt,
),
salt,
},
);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/server/src/scheme/user.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export class User extends Document {

@Prop()
permissions?: Permission[];

@Prop()
salt: string;
}

export const UserSchema = SchemaFactory.createForClass(User);
33 changes: 25 additions & 8 deletions packages/server/src/utils/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,44 @@
*/

import * as crypto from 'crypto';
import { sha256 } from 'js-sha256';

// 随机盐
export function makeSalt(): string {
return crypto.randomBytes(3).toString('base64');
return crypto.randomBytes(32).toString('base64');
}

/**
* 使用盐加密明文密码
* 使用盐加密浏览器端密🐎
* @param username 用户名
* @param password 密码
* @param salt 密码盐
*/
export function encryptPassword(password: string, salt: string): string {
if (!password || !salt) {
export function encryptPassword(
username: string,
password: string,
salt: string,
): string {
if (!username || !password || !salt) {
return '';
}
const tempSalt = Buffer.from(salt, 'base64');
return (
// 10000 代表迭代次数 16代表长度
crypto.pbkdf2Sync(password, tempSalt, 10000, 16, 'sha1').toString('base64')
return sha256(
sha256(username + sha256(password + salt)) + salt + sha256(username + salt),
);
}
/**
* 把没加过盐的密码洗成加盐的
* @param username 用户名
* @param password 密码
* @param salt 密码盐
*/
export function washPassword(username: string, password: string, salt: string) {
username = username.toLowerCase();
const browserPassword = sha256(
username + sha256(sha256(sha256(sha256(password))) + sha256(username)),
);
return encryptPassword(username, browserPassword, salt);
}

// 计算 流 MD5
export function encryptFileMD5(buffer: Buffer) {
Expand Down
Loading

0 comments on commit 61ab13d

Please sign in to comment.