Password reset (#7494)

* wip

* wip

* Update well-known.ts

* wip

* clean up

* Update request-reset-password.ts

* Update forgot-password.vue

* Update reset-password.ts

* Update request-reset-password.ts
This commit is contained in:
syuilo
2021-05-04 15:05:34 +09:00
committed by GitHub
parent a34d8549d0
commit 6ae642245e
13 changed files with 333 additions and 3 deletions

View File

@ -0,0 +1,73 @@
import $ from 'cafy';
import { publishMainStream } from '../../../services/stream';
import define from '../define';
import rndstr from 'rndstr';
import config from '@/config';
import * as ms from 'ms';
import { Users, UserProfiles, PasswordResetRequests } from '../../../models';
import { sendEmail } from '../../../services/send-email';
import { ApiError } from '../error';
import { genId } from '@/misc/gen-id';
import { IsNull } from 'typeorm';
export const meta = {
requireCredential: false as const,
limit: {
duration: ms('1hour'),
max: 3
},
params: {
username: {
validator: $.str
},
email: {
validator: $.str
},
},
errors: {
}
};
export default define(meta, async (ps) => {
const user = await Users.findOne({
usernameLower: ps.username.toLowerCase(),
host: IsNull()
});
// 合致するユーザーが登録されていなかったら無視
if (user == null) {
return;
}
const profile = await UserProfiles.findOneOrFail(user.id);
// 合致するメアドが登録されていなかったら無視
if (profile.email !== ps.email) {
return;
}
// メアドが認証されていなかったら無視
if (!profile.emailVerified) {
return;
}
const token = rndstr('a-z0-9', 64);
await PasswordResetRequests.insert({
id: genId(),
createdAt: new Date(),
userId: profile.userId,
token
});
const link = `${config.url}/reset-password/${token}`;
sendEmail(ps.email, 'Password reset requested',
`To reset password, please click this link:<br><a href="${link}">${link}</a>`,
`To reset password, please click this link: ${link}`);
});

View File

@ -0,0 +1,45 @@
import $ from 'cafy';
import * as bcrypt from 'bcryptjs';
import { publishMainStream } from '../../../services/stream';
import define from '../define';
import { Users, UserProfiles, PasswordResetRequests } from '../../../models';
import { ApiError } from '../error';
export const meta = {
requireCredential: false as const,
params: {
token: {
validator: $.str
},
password: {
validator: $.str
}
},
errors: {
}
};
export default define(meta, async (ps, user) => {
const req = await PasswordResetRequests.findOneOrFail({
token: ps.token,
});
// 発行してから30分以上経過していたら無効
if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) {
throw new Error(); // TODO
}
// Generate hash of password
const salt = await bcrypt.genSalt(8);
const hash = await bcrypt.hash(ps.password, salt);
await UserProfiles.update(req.userId, {
password: hash
});
PasswordResetRequests.delete(req.id);
});

View File

@ -61,6 +61,11 @@ router.get('/.well-known/nodeinfo', async ctx => {
ctx.body = { links };
});
/* TODO
router.get('/.well-known/change-password', async ctx => {
});
*/
router.get(webFingerPath, async ctx => {
const fromId = (id: User['id']): Record<string, any> => ({
id,