Skip to content

Commit 6274095

Browse files
committed
fixing login with 2fa
1 parent 6fd2a27 commit 6274095

File tree

5 files changed

+92
-64
lines changed

5 files changed

+92
-64
lines changed

src/models/user.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ const userSchema = new Schema(
103103
type: String,
104104

105105
},
106-
oneTimeCode: {
106+
TwoWayVerificationToken: {
107107
type: String,
108-
code: String,
109-
required: false
108+
required: false,
109+
default:null
110110
},
111111
oneTimeCodeExpiresAt: {
112112
type: Date,

src/resolvers/2fa.resolvers.ts

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { AuthenticationError } from 'apollo-server-errors'
22
import mongoose from 'mongoose'
33

4-
import jwt from 'jsonwebtoken';
5-
import { generateTokenUserExists } from '../helpers/user.helpers';
6-
import { sendEmail } from '../utils/sendEmail';
7-
import { verifyOtpToken } from '../utils/2WayAuthentication';
8-
import { GraphQLError } from 'graphql';
9-
import { User } from '../models/user';
10-
import { logGeoActivity, loginsCount } from './userResolver';
4+
import jwt from 'jsonwebtoken'
5+
import { generateTokenUserExists } from '../helpers/user.helpers'
6+
import { sendEmail } from '../utils/sendEmail'
7+
import { verifyOtpToken } from '../utils/2WayAuthentication'
8+
import { GraphQLError } from 'graphql'
9+
import { User } from '../models/user'
10+
import { logGeoActivity, loginsCount } from './userResolver'
1111

1212
interface Enable2FAInput {
1313
email: string
@@ -17,7 +17,7 @@ interface Disable2FAInput {
1717
email: string
1818
}
1919

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

7070
await user.save()
71+
await sendEmail(
72+
email,
73+
' Two-Factor Authentication disabled ',
74+
'Two-Factor Authentication has been disabled on your account',
75+
null,
76+
process.env.ADMIN_EMAIL,
77+
process.env.ADMIN_PASS
78+
)
7179

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

7886
loginWithTwoFactorAuthentication: async (
7987
_: any,
80-
{ id, email, otp, TwoWayVerificationToken }: { id?: string; email?: string; otp: string; TwoWayVerificationToken: string }, context: any
88+
{
89+
id,
90+
email,
91+
otp,
92+
93+
}: {
94+
id?: string
95+
email?: string
96+
otp: string
97+
98+
},
99+
context: any
81100
) => {
82-
const { clientIpAdress } = context;
83-
// Verify OTP
84-
const isValidOtp = verifyOtpToken(TwoWayVerificationToken, otp);
85-
86-
if (!isValidOtp) {
87-
throw new GraphQLError('Invalid OTP. Please try again.');
88-
}
101+
const { clientIpAdress } = context
89102

90103
// Fetch user by either ID or email
91-
let user: any;
104+
let user: any
92105
if (id) {
93-
user = await User.findById(id);
106+
user = await User.findById(id)
94107
} else if (email) {
95-
user = await User.findOne({ email });
108+
user = await User.findOne({ email })
96109
}
97110

98111
// Check if user was found
99112
if (!user) {
100-
throw new GraphQLError('User not found.');
113+
throw new GraphQLError('User not found.')
114+
}
115+
// Verify OTP
116+
const isValidOtp = verifyOtpToken(user.TwoWayVerificationToken, otp)
117+
118+
if (!isValidOtp) {
119+
throw new GraphQLError('Invalid OTP. Please try again.')
101120
}
102121

103122
// Generate JWT token
104123
const token = jwt.sign(
105124
{ userId: user._id, role: user._doc?.role || 'user' },
106125
SECRET,
107126
{ expiresIn: '2h' }
108-
);
127+
)
109128

110129
const geoData = await logGeoActivity(user, clientIpAdress)
111-
const organizationName = user.organizations[0];
130+
const organizationName = user.organizations[0]
112131
if (organizationName) {
113-
const location = geoData.city && geoData.country_name ? `${geoData.city}-${geoData.country_name}` : null;
114-
await loginsCount(organizationName, location);
132+
const location =
133+
geoData.city && geoData.country_name
134+
? `${geoData.city}-${geoData.country_name}`
135+
: null
136+
await loginsCount(organizationName, location)
115137
}
138+
user.TwoWayVerificationToken = null
139+
await user.save()
116140

117141
return {
118142
token,
119143
user: user.toJSON(),
120144

121145
message: 'Logged in successfully',
122-
};
146+
}
123147
},
124-
125148
},
126149
}
127150

src/resolvers/userResolver.ts

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -372,14 +372,14 @@ const resolvers: any = {
372372
context: any
373373
) {
374374
// Check organization validity
375-
const org = await checkLoggedInOrganization(orgToken)
376-
const { clientIpAdress } = context
375+
const org = await checkLoggedInOrganization(orgToken);
376+
const { clientIpAdress } = context;
377377
if (!org) {
378378
throw new GraphQLError('Organization not found', {
379379
extensions: { code: 'InvalidOrganization' },
380-
})
380+
});
381381
}
382-
382+
383383
// Find user with populated fields
384384
const user: any = await User.findOne({ email }).populate({
385385
path: 'cohort',
@@ -395,30 +395,30 @@ const resolvers: any = {
395395
strictPopulate: false,
396396
},
397397
},
398-
})
399-
398+
});
399+
400400
// Check if user exists
401401
if (!user) {
402402
throw new GraphQLError('Invalid credentials', {
403403
extensions: { code: 'AccountNotFound' },
404-
})
404+
});
405405
}
406-
406+
407407
// Check if account is active
408408
if (user.status?.status !== 'active') {
409409
throw new GraphQLError(
410410
`Account is ${user.status?.status}. Contact admin.`,
411411
{
412412
extensions: { code: 'AccountInactive' },
413413
}
414-
)
414+
);
415415
}
416-
416+
417417
// Check if two-factor authentication is enabled
418418
if (user.twoFactorAuth) {
419-
const otp = generateOtp() // Generate OTP
420-
const TwoWayVerificationToken = encodeOtpToToken(otp, email) // Encode OTP
421-
419+
const otp = generateOtp(); // Generate OTP
420+
const TwoWayVerificationToken = encodeOtpToToken(otp, email); // Encode OTP
421+
422422
// Send email with OTP
423423
await sendEmail(
424424
email,
@@ -427,51 +427,55 @@ const resolvers: any = {
427427
null,
428428
process.env.ADMIN_EMAIL,
429429
process.env.ADMIN_PASS
430-
)
431-
432-
// Return response with encoded OTP token and message
430+
);
431+
432+
// Save the Two-Way Verification Token to the database
433+
user.TwoWayVerificationToken = TwoWayVerificationToken;
434+
await user.save();
435+
436+
// Return a response without exposing the token
433437
return {
434438
message: 'Check your email for the OTP code.',
435439
otpRequired: true,
436-
TwoWayVerificationToken,
437440
user: { id: user._id },
438-
}
441+
};
439442
} else {
440443
// Verify password if 2FA is not enabled
441-
const passwordMatch = await user?.checkPass(password)
444+
const passwordMatch = await user?.checkPass(password);
442445
if (!passwordMatch) {
443446
throw new GraphQLError('Invalid credentials', {
444447
extensions: { code: 'InvalidCredential' },
445-
})
448+
});
446449
}
447-
450+
448451
// Generate token for authenticated user
449452
const token = jwt.sign(
450453
{ userId: user._id, role: user._doc?.role || 'user' },
451454
SECRET,
452455
{ expiresIn: '2h' }
453-
)
454-
455-
const geoData = await logGeoActivity(user, clientIpAdress) // Log activity
456-
457-
const organizationName = user.organizations[0]
456+
);
457+
458+
const geoData = await logGeoActivity(user, clientIpAdress); // Log activity
459+
460+
const organizationName = user.organizations[0];
458461
if (organizationName) {
459462
const location =
460463
geoData.city && geoData.country_name
461464
? `${geoData.city}-${geoData.country_name}`
462-
: null
463-
await loginsCount(organizationName, location)
465+
: null;
466+
await loginsCount(organizationName, location);
464467
}
465-
468+
466469
// Return token and user data
467470
return {
468471
token,
469472
user: user.toJSON(),
470473
geoData,
471474
otpRequired: false,
472-
}
475+
};
473476
}
474477
},
478+
475479
async deleteUser(_: any, { input }: any, context: { userId: any }) {
476480
const requester = await User.findById(context.userId)
477481
if (!requester) {

src/schema/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const Schema = gql`
8181
status: StatusType
8282
ratings: [Rating]
8383
twoFactorAuth:Boolean!
84+
TwoWayVerificationToken:String
8485
}
8586
input RegisterInput {
8687
email: String!
@@ -157,7 +158,7 @@ const Schema = gql`
157158
user: User
158159
message:String
159160
otpRequired:Boolean
160-
TwoWayVerificationToken:String
161+
161162
}
162163
type OrgLogin {
163164
token: String
@@ -312,9 +313,9 @@ const Schema = gql`
312313
313314
type Mutation {
314315
enableTwoFactorAuth(email: String!): String
315-
oneTimeCode: String!
316+
# //TwoWayVerificationToken: String!
316317
disableTwoFactorAuth(email: String!): String
317-
loginWithTwoFactorAuthentication(email: String!, otp: String!, TwoWayVerificationToken: String!): LoginResponse!
318+
loginWithTwoFactorAuthentication(email: String!, otp: String!): LoginResponse!
318319
createUserRole(name: String!): UserRole!
319320
uploadResume(userId: ID!, resume: String!): Profile
320321
dropTTLUser(email: String!, reason: String!): String!

src/utils/2WayAuthentication.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function generateOtp(length = 6): string {
88
}
99
return otp
1010
}
11-
const SECRET: string = process.env.SECRET ?? 'test_secret'
11+
const SECRET = (process.env.SECRET as string) || 'mysq_unique_secret'
1212

1313
export function encodeOtpToToken(otp: string, email: string): string {
1414
const payload = { otp, email }

0 commit comments

Comments
 (0)