Skip to content

Commit 2dc3c74

Browse files
committed
feat: added jwt verify middleware
1 parent cce5aa7 commit 2dc3c74

12 files changed

Lines changed: 625 additions & 158 deletions

File tree

backend/src/entities/User.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ export class User {
3232
profileImageUrl?: string;
3333

3434
@Column({ nullable: true })
35-
password?: string;
35+
passwordHash?: string;
36+
37+
@Column({ default: false })
38+
isPhoneVerified!: boolean;
3639

3740
@CreateDateColumn()
3841
createdAt!: Date;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Request, Response, NextFunction } from 'express';
2+
import jwt from 'jsonwebtoken';
3+
4+
interface AuthRequest extends Request {
5+
user?: {
6+
id: string;
7+
};
8+
}
9+
10+
export const requireAuth = (
11+
req: AuthRequest,
12+
res: Response,
13+
next: NextFunction,
14+
) => {
15+
const authHeader = req.headers.authorization;
16+
17+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
18+
return res.status(401).json({ message: 'Unauthorized' });
19+
}
20+
21+
const token = authHeader.split(' ')[1];
22+
23+
try {
24+
const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET!) as {
25+
userId: string;
26+
};
27+
28+
req.user = { id: decoded.userId };
29+
next();
30+
} catch (err) {
31+
return res.status(401).json({ message: 'Invalid or expired token',error:err });
32+
}
33+
};

backend/src/modules/auth/auth.controller.ts

Lines changed: 139 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Request, Response, NextFunction } from 'express';
22
import { logger } from '../../utils/logger';
3-
import { registerSchema, phoneSchema } from './auth.schema';
3+
import { registerSchema, phoneSchema, loginSchema } from './auth.schema';
44
import { sendOtpSms } from '../../Services/sms.service';
55
import { generateotp } from '../../utils/otp';
66
import { appDataSouce } from '../../data-source';
77
import { Otp } from '../../entities/opt';
88
import { User } from '../../entities/User';
99
import { signAccessToken } from '../../Services/jwt.service';
1010
import { createRefreshTokenSession } from '../../Services/authToken';
11+
import bcrypt from 'bcrypt';
1112

1213
export const sendOtp = async (
1314
req: Request,
@@ -16,26 +17,54 @@ export const sendOtp = async (
1617
) => {
1718
try {
1819
logger.info('reached');
20+
1921
const result = phoneSchema.safeParse(req.body);
2022
if (!result.success) {
21-
return res
22-
.status(400)
23-
.json({ message: 'validation vailed', error: result.error?.format() });
23+
return res.status(400).json({
24+
message: 'validation failed',
25+
error: result.error.format(),
26+
});
27+
}
28+
29+
const phoneNumber = result.data.phoneNumber;
30+
31+
const userRepo = appDataSouce.getRepository(User);
32+
const otpRepo = appDataSouce.getRepository(Otp);
33+
34+
const existingUser = await userRepo.findOne({
35+
where: { phoneNumber },
36+
});
37+
38+
if (
39+
existingUser &&
40+
existingUser.isPhoneVerified === true &&
41+
existingUser.passwordHash
42+
) {
43+
return res.status(409).json({
44+
success: false,
45+
next: 'login',
46+
message: 'Account already exists. Please login with password.',
47+
});
2448
}
25-
const otpRep = appDataSouce.getRepository(Otp);
26-
const otpcode = generateotp();
27-
logger.debug({ otpcode }, 'otp is');
28-
const phoneNumber = result.data?.phoneNumber;
29-
await sendOtpSms(phoneNumber, otpcode.toString());
30-
await otpRep.delete({ phoneNumber });
31-
await otpRep.save({
49+
50+
const otpCode = generateotp();
51+
logger.debug({ otpCode }, 'otp is');
52+
53+
await sendOtpSms(phoneNumber, otpCode.toString());
54+
55+
await otpRepo.delete({ phoneNumber });
56+
await otpRepo.save({
3257
phoneNumber,
33-
otp: otpcode.toString(),
58+
otp: otpCode.toString(),
3459
expiresAt: new Date(Date.now() + 5 * 60 * 1000),
3560
});
36-
res.status(200).json({ message: 'otp sent success fully ', success: true });
61+
62+
return res.status(200).json({
63+
success: true,
64+
message: 'OTP sent successfully',
65+
});
3766
} catch (err) {
38-
logger.error('error in register');
67+
logger.error({ err }, 'error in sendOtp');
3968
next(err);
4069
}
4170
};
@@ -54,9 +83,7 @@ export const verifyotp = async (
5483
.status(400)
5584
.json({ message: ' otp and phoneNumber are required' });
5685
}
57-
5886
const otpRepo = appDataSouce.getRepository(Otp);
59-
const userRepo = appDataSouce.getRepository(User);
6087
const otpRecord = await otpRepo.findOne({
6188
where: {
6289
phoneNumber,
@@ -79,26 +106,7 @@ export const verifyotp = async (
79106
return res.status(400).json({ message: 'Invalid OTP' });
80107
}
81108
otpRecord.verified = true;
82-
const existingUser = await userRepo.findOne({
83-
where: { phoneNumber },
84-
});
85109
await otpRepo.save(otpRecord);
86-
console.log('ACCESS_TOKEN_SECRET:', process.env.ACCESS_TOKEN_SECRET);
87-
88-
if (existingUser) {
89-
const accessToken = signAccessToken({
90-
userId: existingUser.id,
91-
});
92-
93-
const refreshToken = await createRefreshTokenSession(existingUser);
94-
95-
return res.status(200).json({
96-
success: true,
97-
userExists: true,
98-
accessToken,
99-
refreshToken,
100-
});
101-
}
102110

103111
return res.status(200).json({
104112
success: true,
@@ -121,26 +129,42 @@ export const register = async (
121129
const result = registerSchema.safeParse(req.body);
122130
if (!result.success) {
123131
return res.status(400).json({
124-
message: 'invalid registation data',
132+
message: 'invalid registration data',
125133
errors: result.error.format(),
126134
});
127135
}
128-
const { otpId, name, age, gender, interests, email } = result.data;
136+
137+
const {
138+
otpId,
139+
name,
140+
age,
141+
gender,
142+
interests,
143+
email,
144+
password,
145+
confirmPassword,
146+
} = result.data;
147+
129148
if (!otpId) {
130-
return res.status(400).json({ message: 'otpid requuired' });
149+
return res.status(400).json({ message: 'otpId required' });
150+
}
151+
152+
if (password !== confirmPassword) {
153+
return res.status(400).json({
154+
message: 'Passwords do not match',
155+
});
131156
}
157+
132158
const otpRepo = appDataSouce.getRepository(Otp);
133159
const userRepo = appDataSouce.getRepository(User);
134160

135161
const otpRecord = await otpRepo.findOne({
136162
where: { id: otpId },
137163
});
138-
if (!otpRecord) {
139-
return res.status(400).json({ message: 'invalid registration ' });
140-
}
141-
if (!otpRecord.verified) {
164+
165+
if (!otpRecord || !otpRecord.verified) {
142166
return res.status(400).json({
143-
message: 'OTP not verified',
167+
message: 'OTP not verified or invalid',
144168
});
145169
}
146170

@@ -150,10 +174,13 @@ export const register = async (
150174
where: { phoneNumber },
151175
});
152176

153-
if (existingUser) {
154-
return res.status(409).json({ message: 'User already exists' });
177+
if (existingUser && existingUser.passwordHash) {
178+
return res.status(409).json({
179+
message: 'User already exists',
180+
});
155181
}
156-
// const hashedPassword = await bcrypt.hash(password,10)
182+
183+
const passwordHash = await bcrypt.hash(password, 10);
157184

158185
const user = userRepo.create({
159186
phoneNumber,
@@ -162,9 +189,12 @@ export const register = async (
162189
gender,
163190
interests,
164191
email,
192+
passwordHash,
193+
isPhoneVerified: true,
165194
});
166195

167196
await userRepo.save(user);
197+
168198
await otpRepo.delete({ id: otpId });
169199

170200
const accessToken = signAccessToken({
@@ -184,3 +214,66 @@ export const register = async (
184214
next(err);
185215
}
186216
};
217+
218+
export const login = async (
219+
req: Request,
220+
res: Response,
221+
next: NextFunction,
222+
) => {
223+
try {
224+
const result = loginSchema.safeParse(req.body);
225+
if (!result.success) {
226+
return res.status(400).json({
227+
message: 'Invalid login data',
228+
errors: result.error.format(),
229+
});
230+
}
231+
232+
const { phoneNumber, password } = result.data;
233+
234+
const userRepo = appDataSouce.getRepository(User);
235+
236+
// 2️⃣ find user
237+
const user = await userRepo.findOne({
238+
where: { phoneNumber },
239+
});
240+
241+
if (!user) {
242+
return res.status(401).json({
243+
message: 'Invalid phone number or password',
244+
});
245+
}
246+
247+
// 3️⃣ block incomplete registration
248+
if (!user.passwordHash || !user.isPhoneVerified) {
249+
return res.status(403).json({
250+
message: 'Account not fully registered',
251+
});
252+
}
253+
254+
// 4️⃣ compare password
255+
const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
256+
257+
if (!isPasswordValid) {
258+
return res.status(401).json({
259+
message: 'Invalid phone number or password',
260+
});
261+
}
262+
263+
// 5️⃣ issue tokens
264+
const accessToken = signAccessToken({
265+
userId: user.id,
266+
});
267+
268+
const refreshToken = await createRefreshTokenSession(user);
269+
270+
return res.status(200).json({
271+
success: true,
272+
accessToken,
273+
refreshToken,
274+
});
275+
} catch (err) {
276+
logger.error({ err }, 'error in login');
277+
next(err);
278+
}
279+
};
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import express from 'express';
2-
import { sendOtp, verifyotp, register } from './auth.controller';
2+
import { sendOtp, verifyotp, register, login } from './auth.controller';
33
const authRouter = express.Router();
44
authRouter.post('/send-otp', sendOtp);
55
authRouter.post('/verify-otp', verifyotp);
66
authRouter.post('/register', register);
7+
authRouter.post('/login', login);
78

89
export default authRouter;

backend/src/modules/auth/auth.schema.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,33 @@ export const phoneSchema = z.object({
33
phoneNumber: z.string().min(10, 'Invalid phone number'),
44
});
55

6-
export const registerSchema = z.object({
7-
otpId: z.string().uuid(),
8-
name: z.string().min(1, 'name is required'),
9-
age: z.number().int().positive().optional(),
10-
gender: z.enum(['male', 'female', 'other']).optional(),
11-
interests: z.array(z.string()).optional(),
12-
email: z.string().email(),
13-
// password: z.string().min(8, 'Password must be at least 8 characters').regex(/\d/, 'Password must contain at least one number'),
6+
export const registerSchema = z
7+
.object({
8+
otpId: z.string().uuid(),
9+
10+
name: z.string().min(1, 'Name is required'),
11+
12+
age: z.number().int().positive().optional(),
13+
14+
gender: z.enum(['male', 'female', 'other']).optional(),
15+
16+
interests: z.array(z.string()).optional(),
17+
18+
email: z.string().email('Invalid email address'),
19+
20+
password: z.string().min(8, 'Password must be at least 8 characters'),
21+
22+
confirmPassword: z
23+
.string()
24+
.min(8, 'Confirm password must be at least 8 characters'),
25+
})
26+
.refine((data) => data.password === data.confirmPassword, {
27+
message: 'Passwords do not match',
28+
path: ['confirmPassword'],
29+
});
30+
31+
export const loginSchema = z.object({
32+
phoneNumber: z.string().min(10, 'Phone number is required'),
33+
34+
password: z.string().min(1, 'Password is required'),
1435
});

frontend/app/(auth)/login.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import LoginScreen from '@/screens/auth/loginScreen';
2+
export default function Login() {
3+
return <LoginScreen />;
4+
}

frontend/lib/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import axios from 'axios';
22
const api = axios.create({
3-
baseURL: 'http://10.222.145.137:4000',
3+
baseURL: 'http://10.10.1.200:4000',
44
timeout: 20000,
55
headers: {
66
'Content-Type': 'application/json',

0 commit comments

Comments
 (0)