Skip to content

Commit

Permalink
fixing login with 2fa
Browse files Browse the repository at this point in the history
  • Loading branch information
janet-barbie committed Nov 21, 2024
1 parent 5f5a2d1 commit 736c1cb
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 64 deletions.
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

0 comments on commit 736c1cb

Please sign in to comment.