Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import invitationSchema from './schema/invitation.schema'
import TableViewInvitationResolver from './resolvers/TableViewInvitationResolver'
import eventSchema from './schema/event.schema'
import './utils/cron-jobs/team-jobs'
import faMutation from './resolvers/2fa.resolvers';

const PORT: number = parseInt(process.env.PORT!) || 4000

Expand Down Expand Up @@ -93,6 +94,7 @@ export const resolvers = mergeResolvers([

invitationResolvers,
TableViewInvitationResolver,
faMutation,
])

async function startApolloServer(
Expand Down
18 changes: 18 additions & 0 deletions src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,24 @@ const userSchema = new Schema(
type: Boolean,
default: true,
},
twoFactorAuth: {
type: Boolean,
default: false,
},
twoFactorSecret: {
type: String,

},
oneTimeCode: {
type: String,
code: String,
required: false
},
oneTimeCodeExpiresAt: {
type: Date,
required: false
},

},

{
Expand Down
125 changes: 125 additions & 0 deletions src/resolvers/2fa.resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { AuthenticationError } from 'apollo-server-errors'
import mongoose from 'mongoose'

import jwt from 'jsonwebtoken';
import { generateTokenUserExists } from '../helpers/user.helpers';
import { sendEmail } from '../utils/sendEmail';
import { verifyOtpToken } from '../utils/2WayAuthentication';
import { GraphQLError } from 'graphql';
import { User } from '../models/user';

interface Enable2FAInput {
email: string
}

interface Disable2FAInput {
email: string
}

const SECRET: string = process.env.SECRET ?? 'test_secret'
const resolvers = {
Mutation: {
enableTwoFactorAuth: async (_: any, { email }: Enable2FAInput) => {
try {
const UserModel = mongoose.model('User')
const user = await UserModel.findOne({ email })

if (!user) {
throw new Error('User not found')
}

if (user.twoFactorAuth) {
// 2FA is already enabled for this user
return 'Two-factor authentication is already enabled for this user.'
}

user.twoFactorAuth = true

await user.save()


await sendEmail(
email,
' Two-Factor Authentication enabled ',
'Two-Factor Authentication has been enabled on your account next time . You will be asked to verify your account with an OTP code',
null,
process.env.ADMIN_EMAIL,
process.env.ADMIN_PASS
)
return 'Two-factor authentication enabled.'
} catch (error) {
console.error('Enable 2FA Error:', error)
// Add this for more detailed error logging
throw new Error('Failed to enable two-factor authentication')
}
},
disableTwoFactorAuth: async (_: any, { email }: Disable2FAInput) => {
try {
const UserModel = mongoose.model('User')
const user = await UserModel.findOne({ email })

if (!user) {
throw new Error('User not found')
}

// Disable 2FA by clearing the secret and one-time code
user.twoFactorSecret = null
user.twoFactorAuth = false
user.oneTimeCode = null

await user.save()

return 'Two-factor authentication disabled.'
} catch (error) {
throw new Error('Failed to disable two-factor authentication')
}
},


loginWithTwoFactorAuthentication: async (
_: any,
{ id, email, otp, TwoWayVerificationToken }: { id?: string; email?: string; otp: string; TwoWayVerificationToken: string }
) => {

// Verify OTP
const isValidOtp = verifyOtpToken(TwoWayVerificationToken, otp);

if (!isValidOtp) {
throw new GraphQLError('Invalid OTP. Please try again.');
}

// Fetch user by either ID or email
let user: any;
if (id) {
user = await User.findById(id);
} else if (email) {
user = await User.findOne({ email });
}

// Check if user was found
if (!user) {
throw new GraphQLError('User not found.');
}

// Generate JWT token
const token = jwt.sign(
{ userId: user._id, role: user._doc?.role || 'user' },
SECRET,
{ expiresIn: '2h' }
);



return {
token,
user: user.toJSON(),

message: 'Logged in successfully',
};
},

},
};


export default resolvers
Loading
Loading