Newer
Older
taehui / taehui-fe / src / app / www / systems / avatar.ts
@Taehui Taehui on 17 Mar 4 KB 2024-03-17 오후 11:29
import Configure from "@/app/www/system/Configure";
import DB from "@/app/www/system/DB";
import { AVATAR_ENTRY_PATH } from "@/app/www/utilities/Path";
import { equalCipher } from "@/app/www/utilities/Utility";
import { pbkdf2, randomBytes } from "crypto";
import dayjs from "dayjs";
import { readFile } from "fs";
import { PoolConnection } from "mariadb";
import { join } from "path";
import { getDatetime } from "taehui-ts/date";
import { promisify } from "util";

const pw = promisify(pbkdf2);
const rf = promisify(readFile);

const putTotem = async (db: PoolConnection, avatarID: string) => {
  const data = await DB.getTotem(db, avatarID);

  const datetime = getDatetime();
  if (data && dayjs(data.date).add(1, "hour").isAfter(datetime)) {
    await DB.setTotemDateAsAvatarID(db, avatarID, datetime);
    return data.totem;
  }

  return DB.putTotem(db, avatarID, datetime);
};

export const getTotem = async (
  pendingAvatarID: string,
  pendingAvatarCipher: string,
) => {
  return DB.ta(async (db) => {
    const data = await DB.getAvatarAsAvatarID(db, pendingAvatarID);
    if (data) {
      const { avatarID, avatarCipher, avatarName, level, fax, avatarIntro } =
        data;
      const isOK = await equalCipher(avatarCipher, pendingAvatarCipher);
      if (isOK) {
        await DB.setLastDate(db, avatarID, getDatetime());
        return {
          totem: await putTotem(db, avatarID),
          avatarID,
          avatarName,
          level,
          fax,
          avatarIntro,
        };
      } else {
        return null;
      }
    }
  });
};

const getDBCipher = async (avatarCipher: string) => {
  const salt = randomBytes(24).toString("base64");

  return `sha256:12000:${salt}:${Buffer.from(
    await pw(avatarCipher, salt, 12000, 24, "sha256"),
  ).toString("base64")}`;
};

export const doModifyAvatar = async (
  avatarID: string,
  avatarCipher: string,
  avatarCipherModified: string,
  avatarName: string,
  fax: string,
  avatarIntro: string,
) => {
  if (
    avatarCipher &&
    avatarName &&
    avatarName.length <= 16 &&
    (!fax || /^.+@.+$/.test(fax))
  ) {
    return DB.ta(async (db) => {
      if (
        await DB.doModifyAvatar(
          db,
          avatarID,
          avatarCipher,
          avatarCipherModified && (await getDBCipher(avatarCipherModified)),
          avatarName,
          fax,
          avatarIntro,
        )
      ) {
        if (avatarCipherModified) {
          await DB.wipeTotem(db, avatarID);
          await fetch(`${Configure.www.qwilight}/totem`, {
            method: "PATCH",
            body: JSON.stringify({ avatarID }),
          });
        }
        return true;
      } else {
        return false;
      }
    });
  } else {
    return false;
  }
};

export const doModifyAvatarIntro = async (
  avatarID: string,
  avatarIntro: string,
) => {
  return DB.doModifyAvatarIntro(avatarID, avatarIntro);
};

export const postAvatar = async (
  avatarID: string,
  avatarCipher: string,
  avatarName: string,
  avatarIP: string,
  fax: string,
) => {
  return (
    avatarID &&
    !avatarID.startsWith("@") &&
    !avatarID.startsWith("#") &&
    !avatarID.startsWith("$") &&
    !avatarID.startsWith("*") &&
    avatarCipher &&
    avatarName &&
    avatarName.length <= 16 &&
    (!fax || /^.+@.+$/.test(fax)) &&
    DB.ta(async (db) => {
      if (await DB.getAvatarAsAvatarID(db, avatarID)) {
        return false;
      } else {
        return DB.postAvatar(
          avatarID,
          await getDBCipher(avatarCipher),
          avatarName,
          avatarIP,
          fax,
          getDatetime(),
        );
      }
    })
  );
};

export const wipeTotem = async (avatarID: string) => {
  return DB.ta(async (db) =>
    Promise.all([
      DB.setLastDate(db, avatarID, getDatetime()),
      DB.wipeTotem(db, avatarID),
    ]),
  );
};

export const getAvatarDrawing = async (avatarID: string) => {
  try {
    return await rf(join(AVATAR_ENTRY_PATH, avatarID + ".png"));
  } catch (e: any) {
    if (e.code === "ENOENT") {
      return null;
    }

    throw e;
  }
};

export const getLatestAvatars = async () => {
  return DB.getLatestAvatars();
};

export const doInvalidateDrawing = async (avatarID: string) => {
  await fetch(`${Configure.www.qwilight}/drawing`, {
    method: "PATCH",
    body: JSON.stringify({ avatarID }),
  });
};