2021-04-16 15:29:23 +02:00
|
|
|
import { EventEmitter } from 'events';
|
|
|
|
import { Knex } from 'knex';
|
2021-05-02 21:11:17 +02:00
|
|
|
import metricsHelper from '../util/metrics-helper';
|
2021-04-29 10:21:29 +02:00
|
|
|
import { DB_TIME } from '../metric-events';
|
2021-04-16 15:29:23 +02:00
|
|
|
import { Logger, LogProvider } from '../logger';
|
|
|
|
import NotFoundError from '../error/notfound-error';
|
2021-08-12 15:04:37 +02:00
|
|
|
import {
|
|
|
|
IResetQuery,
|
|
|
|
IResetToken,
|
|
|
|
IResetTokenCreate,
|
|
|
|
IResetTokenQuery,
|
|
|
|
IResetTokenStore,
|
|
|
|
} from '../types/stores/reset-token-store';
|
2021-04-16 15:29:23 +02:00
|
|
|
|
|
|
|
const TABLE = 'reset_tokens';
|
|
|
|
|
|
|
|
interface IResetTokenTable {
|
|
|
|
reset_token: string;
|
|
|
|
user_id: number;
|
|
|
|
expires_at: Date;
|
|
|
|
created_at: Date;
|
|
|
|
created_by: string;
|
|
|
|
used_at: Date;
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
const rowToResetToken = (row: IResetTokenTable): IResetToken => ({
|
|
|
|
userId: row.user_id,
|
|
|
|
token: row.reset_token,
|
|
|
|
expiresAt: row.expires_at,
|
|
|
|
createdAt: row.created_at,
|
|
|
|
createdBy: row.created_by,
|
|
|
|
usedAt: row.used_at,
|
|
|
|
});
|
2021-04-16 15:29:23 +02:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
export class ResetTokenStore implements IResetTokenStore {
|
2021-04-16 15:29:23 +02:00
|
|
|
private logger: Logger;
|
|
|
|
|
|
|
|
private timer: Function;
|
|
|
|
|
|
|
|
private db: Knex;
|
|
|
|
|
|
|
|
constructor(db: Knex, eventBus: EventEmitter, getLogger: LogProvider) {
|
|
|
|
this.db = db;
|
2021-08-12 15:04:37 +02:00
|
|
|
this.logger = getLogger('db/reset-token-store.ts');
|
2021-04-16 15:29:23 +02:00
|
|
|
this.timer = (action: string) =>
|
|
|
|
metricsHelper.wrapTimer(eventBus, DB_TIME, {
|
|
|
|
store: 'reset-tokens',
|
|
|
|
action,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async getActive(token: string): Promise<IResetToken> {
|
|
|
|
const row = await this.db<IResetTokenTable>(TABLE)
|
|
|
|
.where({ reset_token: token })
|
|
|
|
.where('expires_at', '>', new Date())
|
|
|
|
.first();
|
|
|
|
if (!row) {
|
|
|
|
throw new NotFoundError('Could not find an active token');
|
|
|
|
}
|
|
|
|
return rowToResetToken(row);
|
|
|
|
}
|
|
|
|
|
2021-04-23 10:58:47 +02:00
|
|
|
async getActiveTokens(): Promise<IResetToken[]> {
|
|
|
|
const rows = await this.db<IResetTokenTable>(TABLE)
|
|
|
|
.whereNull('used_at')
|
|
|
|
.andWhere('expires_at', '>', new Date());
|
|
|
|
|
|
|
|
return rows.map(rowToResetToken);
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:29:23 +02:00
|
|
|
async insert(newToken: IResetTokenCreate): Promise<IResetToken> {
|
|
|
|
const [row] = await this.db<IResetTokenTable>(TABLE)
|
|
|
|
.insert(newToken)
|
|
|
|
.returning(['created_at']);
|
|
|
|
return {
|
|
|
|
userId: newToken.user_id,
|
|
|
|
token: newToken.reset_token,
|
|
|
|
expiresAt: newToken.expires_at,
|
|
|
|
createdAt: row.created_at,
|
|
|
|
createdBy: newToken.created_by,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async useToken(token: IResetQuery): Promise<boolean> {
|
|
|
|
try {
|
|
|
|
await this.db<IResetTokenTable>(TABLE)
|
|
|
|
.update({ used_at: new Date() })
|
|
|
|
.where({ reset_token: token.token, user_id: token.userId });
|
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async deleteFromQuery({ reset_token }: IResetTokenQuery): Promise<void> {
|
|
|
|
return this.db(TABLE).where(reset_token).del();
|
2021-04-16 15:29:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async deleteAll(): Promise<void> {
|
|
|
|
return this.db(TABLE).del();
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteExpired(): Promise<void> {
|
2021-08-12 15:04:37 +02:00
|
|
|
return this.db(TABLE).where('expires_at', '<', new Date()).del();
|
2021-04-16 15:29:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async expireExistingTokensForUser(user_id: number): Promise<void> {
|
2021-08-12 15:04:37 +02:00
|
|
|
await this.db<IResetTokenTable>(TABLE).where({ user_id }).update({
|
|
|
|
expires_at: new Date(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async delete(reset_token: string): Promise<void> {
|
|
|
|
await this.db(TABLE).where({ reset_token }).del();
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy(): void {}
|
|
|
|
|
|
|
|
async exists(reset_token: string): Promise<boolean> {
|
|
|
|
const result = await this.db.raw(
|
|
|
|
`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE reset_token = ?) AS present`,
|
|
|
|
[reset_token],
|
|
|
|
);
|
|
|
|
const { present } = result.rows[0];
|
|
|
|
return present;
|
|
|
|
}
|
|
|
|
|
|
|
|
async get(key: string): Promise<IResetToken> {
|
|
|
|
const row = await this.db(TABLE).where({ reset_token: key }).first();
|
|
|
|
return rowToResetToken(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getAll(): Promise<IResetToken[]> {
|
|
|
|
const rows = await this.db(TABLE).select();
|
|
|
|
return rows.map(rowToResetToken);
|
2021-04-16 15:29:23 +02:00
|
|
|
}
|
|
|
|
}
|