import crypto from "crypto"; import moment from "moment-timezone"; import { Repository } from "typeorm"; // tslint:disable-next-line:no-submodule-imports import { v4 as uuidv4 } from "uuid"; import { DAYS, DBDateFormat } from "../utils"; import { BaseRepository } from "./BaseRepository"; import { dataSource } from "./dataSource"; import { ApiLogin } from "./entities/ApiLogin"; import { randomBytes } from "node:crypto"; const LOGIN_EXPIRY_TIME = 1 * DAYS; export class ApiLogins extends BaseRepository { private apiLogins: Repository; constructor() { super(); this.apiLogins = dataSource.getRepository(ApiLogin); } async getUserIdByApiKey(apiKey: string): Promise { const [loginId, token] = apiKey.split("."); if (!loginId || !token) { return null; } const login = await this.apiLogins .createQueryBuilder() .where("id = :id", { id: loginId }) .andWhere("expires_at > NOW()") .getOne(); if (!login) { return null; } const hash = crypto.createHash("sha256"); hash.update(loginId + token); // Remember to use loginId as the salt const hashedToken = hash.digest("hex"); if (hashedToken !== login.token) { return null; } return login.user_id; } async addLogin(userId: string): Promise { // Generate random login id let loginId; while (true) { loginId = randomBytes(16).toString("hex"); const existing = await this.apiLogins.findOne({ where: { id: loginId, }, }); if (!existing) break; } // Generate token const token = randomBytes(32).toString("hex"); const hash = crypto.createHash("sha256"); hash.update(loginId + token); // Use loginId as a salt const hashedToken = hash.digest("hex"); // Save this to the DB await this.apiLogins.insert({ id: loginId, token: hashedToken, user_id: userId, logged_in_at: moment.utc().format(DBDateFormat), expires_at: moment.utc().add(LOGIN_EXPIRY_TIME, "ms").format(DBDateFormat), }); return `${loginId}.${token}`; } expireApiKey(apiKey) { const [loginId, token] = apiKey.split("."); if (!loginId || !token) return; return this.apiLogins.update( { id: loginId }, { expires_at: moment.utc().format(DBDateFormat), }, ); } async refreshApiKeyExpiryTime(apiKey) { const [loginId, token] = apiKey.split("."); if (!loginId || !token) return; const updatedTime = moment().utc().add(LOGIN_EXPIRY_TIME, "ms"); const login = await this.apiLogins.createQueryBuilder().where("id = :id", { id: loginId }).getOne(); if (!login || moment.utc(login.expires_at).isSameOrAfter(updatedTime)) return; await this.apiLogins.update( { id: loginId }, { expires_at: updatedTime.format(DBDateFormat), }, ); } }