File

src/users/users.service.ts

Index

Properties

Properties

file
file:
mime
mime:
name
name: string
Type : string
user
user: string
Type : string
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
import { DatabaseService } from '../database/database.service';
import { UtilsService } from '../utils/utils.service';
import * as Enums from '../utils/enums/responses.enum';
import { FileglassMainDashBoard } from '../../types';
import Prisma, { PFP_TYPE } from '@prisma/client';
import { AlreadyRegisteredException } from '../exceptions/already-registered.exception';
import { ConfigService } from '@nestjs/config';
import { ApikeyException } from '../exceptions/apikey.exception';
import { BlockedException } from '../exceptions/blocked.exception';
import { CodesService } from '../codes/codes.service';
import { EmailsService } from '../emails/emails.service';
import { CloudflareService } from '../cloud/cloudflare/cloudflare.service';
import { BezosService } from '../cloud/bezos.service';
import { CsamService } from '../cloud/csam/csam.service';
import { GenericException } from '../exceptions/generic.exception';
import { GenericRespEnum } from '../utils/enums/responses.enum';
import { UploadsService } from '../uploads/uploads.service';

@Injectable()
export class UsersService {
  private readonly logger = new Logger(UsersService.name);
  constructor(
    private readonly databaseService: DatabaseService,
    private readonly utilService: UtilsService,
    private readonly configService: ConfigService,
    private readonly codesService: CodesService,
    private readonly emailService: EmailsService,
    private readonly cloudflareService: CloudflareService,
    private readonly bezosService: BezosService,
    private readonly csamService: CsamService,
    @Inject(forwardRef(() => UploadsService))
    private readonly uploadService: UploadsService,
  ) {}

  public async create(
    username: string,
    password: string,
    email: string,
    customSnw?: string,
    sendEmail = true,
  ): Promise<string> {
    const emailHash = this.utilService.sha(email);
    if (await this.userExists(emailHash, username)) {
      throw new AlreadyRegisteredException();
    }

    const snowflake = customSnw || (await this.utilService.generateSnowflake());
    await this.databaseService.write.users.create({
      data: {
        snowflake,
        username,
        email_hash: emailHash,
        email: this.utilService.encrypt(email),
        password: await this.utilService.hashPass(password),
        blocked: false, // idk jesus is lazy so imma need to do that
        admin: false,
        twostep: false,
        date: Date.now().toString(),
        init_ip: 'ip',
        sus: false,
        email_verified: !sendEmail,
      },
    });
    if (sendEmail) {
      await this.sendEmailConfirmation(email, snowflake, username);
    }

    return Enums.RegisterRespEnum.USER_REGISTERED;
  }

  public async sendEmailConfirmation(
    email: string,
    snowflake: string,
    username: string,
  ) {
    const code = await this.codesService.createCode(
      'EMAIL_CONFIRM_INIT',
      20,
      snowflake,
    );
    const body = this.emailService.bodies.confirmEmail(
      this.utilService.buildConfirmUrl(code.code),
      username,
      true,
    );
    await new this.emailService.Mailer(email, 'Confirm your email', true)
      .append(body)
      .deliver();
  }

  public async isIpRegistered(ip: string) {
    return (
      (await this.databaseService.read.users.count({
        where: { init_ip: ip },
      })) > 0
    );
  }

  public async userExists(
    email_hash: string,
    username: string,
  ): Promise<boolean> {
    const queryResult = await this.databaseService.read.users.count({
      where: {
        OR: [
          {
            email_hash,
          },
          {
            username,
          },
        ],
      },
    });

    return queryResult > 0;
  }

  public async getUser(
    data: string,
    dataType: 'username' | 'email_hash',
  ): Promise<{ snowflake: string; emailVerified: boolean } | undefined> {
    this.logger.debug(`Selecting ${dataType} with value: ${data}`);
    const retval = await this.databaseService.read.users.findFirst({
      where: {
        [dataType]: data,
      },
      select: {
        snowflake: true,
        email_verified: true,
      },
    });
    return retval
      ? { snowflake: retval.snowflake, emailVerified: retval.email_verified }
      : undefined;
  }

  public async getPassword(snowflake: string): Promise<string> {
    const r = await this.databaseService.read.users.findFirst({
      where: {
        snowflake,
      },
      select: {
        password: true,
      },
    });
    return r.password;
  }
  public async getUploadMinimalDataFromUser(user: string) {
    const akey = await this.getUserApiKey(user);
    const result = await this.databaseService.read.api_keys.findFirst({
      where: {
        hash: akey.hash,
      },
      select: {
        owner: true,
        ip: true,
        users: {
          select: {
            blocked: true,
            cname: true,
            domain: true,
            raw: true,
            embed: true,
          },
        },
      },
    });
    return {
      snowflake: result.owner,
      cname: result.users.cname!,
      ip: result.ip!,
      domain: result.users.domain,
      raw: result.users.raw,
      embed: result!.users.embed!,
    };
  }
  public async getUploadMinimalDataFromApikey(apikey: string, ip: string) {
    const result = await this.databaseService.read.api_keys.findFirst({
      where: {
        hash: this.utilService.sha(apikey),
      },
      select: {
        owner: true,
        ip: true,
        users: {
          select: {
            blocked: true,
            cname: true,
            domain: true,
            raw: true,
            embed: true,
          },
        },
      },
    });
    if (result) {
      if (!result.users.blocked) {
        if (result.ip === ip || result.ip === '*') {
          return {
            snowflake: result.owner,
            cname: result.users.cname!,
            ip: result.ip!,
            domain: result.users.domain,
            raw: result.users.raw,
            embed: result!.users.embed!,
          };
        } else {
          throw new BlockedException(true);
        }
      } else {
        this.logger.log('Resulting as false');
        throw new BlockedException();
      }
    } else {
      this.logger.log('Throwing invalid apikey exception');
      throw new ApikeyException(true);
    }
  }

  public async isSnowflakeValid<T = Prisma.Prisma.usersSelect>(
    snowflake: string,
    excessData?: T,
  ): Promise<{
    valid: boolean;
    excessResult: Prisma.Prisma.CheckSelect<T, any, any>;
  }> {
    const data = await this.databaseService.read.users.findFirst({
      where: {
        snowflake,
      },
      select: {
        snowflake: true,
        ...((excessData as Prisma.Prisma.usersSelect) || undefined),
      },
    });
    return {
      valid: data !== null,
      excessResult: excessData
        ? (data as Prisma.Prisma.CheckSelect<T, any, any>) ||
          (this.utilService.setAllPropsTo(
            excessData as unknown as { [key: string]: unknown },
            false,
          ) as Prisma.Prisma.CheckSelect<T, any, any>)
        : undefined,
    };
  }

  public async getUserEmail(snowflake: string): Promise<string> {
    const r = await this.databaseService.read.users.findFirst({
      where: { snowflake },
      select: { email: true },
    });

    return this.utilService.decrypt(r.email);
  }

  public async confirmUserEmail(snowflake: string, code: string) {
    this.logger.debug(`Confirming user email with code ${code}`);
    await this.databaseService.write.users.update({
      where: { snowflake },
      data: {
        email_verified: true,
        codes: { update: { where: { code }, data: { used: true } } },
      },
    });
  }

  public async getUserApiKey(snowflake: string) {
    return await this.databaseService.read.api_keys.findFirst({
      where: { owner: snowflake },
    });
  }
  public async deleteUserApiKey(snowflake: string) {
    return await this.databaseService.write.api_keys.deleteMany({
      where: { owner: snowflake },
    });
  }

  public async createUserApikey(user: string, ip: string) {
    const key = this.utilService.sha(
      await this.utilService.generateSnowflake(50),
    );
    await this.deleteUserApiKey(user);
    await this.databaseService.write.api_keys.create({
      data: {
        ip,
        owner: user,
        api_key: this.utilService.encrypt(key),
        hash: this.utilService.sha(key),
      },
    });
  }
  public async getDevEmails(): Promise<string[]> {
    return (
      await this.databaseService.read.users.findMany({
        where: { admin: true },
        select: { email: true },
      })
    ).map((data) => this.utilService.decrypt(data.email));
  }

  public async getUsername(snowflake: string): Promise<string> {
    return (
      await this.databaseService.read.users.findFirst({
        where: { snowflake },
        select: { username: true },
      })
    ).username;
  }
  public async getCoreUserData(
    snowflake: string,
  ): Promise<FileglassMainDashBoard> {
    const r = await this.databaseService.read.users.findFirst({
      where: { snowflake },
      select: {
        username: true,
        tier: true,
        email: true,
        pfp: true,
        email_verified: true,
        leaderboard_private: true,
        admin: true,
        domain: true,
        cname: true,
        raw: true,
        pfp_type: true,
        twostep: true,
        uploads_uploadsTousers: {
          select: {
            size: true,
            date: true,
            views: true,
            likes: true,
            path: true,
            mime: true,
          },
          orderBy: { date: 'desc' },
        },
      },
    });
    let totalSize = 0;
    let totalViews = 0;
    const dates: string[] = [];
    r.uploads_uploadsTousers.forEach((upload) => {
      totalSize += parseInt(upload.size!);
      totalViews += upload.views!;
      dates.push(upload.date!);
    });
    let newestUpload;
    try {
      newestUpload = `${r.uploads_uploadsTousers[0].path}.${
        r.uploads_uploadsTousers[0].mime!.split('/')[1]
      }`;
    } catch (err) {
      newestUpload = '0i850.png';
    }
    return {
      memUsage: totalSize,
      totalPosts: r.uploads_uploadsTousers.length,
      newestUpload,
      totalViews,
      dates,
      username: r!.username!,
      tier: r!.tier!,
      email: this.utilService.decrypt(r!.email!),
      pfp: r!.pfp!,
      emailVerified: r!.email_verified!,
      leaderboardPrivate: r!.leaderboard_private,
      isAdmin: r!.admin || false,
      domain: r!.domain,
      cname: r!.cname === 'no-cname' ? 'c' : r!.cname!,
      raw: r!.raw,
      pfpDescriptor: r!.pfp_type,
      twostep: r!.twostep,
    };
  }

  public async getLeaderBoard(
    // proudly pasted from the old src
    total = 10,
  ): Promise<{ count: number; tier: number; username: string; pfp: string }[]> {
    const r = await this.databaseService.read.users.findMany({
      select: {
        uploads: true,
        username: true,
        tier: true,
        pfp: true,
      },
      orderBy: {
        uploads: 'desc',
      },
      where: {
        leaderboard_private: false,
      },
      take: total,
    });
    return r.map((user) => {
      return {
        count: user!.uploads!,
        tier: user!.tier!,
        username: user!.username!,
        pfp: user.pfp!,
      };
    });
  }
  public async setFieldData<T extends keyof Prisma.Prisma.usersUpdateInput>(
    user: string,
    field: T,
    state: Prisma.Prisma.usersUpdateInput[T],
  ) {
    await this.databaseService.write.users.update({
      where: {
        snowflake: user,
      },
      data: {
        [field]: state,
      },
    });
  }

  public async getUserPasswordHash(user: string) {
    return (
      await this.databaseService.read.users.findFirst({
        where: {
          snowflake: user,
        },
        select: {
          password: true,
        },
      })
    ).password;
  }

  public async getUserDomain(user: string) {
    const r = await this.databaseService.read.users.findFirst({
      where: { snowflake: user },
      select: { domain: true },
    });
    return r.domain;
  }
  public async getUserCname(user: string) {
    const r = await this.databaseService.read.users.findFirst({
      where: { snowflake: user },
      select: { cname: true },
    });
    this.logger.debug(`User cname: ${r.cname}`);
    return r.cname;
  }

  private async isCnameSavedWithDomain(cname: string, domain: string) {
    const r = await this.databaseService.read.cnames.count({
      where: {
        domain,
        cname,
      },
    });
    return r > 0;
  }
  public async setUserCname(user: string, cname: string, domain?: string) {
    domain = domain || (await this.getUserDomain(user));
    const cnameId = await this.cloudflareService.createCname(cname, domain);
    this.logger.debug('Cname', cname, 'cf response', cnameId);
    if (!(await this.isCnameSavedWithDomain(cname, domain))) {
      try {
        await this.databaseService.write.cnames.create({
          data: {
            cname,
            worker_bind: cnameId.workerId,
            id: cnameId.dnsId,
            creator: user,
          },
        });
      } catch (err) {}
    }

    await this.databaseService.write.users.update({
      where: { snowflake: user },
      data: {
        cname,
        domain,
      },
    });
  }

  public async setUserDomain(user: string, domain: string, cname?: string) {
    await this.setUserCname(
      user,
      cname || (await this.getUserCname(user)),
      domain,
    );
  }
  public async getActiveBlocks() {
    return await this.databaseService.read.users.findMany({
      where: {
        blocked: true,
      },
      select: {
        blocked_until: true,
        snowflake: true,
        ban_id: true,
      },
    });
  }

  /**
   * Sends an email to the user (body has to be HTML)
   * @param user
   * @param emailBody
   * @param subject
   */
  public async sendEmailToUser(
    user: string,
    emailBody: string,
    subject: string,
  ) {
    const email = await this.getUserEmail(user);
    const mailer = new this.emailService.Mailer(email, subject, true);
    mailer.append(emailBody);
    await mailer.deliver();
    this.logger.log(`Email sent to: ${email} with subject: ${subject}`);
  }
  public async getUserBlockData(user: string) {
    return await this.databaseService.read.users.findFirst({
      where: {
        snowflake: user,
      },
      select: {
        blocked: true,
        ban_id: true,
        block_msg: true,
        blocked_until: true,
        username: true,
      },
    });
  }
  public async unblockUser(user: string) {
    const blockData = await this.getUserBlockData(user);
    const body = this.emailService.bodies.unblockMessage(
      blockData.ban_id,
      blockData.username,
    );
    await this.databaseService.write.users.update({
      where: {
        snowflake: user,
      },
      data: {
        blocked: false,
        bans: {
          update: {
            where: {
              ban_id: blockData.ban_id,
            },
            data: {
              ended: true,
            },
          },
        },
      },
    });
    await this.sendEmailToUser(user, body, 'Account Suspension');
    this.logger.log(`User ${blockData.username} unblocked`);
  }

  public async blockUser(
    user: string,
    until: number,
    reason: string,
    perma: boolean,
  ) {
    until = perma ? 95647388400000 : until; //until 5000-12-12
    const banid = await this.utilService.generateSnowflake(15);
    await this.databaseService.write.users.update({
      where: {
        snowflake: user,
      },
      data: {
        blocked: true,
        blocked_until: until.toString(),
        ban_id: banid,
        block_msg: reason,
        bans: {
          create: {
            ban_id: banid,
            ends: until.toString(),
            ended: false,
            reason,
          },
        },
      },
    });
    const username = await this.getUsername(user);
    await this.sendEmailToUser(
      user,
      this.emailService.bodies.blockMessage(
        until,
        perma,
        reason,
        banid,
        username,
      ),
      'Account Suspension',
    );
    this.logger.log(
      `User ${username} blocked ${
        perma ? 'permanently' : `for ${this.utilService.humanize(until)}`
      }`,
    );
  }

  public async changeUserEmail(user: string, email: string) {
    const code = await this.codesService.createCode(
      'EMAIL_CONFIRM_UPDATE',
      20,
      user,
      email,
    );
    const username = await this.getUsername(user);
    const body = this.emailService.bodies.confirmEmail(
      this.utilService.buildConfirmUrl(code.code),
      username,
      false,
    );
    const mailer = new this.emailService.Mailer(
      email,
      'Confirm your email',
      true,
    );
    await mailer.append(body).deliver();
  }

  public async getUserPfp(user: string) {
    const r = await this.databaseService.read.users.findFirst({
      where: {
        snowflake: user,
      },
      select: {
        pfp: true,
        pfp_type: true,
      },
    });
    return { url: r.pfp, type: r.pfp_type };
  }

  private async writePfpNameToDb(
    user: string,
    name: string,
    type: keyof typeof PFP_TYPE,
  ) {
    await this.databaseService.write.users.update({
      where: {
        snowflake: user,
      },
      data: {
        pfp: name,
        pfp_type: type,
      },
    });
  }
  public async setUserPfp<Type extends PFP_TYPE>(
    type: Type,
    args: PfpSetterArgs<Type>,
  ) {
    const curr = await this.getUserPfp(args.user);
    switch (type) {
      case 'CUSTOM':
        await this.bezosService.removeFromCdn(curr.url, 'pfps');
        const isSafe = await this.csamService.isSafe(
          args.file,
          args.name,
          args.mime,
        );
        if (!isSafe) {
          throw new GenericException(GenericRespEnum.ERR_IMAGE_FILTERED, 403);
        }
        const fName = await this.utilService.generateSnowflake();
        const ext = this.utilService.getImageExtension(args.name);
        await this.bezosService.uploadToCdn(
          args.file,
          `${fName}.${ext}`,
          args.mime,
          'pfps',
        );
        await this.writePfpNameToDb(args.user, `${fName}.${ext}`, type);
        return GenericRespEnum.PFP_UPDATED;
      case 'DISCORD':
        await this.writePfpNameToDb(args.user, args.name, type);
        return GenericRespEnum.PFP_UPDATED;
      case 'GITHUB':
        await this.writePfpNameToDb(args.user, args.name, type);
        return GenericRespEnum.PFP_UPDATED;
    }
  }

  public async deleteUserAccount(user: string) {
    const uname = await this.getUsername(user);
    const emailaddr = await this.getUserEmail(user);
    const akey = await this.getUserApiKey(user);
    const emailBod = this.emailService.bodies.accountDelete(uname);
    await new this.emailService.Mailer(
      emailaddr,
      'Account marked for deletion',
      true,
    )
      .append(emailBod)
      .deliver();
    this.uploadService.wipeUserUploads(user).then(async () => {
      await this.databaseService.write.uploads.deleteMany({
        where: {
          owner: user,
        },
      });
    });
    await this.databaseService.write.users.delete({
      where: {
        snowflake: user,
      },
    });
    if (akey?.api_key) {
      await this.databaseService.write.api_keys.delete({
        where: {
          api_key: akey.api_key,
        },
      });
    }
    return GenericRespEnum.ACCOUNT_DELETED;
  }
}

interface PfpSetterArgs<Type extends PFP_TYPE> {
  file: Type extends typeof PFP_TYPE.CUSTOM ? Buffer : undefined;
  mime: Type extends typeof PFP_TYPE.CUSTOM ? string : undefined;
  name: string;
  user: string;
}

results matching ""

    No results matching ""