All files / src/lib/db reset-token-store.ts

65.71% Statements 23/35
100% Branches 1/1
50% Functions 8/16
64.71% Lines 22/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138    69x 69x   69x                 69x                     69x                 69x               87x 87x 87x               19x       19x 3x   16x       3x       3x       32x     32x                   4x 4x     4x                     1x               32x                                                            
import { EventEmitter } from 'events';
import { Knex } from 'knex';
import metricsHelper from '../util/metrics-helper';
import { DB_TIME } from '../metric-events';
import { Logger, LogProvider } from '../logger';
import NotFoundError from '../error/notfound-error';
import {
    IResetQuery,
    IResetToken,
    IResetTokenCreate,
    IResetTokenQuery,
    IResetTokenStore,
} from '../types/stores/reset-token-store';
 
const TABLE = 'reset_tokens';
 
interface IResetTokenTable {
    reset_token: string;
    user_id: number;
    expires_at: Date;
    created_at: Date;
    created_by: string;
    used_at: Date;
}
 
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,
});
 
export class ResetTokenStore implements IResetTokenStore {
    private logger: Logger;
 
    private timer: Function;
 
    private db: Knex;
 
    constructor(db: Knex, eventBus: EventEmitter, getLogger: LogProvider) {
        this.db = db;
        this.logger = getLogger('db/reset-token-store.ts');
        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);
    }
 
    async getActiveTokens(): Promise<IResetToken[]> {
        const rows = await this.db<IResetTokenTable>(TABLE)
            .whereNull('used_at')
            .andWhere('expires_at', '>', new Date());
 
        return rows.map(rowToResetToken);
    }
 
    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;
        }
    }
 
    async deleteFromQuery({ reset_token }: IResetTokenQuery): Promise<void> {
        return this.db(TABLE).where(reset_token).del();
    }
 
    async deleteAll(): Promise<void> {
        return this.db(TABLE).del();
    }
 
    async deleteExpired(): Promise<void> {
        return this.db(TABLE).where('expires_at', '<', new Date()).del();
    }
 
    async expireExistingTokensForUser(user_id: number): Promise<void> {
        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);
    }
}