auth token fix
This commit is contained in:
parent
aab7ecb03d
commit
b04d23c7a4
|
|
@ -0,0 +1,393 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>API Tester - ChitFund</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
opacity: 0.9;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h2 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
color: #555;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.success {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.info {
|
||||||
|
background: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-display {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
max-height: 100px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.response-display {
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-btn {
|
||||||
|
background: #28a745;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn {
|
||||||
|
background: #dc3545;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🔧 ChitFund API Tester</h1>
|
||||||
|
<p>Test your API endpoints with authentication</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<!-- Login Section -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>🔐 Step 1: Login</h2>
|
||||||
|
<div id="loginStatus" class="status"></div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="mobile">Mobile Number</label>
|
||||||
|
<input type="text" id="mobile" placeholder="9876543210">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" placeholder="Enter password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="login()">Login</button>
|
||||||
|
|
||||||
|
<div id="tokenDisplay" style="display: none;">
|
||||||
|
<label style="margin-top: 15px;">Auth Token:</label>
|
||||||
|
<div class="token-display" id="tokenText"></div>
|
||||||
|
<button class="copy-btn" onclick="copyToken()" style="margin-top: 10px;">Copy Token</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- API Testing Section -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>🚀 Step 2: Test API Endpoints</h2>
|
||||||
|
<div id="apiStatus" class="status"></div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="endpoint-btn" onclick="getReviewQueue()">📋 Get Review Queue</button>
|
||||||
|
<button class="endpoint-btn" onclick="getUPISettings()">💳 Get UPI Settings</button>
|
||||||
|
<button class="endpoint-btn" onclick="getSyncStats()">📊 Get Sync Stats</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 15px;">
|
||||||
|
<label>Custom Endpoint:</label>
|
||||||
|
<input type="text" id="customEndpoint" placeholder="/payments/phonepe/settings/upi">
|
||||||
|
<button onclick="callCustomEndpoint()" style="margin-top: 10px;">Call Custom Endpoint</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 15px;">
|
||||||
|
<button class="clear-btn" onclick="clearResponse()">Clear Response</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Response Section -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>📦 Response</h2>
|
||||||
|
<div class="response-display" id="response">
|
||||||
|
No response yet. Login and test an endpoint!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const API_BASE = 'https://chitfund.deepteklabs.com/api';
|
||||||
|
let authToken = localStorage.getItem('authToken');
|
||||||
|
|
||||||
|
// Check if token exists on load
|
||||||
|
if (authToken) {
|
||||||
|
showToken(authToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login() {
|
||||||
|
const mobile = document.getElementById('mobile').value;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const status = document.getElementById('loginStatus');
|
||||||
|
|
||||||
|
if (!mobile || !password) {
|
||||||
|
showStatus(status, 'Please enter mobile number and password', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showStatus(status, 'Logging in...', 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/auth/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
mobile_number: mobile,
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
authToken = data.data.token;
|
||||||
|
localStorage.setItem('authToken', authToken);
|
||||||
|
showStatus(status, `✅ Login successful! Welcome ${data.data.user.full_name}`, 'success');
|
||||||
|
showToken(authToken);
|
||||||
|
displayResponse(data);
|
||||||
|
} else {
|
||||||
|
showStatus(status, `❌ Login failed: ${data.message}`, 'error');
|
||||||
|
displayResponse(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showStatus(status, `❌ Error: ${error.message}`, 'error');
|
||||||
|
displayResponse({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToken(token) {
|
||||||
|
document.getElementById('tokenDisplay').style.display = 'block';
|
||||||
|
document.getElementById('tokenText').textContent = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToken() {
|
||||||
|
navigator.clipboard.writeText(authToken);
|
||||||
|
alert('Token copied to clipboard!');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getReviewQueue() {
|
||||||
|
await callAPI('/transaction-sync/review-queue', 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUPISettings() {
|
||||||
|
await callAPI('/payments/phonepe/settings/upi', 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSyncStats() {
|
||||||
|
await callAPI('/transaction-sync/stats', 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function callCustomEndpoint() {
|
||||||
|
const endpoint = document.getElementById('customEndpoint').value;
|
||||||
|
if (!endpoint) {
|
||||||
|
alert('Please enter an endpoint');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await callAPI(endpoint, 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function callAPI(endpoint, method = 'GET') {
|
||||||
|
const status = document.getElementById('apiStatus');
|
||||||
|
|
||||||
|
if (!authToken) {
|
||||||
|
showStatus(status, '⚠️ Please login first!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showStatus(status, `Calling ${endpoint}...`, 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}${endpoint}`, {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${authToken}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showStatus(status, `✅ Success! Status: ${response.status}`, 'success');
|
||||||
|
} else {
|
||||||
|
showStatus(status, `❌ Error: ${data.message || response.statusText} (${response.status})`, 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
displayResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
showStatus(status, `❌ Error: ${error.message}`, 'error');
|
||||||
|
displayResponse({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showStatus(element, message, type) {
|
||||||
|
element.textContent = message;
|
||||||
|
element.className = `status ${type}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayResponse(data) {
|
||||||
|
const responseEl = document.getElementById('response');
|
||||||
|
responseEl.textContent = JSON.stringify(data, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearResponse() {
|
||||||
|
document.getElementById('response').textContent = 'Response cleared. Test an endpoint to see results.';
|
||||||
|
document.getElementById('apiStatus').className = 'status';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -48,6 +48,9 @@ if (process.env.NODE_ENV === 'development') {
|
||||||
app.use(morgan('dev'));
|
app.use(morgan('dev'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serve static files (for API tester)
|
||||||
|
app.use('/test', express.static('public'));
|
||||||
|
|
||||||
// Health check endpoint
|
// Health check endpoint
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,12 @@ static const String tokenKey = 'auth_token';
|
||||||
|
|
||||||
late Dio _dio;
|
late Dio _dio;
|
||||||
|
|
||||||
ApiService() {
|
// Singleton pattern
|
||||||
|
static final ApiService _instance = ApiService._internal();
|
||||||
|
factory ApiService() => _instance;
|
||||||
|
|
||||||
|
ApiService._internal() {
|
||||||
|
print('🏗️ [ApiService] Creating singleton instance');
|
||||||
_dio = Dio(BaseOptions(
|
_dio = Dio(BaseOptions(
|
||||||
baseUrl: baseUrl,
|
baseUrl: baseUrl,
|
||||||
connectTimeout: const Duration(seconds: 30),
|
connectTimeout: const Duration(seconds: 30),
|
||||||
|
|
@ -23,13 +28,20 @@ static const String tokenKey = 'auth_token';
|
||||||
_dio.interceptors.add(InterceptorsWrapper(
|
_dio.interceptors.add(InterceptorsWrapper(
|
||||||
onRequest: (options, handler) async {
|
onRequest: (options, handler) async {
|
||||||
final token = await _getToken();
|
final token = await _getToken();
|
||||||
|
print('🔐 [ApiService] Token retrieved: ${token != null ? "YES (${token.substring(0, 20)}...)" : "NO - NULL!"}');
|
||||||
|
print('🔐 [ApiService] Request: ${options.method} ${options.path}');
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
options.headers['Authorization'] = 'Bearer $token';
|
options.headers['Authorization'] = 'Bearer $token';
|
||||||
|
print('🔐 [ApiService] Authorization header added');
|
||||||
|
} else {
|
||||||
|
print('⚠️ [ApiService] WARNING: No token found! Request will fail!');
|
||||||
}
|
}
|
||||||
handler.next(options);
|
handler.next(options);
|
||||||
},
|
},
|
||||||
onError: (error, handler) {
|
onError: (error, handler) {
|
||||||
|
print('❌ [ApiService] Error: ${error.response?.statusCode} - ${error.message}');
|
||||||
if (error.response?.statusCode == 401) {
|
if (error.response?.statusCode == 401) {
|
||||||
|
print('❌ [ApiService] 401 Unauthorized - Clearing token');
|
||||||
// Token expired or invalid
|
// Token expired or invalid
|
||||||
_clearToken();
|
_clearToken();
|
||||||
}
|
}
|
||||||
|
|
@ -40,18 +52,41 @@ static const String tokenKey = 'auth_token';
|
||||||
|
|
||||||
// Token management
|
// Token management
|
||||||
Future<String?> _getToken() async {
|
Future<String?> _getToken() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
try {
|
||||||
return prefs.getString(tokenKey);
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final token = prefs.getString(tokenKey);
|
||||||
|
print('🔑 [ApiService._getToken] Looking for key: "$tokenKey"');
|
||||||
|
print('🔑 [ApiService._getToken] Token found: ${token != null}');
|
||||||
|
if (token != null) {
|
||||||
|
print('🔑 [ApiService._getToken] Token length: ${token.length}');
|
||||||
|
print('🔑 [ApiService._getToken] Token starts with: ${token.substring(0, token.length > 20 ? 20 : token.length)}...');
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
} catch (e) {
|
||||||
|
print('❌ [ApiService._getToken] Error: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveToken(String token) async {
|
Future<void> _saveToken(String token) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
try {
|
||||||
await prefs.setString(tokenKey, token);
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString(tokenKey, token);
|
||||||
|
print('✅ [ApiService._saveToken] Token saved with key: "$tokenKey"');
|
||||||
|
print('✅ [ApiService._saveToken] Token length: ${token.length}');
|
||||||
|
} catch (e) {
|
||||||
|
print('❌ [ApiService._saveToken] Error: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _clearToken() async {
|
Future<void> _clearToken() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
try {
|
||||||
await prefs.remove(tokenKey);
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove(tokenKey);
|
||||||
|
print('🗑️ [ApiService._clearToken] Token cleared');
|
||||||
|
} catch (e) {
|
||||||
|
print('❌ [ApiService._clearToken] Error: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authentication APIs
|
// Authentication APIs
|
||||||
|
|
@ -510,6 +545,17 @@ static const String tokenKey = 'auth_token';
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
await _clearToken();
|
await _clearToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug: Check if token exists
|
||||||
|
Future<bool> hasToken() async {
|
||||||
|
final token = await _getToken();
|
||||||
|
return token != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Get current token (for debugging)
|
||||||
|
Future<String?> getCurrentToken() async {
|
||||||
|
return await _getToken();
|
||||||
|
}
|
||||||
|
|
||||||
// Error handling
|
// Error handling
|
||||||
Map<String, dynamic> _handleError(DioException e) {
|
Map<String, dynamic> _handleError(DioException e) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue