247 lines
11 KiB
TypeScript
247 lines
11 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, SMSVerif, SMSVerifInput } 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';
|
|
import FirebaseAuth from '@seed/services/auth/FirebaseAuthService';
|
|
import { SuccessResponse } from '@seed/interfaces/response';
|
|
|
|
// TODO - Handle change emails & change phone number
|
|
|
|
@Resolver(AccountModel)
|
|
export class AccountQueryMFAAuthResolver {
|
|
/*
|
|
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
|
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
|
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
|
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
|
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
|
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
|
*/
|
|
@Query(() => SimpleResult)
|
|
async sendEmailVerificationCode(@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 sendPhoneVerificationCode(@Arg('input') input: SMSVerifInput): Promise<SuccessResponse> {
|
|
try {
|
|
// Check if account exists
|
|
|
|
// Get Phone Number - TODO : google-libphonenumber
|
|
const phoneNumber = '+32484740444';
|
|
// Get Code
|
|
|
|
return await FirebaseAuth.getInstance().sendSMSVerification({ phoneNumber, recaptchaToken: input.recaptchaToken });
|
|
} 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 {
|
|
/*
|
|
███████╗██╗███████╗██╗ ██████╗ ███████╗
|
|
██╔════╝██║██╔════╝██║ ██╔══██╗██╔════╝
|
|
█████╗ ██║█████╗ ██║ ██║ ██║███████╗
|
|
██╔══╝ ██║██╔══╝ ██║ ██║ ██║╚════██║
|
|
██║ ██║███████╗███████╗██████╔╝███████║
|
|
╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚══════╝
|
|
*/
|
|
/*
|
|
██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗
|
|
██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝
|
|
██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝
|
|
██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
|
|
╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║
|
|
╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝
|
|
*/
|
|
/*
|
|
███╗ ███╗██╗ ██╗████████╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗
|
|
████╗ ████║██║ ██║╚══██╔══╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝
|
|
██╔████╔██║██║ ██║ ██║ ███████║ ██║ ██║ ██║██████╔╝███████╗
|
|
██║╚██╔╝██║██║ ██║ ██║ ██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║
|
|
██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████║
|
|
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
|
|
|
*/
|
|
}
|
|
|
|
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 };
|
|
}
|