backend/services/module-accounts/resolvers/auth/account.auth.resolver.ts
2025-05-14 21:45:16 +02:00

272 lines
12 KiB
TypeScript

/* 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<SimpleResult> {
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<SimpleResult> {
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<FirebaseTokenResult> {
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<SimpleResult> {
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<SimpleResult> {
// 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 };
}