Initial commit from template
Some checks failed
Cloudflare Worker API Template / Deploy to ${{ github.ref_name }} environment (push) Failing after 15s
Some checks failed
Cloudflare Worker API Template / Deploy to ${{ github.ref_name }} environment (push) Failing after 15s
This commit is contained in:
16
src/db/schema.sql
Normal file
16
src/db/schema.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Schema for user_data table
|
||||
CREATE TABLE IF NOT EXISTS user_data (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uid TEXT UNIQUE NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
firebase_uid TEXT,
|
||||
firstname TEXT,
|
||||
lastname TEXT,
|
||||
company_name TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Index for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_user_data_uid ON user_data(uid);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_data_email ON user_data(email);
|
||||
141
src/index.ts
Normal file
141
src/index.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { getCookie } from 'hono/cookie';
|
||||
import { decryptAuthCookie } from './services/decrypt-service';
|
||||
import { handleSwaggerRequest } from './swagger-ui';
|
||||
import { authMiddleware } from './middleware/auth';
|
||||
import { saveUserData, getUserByUid } from './services/db-service';
|
||||
|
||||
// Create a new Hono app
|
||||
const app = new Hono();
|
||||
|
||||
// Add middleware
|
||||
app.use('*', async (c, next) => {
|
||||
console.log(`[${c.req.method}] ${c.req.url}`);
|
||||
await next();
|
||||
});
|
||||
|
||||
// Add CORS middleware
|
||||
app.use('*', cors());
|
||||
|
||||
// Create a group for protected routes with auth middleware
|
||||
// Exclude Swagger docs routes from auth middleware
|
||||
app.use('/api/cf-template/*', async (c, next) => {
|
||||
const path = new URL(c.req.url).pathname;
|
||||
|
||||
// Skip auth for Swagger docs routes
|
||||
if (path.includes('/docs') || path.includes('/swagger.json') || path.includes('/openapi.json')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Apply auth middleware for all other routes
|
||||
return authMiddleware(c, next);
|
||||
});
|
||||
|
||||
// Add auth validation endpoint (GET method)
|
||||
app.get('/api/cf-template/auth/validate', async (c) => {
|
||||
try {
|
||||
// Get auth from query parameter
|
||||
const authToken = c.req.query('auth');
|
||||
|
||||
if (!authToken) {
|
||||
return c.json({ error: 'No auth parameter found' }, 401);
|
||||
}
|
||||
|
||||
// Decrypt the auth token
|
||||
const decryptedData = await decryptAuthCookie(authToken, c.env);
|
||||
|
||||
// Check if the decrypted data contains the expected fields
|
||||
if (!decryptedData || !decryptedData.firstname || !decryptedData.lastname) {
|
||||
console.error('Invalid decrypted data format:', decryptedData);
|
||||
return c.json({ error: 'Invalid auth parameter' }, 401);
|
||||
}
|
||||
return c.json(decryptedData, 200);
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
return c.json({ error: 'Authentication failed' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Add auth validation endpoint (POST method)
|
||||
app.post('/api/cf-template/auth/validate', async (c) => {
|
||||
try {
|
||||
// Get auth from request body
|
||||
const body = await c.req.json();
|
||||
const authToken = body.auth;
|
||||
|
||||
if (!authToken) {
|
||||
return c.json({ error: 'No auth parameter found in request body' }, 401);
|
||||
}
|
||||
|
||||
// Decrypt the auth token
|
||||
const decryptedData = await decryptAuthCookie(authToken, c.env);
|
||||
|
||||
// Check if the decrypted data contains the expected fields
|
||||
if (!decryptedData || !decryptedData.firstname || !decryptedData.lastname) {
|
||||
console.error('Invalid decrypted data format:', decryptedData);
|
||||
return c.json({ error: 'Invalid auth parameter' }, 401);
|
||||
}
|
||||
return c.json(decryptedData, 200);
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
return c.json({ error: 'Authentication failed' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Add endpoint to decrypt and save cookie data to D1 database
|
||||
app.post('/api/cf-template/auth/decrypt-and-save', async (c) => {
|
||||
try {
|
||||
// Get auth token from request body
|
||||
const body = await c.req.json();
|
||||
const authToken = body.auth;
|
||||
|
||||
if (!authToken) {
|
||||
return c.json({ error: 'No auth parameter found in request body' }, 401);
|
||||
}
|
||||
|
||||
// Decrypt the auth token
|
||||
const decryptedData = await decryptAuthCookie(authToken, c.env);
|
||||
|
||||
// Check if the decrypted data contains the expected fields
|
||||
if (!decryptedData || !decryptedData.uid || !decryptedData.email) {
|
||||
console.error('Invalid decrypted data format:', decryptedData);
|
||||
return c.json({ error: 'Invalid auth parameter' }, 401);
|
||||
}
|
||||
|
||||
// Save the decrypted data to the D1 database
|
||||
const result = await saveUserData(decryptedData, c.env);
|
||||
|
||||
if (!result.success) {
|
||||
return c.json({ error: result.error || 'Failed to save user data' }, 500);
|
||||
}
|
||||
|
||||
// Return success response with the saved data
|
||||
return c.json({
|
||||
success: true,
|
||||
message: result.message,
|
||||
updated: result.updated,
|
||||
uid: result.uid,
|
||||
userData: decryptedData
|
||||
}, 200);
|
||||
} catch (error) {
|
||||
console.error('Error processing request:', error);
|
||||
return c.json({ error: 'Failed to process request', details: error.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Add health check endpoint
|
||||
app.get('/api/cf-template/health', (c) => {
|
||||
return c.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
// Add Swagger UI routes
|
||||
app.get('/api/cf-template/docs', (c) => handleSwaggerRequest(c.req.raw, '/api/cf-template'));
|
||||
app.get('/api/cf-template/docs/', (c) => handleSwaggerRequest(c.req.raw, '/api/cf-template'));
|
||||
app.get('/api/cf-template/swagger.json', (c) => handleSwaggerRequest(c.req.raw, '/api/cf-template'));
|
||||
app.get('/api/cf-template/openapi.json', (c) => handleSwaggerRequest(c.req.raw, '/api/cf-template'));
|
||||
|
||||
// Export the app
|
||||
export default {
|
||||
fetch: app.fetch,
|
||||
};
|
||||
92
src/middleware/auth.js
Normal file
92
src/middleware/auth.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Authorization middleware for validating API keys and Bearer tokens
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates an API key or Bearer token against the CM_BASE_URL
|
||||
* @param {string} token - The API key or Bearer token to validate
|
||||
* @param {Object} env - Environment variables
|
||||
* @returns {Promise<boolean>} - Whether the token is valid
|
||||
*/
|
||||
async function validateToken(token, env) {
|
||||
try {
|
||||
// Construct the validation URL
|
||||
const validationUrl = `${env.CM_BASE_URL}/api/keys/validate?key=${token}`;
|
||||
|
||||
// Make the validation request
|
||||
const response = await fetch(validationUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'X-Api-Key': token
|
||||
}
|
||||
});
|
||||
|
||||
// Check if the response is successful
|
||||
if (!response.ok) {
|
||||
console.error(`Token validation failed with status: ${response.status}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
const data = await response.json();
|
||||
|
||||
// Check if the token is valid
|
||||
return data.valid === true;
|
||||
} catch (error) {
|
||||
console.error('Error validating token:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to check for valid API key or Bearer token
|
||||
* @param {Object} c - Hono context
|
||||
* @param {Function} next - Next middleware function
|
||||
* @returns {Promise<Response|void>} - Response or next middleware
|
||||
*/
|
||||
export async function authMiddleware(c, next) {
|
||||
// Get the authorization header
|
||||
const authHeader = c.req.header('Authorization');
|
||||
const apiKey = c.req.header('X-Api-Key');
|
||||
|
||||
let token = null;
|
||||
|
||||
// Check for API key
|
||||
if (apiKey) {
|
||||
token = apiKey;
|
||||
}
|
||||
// Check for Bearer token
|
||||
else if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
token = authHeader.substring(7);
|
||||
}
|
||||
|
||||
// If no token is provided, return 401
|
||||
if (!token) {
|
||||
return c.json({
|
||||
error: 'Unauthorized',
|
||||
message: 'API key or Bearer token required'
|
||||
}, 401);
|
||||
}
|
||||
|
||||
// Special case for testing: allow system-key to bypass validation
|
||||
if (token === 'system-key') {
|
||||
console.log('Using system-key bypass for testing');
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the token
|
||||
const isValid = await validateToken(token, c.env);
|
||||
|
||||
// If the token is not valid, return 401
|
||||
if (!isValid) {
|
||||
return c.json({
|
||||
error: 'Unauthorized',
|
||||
message: 'Invalid API key or Bearer token'
|
||||
}, 401);
|
||||
}
|
||||
|
||||
// Token is valid, continue to the next middleware
|
||||
await next();
|
||||
}
|
||||
146
src/services/db-service.js
Normal file
146
src/services/db-service.js
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Database service for user data operations
|
||||
*/
|
||||
|
||||
import { saveUserDataLocal, getUserByUidLocal, initLocalDb } from './local-db-service';
|
||||
|
||||
/**
|
||||
* Initialize the database schema
|
||||
* @param {Object} env - Environment variables containing D1 database binding
|
||||
* @returns {Promise<boolean>} - Whether initialization was successful
|
||||
*/
|
||||
async function initializeDatabase(env) {
|
||||
try {
|
||||
if (!env.CF_TEMPLATE_DB) {
|
||||
console.log('D1 database binding not found, skipping initialization');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create user_data table if it doesn't exist
|
||||
try {
|
||||
// Use a single-line SQL statement to avoid parsing issues with D1
|
||||
await env.CF_TEMPLATE_DB.exec(`CREATE TABLE IF NOT EXISTS user_data (id INTEGER PRIMARY KEY AUTOINCREMENT, uid TEXT UNIQUE NOT NULL, email TEXT NOT NULL, firebase_uid TEXT, firstname TEXT, lastname TEXT, company_name TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`);
|
||||
|
||||
// Create indexes in a single-line format as well
|
||||
await env.CF_TEMPLATE_DB.exec(`CREATE INDEX IF NOT EXISTS idx_user_data_uid ON user_data(uid);`);
|
||||
await env.CF_TEMPLATE_DB.exec(`CREATE INDEX IF NOT EXISTS idx_user_data_email ON user_data(email);`);
|
||||
|
||||
console.log('Database schema initialized successfully');
|
||||
} catch (dbError) {
|
||||
console.error('Error creating schema:', dbError);
|
||||
// If we can't create the table, just return mock data
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error initializing database schema:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save user data from decrypted cookie to D1 database
|
||||
* @param {Object} userData - The decrypted user data
|
||||
* @param {Object} env - Environment variables containing D1 database binding
|
||||
* @returns {Promise<Object>} - Result of the operation
|
||||
*/
|
||||
export async function saveUserData(userData, env) {
|
||||
try {
|
||||
// Initialize database schema if needed
|
||||
await initializeDatabase(env);
|
||||
// Check if required database binding exists
|
||||
if (!env.CF_TEMPLATE_DB) {
|
||||
console.log('D1 database binding not found, using local database');
|
||||
// Use our local database implementation
|
||||
return await saveUserDataLocal(userData);
|
||||
}
|
||||
|
||||
// Extract user data from the decrypted cookie
|
||||
const { uid, email, firebase_uid, firstname, lastname, company_name } = userData;
|
||||
|
||||
// Validate required fields
|
||||
if (!uid || !email) {
|
||||
return { success: false, error: 'Missing required fields (uid, email)' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if user already exists
|
||||
const existingUser = await env.CF_TEMPLATE_DB.prepare(
|
||||
'SELECT id FROM user_data WHERE uid = ?'
|
||||
).bind(uid).first();
|
||||
|
||||
let result;
|
||||
|
||||
if (existingUser) {
|
||||
// Update existing user
|
||||
result = await env.CF_TEMPLATE_DB.prepare(`
|
||||
UPDATE user_data
|
||||
SET email = ?, firebase_uid = ?, firstname = ?, lastname = ?, company_name = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE uid = ?
|
||||
`).bind(email, firebase_uid, firstname, lastname, company_name, uid).run();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'User data updated successfully',
|
||||
updated: true,
|
||||
uid
|
||||
};
|
||||
} else {
|
||||
// Insert new user
|
||||
result = await env.CF_TEMPLATE_DB.prepare(`
|
||||
INSERT INTO user_data (uid, email, firebase_uid, firstname, lastname, company_name)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).bind(uid, email, firebase_uid, firstname, lastname, company_name).run();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'User data saved successfully',
|
||||
updated: false,
|
||||
uid
|
||||
};
|
||||
}
|
||||
} catch (dbError) {
|
||||
console.error('Database operation failed:', dbError);
|
||||
// Return success with the data even if DB operation failed
|
||||
return {
|
||||
success: true,
|
||||
message: 'User data processed (DB operation failed)',
|
||||
error: dbError.message,
|
||||
updated: false,
|
||||
uid,
|
||||
userData
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving user data:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user data by uid
|
||||
* @param {string} uid - The user's unique identifier
|
||||
* @param {Object} env - Environment variables containing D1 database binding
|
||||
* @returns {Promise<Object|null>} - User data or null if not found
|
||||
*/
|
||||
export async function getUserByUid(uid, env) {
|
||||
try {
|
||||
// Initialize database schema if needed
|
||||
await initializeDatabase(env);
|
||||
if (!env.CF_TEMPLATE_DB) {
|
||||
console.log('D1 database binding not found, using local database');
|
||||
// Use our local database implementation
|
||||
return await getUserByUidLocal(uid);
|
||||
}
|
||||
|
||||
const user = await env.CF_TEMPLATE_DB.prepare(
|
||||
'SELECT * FROM user_data WHERE uid = ?'
|
||||
).bind(uid).first();
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('Error getting user data:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
47
src/services/decrypt-service.js
Normal file
47
src/services/decrypt-service.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Service for decrypting authentication cookies
|
||||
*/
|
||||
|
||||
/**
|
||||
* Decrypts an authentication cookie by calling the decrypt API
|
||||
*/
|
||||
export async function decryptAuthCookie(authCookie, env) {
|
||||
try {
|
||||
// For development/testing: handle test-token specially
|
||||
if (authCookie === 'test-token') {
|
||||
console.log('Using test token, returning mock data');
|
||||
return {
|
||||
uid: '426bcea5-adb9-4580-a8ca-3a40fdb0ef85',
|
||||
email: 'humanizeiq@eteaminc.com',
|
||||
firebase_uid: 'wFyjGE1j8wclXayUvPbkF4c15f92',
|
||||
firstname: 'Rajeev',
|
||||
lastname: 'Borborah',
|
||||
company_name: null,
|
||||
success: true,
|
||||
message: 'All cookies decrypted successfully'
|
||||
};
|
||||
}
|
||||
console.log(`Calling decrypt API at ${env.DECRYPT_API_URL}/secure-auth/decrypt-cookie`);
|
||||
const response = await fetch(`${env.DECRYPT_API_URL}/secure-auth/decrypt-cookie`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'X-API-Key': env.DECRYPT_API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ auth: authCookie }),
|
||||
});
|
||||
console.log("Payload",env.DECRYPT_API_KEY,authCookie)
|
||||
if (!response.ok) {
|
||||
console.error('Decrypt API error:', response.status);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Decrypted data:', JSON.stringify(data));
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error decrypting auth cookie:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
91
src/services/local-db-service.js
Normal file
91
src/services/local-db-service.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Local SQLite database service for development
|
||||
*/
|
||||
|
||||
// This service provides a fallback for when Cloudflare D1 is not available
|
||||
// It uses in-memory storage for development and testing
|
||||
|
||||
// In-memory database store
|
||||
const inMemoryDb = {
|
||||
users: new Map()
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the local database
|
||||
*/
|
||||
export function initLocalDb() {
|
||||
console.log('Initializing local in-memory database');
|
||||
// Nothing to do for in-memory database
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save user data to local storage
|
||||
* @param {Object} userData - The user data to save
|
||||
* @returns {Promise<Object>} - Result of the operation
|
||||
*/
|
||||
export async function saveUserDataLocal(userData) {
|
||||
try {
|
||||
if (!userData || !userData.uid) {
|
||||
return { success: false, error: 'Missing required user data' };
|
||||
}
|
||||
|
||||
const { uid } = userData;
|
||||
const existingUser = inMemoryDb.users.get(uid);
|
||||
const isUpdate = !!existingUser;
|
||||
|
||||
// Add timestamp
|
||||
const now = new Date().toISOString();
|
||||
const userWithTimestamp = {
|
||||
...userData,
|
||||
created_at: existingUser?.created_at || now,
|
||||
updated_at: now
|
||||
};
|
||||
|
||||
// Save to in-memory database
|
||||
inMemoryDb.users.set(uid, userWithTimestamp);
|
||||
|
||||
console.log(`User data ${isUpdate ? 'updated' : 'saved'} in local database:`, uid);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `User data ${isUpdate ? 'updated' : 'saved'} successfully in local database`,
|
||||
updated: isUpdate,
|
||||
uid
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error saving user data to local database:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user data by uid from local storage
|
||||
* @param {string} uid - The user's unique identifier
|
||||
* @returns {Promise<Object|null>} - User data or null if not found
|
||||
*/
|
||||
export async function getUserByUidLocal(uid) {
|
||||
try {
|
||||
if (!uid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return inMemoryDb.users.get(uid) || null;
|
||||
} catch (error) {
|
||||
console.error('Error getting user data from local database:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users from local storage
|
||||
* @returns {Promise<Array>} - Array of users
|
||||
*/
|
||||
export async function getAllUsersLocal() {
|
||||
try {
|
||||
return Array.from(inMemoryDb.users.values());
|
||||
} catch (error) {
|
||||
console.error('Error getting all users from local database:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
93
src/swagger-ui.js
Normal file
93
src/swagger-ui.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { swaggerConfig } from './swagger';
|
||||
|
||||
// HTML template for Swagger UI
|
||||
const swaggerHtml = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>API Documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css">
|
||||
<style>
|
||||
html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }
|
||||
*, *:before, *:after { box-sizing: inherit; }
|
||||
body { margin: 0; background: #fafafa; }
|
||||
.topbar { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
const ui = SwaggerUIBundle({
|
||||
spec: SWAGGER_SPEC,
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
/**
|
||||
* Handles Swagger UI requests
|
||||
* @param {Request} request - The incoming request
|
||||
* @param {string} basePath - The base path for the API
|
||||
* @returns {Response} - The response with Swagger UI or JSON
|
||||
*/
|
||||
export function handleSwaggerRequest(request, basePath = '') {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
|
||||
// Serve the OpenAPI/Swagger JSON specification
|
||||
if (path === `${basePath}/openapi.json` || path === `${basePath}/swagger.json`) {
|
||||
return new Response(JSON.stringify(swaggerConfig), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
// Serve the Swagger UI HTML
|
||||
if (path === `${basePath}/docs` || path === `${basePath}/docs/` || path === basePath) {
|
||||
// Replace the placeholder with the actual Swagger definition
|
||||
const html = swaggerHtml.replace(
|
||||
'SWAGGER_SPEC',
|
||||
JSON.stringify(swaggerConfig)
|
||||
);
|
||||
|
||||
return new Response(html, {
|
||||
headers: { 'Content-Type': 'text/html' },
|
||||
});
|
||||
}
|
||||
|
||||
// If not a valid Swagger UI path, return a JSON error
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Not Found',
|
||||
message: 'The requested Swagger UI resource was not found'
|
||||
}), {
|
||||
status: 404,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} catch (error) {
|
||||
// Return a proper JSON error response instead of HTML
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Swagger UI Error',
|
||||
details: error.message
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
}
|
||||
7
src/swagger.js
Normal file
7
src/swagger.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* OpenAPI/Swagger configuration
|
||||
*/
|
||||
|
||||
import { swaggerConfig } from './swagger/config';
|
||||
|
||||
export { swaggerConfig };
|
||||
46
src/swagger/config.js
Normal file
46
src/swagger/config.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Main Swagger configuration
|
||||
*/
|
||||
import { healthEndpoint } from './endpoints/health';
|
||||
import { authValidateEndpoint } from './endpoints/auth-validate';
|
||||
import { decryptAndSaveEndpoint } from './endpoints/decrypt-and-save';
|
||||
|
||||
export const swaggerConfig = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'Auth SecureChat API',
|
||||
version: '1.0.0',
|
||||
description: 'API for authentication with SecureChat using AnythingLLM',
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: '/api/leave-agent',
|
||||
description: 'API Base Path',
|
||||
},
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
ApiKeyAuth: {
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
name: 'X-Api-Key',
|
||||
description: 'API key for authorization'
|
||||
},
|
||||
BearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
description: 'Bearer token for authorization'
|
||||
}
|
||||
}
|
||||
},
|
||||
security: [
|
||||
{ ApiKeyAuth: [] },
|
||||
{ BearerAuth: [] }
|
||||
],
|
||||
paths: {
|
||||
...healthEndpoint,
|
||||
...authValidateEndpoint,
|
||||
...decryptAndSaveEndpoint
|
||||
}
|
||||
};
|
||||
168
src/swagger/endpoints/auth-validate.js
Normal file
168
src/swagger/endpoints/auth-validate.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Auth validation endpoint Swagger configuration
|
||||
*/
|
||||
|
||||
export const authValidateEndpoint = {
|
||||
'/auth/validate': {
|
||||
get: {
|
||||
summary: 'Validate authentication token (GET)',
|
||||
description: 'Validates the auth token parameter via query parameter',
|
||||
security: [
|
||||
{ ApiKeyAuth: [] },
|
||||
{ BearerAuth: [] }
|
||||
],
|
||||
parameters: [
|
||||
{
|
||||
name: 'auth',
|
||||
in: 'query',
|
||||
description: 'Authentication token',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Authentication successful',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
firstname: {
|
||||
type: 'string',
|
||||
example: 'John'
|
||||
},
|
||||
lastname: {
|
||||
type: 'string',
|
||||
example: 'Doe'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
example: 'john.doe@example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'401': {
|
||||
description: 'Authentication failed',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: {
|
||||
type: 'string',
|
||||
example: 'Authentication failed',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'500': {
|
||||
description: 'Server error',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: {
|
||||
type: 'string',
|
||||
example: 'Authentication failed',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
post: {
|
||||
summary: 'Validate authentication token (POST)',
|
||||
description: 'Validates the auth token parameter via request body',
|
||||
security: [
|
||||
{ ApiKeyAuth: [] },
|
||||
{ BearerAuth: [] }
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['auth'],
|
||||
properties: {
|
||||
auth: {
|
||||
type: 'string',
|
||||
description: 'Authentication token'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Authentication successful',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
firstname: {
|
||||
type: 'string',
|
||||
example: 'John'
|
||||
},
|
||||
lastname: {
|
||||
type: 'string',
|
||||
example: 'Doe'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
example: 'john.doe@example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'401': {
|
||||
description: 'Authentication failed',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: {
|
||||
type: 'string',
|
||||
example: 'Authentication failed'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'500': {
|
||||
description: 'Server error',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: {
|
||||
type: 'string',
|
||||
example: 'Authentication failed'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
125
src/swagger/endpoints/decrypt-and-save.js
Normal file
125
src/swagger/endpoints/decrypt-and-save.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Decrypt and save endpoint Swagger configuration
|
||||
*/
|
||||
|
||||
export const decryptAndSaveEndpoint = {
|
||||
'/auth/decrypt-and-save': {
|
||||
post: {
|
||||
summary: 'Decrypt auth token and save user data to database',
|
||||
description: 'Decrypts the provided auth token and saves the user data to the D1 database',
|
||||
security: [
|
||||
{ ApiKeyAuth: [] },
|
||||
{ BearerAuth: [] }
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['auth'],
|
||||
properties: {
|
||||
auth: {
|
||||
type: 'string',
|
||||
description: 'Authentication token to decrypt'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'User data successfully saved',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
example: true
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
example: 'User data saved successfully'
|
||||
},
|
||||
updated: {
|
||||
type: 'boolean',
|
||||
example: false
|
||||
},
|
||||
uid: {
|
||||
type: 'string',
|
||||
example: '426bcea5-adb9-4580-a8ca-3a40fdb0ef85'
|
||||
},
|
||||
userData: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uid: {
|
||||
type: 'string',
|
||||
example: '426bcea5-adb9-4580-a8ca-3a40fdb0ef85'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
example: 'humanizeiq@eteaminc.com'
|
||||
},
|
||||
firebase_uid: {
|
||||
type: 'string',
|
||||
example: 'wFyjGE1j8wclXayUvPbkF4c15f92'
|
||||
},
|
||||
firstname: {
|
||||
type: 'string',
|
||||
example: 'Rajeev'
|
||||
},
|
||||
lastname: {
|
||||
type: 'string',
|
||||
example: 'Borborah'
|
||||
},
|
||||
company_name: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
example: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'401': {
|
||||
description: 'Authentication failed',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: {
|
||||
type: 'string',
|
||||
example: 'Invalid auth parameter'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'500': {
|
||||
description: 'Server error',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: {
|
||||
type: 'string',
|
||||
example: 'Failed to save user data'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
42
src/swagger/endpoints/health.js
Normal file
42
src/swagger/endpoints/health.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Health endpoint Swagger configuration
|
||||
*/
|
||||
|
||||
export const healthEndpoint = {
|
||||
'/health': {
|
||||
get: {
|
||||
summary: 'Health check endpoint',
|
||||
description: 'Returns the status of the API',
|
||||
security: [
|
||||
{ ApiKeyAuth: [] },
|
||||
{ BearerAuth: [] }
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'API is running',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
type: 'string',
|
||||
example: 'ok',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
example: 'Auth SecureChat API is running',
|
||||
},
|
||||
version: {
|
||||
type: 'string',
|
||||
example: '1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user