wip
This commit is contained in:
parent
a34d8549d0
commit
98e45b7112
5 changed files with 105 additions and 0 deletions
migration
src
20
migration/1619942102890-password-reset.ts
Normal file
20
migration/1619942102890-password-reset.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class passwordReset1619942102890 implements MigrationInterface {
|
||||
name = 'passwordReset1619942102890'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "password_reset_request" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "token" character varying(256) NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "PK_fcf4b02eae1403a2edaf87fd074" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0b575fa9a4cfe638a925949285" ON "password_reset_request" ("token") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_4bb7fd4a34492ae0e6cc8d30ac" ON "password_reset_request" ("userId") `);
|
||||
await queryRunner.query(`ALTER TABLE "password_reset_request" ADD CONSTRAINT "FK_4bb7fd4a34492ae0e6cc8d30ac8" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "password_reset_request" DROP CONSTRAINT "FK_4bb7fd4a34492ae0e6cc8d30ac8"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_4bb7fd4a34492ae0e6cc8d30ac"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0b575fa9a4cfe638a925949285"`);
|
||||
await queryRunner.query(`DROP TABLE "password_reset_request"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -70,6 +70,7 @@ import { Channel } from '../models/entities/channel';
|
|||
import { ChannelFollowing } from '../models/entities/channel-following';
|
||||
import { ChannelNotePining } from '../models/entities/channel-note-pining';
|
||||
import { RegistryItem } from '../models/entities/registry-item';
|
||||
import { PasswordResetRequest } from '@/models/entities/password-reset-request';
|
||||
|
||||
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
|
||||
|
||||
|
@ -169,6 +170,7 @@ export const entities = [
|
|||
ChannelFollowing,
|
||||
ChannelNotePining,
|
||||
RegistryItem,
|
||||
PasswordResetRequest,
|
||||
...charts as any
|
||||
];
|
||||
|
||||
|
|
30
src/models/entities/password-reset-request.ts
Normal file
30
src/models/entities/password-reset-request.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { PrimaryColumn, Entity, Index, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { id } from '../id';
|
||||
import { User } from './user';
|
||||
|
||||
@Entity()
|
||||
export class PasswordResetRequest {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone')
|
||||
public createdAt: Date;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public token: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
}
|
|
@ -60,6 +60,7 @@ import { MutedNote } from './entities/muted-note';
|
|||
import { ChannelFollowing } from './entities/channel-following';
|
||||
import { ChannelNotePining } from './entities/channel-note-pining';
|
||||
import { RegistryItem } from './entities/registry-item';
|
||||
import { PasswordResetRequest } from './entities/password-reset-request';
|
||||
|
||||
export const Announcements = getRepository(Announcement);
|
||||
export const AnnouncementReads = getRepository(AnnouncementRead);
|
||||
|
@ -122,3 +123,4 @@ export const Channels = getCustomRepository(ChannelRepository);
|
|||
export const ChannelFollowings = getRepository(ChannelFollowing);
|
||||
export const ChannelNotePinings = getRepository(ChannelNotePining);
|
||||
export const RegistryItems = getRepository(RegistryItem);
|
||||
export const PasswordResetRequests = getRepository(PasswordResetRequest);
|
||||
|
|
51
src/server/api/endpoints/reset-password.ts
Normal file
51
src/server/api/endpoints/reset-password.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
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';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: false as const,
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 3
|
||||
},
|
||||
|
||||
params: {
|
||||
email: {
|
||||
validator: $.str
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const profile = await UserProfiles.findOneOrFail({
|
||||
email: ps.email,
|
||||
emailVerified: true
|
||||
});
|
||||
|
||||
const token = rndstr('a-z0-9', 128);
|
||||
|
||||
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}`);
|
||||
});
|
Loading…
Add table
Reference in a new issue