Skip to content

Commit 2c569ea

Browse files
committed
Migrate to jwt token management
1 parent af865be commit 2c569ea

9 files changed

Lines changed: 265 additions & 50 deletions

File tree

‎frontend/admin-one-vue-tailwind-master/src/router/index.js‎

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,16 +169,50 @@ const router = createRouter({
169169
}
170170
})
171171

172+
// Helper function to validate JWT token format
173+
function isValidJWT(token) {
174+
if (!token) return false;
175+
176+
// JWT has 3 parts separated by dots
177+
const parts = token.split('.');
178+
if (parts.length !== 3) return false;
179+
180+
// Each part should be base64url encoded
181+
try {
182+
// Decode header to check if it's a valid JWT
183+
const header = JSON.parse(atob(parts[0].replace(/-/g, '+').replace(/_/g, '/')));
184+
return header.typ === 'JWT' && (header.alg === 'HS256' || header.alg === 'RS256');
185+
} catch (e) {
186+
return false;
187+
}
188+
}
189+
190+
// Helper function to check if JWT is expired
191+
function isJWTExpired(token) {
192+
try {
193+
const parts = token.split('.');
194+
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
195+
196+
// Check if token has expiration
197+
if (payload.exp) {
198+
const currentTime = Math.floor(Date.now() / 1000);
199+
return payload.exp < currentTime;
200+
}
201+
202+
// If no expiration, consider it valid
203+
return false;
204+
} catch (e) {
205+
return true; // If we can't parse, consider expired
206+
}
207+
}
208+
172209
router.beforeEach((to, from, next) => {
173210
// Check if the route requires authentication
174211
if (to.matched.some(record => record.meta.requiresAuth)) {
175212
const token = localStorage.getItem('token');
176213

177-
// Regex pattern to match the token
178-
const regex = /^[A-Z0-9]{6}$/; // Simplified regex without quotes
179-
180-
// If the token is not valid, redirect to login
181-
if (!token || !regex.test(token)) {
214+
// Check if token is a valid JWT and not expired
215+
if (!token || !isValidJWT(token) || isJWTExpired(token)) {
182216
next({
183217
path: '/login',
184218
query: { redirect: to.fullPath } // Save the intended route for after login

‎frontend/admin-one-vue-tailwind-master/src/stores/main.js‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ import { defineStore } from 'pinia'
22
import { ref, computed } from 'vue'
33
import axios from 'axios'
44

5+
// Helper function to decode JWT token
6+
function decodeJWT(token) {
7+
try {
8+
const parts = token.split('.');
9+
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
10+
return payload;
11+
} catch (e) {
12+
return null;
13+
}
14+
}
15+
516
export const useMainStore = defineStore('main', () => {
617
const userName = ref(localStorage.getItem('username'))
718
const userEmail = ref('doe.doe.doe@example.com')
@@ -34,6 +45,28 @@ export const useMainStore = defineStore('main', () => {
3445
}
3546
}
3647

48+
// Get user info from JWT token
49+
function getUserFromJWT() {
50+
const token = localStorage.getItem('token');
51+
if (!token) return null;
52+
53+
const payload = decodeJWT(token);
54+
if (!payload) return null;
55+
56+
return {
57+
userId: payload.user_id,
58+
compId: payload.comp_id,
59+
isAdmin: payload.is_admin,
60+
username: localStorage.getItem('username')
61+
};
62+
}
63+
64+
// Check if current user is admin
65+
function isCurrentUserAdmin() {
66+
const userInfo = getUserFromJWT();
67+
return userInfo ? userInfo.isAdmin : false;
68+
}
69+
3770
function fetchSampleClients() {
3871
axios
3972
.get(`data-sources/clients.json?v=3`)
@@ -96,6 +129,7 @@ export const useMainStore = defineStore('main', () => {
96129
const url = "http://localhost:5000/clients"
97130
const clientsPayload = {
98131
user_id: Number(localStorage.getItem('userId')),
132+
token: localStorage.getItem('token')
99133
};
100134
axios
101135
.post(url, clientsPayload)
@@ -231,6 +265,8 @@ export const useMainStore = defineStore('main', () => {
231265
getCompanyCashFlow,
232266
deleteEmployee,
233267
editCommission,
234-
calculateSalesRevenue
268+
calculateSalesRevenue,
269+
getUserFromJWT,
270+
isCurrentUserAdmin
235271
}
236272
})

‎server/api/auth/jwt_utils.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from flask import current_app
2+
import jwt
3+
import traceback
4+
5+
6+
def issue_token(user_id: int, comp_id: int, is_admin: bool) -> str:
7+
""" Create a new token with user information """
8+
payload = {
9+
'user_id': user_id,
10+
'comp_id': comp_id,
11+
'is_admin': is_admin,
12+
}
13+
private_key = current_app.config.get('JWT_PRIVATE_PEM')
14+
if private_key:
15+
try:
16+
token = jwt.encode(payload, key=private_key, algorithm='RS256')
17+
str_token = token.decode('utf-8') if isinstance(token, (bytes, bytearray)) else token
18+
return str_token
19+
except Exception as e:
20+
raise Exception('Issue RS256 token failed; falling back to HS256', e) from e
21+
22+
23+
def validate_token(token: str):
24+
""" Validate JWT token """
25+
print("token", token)
26+
if not token:
27+
return False, None
28+
29+
try:
30+
jwt.get_unverified_header(token)
31+
except Exception as e:
32+
print("Error decoding header", e, traceback.format_exc())
33+
return False, None
34+
35+
try:
36+
public_key_pem = current_app.config.get('JWT_PUBLIC_PEM', '')
37+
if public_key_pem:
38+
# Use PEM string directly - PyJWT 1.5.0 should handle this
39+
payload = jwt.decode(
40+
token,
41+
key=public_key_pem,
42+
)
43+
return True, payload
44+
except Exception as e:
45+
print("Error decoding RS256 with public key", e)
46+
pass
47+
return False, None

‎server/api/auth/routes.py‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from flask import Flask, Blueprint, request, jsonify, current_app
1+
from flask import Blueprint, request, jsonify, current_app
22
from db.db_connector import DBConnector
33
from Crypto.Cipher import DES
44
from Crypto.Util.Padding import pad, unpad
55
import base64
6+
from .jwt_utils import issue_token, validate_token
67

78
auth = Blueprint('auth', __name__)
89

@@ -44,23 +45,24 @@ def login():
4445
decrypted_password = str(decrypt_password(encrypted_password, DES_KEY))
4546
print(f'Password comparsion!! input: {password} vs decrypted_password: {decrypted_password}')
4647
if password == decrypted_password:
47-
result = dbc.execute_query(query='update_user_activity', args={
48+
dbc.execute_query(query='update_user_activity', args={
4849
'user_id': _id,
4950
'active': True
5051
})
5152
is_admin = dbc.execute_query(query='get_user_admin', args=_id)
5253
print(f'Admin --> {is_admin}')
5354
if is_admin == 1:
5455
is_admin = 'true'
55-
token = current_app.config['ADMIN_AUTH_TOKEN']
5656
else:
5757
is_admin = 'false'
58-
token = current_app.config['AUTH_TOKEN']
5958

6059
comp_id = dbc.execute_query(query='get_user_comp_id', args=_id)
6160
if not isinstance(comp_id, int):
6261
return jsonify({'status': 'Bad request'}), 400
6362

63+
token: str = issue_token(user_id=_id, comp_id=comp_id, is_admin=(is_admin == 'true'))
64+
print(token)
65+
6466
return jsonify({'status': 'Ok', 'user_id': _id, 'token': token, 'is_admin': is_admin, 'comp_id': comp_id}), 200
6567

6668
return jsonify({'status': 'Bad credentials'}), 403
@@ -87,8 +89,8 @@ def reset_password():
8789
user_id = dict_data['user_id']
8890
new_password = dict_data['new_password']
8991
token = dict_data['token']
90-
91-
if token != current_app.config["AUTH_TOKEN"] and token != current_app.config["ADMIN_AUTH_TOKEN"]:
92+
is_valid, _payload = validate_token(token)
93+
if not is_valid:
9294
return jsonify({'status': 'Unauthorised'}), 403
9395

9496
encrypted_password = encrypt_password(new_password, DES_KEY)
@@ -133,14 +135,15 @@ def signup():
133135
'user_id': user_id,
134136
'comp_id': comp_id
135137
})
138+
token: str = issue_token(user_id=user_id, comp_id=comp_id, is_admin=True)
136139
if isinstance(result, int):
137140
return jsonify(
138141
{
139142
'status': 'Ok',
140143
'comp_id': comp_id,
141144
'user_id': user_id,
142145
'is_admin': True,
143-
'token': current_app.config["ADMIN_AUTH_TOKEN"]
146+
'token': token
144147
}
145148
), 200
146149
else:
@@ -151,7 +154,8 @@ def new_employee():
151154
''' Create new employee function '''
152155
dbc = DBConnector()
153156
dict_data = request.get_json()
154-
if dict_data['token'] != current_app.config['ADMIN_AUTH_TOKEN']:
157+
is_valid, payload = validate_token(dict_data.get('token'))
158+
if not is_valid or not payload.get('is_admin'):
155159
return jsonify({'status': 'Unauthorized'}), 403
156160
result = dbc.execute_query('create_user_employee', args={
157161
'username': dict_data['username'],
@@ -168,7 +172,8 @@ def retire():
168172
''' Retire function, delete company and all employees '''
169173
dbc = DBConnector()
170174
dict_data = request.get_json()
171-
if dict_data['token'] != current_app.config['ADMIN_AUTH_TOKEN']:
175+
is_valid, payload = validate_token(dict_data.get('token'))
176+
if not is_valid or not payload.get('is_admin'):
172177
return jsonify({'status': 'Unauthorized'}), 403
173178
result = dbc.execute_query(query='delete_sales_by_comp_id', args=dict_data['comp_id'])
174179
if result is False:
@@ -195,7 +200,8 @@ def delete_employee():
195200
''' Delete employee function '''
196201
dbc = DBConnector()
197202
dict_data = request.get_json()
198-
if dict_data['token'] != current_app.config['ADMIN_AUTH_TOKEN']:
203+
is_valid, payload = validate_token(dict_data.get('token'))
204+
if not is_valid or not payload.get('is_admin'):
199205
return jsonify({'status': 'Unauthorized'}), 403
200206
result = dbc.execute_query('delete_user_by_id', dict_data['employee_id'])
201207
if result is True:

‎server/api/clients/routes.py‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from flask import Flask, Blueprint, request, jsonify, current_app
1+
from flask import Blueprint, request, jsonify
22
from db.db_connector import DBConnector
3-
import json
3+
from api.auth.jwt_utils import validate_token
44

55
clients = Blueprint('clients', __name__)
66

@@ -9,7 +9,11 @@ def list_clients():
99
''' List clients function'''
1010
dbc = DBConnector()
1111
dict_data = request.get_json()
12-
comp_id = dbc.execute_query(query='get_compnay_id_by_user', args=dict_data['user_id'])
12+
token = dict_data['token']
13+
is_valid, payload = validate_token(token)
14+
if not is_valid or not payload.get('is_admin'):
15+
return jsonify({'status': 'Unauthorised'}), 403
16+
comp_id = dbc.execute_query(query='get_compnay_id_by_user', args=payload['user_id'])
1317
results = dbc.execute_query(query='get_clients_list', args=comp_id)
1418
if isinstance(results, list):
1519
return jsonify({'status': 'Ok', 'clients': results}), 200
@@ -41,7 +45,8 @@ def delete_client():
4145
dbc = DBConnector()
4246
dict_data = request.get_json()
4347
token = dict_data['token']
44-
if token != current_app.config["ADMIN_AUTH_TOKEN"]:
48+
is_valid, payload = validate_token(token)
49+
if not is_valid or not payload.get('is_admin'):
4550
return jsonify({'status': 'Unauthorised'}), 403
4651
result = dbc.execute_query(query='delete_client_by_id', args=dict_data['client_id'])
4752
if isinstance(result, int):

‎server/api/company/routes.py‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import os
2-
from flask import Flask, Blueprint, request, jsonify, current_app, abort, send_file
2+
from flask import Blueprint, request, jsonify, abort, send_file
33
from db.db_connector import DBConnector
44
from services.process_file import ProcessFile
55
from services.process_cash_flow import ProcessCashFlow
66
from services.process_sales import ProcessSales
7+
from api.auth.jwt_utils import validate_token
78

89
company = Blueprint('company', __name__)
910

@@ -12,7 +13,8 @@ def list_clients():
1213
''' List Sales function'''
1314
dbc = DBConnector()
1415
dict_data = request.get_json()
15-
if dict_data['token'] != current_app.config['ADMIN_AUTH_TOKEN']:
16+
is_valid, payload = validate_token(dict_data.get('token'))
17+
if not is_valid or not payload.get('is_admin'):
1618
return jsonify({'status': 'Unauthorised'}), 403
1719
results = dbc.execute_query(query='get_company_sales', args=dict_data['comp_id'])
1820
pcf = ProcessCashFlow(dict_data['comp_id'], 'PT', month=7) ### in this case we don't cate about the month, let's give one value just to simplify
@@ -28,7 +30,8 @@ def list_employees():
2830
''' List employees function'''
2931
dbc = DBConnector()
3032
dict_data = request.get_json()
31-
if dict_data['token'] != current_app.config['ADMIN_AUTH_TOKEN']:
33+
is_valid, payload = validate_token(dict_data.get('token'))
34+
if not is_valid or not payload.get('is_admin'):
3235
return jsonify({'status': 'Unauthorised'}), 403
3336
results = dbc.execute_query(query='get_employees_list', args=dict_data['comp_id'])
3437
if isinstance(results, list):
@@ -40,7 +43,8 @@ def list_products():
4043
''' List products for given company '''
4144
dbc = DBConnector()
4245
dict_data = request.get_json()
43-
if dict_data['token'] != current_app.config['ADMIN_AUTH_TOKEN'] and dict_data['token'] != current_app.config['AUTH_TOKEN']:
46+
is_valid, _payload = validate_token(dict_data.get('token'))
47+
if not is_valid:
4448
return jsonify({'status': 'Unauthorised'}), 403
4549
results = dbc.execute_query(query='get_products_list', args=dict_data['comp_id'])
4650
if isinstance(results, list):
@@ -74,8 +78,9 @@ def update_commission():
7478
print(dict_data)
7579
seller_id = dict_data['seller_id']
7680
new_commission = dict_data['new_commission']
77-
print(token + '======' + current_app.config['ADMIN_AUTH_TOKEN'])
78-
if token != current_app.config['ADMIN_AUTH_TOKEN']:
81+
# intentionally not comparing with static admin token anymore
82+
is_valid, payload = validate_token(token)
83+
if not is_valid or not payload.get('is_admin'):
7984
return jsonify({'status': 'Unauthorized'}), 403
8085
dbc = DBConnector()
8186
dbc.execute_query(query='update_seller_commission', args={'seller_id': seller_id, 'new_commission':new_commission})
@@ -86,7 +91,8 @@ def upload_excel():
8691
''' Update company products from csv or xlsx '''
8792
token = request.form.get('token')
8893
comp_id = request.form.get('comp_id')
89-
if token != current_app.config['ADMIN_AUTH_TOKEN']:
94+
is_valid, payload = validate_token(token)
95+
if not is_valid or not payload.get('is_admin'):
9096
return jsonify({'status': 'Unauthorized'}), 403
9197
if 'file' not in request.files:
9298
return jsonify({'error': 'No file part in the request'}), 400
@@ -104,7 +110,8 @@ def upload_excel():
104110
def cash_flow():
105111
''' Calculate compnay's cash flow '''
106112
dict_data = request.get_json()
107-
if dict_data['token'] != current_app.config['ADMIN_AUTH_TOKEN']:
113+
is_valid, payload = validate_token(dict_data.get('token'))
114+
if not is_valid or not payload.get('is_admin'):
108115
return jsonify({'status': 'Unauthorised'}), 403
109116

110117
#### To Simplify, we will only use 3 fixed months of sales ####

0 commit comments

Comments
 (0)