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
6 changes: 3 additions & 3 deletions src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ const userSchema = new Schema(
type: String,

},
oneTimeCode: {
TwoWayVerificationToken: {
type: String,
code: String,
required: false
required: false,
default:null
},
oneTimeCodeExpiresAt: {
type: Date,
Expand Down
77 changes: 50 additions & 27 deletions src/resolvers/2fa.resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
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';
import { logGeoActivity, loginsCount } from './userResolver';
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'
import { logGeoActivity, loginsCount } from './userResolver'

interface Enable2FAInput {
email: string
Expand All @@ -17,7 +17,7 @@ interface Disable2FAInput {
email: string
}

const SECRET: string = process.env.SECRET ?? 'test_secret'
const SECRET = (process.env.SECRET as string) || 'mysq_unique_secret'
const resolvers = {
Mutation: {
enableTwoFactorAuth: async (_: any, { email }: Enable2FAInput) => {
Expand Down Expand Up @@ -65,9 +65,17 @@ const resolvers = {
// Disable 2FA by clearing the secret and one-time code
user.twoFactorSecret = null
user.twoFactorAuth = false
user.oneTimeCode = null
user.TwoWayVerificationToken = null

await user.save()
await sendEmail(
email,
' Two-Factor Authentication disabled ',
'Two-Factor Authentication has been disabled on your account',
null,
process.env.ADMIN_EMAIL,
process.env.ADMIN_PASS
)

return 'Two-factor authentication disabled.'
} catch (error) {
Expand All @@ -77,51 +85,66 @@ const resolvers = {

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

}: {
id?: string
email?: string
otp: string

},
context: any
) => {
const { clientIpAdress } = context;
// Verify OTP
const isValidOtp = verifyOtpToken(TwoWayVerificationToken, otp);

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

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

// Check if user was found
if (!user) {
throw new GraphQLError('User not found.');
throw new GraphQLError('User not found.')
}
// Verify OTP
const isValidOtp = verifyOtpToken(user.TwoWayVerificationToken, otp)

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

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

const geoData = await logGeoActivity(user, clientIpAdress)
const organizationName = user.organizations[0];
const organizationName = user.organizations[0]
if (organizationName) {
const location = geoData && geoData.city && geoData.country_name ? `${geoData.city}-${geoData.country_name}` : null;
await loginsCount(organizationName, location);
const location =
geoData.city && geoData.country_name
? `${geoData.city}-${geoData.country_name}`
: null
await loginsCount(organizationName, location)
}
user.TwoWayVerificationToken = null
await user.save()

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

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

},
}

Expand Down
64 changes: 34 additions & 30 deletions src/resolvers/userResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,14 +377,14 @@ const resolvers: any = {
context: any
) {
// Check organization validity
const org = await checkLoggedInOrganization(orgToken)
const { clientIpAdress } = context
const org = await checkLoggedInOrganization(orgToken);
const { clientIpAdress } = context;
if (!org) {
throw new GraphQLError('Organization not found', {
extensions: { code: 'InvalidOrganization' },
})
});
}

// Find user with populated fields
const user: any = await User.findOne({ email }).populate({
path: 'cohort',
Expand All @@ -400,29 +400,30 @@ const resolvers: any = {
strictPopulate: false,
},
},
})

});
// Check if user exists
if (!user) {
throw new GraphQLError('Invalid credentials', {
extensions: { code: 'AccountNotFound' },
})
});
}

// Check if account is active
if (user.status?.status !== 'active') {
throw new GraphQLError(
`Account is ${user.status?.status}. Contact admin.`,
{
extensions: { code: 'AccountInactive' },
}
)
);
}

// Check if two-factor authentication is enabled
if (user.twoFactorAuth) {
const otp = generateOtp() // Generate OTP
const TwoWayVerificationToken = encodeOtpToToken(otp, email) // Encode OTP

const otp = generateOtp(); // Generate OTP
const TwoWayVerificationToken = encodeOtpToToken(otp, email); // Encode OTP
// Send email with OTP
await sendEmail(
email,
Expand All @@ -431,52 +432,55 @@ const resolvers: any = {
null,
process.env.ADMIN_EMAIL,
process.env.ADMIN_PASS
)

// Return response with encoded OTP token and message
);

// Save the Two-Way Verification Token to the database
user.TwoWayVerificationToken = TwoWayVerificationToken;
await user.save();

// Return a response without exposing the token
return {
message: 'Check your email for the OTP code.',
otpRequired: true,
TwoWayVerificationToken,
user: { id: user._id },
}
};
} else {
// Verify password if 2FA is not enabled
const passwordMatch = await user?.checkPass(password)
const passwordMatch = await user?.checkPass(password);
if (!passwordMatch) {
throw new GraphQLError('Invalid credentials', {
extensions: { code: 'InvalidCredential' },
})
});
}

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


const geoData = await logGeoActivity(user, clientIpAdress) // Log activity

const organizationName = user.organizations[0]
);

const geoData = await logGeoActivity(user, clientIpAdress); // Log activity

const organizationName = user.organizations[0];
if (organizationName) {
const location =
geoData && geoData.city && geoData.country_name
? `${geoData.city}-${geoData.country_name}`
: null
await loginsCount(organizationName, location)
: null;
await loginsCount(organizationName, location);
}

// Return token and user data
return {
token,
user: user.toJSON(),
geoData,
otpRequired: false,
}
};
}
},

async deleteUser(_: any, { input }: any, context: { userId: any }) {
const requester = await User.findById(context.userId)
if (!requester) {
Expand Down
7 changes: 4 additions & 3 deletions src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const Schema = gql`
status: StatusType
ratings: [Rating]
twoFactorAuth:Boolean!
TwoWayVerificationToken:String
}
input RegisterInput {
email: String!
Expand Down Expand Up @@ -157,7 +158,7 @@ const Schema = gql`
user: User
message:String
otpRequired:Boolean
TwoWayVerificationToken:String

}
type OrgLogin {
token: String
Expand Down Expand Up @@ -314,9 +315,9 @@ const Schema = gql`

type Mutation {
enableTwoFactorAuth(email: String!): String
oneTimeCode: String!
# //TwoWayVerificationToken: String!
disableTwoFactorAuth(email: String!): String
loginWithTwoFactorAuthentication(email: String!, otp: String!, TwoWayVerificationToken: String!): LoginResponse!
loginWithTwoFactorAuthentication(email: String!, otp: String!): LoginResponse!
createUserRole(name: String!): UserRole!
uploadResume(userId: ID!, resume: String!): Profile
dropTTLUser(email: String!, reason: String!): String!
Expand Down
2 changes: 1 addition & 1 deletion src/utils/2WayAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function generateOtp(length = 6): string {
}
return otp
}
const SECRET: string = process.env.SECRET ?? 'test_secret'
const SECRET = (process.env.SECRET as string) || 'mysq_unique_secret'

export function encodeOtpToToken(otp: string, email: string): string {
const payload = { otp, email }
Expand Down
Loading