/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/no-use-before-define */ import { Arg, Ctx, Mutation, Query, Resolver } from 'type-graphql'; import _ from 'lodash'; import { v4 as uuid } from 'uuid'; import AccountModel from '@src/accounts/account.model'; import { CodeConfirmInput, CodeRequestInput } from '@services/module-accounts/account.components'; import Firebase from '@lib/seed/services/auth/FirebaseService'; import { sendEmail } from '@lib/seed/services/email/EmailService'; import { FirebaseTokenResult, SimpleResult } from '@lib/seed/interfaces/components'; import moment from 'moment'; import { generateCodeNumber } from '@seed/helpers/Utils'; import { AccountTypeEnum } from '@src/accounts/account.components'; import { ApolloContext } from '@seed/interfaces/context'; import { newError } from '@seed/helpers/Error'; import { AvailableTranslation } from '@src/__components/components'; import { DateTime } from 'luxon'; @Resolver(AccountModel) export class AccountQueryCodeAuthResolver { /* ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗ ██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝ ██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝ ██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝ ╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║ ╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ */ @Query(() => SimpleResult) async logInMagicLink(@Arg('input') input: CodeRequestInput, @Ctx() ctx: ApolloContext): Promise { try { const magicLink = await Firebase.getInstance().getMagicLink(input.email); await sendEmail('magicLinkLogIn', input.language || AvailableTranslation.en, input.email, { ...input, magicLink }); return { message: 'sent' }; } catch (error) { throw error; } } @Query(() => SimpleResult) async logInEmailCodeRequest(@Arg('input') input: CodeRequestInput): Promise { const { email } = input; try { // Check if exists on firebase already const { accountCheck, model } = await getAccount(email); // Add the codes to the account const code = await getCode(model, accountCheck); await sendEmail('emailCodeLogIn', accountCheck.language, input.email, { firstname: accountCheck.firstName || 'stranger', code }); return { message: 'sent' }; } catch (error) { throw error; } } /* ███╗ ███╗██╗ ██╗████████╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗ ████╗ ████║██║ ██║╚══██╔══╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝ ██╔████╔██║██║ ██║ ██║ ███████║ ██║ ██║ ██║██████╔╝███████╗ ██║╚██╔╝██║██║ ██║ ██║ ██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║ ██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████║ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ */ @Mutation(() => FirebaseTokenResult) async logInEmailCodeConfirm(@Arg('input') input: CodeConfirmInput): Promise { const { code, email } = input; try { // Check if exists on firebase already const { accountCheck, model, firebaseUserData } = await getAccount(email); // Get the codes const codes = accountCheck.authCodes; // verify the code const currentCode = _.find(codes, { code: code }); if (!currentCode) throw newError(2300); // verify the expiration date if (DateTime.utc() >= DateTime.fromJSDate(currentCode.expiresAt).toUTC()) throw newError(2301); // Get the token from firebase const token = await Firebase.getInstance().createTokenId(email); // remove it from the array await model.updateOneCustom( { _id: firebaseUserData.uid }, { $pull: { authCodes: { code: code } }, }, null, ); return token; } catch (error) { throw error; } } } @Resolver(AccountModel) export class AccountMutationCodeAuthResolver { /* ███████╗██╗███████╗██╗ ██████╗ ███████╗ ██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝ █████╗ ██║█████╗ ██║ ██║ ██║███████╗ ██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║ ██║ ██║███████╗███████╗██████╔╝███████║ ╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝ */ /* ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗ ██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝ ██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝ ██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝ ╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║ ╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ */ /* ███╗ ███╗██╗ ██╗████████╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗ ████╗ ████║██║ ██║╚══██╔══╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝ ██╔████╔██║██║ ██║ ██║ ███████║ ██║ ██║ ██║██████╔╝███████╗ ██║╚██╔╝██║██║ ██║ ██║ ██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║ ██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████║ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ */ @Mutation(() => SimpleResult) async signInMagicLink(@Arg('input') input: CodeRequestInput, @Ctx() ctx: ApolloContext): Promise { try { const { account } = await createAccount(input); const magicLink = await Firebase.getInstance().getMagicLink(input.email); await sendEmail('magicLinkSignIn', account.language, account.email, { ...account, magicLink }); return { message: 'sent' }; } catch (error) { throw error; } } @Mutation(() => SimpleResult) async signInEmailCodeRequest(@Arg('input') input: CodeRequestInput): Promise { // Check if exists on firebase already try { const { model, firebaseUserData, account } = await createAccount(input); // Add the codes to the account const code = await getCode(model, firebaseUserData); await sendEmail('emailCodeSignIn', account.language, input.email, { firstname: account.firstName || 'stranger', code }); return { message: 'sent' }; } catch (error) { throw error; } } } async function getCode(model: AccountModel, data: AccountModel) { // Get the codes let codes = data.authCodes; if (codes) { // Remove the expired ones _.remove(codes, (e) => { const diffInMs = DateTime.fromJSDate(e.expiresAt).diffNow('milliseconds').milliseconds; const MAX_TIMEOUT = 15 * 60 * 1000; if (diffInMs >= MAX_TIMEOUT) return true; return false; }); } else codes = []; // Check if too many attempt in the last 15 minutes if (codes.length > 5) throw newError(2302); // Add the new one const code = generateCodeNumber(); codes.push({ code: code, expiresAt: DateTime.utc() .plus({ minutes: 15 }) .toJSDate(), }); // Save in DB await model.updateOneCustom( { _id: data._id }, { $set: { authCodes: codes }, }, null, ); return code; } async function getAccount(email: string) { let firebaseUserData; try { firebaseUserData = await Firebase.getInstance().getUserByEmail(email); } catch (error) { throw newError(2004, error); } // Check if in DB or not const model = new AccountModel(); const accountCheck = await (await model.db()).findOne({ _id: firebaseUserData.uid }); // Verify if has thinkific signin if (!accountCheck) throw newError(2004); return { accountCheck, model, firebaseUserData }; } async function createAccount(input: CodeRequestInput) { let firebaseUserData; try { firebaseUserData = await Firebase.getInstance().getUserByEmail(input.email); } catch (error) { try { firebaseUserData = await Firebase.getInstance().createUser({ email: input.email, password: uuid(), }); } catch (error) { throw error; } } const dataToSave = { ...input, firebaseData: { aud: firebaseUserData.aud, emailVerified: firebaseUserData.email_verified, firebase: JSON.stringify(firebaseUserData.firebase), }, updatedAt: new Date(), }; // Check if in DB or not const model = new AccountModel(); const type = AccountTypeEnum.user; let account; const accountCheck = await (await model.db()).findOne({ _id: firebaseUserData.uid }); // Verify if has thinkific signin if (accountCheck) { account = accountCheck; await (await model.db()).updateOne( { _id: firebaseUserData.uid }, { $set: { ...input }, }, ); } else { const types = [type]; account = await model.saveOne( { ...dataToSave, _id: firebaseUserData.uid, types, r: [AccountTypeEnum.admin, firebaseUserData.uid], w: [AccountTypeEnum.admin, firebaseUserData.uid], d: [AccountTypeEnum.admin, firebaseUserData.uid], }, null, ); } return { model, firebaseUserData, account }; }