Clean up authentication troubleshooting artifacts
Build And Push Image / docker (push) Successful in 3m1s Details

- Remove debug files: debug-login.js, LOGIN_FIX_SUMMARY.md, CUSTOM_LOGIN_IMPLEMENTATION.md
- Remove sequential-thinking directory (temporary MCP setup)
- Clean up verbose console logging in auth middleware
- Reduce debug output in direct login API while keeping essential logs
- Streamline session management logging
- Update .gitignore to prevent future debug file commits
- Maintain essential error logging and security logs

All authentication functionality remains intact and working.
This commit is contained in:
Matt 2025-08-07 15:14:02 +02:00
parent 99772ab62c
commit 91cbffe189
12 changed files with 14 additions and 994 deletions

8
.gitignore vendored
View File

@ -34,3 +34,11 @@ node_modules
# Local data directories
data/
logs/
# Debug files and troubleshooting artifacts
debug-*.js
*.debug.log
LOGIN_FIX_*.md
CUSTOM_*_IMPLEMENTATION.md
troubleshooting/
sequential-thinking/

View File

@ -1,238 +0,0 @@
# MonacoUSA Portal - Custom Login System Implementation
## 🎯 Overview
This document describes the implementation of the custom branded login system for the MonacoUSA Portal. The system provides a seamless authentication experience using your Monaco assets while maintaining security through Keycloak integration.
## ✨ Features Implemented
### 🎨 **Beautiful Branded UI**
- **Monaco Background**: High-resolution Monaco image with overlay effects
- **MonacoUSA Logo**: Animated logo with pulse effect
- **Glassmorphism Design**: Modern card with backdrop blur and transparency
- **Responsive Layout**: Works perfectly on desktop, tablet, and mobile
- **Smooth Animations**: Entrance animations and hover effects
### 🔐 **Direct Authentication**
- **Password-based Login**: Users never leave your portal
- **Remember Me**: Extended 30-day sessions vs 7-day standard
- **Rate Limiting**: 5 attempts per 15 minutes, 1-hour IP blocking
- **Input Validation**: Client and server-side validation
- **Security Logging**: Comprehensive logging for monitoring
### 🔄 **Password Reset**
- **Email-based Reset**: Integration with Keycloak's email system
- **Beautiful Dialog**: Modal with branded styling
- **Security-first**: Doesn't reveal if email exists
- **Auto-close**: Success message with automatic dialog closure
## 📁 Files Created/Modified
### **Frontend Components**
```
pages/login.vue # Main branded login page
components/ForgotPasswordDialog.vue # Password reset modal
middleware/guest.ts # Redirect authenticated users
```
### **Backend APIs**
```
server/api/auth/direct-login.post.ts # Direct password authentication
server/api/auth/forgot-password.post.ts # Password reset functionality
server/utils/security.ts # Rate limiting and validation
```
### **Enhanced Files**
```
server/utils/session.ts # Added Remember Me support
.env.example # Added COOKIE_DOMAIN variable
```
## 🚀 How It Works
### **Authentication Flow**
1. User visits `/login` with beautiful Monaco-themed page
2. Enters credentials with optional "Remember Me"
3. Client-side validation and security checks
4. Direct authentication with Keycloak via Resource Owner Password Credentials
5. Session creation with appropriate expiration (7 or 30 days)
6. Redirect to dashboard upon success
### **Password Reset Flow**
1. User clicks "Forgot Password?" link
2. Modal dialog opens with email input
3. Email sent via Keycloak admin API
4. User receives reset link in email
5. Keycloak handles the actual password reset
### **Security Features**
- **Rate Limiting**: 5 failed attempts = 15-minute window, then 1-hour IP block
- **Input Validation**: Prevents XSS and injection attacks
- **Secure Sessions**: Encrypted cookies with domain scoping
- **Audit Logging**: All authentication events logged
- **CSRF Protection**: Built into Nuxt framework
## 🎨 Design Specifications
### **Visual Elements**
- **Primary Color**: `#a31515` (MonacoUSA red)
- **Background**: Monaco high-resolution image with gradient overlay
- **Card Style**: Glassmorphism with `backdrop-filter: blur(15px)`
- **Logo**: Animated with 3-second pulse effect
- **Typography**: Clean, modern fonts with proper hierarchy
### **Responsive Breakpoints**
- **Desktop**: Full glassmorphism effects, fixed background
- **Tablet**: Optimized card sizing and spacing
- **Mobile**: Scrollable background, adjusted padding
## 🔧 Configuration Required
### **Environment Variables**
```env
# Keycloak Configuration
NUXT_KEYCLOAK_ISSUER=https://auth.monacousa.org/realms/monacousa
NUXT_KEYCLOAK_CLIENT_ID=monacousa-portal
NUXT_KEYCLOAK_CLIENT_SECRET=[your-secret-from-keycloak]
NUXT_KEYCLOAK_CALLBACK_URL=https://portal.monacousa.org/auth/callback
# Cookie Configuration
COOKIE_DOMAIN=.monacousa.org
# Security Configuration
NUXT_SESSION_SECRET=[generate-48-chars]
NUXT_ENCRYPTION_KEY=[generate-32-chars]
# Public Domain
NUXT_PUBLIC_DOMAIN=monacousa.org
```
### **Keycloak Client Settings**
In your Keycloak admin console, configure the `monacousa-portal` client:
1. **Settings Tab**:
- Client authentication: `ON`
- Standard flow: `ON` (keep for fallback)
- **Direct access grants: `ON`** ⚠️ **REQUIRED**
- Implicit flow: `OFF`
2. **Advanced Settings**:
- Access Token Lifespan: `15 minutes`
- Refresh Token Lifespan: `30 days` (for Remember Me)
3. **Valid Redirect URIs**:
- `https://portal.monacousa.org/*`
- `http://localhost:6060/*` (development)
4. **Web Origins**:
- `https://portal.monacousa.org`
- `http://localhost:6060`
## 🖼️ Assets Used
### **Images in `/public/`**
- `MONACOUSA-Flags_376x376.png` - Logo (376x376px)
- `monaco_high_res.jpg` - Background image (high resolution)
### **Asset Optimization**
- Logo: Transparent PNG, optimized for web
- Background: High-quality JPEG, properly compressed
- Both images are served directly from `/public/` directory
## 🔍 Testing the Implementation
### **Manual Testing Checklist**
- [ ] Login page loads with Monaco background and logo
- [ ] Form validation works (client and server-side)
- [ ] Successful login redirects to dashboard
- [ ] Failed login shows appropriate error messages
- [ ] Rate limiting blocks after 5 failed attempts
- [ ] Remember Me extends session to 30 days
- [ ] Forgot Password dialog opens and functions
- [ ] Password reset email is sent via Keycloak
- [ ] Responsive design works on mobile devices
- [ ] Animations and hover effects work smoothly
### **Browser Testing**
- Chrome/Edge (Chromium-based)
- Firefox
- Safari (desktop and mobile)
- Mobile browsers (iOS Safari, Android Chrome)
## 🚨 Security Considerations
### **Implemented Security Measures**
1. **Rate Limiting**: Prevents brute force attacks
2. **Input Validation**: Prevents injection attacks
3. **Secure Cookies**: HttpOnly, Secure, SameSite protection
4. **Domain Scoping**: Cookies work across subdomains
5. **Session Encryption**: All session data encrypted
6. **Audit Logging**: Security events logged for monitoring
7. **Error Handling**: Generic error messages to prevent information disclosure
### **Production Recommendations**
1. **HTTPS Only**: Ensure all traffic uses HTTPS
2. **Security Headers**: Implement CSP, HSTS, etc.
3. **Log Monitoring**: Monitor failed login attempts
4. **Regular Updates**: Keep Keycloak and dependencies updated
5. **Backup Strategy**: Regular backups of Keycloak realm
## 🐛 Troubleshooting
### **Common Issues**
#### **Login Redirects to Itself**
- Check Keycloak configuration has Direct Access Grants enabled
- Verify environment variables are set correctly
- Check browser console for JavaScript errors
#### **Cookies Not Working**
- Ensure `COOKIE_DOMAIN=.monacousa.org` is set
- Verify HTTPS is working in production
- Check browser developer tools for cookie issues
#### **Password Reset Not Working**
- Verify Keycloak client has admin permissions
- Check SMTP configuration in Keycloak
- Review server logs for Keycloak API errors
#### **Rate Limiting Too Aggressive**
- Adjust limits in `server/api/auth/direct-login.post.ts`
- Consider implementing Redis for production rate limiting
- Monitor logs for legitimate users being blocked
### **Debug Logging**
The implementation includes comprehensive logging:
- `🔐` Authentication attempts
- `📝` Login parameters (sanitized)
- `🔧` Configuration validation
- `✅` Successful operations
- `❌` Errors and failures
- `🚨` Security events
## 🔄 Future Enhancements
### **Potential Improvements**
1. **Social Login**: Add Google, Microsoft, GitHub integration
2. **Two-Factor Authentication**: SMS or TOTP support
3. **Progressive Web App**: Enhanced mobile experience
4. **Dark Mode**: Theme switching capability
5. **Internationalization**: Multi-language support
6. **Advanced Analytics**: Login metrics and reporting
### **Performance Optimizations**
1. **Image Optimization**: WebP format support
2. **Lazy Loading**: Optimize initial page load
3. **CDN Integration**: Serve assets from CDN
4. **Caching Strategy**: Implement proper caching headers
## 📊 Implementation Summary
### **Development Time**: ~4 hours
### **Complexity**: Medium (6/10)
### **Files Modified**: 8 files
### **New Components**: 2 components
### **API Endpoints**: 2 endpoints
### **Security Features**: 7 implemented
The custom login system successfully provides a branded, secure, and user-friendly authentication experience while maintaining integration with your existing Keycloak infrastructure.

View File

@ -1,244 +0,0 @@
# MonacoUSA Portal - Login Issue Fix Summary
## Issue Description
The login functionality was experiencing redirect failures after successful authentication. Users could successfully authenticate with Keycloak, but the redirect to the dashboard was failing with 502 Bad Gateway errors and CORS issues.
## Root Causes Identified
1. **Service Worker Interference**: The PWA service worker was caching authentication-related routes, causing conflicts during login redirects.
2. **Cookie Domain Issues**: The session cookie was being set with an explicit domain (`.monacousa.org`) which could cause issues in certain deployment scenarios.
3. **Redirect Method**: Using `window.location.href` for redirects was causing hard page reloads that conflicted with the SPA architecture.
4. **Nginx Proxy Timeouts**: Missing proxy timeout configurations could cause 502 errors during authentication flows.
## Fixes Applied
### 1. PWA Service Worker Configuration (`nuxt.config.ts`)
**Changes Made:**
- Added authentication-related paths to `navigateFallbackDenylist`
- Reduced API cache duration from 24 hours to 5 minutes
- Added `cleanupOutdatedCaches: true` for better cache management
```typescript
navigateFallbackDenylist: [
/^\/api\//,
/^\/auth\//,
/^\/login/,
/^\/dashboard/
],
```
**Why This Fixes It:**
- Prevents service worker from intercepting authentication flows
- Ensures fresh API responses during login
- Eliminates cache-related redirect conflicts
### 2. Cookie Domain Fix (`server/api/auth/direct-login.post.ts`)
**Changes Made:**
- Removed explicit cookie domain setting
- Let cookies default to the current domain
```typescript
// Before: domain: cookieDomain,
// After: No domain specified (defaults to current domain)
setCookie(event, 'monacousa-session', cookieValue, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge,
path: '/',
});
```
**Why This Fixes It:**
- Eliminates cross-domain cookie issues
- Ensures cookies work correctly in all deployment scenarios
- Follows browser security best practices
### 3. Login Page Refactor (`pages/login.vue`)
**Changes Made:**
- Replaced direct API calls with `useAuth()` composable
- Removed `window.location.href` redirect
- Improved state management integration
```typescript
// Before: window.location.href = response.redirectTo || '/dashboard';
// After: Uses useAuth().login() which handles navigation properly
const result = await login({
username: credentials.value.username,
password: credentials.value.password,
rememberMe: credentials.value.rememberMe
});
```
**Why This Fixes It:**
- Proper SPA navigation without hard page reloads
- Better integration with Nuxt's authentication state
- Eliminates service worker conflicts during navigation
### 4. Nginx Proxy Configuration (`nginx-portal.conf`)
**Changes Made:**
- Added proxy timeout configurations
- Improved buffer settings for better performance
```nginx
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
```
**Why This Fixes It:**
- Prevents 502 errors during authentication flows
- Handles larger response payloads better
- Improves overall proxy stability
## Testing the Fix
### 1. Clear Browser Cache
```bash
# Clear all browser data for portal.monacousa.org
# Or use incognito/private browsing mode
```
### 2. Restart Services
```bash
# Restart Nginx
sudo systemctl restart nginx
# Restart the Nuxt application
# (depends on your deployment method)
```
### 3. Test Login Flow
1. Navigate to `https://portal.monacousa.org/login`
2. Enter valid credentials
3. Verify successful redirect to dashboard
4. Check browser console for errors
### 4. Use Debug Script
```bash
# Run the debug script to test API endpoints
node debug-login.js
```
## Verification Steps
### Browser Console Checks
- No CORS errors should appear
- No 502 Bad Gateway errors
- Service worker should not intercept auth routes
### Network Tab Verification
- Login POST request should return 200
- Session cookie should be set correctly
- Dashboard redirect should work without errors
### Server Logs
- Should show successful login messages
- No cookie domain warnings
- Proper session creation logs
## Troubleshooting
### If Login Still Fails
1. **Check Service Worker**
```javascript
// In browser console
navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(registration => registration.unregister());
});
```
2. **Verify Cookie Settings**
- Check browser dev tools > Application > Cookies
- Ensure `monacousa-session` cookie is set
- Verify cookie domain matches current domain
3. **Check Nginx Logs**
```bash
tail -f /var/log/nginx/portal.monacousa.org.error.log
```
4. **Verify Keycloak Configuration**
- Ensure all environment variables are set
- Test Keycloak connectivity
- Verify client configuration
### Common Issues
1. **Still Getting 502 Errors**
- Check if Nuxt application is running on port 6060
- Verify nginx can connect to backend
- Check firewall settings
2. **Cookies Not Being Set**
- Verify HTTPS is working correctly
- Check if secure flag is appropriate for environment
- Ensure no conflicting cookie policies
3. **Service Worker Issues**
- Clear browser cache completely
- Unregister service worker manually
- Check PWA configuration
## Environment Variables
Ensure these are properly configured:
```env
# Keycloak Configuration
NUXT_KEYCLOAK_ISSUER=https://auth.monacousa.org/realms/monacousa
NUXT_KEYCLOAK_CLIENT_ID=monacousa-portal
NUXT_KEYCLOAK_CLIENT_SECRET=your-secret
# Session Security
NUXT_SESSION_SECRET=your-48-character-secret
NUXT_ENCRYPTION_KEY=your-32-character-key
# Public Configuration
NUXT_PUBLIC_DOMAIN=monacousa.org
```
## Deployment Notes
1. **Nginx Configuration**
- Reload nginx after configuration changes
- Test configuration with `nginx -t`
2. **Application Restart**
- Restart Nuxt application to pick up changes
- Clear any application-level caches
3. **SSL Certificates**
- Ensure SSL certificates are valid
- Verify HTTPS is working correctly
## Success Indicators
✅ Login form submits without errors
✅ User is redirected to dashboard
✅ Session is maintained across page refreshes
✅ No console errors related to authentication
✅ Health check endpoint returns "healthy"
✅ Service worker doesn't interfere with auth flows
## Files Modified
1. `nuxt.config.ts` - PWA service worker configuration
2. `pages/login.vue` - Login page refactor
3. `server/api/auth/direct-login.post.ts` - Cookie domain fix and direct encryption
4. `server/utils/session.ts` - Removed cookie domain from session manager
5. `nginx-portal.conf` - Proxy timeout configuration
6. `debug-login.js` - Debug script (new file)
7. `LOGIN_FIX_SUMMARY.md` - This documentation (new file)
The fixes address the core issues causing login redirect failures and should provide a stable authentication experience.

View File

@ -1,89 +0,0 @@
#!/usr/bin/env node
// Simple debug script to test the login flow
const https = require('https');
const http = require('http');
const BASE_URL = 'https://portal.monacousa.org';
async function makeRequest(url, options = {}) {
return new Promise((resolve, reject) => {
const protocol = url.startsWith('https') ? https : http;
const req = protocol.request(url, {
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'Debug-Script/1.0',
...options.headers
}
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
resolve({
status: res.statusCode,
headers: res.headers,
body: data
});
});
});
req.on('error', reject);
if (options.body) {
req.write(JSON.stringify(options.body));
}
req.end();
});
}
async function testLoginFlow() {
console.log('🔍 Testing MonacoUSA Portal Login Flow');
console.log('=====================================\n');
try {
// Test 1: Health check
console.log('1. Testing health endpoint...');
const health = await makeRequest(`${BASE_URL}/api/health`);
console.log(` Status: ${health.status}`);
console.log(` Response: ${health.body.substring(0, 100)}...\n`);
// Test 2: Session check (should be unauthenticated)
console.log('2. Testing session endpoint...');
const session = await makeRequest(`${BASE_URL}/api/auth/session`);
console.log(` Status: ${session.status}`);
console.log(` Response: ${session.body.substring(0, 100)}...\n`);
// Test 3: Login attempt
console.log('3. Testing direct login...');
const login = await makeRequest(`${BASE_URL}/api/auth/direct-login`, {
method: 'POST',
body: {
username: 'present',
password: 'your-password-here',
rememberMe: false
}
});
console.log(` Status: ${login.status}`);
console.log(` Headers: ${JSON.stringify(login.headers, null, 2)}`);
console.log(` Response: ${login.body.substring(0, 200)}...\n`);
// Test 4: Check if we can access dashboard
console.log('4. Testing dashboard access...');
const dashboard = await makeRequest(`${BASE_URL}/dashboard`, {
headers: {
'Cookie': login.headers['set-cookie'] ? login.headers['set-cookie'].join('; ') : ''
}
});
console.log(` Status: ${dashboard.status}`);
console.log(` Response: ${dashboard.body.substring(0, 100)}...\n`);
} catch (error) {
console.error('❌ Error during testing:', error.message);
}
}
// Run the test
testLoginFlow();

View File

@ -7,23 +7,12 @@ export default defineNuxtRouteMiddleware(async (to) => {
// Use the same auth system as the rest of the app
const { isAuthenticated, checkAuth, user } = useAuth();
console.log('🛡️ Auth middleware running for:', to.path);
// Ensure auth is checked if user isn't loaded
if (!user.value) {
console.log('🔄 User not loaded, checking auth...');
await checkAuth();
}
console.log('✅ Auth middleware check:', {
isAuthenticated: isAuthenticated.value,
user: user.value?.email
});
if (!isAuthenticated.value) {
console.log('❌ User not authenticated, redirecting to login');
return navigateTo('/login');
}
console.log('✅ Auth middleware passed, allowing access to:', to.path);
});

View File

@ -1,4 +0,0 @@
node_modules/
build/
*.log
.env*

View File

@ -1,70 +0,0 @@
# sequential-thinking MCP Server
A Model Context Protocol server
This is a TypeScript-based MCP server that implements a simple notes system. It demonstrates core MCP concepts by providing:
- Resources representing text notes with URIs and metadata
- Tools for creating new notes
- Prompts for generating summaries of notes
## Features
### Resources
- List and access notes via `note://` URIs
- Each note has a title, content and metadata
- Plain text mime type for simple content access
### Tools
- `create_note` - Create new text notes
- Takes title and content as required parameters
- Stores note in server state
### Prompts
- `summarize_notes` - Generate a summary of all stored notes
- Includes all note contents as embedded resources
- Returns structured prompt for LLM summarization
## Development
Install dependencies:
```bash
npm install
```
Build the server:
```bash
npm run build
```
For development with auto-rebuild:
```bash
npm run watch
```
## Installation
To use with Claude Desktop, add the server config:
On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"sequential-thinking": {
"command": "/path/to/sequential-thinking/build/index.js"
}
}
}
```
### Debugging
Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
```bash
npm run inspector
```
The Inspector will provide a URL to access debugging tools in your browser.

View File

@ -1,26 +0,0 @@
{
"name": "sequential-thinking",
"version": "0.1.0",
"description": "A Model Context Protocol server",
"private": true,
"type": "module",
"bin": {
"sequential-thinking": "./build/index.js"
},
"files": [
"build"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "0.6.0"
},
"devDependencies": {
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
}
}

View File

@ -1,243 +0,0 @@
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
interface ThoughtStep {
thoughtNumber: number;
thought: string;
isRevision?: boolean;
revisesThought?: number;
branchFromThought?: number;
branchId?: string;
timestamp: string;
}
interface SequentialThinkingArgs {
thought: string;
nextThoughtNeeded: boolean;
thoughtNumber: number;
totalThoughts: number;
isRevision?: boolean;
revisesThought?: number;
branchFromThought?: number;
branchId?: string;
needsMoreThoughts?: boolean;
}
const isValidSequentialThinkingArgs = (
args: any
): args is SequentialThinkingArgs =>
typeof args === 'object' &&
args !== null &&
typeof args.thought === 'string' &&
typeof args.nextThoughtNeeded === 'boolean' &&
typeof args.thoughtNumber === 'number' &&
typeof args.totalThoughts === 'number' &&
(args.isRevision === undefined || typeof args.isRevision === 'boolean') &&
(args.revisesThought === undefined || typeof args.revisesThought === 'number') &&
(args.branchFromThought === undefined || typeof args.branchFromThought === 'number') &&
(args.branchId === undefined || typeof args.branchId === 'string') &&
(args.needsMoreThoughts === undefined || typeof args.needsMoreThoughts === 'boolean');
class SequentialThinkingServer {
private server: Server;
private thoughtHistory: ThoughtStep[] = [];
private disableLogging: boolean;
constructor() {
this.disableLogging = process.env.DISABLE_THOUGHT_LOGGING === 'true';
this.server = new Server(
{
name: 'sequential-thinking-server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'sequential_thinking',
description: 'Facilitates a detailed, step-by-step thinking process for problem-solving and analysis. Supports dynamic revision, branching, and iterative refinement of thoughts.',
inputSchema: {
type: 'object',
properties: {
thought: {
type: 'string',
description: 'The current thinking step or analysis',
},
nextThoughtNeeded: {
type: 'boolean',
description: 'Whether another thought step is needed to continue the analysis',
},
thoughtNumber: {
type: 'integer',
description: 'Current thought number in the sequence',
minimum: 1,
},
totalThoughts: {
type: 'integer',
description: 'Estimated total number of thoughts needed (can be adjusted dynamically)',
minimum: 1,
},
isRevision: {
type: 'boolean',
description: 'Whether this thought revises or refines previous thinking',
},
revisesThought: {
type: 'integer',
description: 'Which thought number is being reconsidered (if isRevision is true)',
minimum: 1,
},
branchFromThought: {
type: 'integer',
description: 'Thought number from which this branches into alternative reasoning',
minimum: 1,
},
branchId: {
type: 'string',
description: 'Identifier for the branch (e.g., "alternative_approach", "edge_case")',
},
needsMoreThoughts: {
type: 'boolean',
description: 'Whether the total number of thoughts should be increased',
},
},
required: ['thought', 'nextThoughtNeeded', 'thoughtNumber', 'totalThoughts'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'sequential_thinking') {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
if (!isValidSequentialThinkingArgs(request.params.arguments)) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid sequential thinking arguments'
);
}
const args = request.params.arguments;
try {
// Create thought step
const thoughtStep: ThoughtStep = {
thoughtNumber: args.thoughtNumber,
thought: args.thought,
isRevision: args.isRevision,
revisesThought: args.revisesThought,
branchFromThought: args.branchFromThought,
branchId: args.branchId,
timestamp: new Date().toISOString(),
};
// Add to history
this.thoughtHistory.push(thoughtStep);
// Log thought if logging is enabled
if (!this.disableLogging) {
console.error(`[Thought ${args.thoughtNumber}/${args.totalThoughts}]${args.isRevision ? ' (Revision)' : ''}${args.branchId ? ` [Branch: ${args.branchId}]` : ''}: ${args.thought}`);
}
// Prepare response
let responseText = `**Thought ${args.thoughtNumber}/${args.totalThoughts}**`;
if (args.isRevision && args.revisesThought) {
responseText += ` *(Revising thought ${args.revisesThought})*`;
}
if (args.branchId) {
responseText += ` *[Branch: ${args.branchId}]*`;
if (args.branchFromThought) {
responseText += ` *(from thought ${args.branchFromThought})*`;
}
}
responseText += `\n\n${args.thought}`;
if (args.needsMoreThoughts) {
responseText += `\n\n*Note: Analysis indicates more thoughts may be needed beyond the initial estimate of ${args.totalThoughts}.*`;
}
if (args.nextThoughtNeeded) {
responseText += `\n\n*Continuing to next thought...*`;
} else {
responseText += `\n\n*Analysis complete.*`;
// Provide summary if this is the final thought
if (this.thoughtHistory.length > 1) {
responseText += `\n\n**Summary**: Completed sequential thinking process with ${this.thoughtHistory.length} thought steps.`;
const revisions = this.thoughtHistory.filter(t => t.isRevision).length;
const branches = new Set(this.thoughtHistory.filter(t => t.branchId).map(t => t.branchId)).size;
if (revisions > 0) {
responseText += ` Included ${revisions} revision${revisions > 1 ? 's' : ''}.`;
}
if (branches > 0) {
responseText += ` Explored ${branches} alternative branch${branches > 1 ? 'es' : ''}.`;
}
}
}
return {
content: [
{
type: 'text',
text: responseText,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error in sequential thinking process: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Sequential Thinking MCP server running on stdio');
}
}
const server = new SequentialThinkingServer();
server.run().catch(console.error);

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

View File

@ -83,19 +83,10 @@ const getClientIP = (event: any): string => {
};
export default defineEventHandler(async (event) => {
console.log('🔐 Direct login endpoint called at:', new Date().toISOString());
try {
const { username, password, rememberMe } = await readBody(event);
const clientIP = getClientIP(event) || 'unknown';
console.log('📝 Login attempt:', {
username: username ? 'present' : 'missing',
hasPassword: !!password,
rememberMe,
ip: clientIP
});
// Input validation
const validationErrors = validateLoginInput(username, password);
if (validationErrors.length > 0) {
@ -127,11 +118,6 @@ export default defineEventHandler(async (event) => {
});
}
console.log('🔧 Using Keycloak config:', {
issuer: config.keycloak.issuer,
clientId: config.keycloak.clientId,
hasSecret: !!config.keycloak.clientSecret
});
// Direct authentication with Keycloak using Resource Owner Password Credentials flow
const tokenResponse = await fetch(`${config.keycloak.issuer}/protocol/openid-connect/token`, {
@ -178,15 +164,13 @@ export default defineEventHandler(async (event) => {
}
const tokens = await tokenResponse.json();
console.log('✅ Token exchange successful');
// Decode the access token to see what's inside
// Decode the access token to extract user information
let tokenPayload = null;
try {
const tokenParts = tokens.access_token.split('.');
const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString());
tokenPayload = payload;
console.log('🔍 Access token payload:', JSON.stringify(payload, null, 2));
} catch (err) {
console.warn('⚠️ Could not decode access token:', err);
}
@ -208,7 +192,6 @@ export default defineEventHandler(async (event) => {
}
const userInfo = await userResponse.json();
console.log('✅ User info retrieved:', JSON.stringify(userInfo, null, 2));
// Extract groups/roles from multiple possible locations
const extractGroups = (tokenPayload: any, userInfo: any): string[] => {
@ -217,19 +200,16 @@ export default defineEventHandler(async (event) => {
// Check userInfo for groups (from userinfo endpoint)
if (userInfo.groups && Array.isArray(userInfo.groups)) {
allGroups.push(...userInfo.groups);
console.log('📋 Found groups in userInfo.groups:', userInfo.groups);
}
// Check userInfo for roles
if (userInfo.roles && Array.isArray(userInfo.roles)) {
allGroups.push(...userInfo.roles);
console.log('📋 Found roles in userInfo.roles:', userInfo.roles);
}
// Check token payload for realm_access.roles
if (tokenPayload?.realm_access?.roles && Array.isArray(tokenPayload.realm_access.roles)) {
allGroups.push(...tokenPayload.realm_access.roles);
console.log('📋 Found roles in token realm_access.roles:', tokenPayload.realm_access.roles);
}
// Check token payload for resource_access roles
@ -238,7 +218,6 @@ export default defineEventHandler(async (event) => {
const clientRoles = tokenPayload.resource_access[clientId]?.roles;
if (clientRoles && Array.isArray(clientRoles)) {
allGroups.push(...clientRoles);
console.log(`📋 Found roles in token resource_access.${clientId}.roles:`, clientRoles);
}
});
}
@ -246,7 +225,6 @@ export default defineEventHandler(async (event) => {
// Check token payload for groups claim
if (tokenPayload?.groups && Array.isArray(tokenPayload.groups)) {
allGroups.push(...tokenPayload.groups);
console.log('📋 Found groups in token.groups:', tokenPayload.groups);
}
// Remove duplicates and filter out default Keycloak roles
@ -254,22 +232,17 @@ export default defineEventHandler(async (event) => {
!['default-roles-monacousa', 'offline_access', 'uma_authorization'].includes(group)
);
console.log('📋 Final extracted groups:', uniqueGroups);
return uniqueGroups;
};
// Tier determination logic - admin > board > user priority
const determineTier = (groups: string[]): 'user' | 'board' | 'admin' => {
console.log('🎯 Determining tier from groups:', groups);
if (groups.includes('admin')) {
console.log('🎯 User has admin tier');
return 'admin';
}
if (groups.includes('board')) {
console.log('🎯 User has board tier');
return 'board';
}
console.log('🎯 User has default user tier');
return 'user'; // Default tier
};
@ -301,12 +274,6 @@ export default defineEventHandler(async (event) => {
lastActivity: Date.now()
};
console.log('📊 Session data size check:', {
userTier,
groupCount: userGroups.length,
sessionSize: JSON.stringify(sessionData).length
});
// Create session with server-side storage
const sessionManager = createSessionManager();
@ -318,9 +285,6 @@ export default defineEventHandler(async (event) => {
const cookieParts = cookieString.split(';')[0].split('=');
const sessionId = cookieParts[1];
console.log(`🍪 Setting session cookie (Remember Me: ${!!rememberMe}), session ID: ${sessionId.substring(0, 8)}...`);
console.log(`📏 Cookie size: ${sessionId.length} chars (much smaller!)`);
// Set the cookie using Nuxt's setCookie helper
const maxAge = !!rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24 * 7; // 30 days vs 7 days
setCookie(event, 'monacousa-session', sessionId, {
@ -331,8 +295,6 @@ export default defineEventHandler(async (event) => {
path: '/',
});
console.log('✅ Session cookie set successfully');
} catch (cookieError) {
console.error('❌ Failed to set session cookie:', cookieError);
throw createError({
@ -345,8 +307,6 @@ export default defineEventHandler(async (event) => {
clearFailedAttempts(clientIP);
console.log('✅ Login successful for user:', userInfo.email);
console.log('🎯 User tier assigned:', userTier);
console.log('📋 User groups assigned:', userGroups);
// Add a small delay to ensure cookie is set
await new Promise(resolve => setTimeout(resolve, 100));
@ -355,8 +315,6 @@ export default defineEventHandler(async (event) => {
setResponseStatus(event, 200);
setHeader(event, 'Content-Type', 'application/json');
console.log('📤 Sending success response');
// Return a minimal response to prevent 502 errors
return {
success: true,

View File

@ -12,12 +12,16 @@ const sessionStore = new Map<string, {
// Cleanup expired sessions every 5 minutes
setInterval(() => {
const now = Date.now();
let cleanedCount = 0;
for (const [sessionId, session] of sessionStore.entries()) {
if (now > session.expiresAt) {
sessionStore.delete(sessionId);
console.log('🧹 Cleaned up expired session:', sessionId.substring(0, 8) + '...');
cleanedCount++;
}
}
if (cleanedCount > 0) {
console.log(`🧹 Cleaned up ${cleanedCount} expired sessions`);
}
}, 5 * 60 * 1000);
export class SessionManager {
@ -43,9 +47,6 @@ export class SessionManager {
rememberMe
});
console.log(`🍪 Creating session cookie (Remember Me: ${rememberMe}) with session ID: ${sessionId.substring(0, 8)}...`);
console.log(`📊 Session store size: ${sessionStore.size} sessions`);
return serialize(this.cookieName, sessionId, {
httpOnly: true,
secure: true,
@ -57,7 +58,6 @@ export class SessionManager {
getSession(cookieHeader?: string): SessionData | null {
if (!cookieHeader) {
console.log('❌ No cookie header provided');
return null;
}
@ -65,21 +65,16 @@ export class SessionManager {
const sessionId = cookies[this.cookieName];
if (!sessionId) {
console.log('❌ No session cookie found');
return null;
}
console.log(`🔍 Looking up session: ${sessionId.substring(0, 8)}...`);
const sessionEntry = sessionStore.get(sessionId);
if (!sessionEntry) {
console.log('❌ Session not found in store');
return null;
}
// Check if session is expired
if (Date.now() > sessionEntry.expiresAt) {
console.log('❌ Session expired, removing from store');
sessionStore.delete(sessionId);
return null;
}
@ -87,7 +82,6 @@ export class SessionManager {
// Update last activity
sessionEntry.data.lastActivity = Date.now();
console.log('✅ Session found and valid for user:', sessionEntry.data.user.email);
return sessionEntry.data;
}