Skip to content

Commit

Permalink
feat(#19): view current location as a driver i want to see my current…
Browse files Browse the repository at this point in the history
… location on a map in real time so that i can track my position and navigate the area effectively

- adding JWT auth token support (refresh token, auth token)
  • Loading branch information
chriscoderdr committed Nov 1, 2024
1 parent 0a1a56d commit c181a51
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 17 deletions.
3 changes: 3 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"bcrypt": "^5.1.1",
"bcryptjs": "^2.4.3",
"ioredis": "^5.4.1",
"jsonwebtoken": "^9.0.2",
"koa": "^2.15.3",
"koa-bodyparser": "^4.4.1",
"mqtt": "^5.10.1",
Expand All @@ -27,12 +28,14 @@
"ts-node": "^10.9.2",
"twilio": "^5.3.5",
"typescript": "^5.6.3",
"uuid": "^11.0.2",
"winston": "^3.15.0"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/bcryptjs": "^2.4.6",
"@types/chai": "^5.0.1",
"@types/jsonwebtoken": "^9.0.7",
"@types/koa": "^2.15.0",
"@types/koa-bodyparser": "^4.3.12",
"@types/koa__router": "^12.0.4",
Expand Down
23 changes: 18 additions & 5 deletions apps/api/src/controllers/driver-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Context } from 'koa';
import { Op } from 'sequelize';
import Driver from '../models/driver';
import logger from '../utils/logger';
import { generateAccessToken, generateRefreshToken } from '../utils/token-utils';

export const registerDriver = async (ctx: Context) => {
const { name, email, phone, password } = ctx.request.body as {
Expand All @@ -10,12 +11,10 @@ export const registerDriver = async (ctx: Context) => {
phone: string;
password: string;
};
logger.info('registerDriver', name, email, phone, password);

if (!name || !email || !phone || !password) {
logger.info(`registerDriver: ${name}, ${email}, ${phone}, ${password}`);
ctx.status = 400;
ctx.body = { error: `registerDriver: ${name}, ${email}, ${phone}, ${password}`};
ctx.body = { error: 'All fields are required.' };
return;
}
if (password.length < 8) {
Expand All @@ -34,11 +33,25 @@ export const registerDriver = async (ctx: Context) => {
return;
}

// Create the new driver
const newDriver = await Driver.create({ name, email, phone, password });

// Generate tokens
const accessToken = generateAccessToken(newDriver.dataValues.id);
const refreshToken = generateRefreshToken();

// Store the refresh token in the database
await newDriver.update({ refreshToken });

ctx.status = 201;
ctx.body = { message: 'Driver registered successfully.', driverId: newDriver.dataValues.id };
ctx.body = {
message: 'Driver registered successfully.',
driverId: newDriver.dataValues.id,
accessToken,
refreshToken
};
} catch (error) {
console.error(error);
logger.error(error);
ctx.status = 500;
ctx.body = { error: 'Server error. Please try again later.' };
}
Expand Down
23 changes: 23 additions & 0 deletions apps/api/src/controllers/token-controlller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Context } from 'koa';
import Driver from '../models/driver';
import { generateAccessToken } from '../utils/token-utils';

export const refreshAccessToken = async (ctx: Context) => {
const { refreshToken } = ctx.request.body as any;
if (!refreshToken) {
ctx.status = 400;
ctx.body = { error: 'Refresh token is required.' };
return;
}

const driver = await Driver.findOne({ where: { refreshToken } });
if (!driver) {
ctx.status = 403;
ctx.body = { error: 'Invalid refresh token.' };
return;
}

const accessToken = generateAccessToken(driver.dataValues.id);
ctx.status = 200;
ctx.body = { accessToken };
};
29 changes: 29 additions & 0 deletions apps/api/src/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import jwt from 'jsonwebtoken';
import { Context, Next } from 'koa';
import Driver from '../models/driver';

const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || 'access-secret';

export const authenticateToken = async (ctx: Context, next: Next) => {
const token = ctx.headers['authorization']?.split(' ')[1];
if (!token) {
ctx.status = 401;
ctx.body = { error: 'Access token required.' };
return;
}

try {
const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as { driverId: number };
const driver = await Driver.findByPk(decoded.driverId);
if (!driver) {
ctx.status = 403;
ctx.body = { error: 'Invalid access token.' };
return;
}
ctx.state.driver = driver;
await next();
} catch (error) {
ctx.status = 403;
ctx.body = { error: 'Invalid or expired access token.' };
}
};
16 changes: 9 additions & 7 deletions apps/api/src/models/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import sequelize from '../config/database';
const saltRounds = parseInt(process.env.BCRYPT_SALT_ROUNDS || '10', 10);

interface DriverAttributes {
id: number;
id: string; // Change `id` to a string to store UUIDs
name: string;
email: string;
phone: string;
password: string;
refreshToken?: string; // Field for storing refresh token
createdAt?: Date;
updatedAt?: Date;
loginAt?: Date;
Expand All @@ -30,8 +31,8 @@ class Driver extends Model<DriverAttributes, DriverCreationAttributes> {
Driver.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
type: DataTypes.UUID, // Set data type to UUID
defaultValue: DataTypes.UUIDV4, // Automatically generate a UUID v4
primaryKey: true
},
name: {
Expand All @@ -53,6 +54,10 @@ Driver.init(
type: DataTypes.STRING,
allowNull: false
},
refreshToken: {
type: DataTypes.STRING,
allowNull: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
Expand Down Expand Up @@ -90,10 +95,7 @@ Driver.init(
tableName: 'drivers',
hooks: {
beforeCreate: async (driver) => {
driver.dataValues.password = await bcrypt.hash(
driver.dataValues.password,
saltRounds
);
driver.dataValues.password = await bcrypt.hash(driver.dataValues.password, saltRounds);
}
},
timestamps: true
Expand Down
15 changes: 15 additions & 0 deletions apps/api/src/utils/token-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid';

const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || 'access-secret';
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || 'refresh-secret';
const ACCESS_TOKEN_EXPIRATION = '1d';
const REFRESH_TOKEN_EXPIRATION = '7d';

export const generateAccessToken = (driverId: string) => {
return jwt.sign({ driverId }, ACCESS_TOKEN_SECRET, { expiresIn: ACCESS_TOKEN_EXPIRATION });
};

export const generateRefreshToken = () => {
return uuidv4(); // Generates a unique ID for the refresh token
};
4 changes: 2 additions & 2 deletions apps/driver-app/src/components/input-phone/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const styles = StyleSheet.create<IInputPhoneStyles>({
flex: 1,
},
phoneContainer: {
backgroundColor: "#FFFFFF",
backgroundColor: 'transparent',
width: "100%",
shadowColor: 'transparent'
},
Expand All @@ -28,7 +28,7 @@ export const styles = StyleSheet.create<IInputPhoneStyles>({
borderRadius: 10,
flexDirection: "row",
alignItems: "center",
backgroundColor: "#FFFFFF",
backgroundColor: "transparent",
},
countryPickerButton: {
position: "absolute",
Expand Down
2 changes: 1 addition & 1 deletion apps/driver-app/src/config/mqtt-config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const MQTT_BROKER_URL = '192.168.68.106';
export const MQTT_PORT = 8883;
export const MQTT_TOPIC = 'morro-taxi/location';
export const MQTT_TOPIC = '/drivers/${driver_id}/location';
4 changes: 2 additions & 2 deletions apps/driver-app/src/services/mqtt-client-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ class MQTTClientService {

publishLocation = (latitude: number, longitude: number) => {
if (this.client.isConnected()) {
const payload = JSON.stringify({ latitude, longitude });
const payload = JSON.stringify({ latitude, longitude, isAvailable: true, timestamp: new Date().getTime() });
const message = new Message(payload);
message.destinationName = MQTT_TOPIC;
message.destinationName = MQTT_TOPIC.replaceAll("${driver_id}", '3b5a05ce-c7cf-464b-8665-47bf054caa57');
this.client.send(message);
console.log("Published location to MQTT:", payload);
} else {
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3518,6 +3518,13 @@
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==

"@types/jsonwebtoken@^9.0.7":
version "9.0.7"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2"
integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==
dependencies:
"@types/node" "*"

"@types/keygrip@*":
version "1.0.6"
resolved "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz"
Expand Down Expand Up @@ -12816,6 +12823,11 @@ [email protected]:
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==

uuid@^11.0.2:
version "11.0.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.2.tgz#a8d68ba7347d051e7ea716cc8dcbbab634d66875"
integrity sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==

uuid@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz"
Expand Down

0 comments on commit c181a51

Please sign in to comment.