Rebuild Mode1Svelte with SvelteKit + Node.js adapter

- Migrated from Bun/Hono to modern SvelteKit tech stack
- Implemented complete authentication module (Azure MSAL + PocketBase)
- Created 5 API endpoints for auth operations (config, graph token, validate, refresh, compare)
- Built interactive Svelte dashboard with mobile optimization (iPhone 16)
- Added Node.js adapter (@sveltejs/adapter-node) for production deployment
- Integrated Tailwind CSS with responsive design
- Fixed orchestrator to spawn Mode1Svelte with Node.js on port 3005
- Updated systemd service (job-info-test.service) to start orchestrator on port 3006
- Port configuration locked: 3005 = Mode1Svelte, 3006 = Orchestrator
- Full TypeScript type safety throughout application
- Services auto-managed by orchestrator with systemd integration
This commit is contained in:
2026-01-23 02:18:34 +00:00
parent d8d53ece93
commit 7b997dc354
33 changed files with 3793 additions and 1462 deletions
+23
View File
@@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
+1
View File
@@ -0,0 +1 @@
engine-strict=true
+275 -120
View File
@@ -1,158 +1,313 @@
# Mode1Svelte - Auth Module Test Suite # Mode1Svelte - SvelteKit Auth Testing Suite
Self-contained test environment for the auth-module. This version is identical to Mode1 currently, serving as a foundation for future Svelte integration. A complete SvelteKit-based testing environment for authentication workflows with Microsoft Azure MSAL and PocketBase. This is a modern rewrite of Mode1 using the latest SvelteKit technology stack.
## Structure ## Tech Stack
``` - **Framework**: SvelteKit 2.x with TypeScript
Mode1Svelte/ - **Backend**: Node.js Adapter (production-ready)
├── auth-module/ # Local copy of auth-module (not linked to root) - **Styling**: Tailwind CSS 4
├── backend/ # Backend server with auth endpoints - **Authentication**:
└── server.ts # Hono app with Graph token and PB validation APIs - Azure MSAL Node (@azure/msal-node) - Microsoft Graph token management
├── frontend/ # Frontend test interface - PocketBase - User OAuth2 and token validation
│ └── index.html # Interactive test dashboard - **Build Tool**: Vite (integrated with SvelteKit)
├── logs/ # Application logs - **Runtime**: Node.js 18+
├── package.json # Dependencies (auth module + Hono + PocketBase)
└── bun.lock # Locked dependencies
```
## Features ## Features
**Frontend Auth (PocketBaseAuth)** ### ✅ Frontend Authentication (PocketBase)
- OAuth2 login initialization - OAuth2 login initialization with Microsoft
- Auth state retrieval - Auth state management and retrieval
- Token refresh - Token refresh and validation
- Logout - Session persistence
- Automatic logout on invalid tokens
**Backend Auth (GraphTokenManager)** ### ✅ Backend Token Management (Microsoft Graph)
- Microsoft Graph token acquisition - App-only token acquisition via MSAL
- Token caching (with 60s expiration buffer) - Automatic token caching with 60-second expiration buffer
- Cache validity checking - Token refresh on demand
- Cache refresh - JWT payload inspection
**PocketBase Validation (PocketBaseValidator)** ### ✅ Token Validation & Comparison
- User token validation - PocketBase user token validation
- User record retrieval - User record retrieval from tokens
- Token comparison (Graph vs PocketBase) - Side-by-side token comparison (Graph vs PocketBase)
- Token payload analysis
## Running Mode1Svelte ### ✅ Interactive Testing Dashboard
- Real-time authentication status
- Visual token testing interface
- API endpoint testing
- Response inspection
### Via Orchestrator (recommended) ## Project Structure
```bash ```
# Switch to Mode1Svelte Mode1Svelte/
curl -X POST http://localhost:3006/switch/Mode1Svelte ├── src/
│ ├── lib/
# Check status │ │ └── auth/
curl http://localhost:3006/status │ │ ├── types.ts # TypeScript interfaces
│ │ ├── backend.ts # Backend auth classes (MSAL, PocketBase)
│ │ └── frontend.ts # Frontend PocketBase auth class
│ ├── routes/
│ │ ├── +layout.svelte # App layout
│ │ ├── +page.svelte # Main dashboard
│ │ └── api/
│ │ └── auth/
│ │ ├── config/ # GET /api/auth/config
│ │ ├── graph/ # GET /api/auth/graph/token
│ │ ├── validate-pb-token/ # POST /api/auth/validate-pb-token
│ │ ├── refresh-graph/ # POST /api/auth/refresh-graph
│ │ └── compare-tokens/ # POST /api/auth/compare-tokens
│ └── app.css # Tailwind directives
├── static/ # Static assets
├── package.json # Dependencies
├── svelte.config.js # SvelteKit config (Node adapter)
├── tailwind.config.js # Tailwind CSS config
├── vite.config.ts # Vite config
└── tsconfig.json # TypeScript config
``` ```
### Direct ## Installation
```bash ```bash
cd Mode1Svelte cd Mode1Svelte
bun install npm install
PORT=3005 bun run backend/server.ts
``` ```
Then visit: `http://localhost:3005` ## Environment Setup
## Future: Svelte Integration Ensure your `.env` file or environment variables are set:
This project is set up as an independent Mode to eventually convert to SvelteKit while maintaining the same API endpoints and auth-module integration.
## Structure
```
Mode1/
├── auth-module/ # Local copy of auth-module (not linked to root)
├── backend/ # Backend server with auth endpoints
│ └── server.ts # Hono app with Graph token and PB validation APIs
├── frontend/ # Frontend test interface
│ └── index.html # Interactive test dashboard
├── logs/ # Application logs
├── package.json # Dependencies (auth module + Hono + PocketBase)
└── bun.lock # Locked dependencies
```
## Features Tested
**Frontend Auth (PocketBaseAuth)**
- OAuth2 login initialization
- Auth state retrieval
- Token refresh
- Logout
**Backend Auth (GraphTokenManager)**
- Microsoft Graph token acquisition
- Token caching (with 60s expiration buffer)
- Cache validity checking
- Cache refresh
**PocketBase Validation (PocketBaseValidator)**
- User token validation
- User record retrieval
## Running Mode1
### Via Orchestrator (recommended)
```bash ```bash
# Switch to Mode1 export CLIENT_ID="<Azure Client ID>"
curl -X POST http://localhost:3006/switch/Mode1 export TENANT_ID="<Azure Tenant ID>"
export CLIENT_SECRET="<Azure Client Secret>"
# Check status export PB_URL="<PocketBase URL>"
curl http://localhost:3006/status export PB_DB="<PocketBase Database URL>"
export PORT=5173
``` ```
### Direct For development, create `/home/admin/secrets/.env`:
```
CLIENT_ID=your_client_id
TENANT_ID=your_tenant_id
CLIENT_SECRET=your_client_secret
PB_URL=https://pocketbase.example.com
PB_DB=https://pocketbase.example.com
```
## Running the Application
### Development Mode
```bash ```bash
# Install dependencies (already done) npm run dev
bun install
# Start server
bun run backend/server.ts
# Access dashboard
open http://localhost:3005
``` ```
Starts the dev server at `http://localhost:5173` with hot reloading.
### Production Build
```bash
npm run build
npm start
```
Builds the application and runs the Node.js server.
### Type Checking
```bash
npm run check
```
Run TypeScript type checking across the project.
## API Endpoints ## API Endpoints
### Configuration ### GET `/api/auth/config`
- `GET /api/auth/config` - Get frontend configuration Returns frontend-safe configuration for PocketBase OAuth setup.
### Graph Token Management **Response:**
- `GET /api/auth/graph/token` - Get current Graph token (with expiration) ```json
- `POST /api/auth/refresh-graph` - Force refresh Graph token {
"pbUrl": "https://pocketbase.example.com",
"provider": "microsoft",
"collection": "Users"
}
```
### PocketBase Validation ### GET `/api/auth/graph/token`
- `POST /api/auth/validate-pb-token` - Validate a PocketBase token Fetches a Microsoft Graph token for backend API access.
### Health **Response:**
- `GET /health` - Health check ```json
{
"success": true,
"token": "eyJ0eXAiOiJKV1Q...",
"fullToken": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"expiresOn": "2026-01-23T02:15:00.000Z",
"isValid": true,
"tokenDetails": {
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/tenant-id",
"scp": "..."
}
}
```
## Environment Variables ### POST `/api/auth/validate-pb-token`
Validates a PocketBase user token and retrieves user information.
Uses `/home/admin/secrets/.env` via auth-module: **Request:**
- `CLIENT_ID` - Microsoft application ID ```json
- `TENANT_ID` - Azure tenant ID {
- `CLIENT_SECRET` - Microsoft client secret "pbToken": "user_token_string"
- `PB_URL` - PocketBase frontend URL }
- `PB_DB` - PocketBase backend URL ```
## Dependencies **Response:**
```json
{
"valid": true,
"user": {
"id": "user_id",
"email": "user@example.com",
"name": "User Name"
}
}
```
All dependencies are installed locally in `node_modules/`: ### POST `/api/auth/refresh-graph`
- `hono` - Web framework Clears the cached Graph token and acquires a new one.
- `pocketbase` - PocketBase client
- `@azure/msal-node` - Microsoft authentication
- `dotenv` - Environment variable loading
## Notes **Response:**
```json
{
"success": true,
"refreshed": true,
"expiresOn": "2026-01-23T02:15:00.000Z"
}
```
- This Mode1 is **completely self-contained** with its own auth-module copy ### POST `/api/auth/compare-tokens`
- No dependencies on root auth-module Compares Graph token (backend) and PocketBase token (user) to demonstrate their differences.
- Safe to modify and test independently
- All secrets come from `/home/admin/secrets/.env` **Request:**
```json
{
"pbToken": "user_token_string"
}
```
**Response:**
```json
{
"success": true,
"comparison": {
"graph_token": {
"issuer": "https://sts.windows.net/tenant",
"audience": "https://graph.microsoft.com",
"app": "App Name",
"scopes": "...",
"issued_at": "2026-01-23T01:15:00.000Z",
"expires_at": "2026-01-23T02:15:00.000Z",
"type": "Microsoft Graph Token (Backend/App-only)",
"source": "@azure/msal-node",
"purpose": "Backend API calls to Microsoft Graph"
},
"pocketbase_token": {
"type": "PocketBase User Token (Frontend/User)",
"source": "PocketBase OAuth2 flow",
"authenticated_user": "user@example.com"
},
"are_different": true
}
}
```
## Authentication Flow
### Frontend (Browser)
1. User clicks "Login with Microsoft"
2. PocketBase initiates OAuth2 flow with Microsoft provider
3. Microsoft redirects back with authorization code
4. PocketBase exchanges code for user token
5. Token stored in PocketBase auth store
6. User dashboard updates with authenticated state
### Backend (Server)
1. Application starts with MSAL configuration
2. On first API request, acquires Graph token using client credentials
3. Token cached for 55 minutes (60-minute lifetime - 5-minute buffer)
4. Subsequent requests use cached token
5. Token automatically refreshed when expired
## Development Notes
- **CORS**: Configured for `localhost:5173` and production URLs
- **Token Caching**: Graph tokens cached in memory; refresh on demand with `/api/auth/refresh-graph`
- **Error Handling**: Comprehensive error messages with specific failure reasons
- **Type Safety**: Full TypeScript support throughout frontend and backend
- **SvelteKit Routing**: File-based routing with `+page.svelte` and `+server.ts` files
## Comparison with Mode1 (Hono/Bun)
| Feature | Mode1Svelte | Mode1 |
|---------|-------------|--------|
| Framework | SvelteKit 2 | SvelteKit 4 (old) |
| Backend | Node.js Adapter | Hono + Bun |
| Frontend | Svelte Components | HTML + Vanilla JS |
| Styling | Tailwind CSS | CSS (custom) |
| Build Tool | Vite | Vite |
| API Routes | `/routes/api/` | `/routes/api/` |
| Auth Module | `/lib/auth/` | `/routes/api/` |
| Type Safety | Full TypeScript | TypeScript (partial) |
| Production Ready | ✅ Yes | ⚠️ Experimental |
## Troubleshooting
### Token Acquisition Fails
- Verify `CLIENT_ID`, `TENANT_ID`, and `CLIENT_SECRET` are correct
- Ensure the Azure application is configured for client credentials flow
- Check that the application has Graph API permissions
### PocketBase Token Validation Fails
- Verify `PB_URL` and `PB_DB` are correct
- Ensure the user is properly logged in via OAuth
- Check that PocketBase is running and accessible
### Login Button Not Appearing
- Ensure JavaScript is enabled
- Check browser console for errors
- Verify that `loginBtn` HTML element ID matches configuration
## Links
- [SvelteKit Documentation](https://svelte.dev/docs/kit)
- [Azure MSAL Node](https://github.com/AzureAD/microsoft-authentication-library-for-js)
- [PocketBase Documentation](https://pocketbase.io/docs/)
- [Tailwind CSS](https://tailwindcss.com/)
## License
Private - Job Info Testing Suite
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
-248
View File
@@ -1,248 +0,0 @@
# Auth Module
Standalone authentication module for PocketBase OAuth2 + Microsoft Graph integration.
## Features
- **Frontend**: PocketBase OAuth2 authentication with Microsoft provider
- **Backend**: Microsoft Graph token management with automatic caching
- **No dependencies on project-specific code** - Use in any project
## Installation
Copy the `auth-module` folder to your project:
```bash
cp -r auth-module /path/to/your/project/
```
Install dependencies:
```bash
bun add pocketbase @azure/msal-node
```
## Frontend Usage
### Basic Setup
**Important**: The frontend is browser-side code. It must receive `pbUrl` from the consuming application (not from the backend).
```typescript
import { PocketBaseAuth } from './auth-module/frontend';
// In your project, load PB_URL from environment/config and pass it to frontend
const auth = new PocketBaseAuth({
pbUrl: process.env.PB_URL, // Required - must be provided by consuming application
collection: 'Users',
provider: 'microsoft',
loginContainerId: 'loginContainer',
userDisplayNameId: 'userDisplayName',
userEmailId: 'userEmailValue',
loginBtnId: 'loginBtn',
loginErrorId: 'loginError',
});
```
### HTML Required
```html
<!-- Login container (shown when not authenticated) -->
<div id="loginContainer" class="hidden">
<button id="loginBtn">Login with Microsoft</button>
<div id="loginError" class="hidden"></div>
</div>
<!-- User display (shown when authenticated) -->
<div id="userDisplayName"></div>
<div id="userEmailValue"></div>
```
### Event Callbacks
```typescript
const auth = new PocketBaseAuth(config, {
onAuthSuccess: (state) => {
console.log('Logged in:', state.user.email);
// Initialize other systems here
},
onAuthFailure: (error) => {
console.error('Login failed:', error);
},
onTokenUpdate: (state) => {
console.log('Token refreshed');
},
onUiUpdate: (state) => {
console.log('UI updated');
},
});
```
### Methods
```typescript
// Get current auth state
const state = auth.getAuthState();
console.log(state.isAuthenticated, state.user);
// Check token status
const status = await auth.checkTokenStatus();
// Logout
auth.logout();
// Get raw PocketBase instance if needed
const pb = auth.getPocketBase();
```
## Backend Usage
### Setup (server.ts)
```typescript
import { GraphTokenManager, PocketBaseValidator, BackendAuth } from './auth-module/backend';
// Option 1: Use individual managers
const graphMgr = new GraphTokenManager({
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
});
const pbValidator = new PocketBaseValidator(process.env.PB_DB);
// Option 2: Use combined manager
const backendAuth = new BackendAuth({
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
}, process.env.PB_DB);
```
### Get Graph Token
```typescript
// Get token string
const token = await graphMgr.getToken();
// Get token with expiration
const { token, expiresOnISO } = await graphMgr.getTokenWithExpiry();
// Check if cached and valid
if (graphMgr.isTokenValid()) {
console.log('Using cached token');
}
// Clear cache (force refresh)
graphMgr.clearCache();
```
### Validate PocketBase Token
```typescript
// Validate token
const isValid = await pbValidator.validateUserToken(token);
// Get user record
const user = await pbValidator.getUserRecord(token);
// Get PocketBase instance
const pb = pbValidator.getPocketBase();
```
### Endpoint Example
```typescript
app.get('/api/graph/status', async (c) => {
try {
const { token, expiresOnISO } = await graphMgr.getTokenWithExpiry();
return c.json({
active: true,
expiresOnISO,
});
} catch (error) {
return c.json(
{ active: false, error: error.message },
500
);
}
});
app.post('/api/submit', async (c) => {
const body = await c.req.json();
const pbToken = body.pbToken;
if (!pbToken) {
return c.json({ error: 'Missing pbToken' }, 401);
}
const isValid = await pbValidator.validateUserToken(pbToken);
if (!isValid) {
return c.json({ error: 'Invalid token' }, 401);
}
// Token is valid, proceed with business logic
// ...
});
```
## Environment Variables
Required in `.env` file:
```env
CLIENT_ID=your-microsoft-app-id
TENANT_ID=your-azure-tenant-id
CLIENT_SECRET=your-microsoft-client-secret
PB_DB=https://your-pocketbase-instance.com
PB_URL=https://your-pocketbase-instance.com
```
## Configuration
### Frontend Config Options
```typescript
interface AuthConfig {
pbUrl: string; // PocketBase URL (required)
collection?: string; // Auth collection (default: Users)
provider?: string; // OAuth provider (default: microsoft)
loginContainerId?: string; // ID of login container element
userDisplayNameId?: string; // ID of user name display element
userEmailId?: string; // ID of user email display element
loginBtnId?: string; // ID of login button element
loginErrorId?: string; // ID of error message element
}
```
### Backend Config Options
```typescript
interface BackendAuthConfig {
clientId?: string; // Microsoft app ID (or CLIENT_ID env var)
tenantId?: string; // Azure tenant ID (or TENANT_ID env var)
clientSecret?: string; // Client secret (or CLIENT_SECRET env var)
}
```
## TypeScript
The module exports TypeScript types for type safety:
```typescript
import { AuthState, AuthConfig, GraphTokenCache } from './auth-module/types';
const state: AuthState = auth.getAuthState();
```
## Notes
- **Frontend** manages user authentication only
- **Backend** manages Graph API tokens (never exposed to client)
- Tokens are cached in-memory on backend with automatic refresh
- Module does not include project-specific features (alerts, etc.)
- Each project can implement its own business logic on top
## License
Same as parent project
-226
View File
@@ -1,226 +0,0 @@
import { Hono } from 'hono';
import { serveStatic } from 'hono/bun';
import { cors } from 'hono/cors';
import path from 'path';
import { fileURLToPath } from 'url';
import { readFile } from 'fs/promises';
import { existsSync } from 'fs';
import { AuthConfigManager, BackendAuth } from '../auth-module/backend';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = new Hono();
const PORT = process.env.PORT || 3005;
// Initialize auth
const backendAuth = new BackendAuth({
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
}, process.env.PB_DB);
// CORS Configuration - Allow requests from frontend domains
app.use('*', cors({
origin: ['https://ji-test.ccllc.pro', 'http://localhost:3005', 'http://localhost:5173'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}));
// Serve bundled auth module JavaScript
app.get('/auth-module.js', async (c) => {
const filePath = path.join(__dirname, '../frontend/dist/auth.js');
if (existsSync(filePath)) {
const content = await readFile(filePath);
c.header('Content-Type', 'application/javascript');
return c.body(content);
}
return c.notFound();
});
// Serve frontend static files
app.use('/', serveStatic({ root: path.join(__dirname, '../frontend') }));
// API: Get frontend config
app.get('/api/auth/config', (c) => {
try {
const config = AuthConfigManager.getFrontendConfig();
return c.json(config);
} catch (error) {
return c.json(
{ error: 'Failed to get config', message: error instanceof Error ? error.message : 'Unknown error' },
500
);
}
});
// API: Get Graph token status
app.get('/api/auth/graph/token', async (c) => {
try {
const { token, expiresOnISO } = await backendAuth.graphTokenManager.getTokenWithExpiry();
if (!token) {
return c.json({ success: false, error: 'Failed to acquire Graph token' }, 500);
}
// Decode JWT to show token details (for verification)
const parts = token.split('.');
let payload = {};
if (parts.length === 3) {
try {
const decoded = JSON.parse(Buffer.from(parts[1], 'base64').toString());
payload = {
aud: decoded.aud,
iss: decoded.iss,
scp: decoded.scp,
app_displayname: decoded.app_displayname,
iat: new Date(decoded.iat * 1000).toISOString(),
exp: new Date(decoded.exp * 1000).toISOString(),
};
} catch (e) {
// Silent fail - just show truncated token
}
}
return c.json({
success: true,
token: token.substring(0, 50) + '...',
fullToken: token, // Include full token for testing
expiresOn: expiresOnISO,
isValid: backendAuth.graphTokenManager.isTokenValid(),
tokenDetails: Object.keys(payload).length > 0 ? payload : 'Could not decode',
});
} catch (error) {
return c.json(
{ success: false, error: error instanceof Error ? error.message : 'Failed to acquire token' },
500
);
}
});
// API: Validate PocketBase token
app.post('/api/auth/validate-pb-token', async (c) => {
try {
const body = await c.req.json();
const token = body.pbToken;
if (!token) {
return c.json({ valid: false, error: 'No token provided' }, 400);
}
const isValid = await backendAuth.pbValidator.validateUserToken(token);
const user = isValid ? await backendAuth.pbValidator.getUserRecord(token) : null;
return c.json({
valid: isValid,
user: user ? {
id: user.id,
email: user.email,
name: user.name,
} : null,
});
} catch (error) {
return c.json(
{ valid: false, error: error instanceof Error ? error.message : 'Validation failed' },
500
);
}
});
// API: Refresh Graph token (test cache)
app.post('/api/auth/refresh-graph', async (c) => {
try {
backendAuth.graphTokenManager.clearCache();
const { token, expiresOnISO } = await backendAuth.graphTokenManager.getTokenWithExpiry();
return c.json({
success: true,
refreshed: true,
expiresOn: expiresOnISO,
});
} catch (error) {
return c.json(
{ success: false, error: error instanceof Error ? error.message : 'Refresh failed' },
500
);
}
});
// API: Compare PB and Graph tokens
app.post('/api/auth/compare-tokens', async (c) => {
try {
const body = await c.req.json();
const pbToken = body.pbToken;
// Get Graph token
const { token: graphToken, expiresOnISO: graphExpires } = await backendAuth.graphTokenManager.getTokenWithExpiry();
if (!graphToken) {
return c.json({ success: false, error: 'Failed to acquire Graph token' }, 500);
}
// Decode both tokens to compare
const decodeJWT = (token: string) => {
try {
const parts = token.split('.');
if (parts.length !== 3) return null;
return JSON.parse(Buffer.from(parts[1], 'base64').toString());
} catch {
return null;
}
};
const graphPayload = decodeJWT(graphToken);
// Validate PB token to get actual user
let authenticatedUser = 'Unknown';
if (pbToken) {
try {
const isValid = await backendAuth.pbValidator.validateUserToken(pbToken);
if (isValid) {
const userRecord = await backendAuth.pbValidator.getUserRecord(pbToken);
authenticatedUser = userRecord?.email || userRecord?.id || 'Unknown';
}
} catch {
authenticatedUser = 'Token invalid or expired';
}
}
return c.json({
success: true,
comparison: {
graph_token: {
issuer: graphPayload?.iss || 'N/A',
audience: graphPayload?.aud || 'N/A',
app: graphPayload?.app_displayname || 'N/A',
scopes: graphPayload?.scp || 'N/A',
issued_at: graphPayload?.iat ? new Date(graphPayload.iat * 1000).toISOString() : 'N/A',
expires_at: graphPayload?.exp ? new Date(graphPayload.exp * 1000).toISOString() : 'N/A',
type: 'Microsoft Graph Token (Backend/App-only)',
source: '@azure/msal-node',
purpose: 'Backend API calls to Microsoft Graph',
},
pocketbase_token: {
note: 'User token from PocketBase OAuth2',
type: 'PocketBase User Token (Frontend/User)',
source: 'PocketBase OAuth2 flow',
purpose: 'User authentication and authorization',
authenticated_user: authenticatedUser,
},
are_different: true,
explanation: 'These are completely different tokens from different systems: Graph token is for app-to-app Microsoft API access, PocketBase token is for user authentication in the application.',
},
});
} catch (error) {
return c.json(
{ success: false, error: error instanceof Error ? error.message : 'Failed to compare tokens' },
500
);
}
});
// Start server
console.log(`Mode1 Auth Test Server running on port ${PORT}`);
export default {
port: PORT,
fetch: app.fetch,
};
-226
View File
@@ -1,226 +0,0 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "mode1-auth-test",
"dependencies": {
"@azure/msal-node": "^5.0.2",
"dotenv": "^17.2.3",
"hono": "^4.10.8",
"pocketbase": "^0.26.5",
},
"devDependencies": {
"@types/bun": "latest",
"typescript": "^5",
"vite": "^7.3.1",
},
"peerDependencies": {
"bun": "latest",
},
},
},
"packages": {
"@azure/msal-common": ["@azure/msal-common@16.0.2", "", {}, "sha512-ZJ/UR7lyqIntURrIJCyvScwJFanM9QhJYcJCheB21jZofGKpP9QxWgvADANo7UkresHKzV+6YwoeZYP7P7HvUg=="],
"@azure/msal-node": ["@azure/msal-node@5.0.2", "", { "dependencies": { "@azure/msal-common": "16.0.2", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-3tHeJghckgpTX98TowJoXOjKGuds0L+FKfeHJtoZFl2xvwE6RF65shZJzMQ5EQZWXzh3sE1i9gE+m3aRMachjA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-27rypIapNkYboOSylkf1tD9UW9Ado2I+P1NBL46Qz29KmOjTL6WuJ7mHDC5O66CYxlOkF5r93NPDAC3lFHYBXw=="],
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-I82xGzPkBxzBKgbl8DsA0RfMQCWTWjNmLjIEkW1ECiv3qK02kHGQ5FGUr/29L/SuvnGsULW4tBTRNZiMzL37nA=="],
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-nqtr+pTsHqusYpG2OZc6s+AmpWDB/FmBvstrK0y5zkti4OqnCuu7Ev2xNjS7uyb47NrAFF40pWqkpaio5XEd7w=="],
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-YaQEAYjBanoOOtpqk/c5GGcfZIyxIIkQ2m1TbHjedRmJNwxzWBhGinSARFkrRIc3F8pRIGAopXKvJ/2rjN1LzQ=="],
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-FR+iJt17rfFgYgpxL3M67AUwujOgjw52ZJzB9vElI5jQXNjTyOKf8eH4meSk4vjlYF3h/AjKYd6pmN0OIUlVKQ=="],
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-egfngj0dfJ868cf30E7B+ye9KUWSebYxOG4l9YP5eWeMXCtenpenx0zdKtAn9qxJgEJym5AN6trtlk+J6x8Lig=="],
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-jRmnX18ak8WzqLrex3siw0PoVKyIeI5AiCv4wJLgSs7VKfOqrPycfHIWfIX2jdn7ngqbHFPzI09VBKANZ4Pckg=="],
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-YeXcJ9K6vJAt1zSkeA21J6pTe7PgDMLTHKGI3nQBiMYnYf7Ob3K+b/ChSCznrJG7No5PCPiQPg4zTgA+BOTmSA=="],
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-7FjVnxnRTp/AgWqSQRT/Vt9TYmvnZ+4M+d9QOKh/Lf++wIFXFGSeAgD6bV1X/yr2UPVmZDk+xdhr2XkU7l2v3w=="],
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.6", "", { "os": "win32", "cpu": "x64" }, "sha512-Sr1KwUcbB0SEpnSPO22tNJppku2khjFluEst+mTGhxHzAGQTQncNeJxDnt3F15n+p9Q+mlcorxehd68n1siikQ=="],
"@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.6", "", { "os": "win32", "cpu": "x64" }, "sha512-PFUa7JL4lGoyyppeS4zqfuoXXih+gSE0XxhDMrCPVEUev0yhGNd/tbWBvcdpYnUth80owENoGjc8s5Knopv9wA=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.3", "", { "os": "android", "cpu": "arm" }, "sha512-qyX8+93kK/7R5BEXPC2PjUt0+fS/VO2BVHjEHyIEWiYn88rcRBHmdLgoJjktBltgAf+NY7RfCGB1SoyKS/p9kg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.3", "", { "os": "android", "cpu": "arm64" }, "sha512-6sHrL42bjt5dHQzJ12Q4vMKfN+kUnZ0atHHnv4V0Wd9JMTk7FDzSY35+7qbz3ypQYMBPANbpGK7JpnWNnhGt8g=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1ht2SpGIjEl2igJ9AbNpPIKzb1B5goXOcmtD0RFxnwNuMxqkR6AUaaErZz+4o+FKmzxcSNBOLrzsICZVNYa1Rw=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-FYZ4iVunXxtT+CZqQoPVwPhH7549e/Gy7PIRRtq4t5f/vt54pX6eG9ebttRH6QSH7r/zxAFA4EZGlQ0h0FvXiA=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-M/mwDCJ4wLsIgyxv2Lj7Len+UMHd4zAXu4GQ2UaCdksStglWhP61U3uowkaYBQBhVoNpwx5Hputo8eSqM7K82Q=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5jZT2c7jBCrMegKYTYTpni8mg8y3uY8gzeq2ndFOANwNuC/xJbVAoGKR9LhMDA0H3nIhvaqUoBEuJoICBudFrA=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.3", "", { "os": "linux", "cpu": "arm" }, "sha512-YeGUhkN1oA+iSPzzhEjVPS29YbViOr8s4lSsFaZKLHswgqP911xx25fPOyE9+khmN6W4VeM0aevbDp4kkEoHiA=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.3", "", { "os": "linux", "cpu": "arm" }, "sha512-eo0iOIOvcAlWB3Z3eh8pVM8hZ0oVkK3AjEM9nSrkSug2l15qHzF3TOwT0747omI6+CJJvl7drwZepT+re6Fy/w=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-DJay3ep76bKUDImmn//W5SvpjRN5LmK/ntWyeJs/dcnwiiHESd3N4uteK9FDLf0S0W8E6Y0sVRXpOCoQclQqNg=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-BKKWQkY2WgJ5MC/ayvIJTHjy0JUGb5efaHCUiG/39sSUvAYRBaO3+/EK0AZT1RF3pSj86O24GLLik9mAYu0IJg=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.3", "", { "os": "linux", "cpu": "none" }, "sha512-Q9nVlWtKAG7ISW80OiZGxTr6rYtyDSkauHUtvkQI6TNOJjFvpj4gcH+KaJihqYInnAzEEUetPQubRwHef4exVg=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.3", "", { "os": "linux", "cpu": "none" }, "sha512-2H5LmhzrpC4fFRNwknzmmTvvyJPHwESoJgyReXeFoYYuIDfBhP29TEXOkCJE/KxHi27mj7wDUClNq78ue3QEBQ=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9S542V0ie9LCTznPYlvaeySwBeIEa7rDBgLHKZ5S9DBgcqdJYburabm8TqiqG6mrdTzfV5uttQRHcbKff9lWtA=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ukxw+YH3XXpcezLgbJeasgxyTbdpnNAkrIlFGDl7t+pgCxZ89/6n1a+MxlY7CegU+nDgrgdqDelPRNQ/47zs0g=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.3", "", { "os": "linux", "cpu": "none" }, "sha512-Iauw9UsTTvlF++FhghFJjqYxyXdggXsOqGpFBylaRopVpcbfyIIsNvkf9oGwfgIcf57z3m8+/oSYTo6HutBFNw=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.3", "", { "os": "linux", "cpu": "none" }, "sha512-3OqKAHSEQXKdq9mQ4eajqUgNIK27VZPW3I26EP8miIzuKzCJ3aW3oEn2pzF+4/Hj/Moc0YDsOtBgT5bZ56/vcA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-0CM8dSVzVIaqMcXIFej8zZrSFLnGrAE8qlNbbHfTw1EEPnFTg1U1ekI0JdzjPyzSfUsHWtodilQQG/RA55berA=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.3", "", { "os": "linux", "cpu": "x64" }, "sha512-+fgJE12FZMIgBaKIAGd45rxf+5ftcycANJRWk8Vz0NnMTM5rADPGuRFTYar+Mqs560xuART7XsX2lSACa1iOmQ=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tMD7NnbAolWPzQlJQJjVFh/fNH3K/KnA7K8gv2dJWCwwnaK6DFCYST1QXYWfu5V0cDwarWC8Sf/cfMHniNq21A=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u5KsqxOxjEeIbn7bUK1MPM34jrnPwjeqgyin4/N6e/KzXKfpE9Mi0nCxcQjaM9lLmPcHmn/xx1yOjgTMtu1jWQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.3", "", { "os": "none", "cpu": "arm64" }, "sha512-vo54aXwjpTtsAnb3ca7Yxs9t2INZg7QdXN/7yaoG7nPGbOBXYXQY41Km+S1Ov26vzOAzLcAjmMdjyEqS1JkVhw=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-HI+PIVZ+m+9AgpnY3pt6rinUdRYrGHvmVdsNQ4odNqQ/eRF78DVpMR7mOq7nW06QxpczibwBmeQzB68wJ+4W4A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-vRByotbdMo3Wdi+8oC2nVxtc3RkkFKrGaok+a62AT8lz/YBuQjaVYAS5Zcs3tPzW43Vsf9J0wehJbUY5xRSekA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.3", "", { "os": "win32", "cpu": "x64" }, "sha512-POZHq7UeuzMJljC5NjKi8vKMFN6/5EOqcX1yGntNLp7rUTpBAXQ1hW8kWPFxYLv07QMcNM75xqVLGPWQq6TKFA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.3", "", { "os": "win32", "cpu": "x64" }, "sha512-aPFONczE4fUFKNXszdvnd2GqKEYQdV5oEsIbKPujJmWlCI9zEsv1Otig8RKK+X9bed9gFUN6LAeN4ZcNuu4zjg=="],
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
"bun": ["bun@1.3.6", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.6", "@oven/bun-darwin-x64": "1.3.6", "@oven/bun-darwin-x64-baseline": "1.3.6", "@oven/bun-linux-aarch64": "1.3.6", "@oven/bun-linux-aarch64-musl": "1.3.6", "@oven/bun-linux-x64": "1.3.6", "@oven/bun-linux-x64-baseline": "1.3.6", "@oven/bun-linux-x64-musl": "1.3.6", "@oven/bun-linux-x64-musl-baseline": "1.3.6", "@oven/bun-windows-x64": "1.3.6", "@oven/bun-windows-x64-baseline": "1.3.6" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-Tn98GlZVN2WM7+lg/uGn5DzUao37Yc0PUz7yzYHdeF5hd+SmHQGbCUIKE4Sspdgtxn49LunK3mDNBC2Qn6GJjw=="],
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
"dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"hono": ["hono@4.11.5", "", {}, "sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g=="],
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
"lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
"lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
"lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
"lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"pocketbase": ["pocketbase@0.26.6", "", {}, "sha512-Pl7V4y3DWglYITC4cBpclmuIzePRGsb/sXk/Wyqxznwu5JsHA5IILJY81PT2XQ3OSKCakWjbxjYBqtdcghzKvA=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"rollup": ["rollup@4.55.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.3", "@rollup/rollup-android-arm64": "4.55.3", "@rollup/rollup-darwin-arm64": "4.55.3", "@rollup/rollup-darwin-x64": "4.55.3", "@rollup/rollup-freebsd-arm64": "4.55.3", "@rollup/rollup-freebsd-x64": "4.55.3", "@rollup/rollup-linux-arm-gnueabihf": "4.55.3", "@rollup/rollup-linux-arm-musleabihf": "4.55.3", "@rollup/rollup-linux-arm64-gnu": "4.55.3", "@rollup/rollup-linux-arm64-musl": "4.55.3", "@rollup/rollup-linux-loong64-gnu": "4.55.3", "@rollup/rollup-linux-loong64-musl": "4.55.3", "@rollup/rollup-linux-ppc64-gnu": "4.55.3", "@rollup/rollup-linux-ppc64-musl": "4.55.3", "@rollup/rollup-linux-riscv64-gnu": "4.55.3", "@rollup/rollup-linux-riscv64-musl": "4.55.3", "@rollup/rollup-linux-s390x-gnu": "4.55.3", "@rollup/rollup-linux-x64-gnu": "4.55.3", "@rollup/rollup-linux-x64-musl": "4.55.3", "@rollup/rollup-openbsd-x64": "4.55.3", "@rollup/rollup-openharmony-arm64": "4.55.3", "@rollup/rollup-win32-arm64-msvc": "4.55.3", "@rollup/rollup-win32-ia32-msvc": "4.55.3", "@rollup/rollup-win32-x64-gnu": "4.55.3", "@rollup/rollup-win32-x64-msvc": "4.55.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-y9yUpfQvetAjiDLtNMf1hL9NXchIJgWt6zIKeoB+tCd3npX08Eqfzg60V9DhIGVMtQ0AlMkFw5xa+AQ37zxnAA=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
}
}
-3
View File
@@ -1,3 +0,0 @@
// Frontend entry point - exports auth module for browser
export { PocketBaseAuth, initPocketBaseAuth } from '../auth-module/frontend';
export type { AuthConfig, AuthState, AuthCallbacks } from '../auth-module/types';
-586
View File
@@ -1,586 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mode1Svelte - Auth Module Test</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: #0f172a;
color: #e2e8f0;
padding: 2rem;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
margin-bottom: 2rem;
color: #f1f5f9;
text-align: center;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
@media (max-width: 1200px) {
.grid {
grid-template-columns: 1fr;
}
}
.section {
background: #1e293b;
border: 1px solid #334155;
border-radius: 8px;
padding: 1.5rem;
}
.section h2 {
font-size: 1.25rem;
margin-bottom: 1rem;
color: #f1f5f9;
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-icon {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
.status-icon.success {
background: #22c55e;
}
.status-icon.pending {
background: #f59e0b;
}
.status-icon.error {
background: #ef4444;
}
button {
background: #3b82f6;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.95rem;
transition: background 0.2s;
width: 100%;
margin-bottom: 0.5rem;
}
button:hover {
background: #2563eb;
}
button:disabled {
background: #64748b;
cursor: not-allowed;
}
.button-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.output {
background: #0f172a;
border: 1px solid #334155;
border-radius: 6px;
padding: 1rem;
margin-top: 1rem;
font-family: 'Monaco', 'Courier New', monospace;
font-size: 0.85rem;
max-height: 300px;
overflow-y: auto;
color: #94a3b8;
}
.output.success {
border-color: #22c55e;
color: #22c55e;
}
.output.error {
border-color: #ef4444;
color: #ef4444;
}
.output.warning {
border-color: #f59e0b;
color: #f59e0b;
}
.info {
background: #1e293b;
border-left: 4px solid #3b82f6;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.timestamp {
font-size: 0.8rem;
color: #64748b;
margin-top: 1rem;
text-align: right;
}
.mode-indicator {
position: fixed;
bottom: 1rem;
left: 1rem;
background: #1e293b;
border: 1px solid #334155;
border-radius: 6px;
padding: 0.75rem 1rem;
font-size: 0.875rem;
color: #94a3b8;
display: flex;
align-items: center;
gap: 0.5rem;
z-index: 1000;
}
.mode-indicator .mode-name {
font-weight: 600;
color: #f1f5f9;
}
.mode-indicator .mode-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #22c55e;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
}
@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
.button-group {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<h1>🔐 Auth Module Test Suite (Mode1Svelte)</h1>
<div class="grid">
<!-- Frontend Auth Section -->
<div class="section">
<h2>
<span class="status-icon pending" id="frontendStatus"></span>
Frontend: PocketBase Auth
</h2>
<div class="info">
Tests OAuth2 login, token refresh, and user state management.
</div>
<div class="button-group">
<button onclick="testFrontendInit()">Initialize Auth</button>
<button onclick="testGetAuthState()">Get Auth State</button>
</div>
<button onclick="testFrontendLogout()">Logout</button>
<div class="output" id="frontendOutput"></div>
<div class="timestamp" id="frontendTime"></div>
</div>
<!-- Backend Auth Section -->
<div class="section">
<h2>
<span class="status-icon pending" id="backendStatus"></span>
Backend: Graph Token & PB Validation
</h2>
<div class="info">
Tests Microsoft Graph token acquisition, caching, and PocketBase token validation.
</div>
<div class="button-group">
<button onclick="testGraphToken()">Get Graph Token</button>
<button onclick="testGraphRefresh()">Refresh Graph Token</button>
</div>
<button onclick="testCompareTokens()">Compare PB vs Graph</button>
<button onclick="testPBValidation()">Validate PB Token</button>
<button onclick="testCompareTokens()">Compare PB vs Graph</button>
<div class="output" id="backendOutput"></div>
<div class="timestamp" id="backendTime"></div>
</div>
</div>
<!-- Job Info Section -->
<div class="section">
<h2>
<span class="status-icon pending" id="jobInfoStatus"></span>
Job Info (First 4 Records)
</h2>
<div class="info">
Fetch and display first 4 records from Job_Info_Prod collection.
</div>
<button onclick="testJobInfo()">Fetch Job Info</button>
<div class="output" id="jobInfoOutput"></div>
<div class="timestamp" id="jobInfoTime"></div>
</div>
</div>
<!-- Login container (shown when not authenticated) -->
<div id="loginContainer" class="hidden">
<button id="loginBtn">Login with Microsoft</button>
<div id="loginError" class="hidden"></div>
</div>
<!-- User display (shown when authenticated) -->
<div id="userDisplayName"></div>
<div id="userEmailValue"></div>
<script src="/auth-module.js"></script>
<script>
let pbAuth = null;
let config = null;
// Utility: Log to output
function log(elementId, message, type = 'info') {
const output = document.getElementById(elementId);
if (!output) return;
const timestamp = new Date().toLocaleTimeString();
const line = `[${timestamp}] ${message}`;
output.textContent += (output.textContent ? '\n' : '') + line;
output.scrollTop = output.scrollHeight;
output.className = `output ${type}`;
// Update timestamp
const timeEl = document.getElementById(elementId.replace('Output', 'Time'));
if (timeEl) timeEl.textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
}
function setStatus(elementId, status) {
const icon = document.getElementById(elementId);
if (!icon) return;
icon.className = `status-icon ${status}`;
}
// Test: Load config
async function loadConfig() {
try {
const response = await fetch('./api/auth/config');
if (!response.ok) throw new Error('Failed to load config');
config = await response.json();
document.getElementById('configOutput').textContent = JSON.stringify(config, null, 2);
document.getElementById('configOutput').className = 'output success';
} catch (error) {
log('configOutput', `Error loading config: ${error.message}`, 'error');
}
}
// Test: Initialize Frontend Auth
async function testFrontendInit() {
setStatus('frontendStatus', 'pending');
log('frontendOutput', 'Initializing PocketBaseAuth...');
try {
if (typeof window.AuthModule === 'undefined' || !window.AuthModule.initPocketBaseAuth) {
throw new Error('Auth module not loaded');
}
pbAuth = await window.AuthModule.initPocketBaseAuth('.', {
onAuthSuccess: (state) => {
log('frontendOutput', `✓ Auth success: ${state.user?.email}`, 'success');
setStatus('frontendStatus', 'success');
},
onAuthFailure: (error) => {
log('frontendOutput', `✗ Auth failed: ${error.message}`, 'error');
setStatus('frontendStatus', 'error');
},
onTokenUpdate: (state) => {
log('frontendOutput', `✓ Token refreshed for ${state.user?.email}`, 'success');
},
onUiUpdate: (state) => {
log('frontendOutput', `✓ UI updated: ${state.isAuthenticated ? 'Authenticated' : 'Not authenticated'}`);
},
});
log('frontendOutput', '✓ Frontend auth initialized', 'success');
setStatus('frontendStatus', 'success');
} catch (error) {
log('frontendOutput', `✗ Initialization failed: ${error.message}`, 'error');
setStatus('frontendStatus', 'error');
}
}
// Test: Get Auth State
function testGetAuthState() {
if (!pbAuth) {
log('frontendOutput', '✗ Auth not initialized', 'error');
return;
}
const state = pbAuth.getAuthState();
log('frontendOutput', `Auth State: ${JSON.stringify(state, null, 2)}`, 'success');
}
// Test: Frontend Logout
function testFrontendLogout() {
if (!pbAuth) {
log('frontendOutput', '✗ Auth not initialized', 'error');
return;
}
pbAuth.logout();
log('frontendOutput', '✓ Logged out', 'success');
setStatus('frontendStatus', 'pending');
}
// Test: Get Graph Token
async function testGraphToken() {
setStatus('backendStatus', 'pending');
log('backendOutput', 'Requesting Graph token from backend...');
try {
const response = await fetch('./api/auth/graph/token');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.success) {
let output = `✓ Token acquired\n`;
output += `Expires: ${data.expiresOn}\n`;
output += `Valid in cache: ${data.isValid}\n\n`;
if (data.tokenDetails && typeof data.tokenDetails === 'object') {
output += `Token Details:\n`;
output += ` Audience: ${data.tokenDetails.aud}\n`;
output += ` Issuer: ${data.tokenDetails.iss}\n`;
output += ` Scopes: ${data.tokenDetails.scp}\n`;
output += ` App: ${data.tokenDetails.app_displayname}\n`;
output += ` Issued: ${data.tokenDetails.iat}\n`;
output += ` Expires: ${data.tokenDetails.exp}\n\n`;
}
output += `Full Token (first 100 chars):\n${data.fullToken.substring(0, 100)}...`;
log('backendOutput', output, 'success');
setStatus('backendStatus', 'success');
} else {
log('backendOutput', `${data.error}`, 'error');
setStatus('backendStatus', 'error');
}
} catch (error) {
log('backendOutput', `✗ Request failed: ${error.message}`, 'error');
setStatus('backendStatus', 'error');
}
}
// Test: Refresh Graph Token
async function testGraphRefresh() {
setStatus('backendStatus', 'pending');
log('backendOutput', 'Refreshing Graph token...');
try {
const response = await fetch('./api/auth/refresh-graph', { method: 'POST' });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.success) {
log('backendOutput', `✓ Token refreshed\nExpires: ${data.expiresOn}`, 'success');
setStatus('backendStatus', 'success');
} else {
log('backendOutput', `${data.error}`, 'error');
setStatus('backendStatus', 'error');
}
} catch (error) {
log('backendOutput', `✗ Refresh failed: ${error.message}`, 'error');
setStatus('backendStatus', 'error');
}
}
// Test: Compare tokens
async function testCompareTokens() {
setStatus('backendStatus', 'pending');
log('backendOutput', 'Comparing PocketBase token vs Graph token...');
if (!pbAuth) {
log('backendOutput', '✗ Auth not initialized', 'error');
setStatus('backendStatus', 'error');
return;
}
try {
const state = pbAuth.getAuthState();
const pbToken = state.token || null;
const response = await fetch('./api/auth/compare-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pbToken }),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.success && data.comparison) {
let output = `✓ Token Comparison:\n\n`;
output += `GRAPH TOKEN (Backend/App-only):\n`;
output += ` Type: ${data.comparison.graph_token.type}\n`;
output += ` Source: ${data.comparison.graph_token.source}\n`;
output += ` Issuer: ${data.comparison.graph_token.issuer}\n`;
output += ` Audience: ${data.comparison.graph_token.audience}\n`;
output += ` App: ${data.comparison.graph_token.app}\n`;
output += ` Scopes: ${data.comparison.graph_token.scopes}\n`;
output += ` Purpose: ${data.comparison.graph_token.purpose}\n\n`;
output += `POCKETBASE TOKEN (Frontend/User):\n`;
output += ` Type: ${data.comparison.pocketbase_token.type}\n`;
output += ` Source: ${data.comparison.pocketbase_token.source}\n`;
output += ` User: ${data.comparison.pocketbase_token.authenticated_user}\n`;
output += ` Purpose: ${data.comparison.pocketbase_token.purpose}\n\n`;
output += `COMPARISON:\n`;
output += ` Are Different: YES ✓\n`;
output += ` ${data.comparison.explanation}\n`;
log('backendOutput', output, 'success');
setStatus('backendStatus', 'success');
} else {
log('backendOutput', `${data.error}`, 'error');
setStatus('backendStatus', 'error');
}
} catch (error) {
log('backendOutput', `✗ Comparison failed: ${error.message}`, 'error');
setStatus('backendStatus', 'error');
}
}
// Test: Validate PocketBase Token
async function testPBValidation() {
setStatus('backendStatus', 'pending');
log('backendOutput', 'Validating PocketBase token...');
if (!pbAuth) {
log('backendOutput', '✗ Auth not initialized', 'error');
setStatus('backendStatus', 'error');
return;
}
try {
const state = pbAuth.getAuthState();
if (!state.token) {
log('backendOutput', '✗ No token available - user not logged in', 'warning');
setStatus('backendStatus', 'warning');
return;
}
log('backendOutput', `Sending token for user: ${state.user?.email}...`);
const response = await fetch('./api/auth/validate-pb-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pbToken: state.token }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (data.valid) {
log('backendOutput', `✓ Token valid\nUser: ${data.user?.email}\nID: ${data.user?.id}`, 'success');
setStatus('backendStatus', 'success');
} else {
log('backendOutput', `✗ Token invalid: ${data.error}`, 'error');
setStatus('backendStatus', 'error');
}
} catch (error) {
log('backendOutput', `✗ Validation failed: ${error.message}`, 'error');
setStatus('backendStatus', 'error');
}
}
// Test: Fetch Job Info
async function testJobInfo() {
setStatus('jobInfoStatus', 'pending');
log('jobInfoOutput', 'Fetching first 4 records from Job_Info_Prod...');
if (!pbAuth) {
log('jobInfoOutput', '✗ Auth not initialized - cannot fetch records', 'error');
setStatus('jobInfoStatus', 'error');
return;
}
try {
const pb = pbAuth.getPocketBase();
const records = await pb.collection('Job_Info_Prod').getList(1, 4);
if (!records.items || records.items.length === 0) {
log('jobInfoOutput', 'No records found', 'warning');
setStatus('jobInfoStatus', 'warning');
return;
}
let output = `✓ Found ${records.items.length} records:\n\n`;
records.items.forEach((record, index) => {
output += `[${index + 1}] ID: ${record.id}\n`;
output += ` Title: ${record.title || record.name || 'N/A'}\n`;
output += ` Status: ${record.status || 'N/A'}\n`;
output += ` Created: ${new Date(record.created).toLocaleDateString()}\n\n`;
});
log('jobInfoOutput', output, 'success');
setStatus('jobInfoStatus', 'success');
} catch (error) {
log('jobInfoOutput', `✗ Failed to fetch records: ${error.message}`, 'error');
setStatus('jobInfoStatus', 'error');
}
}
</script>
<!-- Mode Indicator -->
<div class="mode-indicator">
<div class="mode-dot"></div>
<span class="mode-name">Mode1Svelte</span>
</div>
</body>
</html>
+2823
View File
File diff suppressed because it is too large Load Diff
+32 -24
View File
@@ -1,26 +1,34 @@
{ {
"name": "mode1-auth-test", "name": "mode1svelte",
"version": "1.0.0", "private": true,
"type": "module", "version": "0.0.1",
"private": true, "type": "module",
"scripts": { "scripts": {
"start": "bun run backend/server.ts", "dev": "vite dev --port 3005",
"dev": "bun --watch backend/server.ts", "build": "vite build",
"build": "vite build", "preview": "vite preview",
"prebuild": "vite build" "start": "PORT=3005 node build/index.js",
}, "prepare": "svelte-kit sync || echo ''",
"dependencies": { "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"@azure/msal-node": "^5.0.2", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
"dotenv": "^17.2.3", },
"hono": "^4.10.8", "devDependencies": {
"pocketbase": "^0.26.5" "@sveltejs/adapter-node": "^5.5.2",
}, "@sveltejs/kit": "^2.49.1",
"devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/bun": "latest", "@tailwindcss/postcss": "^4.1.18",
"typescript": "^5", "autoprefixer": "^10.4.23",
"vite": "^7.3.1" "postcss": "^8.5.6",
}, "svelte": "^5.45.6",
"peerDependencies": { "svelte-check": "^4.3.4",
"bun": "latest" "tailwindcss": "^4.1.18",
} "typescript": "^5.9.3",
"vite": "^7.2.6"
},
"dependencies": {
"@azure/msal-node": "^5.0.2",
"@types/node": "^25.0.10",
"dotenv": "^17.2.3",
"pocketbase": "^0.26.6"
}
} }
+5
View File
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
};
+22
View File
@@ -0,0 +1,22 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
sans-serif;
background: #0f172a;
color: #e2e8f0;
}
+13
View File
@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="web-app-status-bar-style" content="black-translucent" />
<meta name="theme-color" content="#1e293b" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1,7 +1,7 @@
import { config } from 'dotenv'; import { config } from 'dotenv';
import { ConfidentialClientApplication } from '@azure/msal-node'; import { ConfidentialClientApplication } from '@azure/msal-node';
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
import { GraphTokenCache, BackendAuthConfig } from './types'; import type { GraphTokenCache, BackendAuthConfig } from './types';
// Load environment variables from shared secrets directory // Load environment variables from shared secrets directory
config({ path: '/home/admin/secrets/.env' }); config({ path: '/home/admin/secrets/.env' });
@@ -1,5 +1,5 @@
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
import { AuthConfig, AuthState, AuthCallbacks } from './types'; import type { AuthConfig, AuthState, AuthCallbacks } from './types';
/** /**
* PocketBase OAuth2 Frontend Module * PocketBase OAuth2 Frontend Module
+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+12
View File
@@ -0,0 +1,12 @@
<script lang="ts">
import favicon from '$lib/assets/favicon.svg';
import '../app.css';
let { children } = $props();
</script>
<svelte:head>
<link rel="icon" href={favicon} />
</svelte:head>
{@render children()}
+259
View File
@@ -0,0 +1,259 @@
<script lang="ts">
import { onMount } from 'svelte';
import { PocketBaseAuth } from '$lib/auth/frontend';
import type { AuthState } from '$lib/auth/types';
let auth: PocketBaseAuth | undefined;
let authState: AuthState = {
isAuthenticated: false,
user: null,
token: null,
};
let graphToken = '';
let pbTokenValid = false;
let comparisonData: string | null = null;
let loading = false;
let error = '';
onMount(async () => {
try {
const response = await fetch('/api/auth/config');
if (!response.ok) throw new Error('Failed to get config');
const config = await response.json();
auth = new PocketBaseAuth(config, {
onAuthSuccess: (state: AuthState) => {
authState = state;
console.log('Auth success:', state);
},
onAuthFailure: (err: unknown) => {
error = 'Authentication failed: ' + (err instanceof Error ? err.message : 'Unknown error');
console.error('Auth error:', err);
},
onTokenUpdate: (state: AuthState) => {
authState = state;
},
onUiUpdate: (state: AuthState) => {
authState = state;
},
});
} catch (err) {
error = err instanceof Error ? err.message : 'Unknown error';
console.error(err);
}
});
async function handleFetchGraphToken() {
loading = true;
error = '';
try {
const response = await fetch('/api/auth/graph/token');
if (!response.ok) throw new Error('Failed to fetch Graph token');
const data = await response.json();
graphToken = JSON.stringify(data, null, 2);
} catch (err) {
error = err instanceof Error ? err.message : 'Unknown error';
} finally {
loading = false;
}
}
async function handleRefreshGraphToken() {
loading = true;
error = '';
try {
const response = await fetch('/api/auth/refresh-graph', { method: 'POST' });
if (!response.ok) throw new Error('Failed to refresh');
const data = await response.json();
graphToken = JSON.stringify(data, null, 2);
} catch (err) {
error = err instanceof Error ? err.message : 'Unknown error';
} finally {
loading = false;
}
}
async function handleValidatePBToken() {
loading = true;
error = '';
try {
const pbToken = auth?.getPocketBase?.()?.authStore?.token;
if (!pbToken) {
error = 'No PocketBase token found. Please log in first.';
pbTokenValid = false;
return;
}
const response = await fetch('/api/auth/validate-pb-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pbToken }),
});
if (!response.ok) throw new Error('Validation failed');
const data = await response.json();
pbTokenValid = data.valid;
error = data.valid ? '' : 'Token validation failed';
} catch (err) {
error = err instanceof Error ? err.message : 'Unknown error';
pbTokenValid = false;
} finally {
loading = false;
}
}
async function handleCompareTokens() {
loading = true;
error = '';
try {
const pbToken = auth?.getPocketBase?.()?.authStore?.token;
const response = await fetch('/api/auth/compare-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pbToken }),
});
if (!response.ok) throw new Error('Comparison failed');
const data = await response.json();
comparisonData = JSON.stringify(data, null, 2);
} catch (err) {
error = err instanceof Error ? err.message : 'Unknown error';
} finally {
loading = false;
}
}
function handleLogout() {
if (auth) {
auth.logout();
authState = {
isAuthenticated: false,
user: null,
token: null,
};
error = '';
}
}
</script>
<div style="min-height: 100vh; background: linear-gradient(to bottom, #0f172a, #1e293b); color: white; padding: 16px; padding-bottom: 100px;">
<div style="max-width: 800px; margin: 0 auto;">
<!-- Header -->
<div style="margin-bottom: 32px;">
<h1 style="font-size: 28px; font-weight: bold; margin: 0 0 8px 0;">Mode1Svelte</h1>
<p style="font-size: 14px; color: #94a3b8; margin: 0;">Auth Testing Dashboard</p>
</div>
<!-- Error Display -->
{#if error}
<div style="background: rgba(127, 29, 29, 0.2); border: 1px solid #dc2626; color: #fca5a5; padding: 12px; border-radius: 8px; margin-bottom: 16px; font-size: 14px;">
{error}
</div>
{/if}
<!-- Auth Status -->
<div style="background: rgba(71, 85, 105, 0.3); border: 1px solid #475569; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
<h2 style="font-size: 20px; font-weight: bold; margin: 0 0 12px 0;">Authentication Status</h2>
{#if authState.isAuthenticated && authState.user !== null}
<div style="display: flex; flex-direction: column; gap: 12px;">
<div style="font-size: 14px;">
<span style="color: #cbd5e1;">Status:</span>
<span style="margin-left: 8px; color: #4ade80; font-weight: bold;">✓ Authenticated</span>
</div>
<div style="font-size: 14px; word-break: break-all;">
<span style="color: #cbd5e1;">User:</span>
<span style="margin-left: 8px; color: #93c5fd;" id="userDisplayName">{authState.user?.name}</span>
</div>
<div style="font-size: 14px; word-break: break-all;">
<span style="color: #cbd5e1;">Email:</span>
<span style="margin-left: 8px; color: #93c5fd;" id="userEmailValue">{authState.user?.email}</span>
</div>
<button
on:click={handleLogout}
style="background: #dc2626; color: white; border: none; padding: 12px 16px; border-radius: 6px; font-weight: bold; font-size: 14px; cursor: pointer; margin-top: 8px; width: 100%;"
>
Logout
</button>
</div>
{:else}
<div id="loginContainer" style="display: flex; flex-direction: column; gap: 12px;">
<p style="font-size: 14px; color: #cbd5e1; margin: 0;">Not authenticated. Log in to begin testing.</p>
<button
id="loginBtn"
style="background: #2563eb; color: white; border: none; padding: 16px; border-radius: 6px; font-weight: bold; font-size: 16px; cursor: pointer; width: 100%;"
>
Login with Microsoft
</button>
<div id="loginError" style="color: #f87171; font-size: 12px; display: none;"></div>
</div>
{/if}
</div>
<!-- API Testing -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px;">
<!-- Graph Token -->
<div style="background: rgba(71, 85, 105, 0.3); border: 1px solid #475569; padding: 12px; border-radius: 8px;">
<h3 style="font-size: 16px; font-weight: bold; margin: 0 0 12px 0;">Graph Token</h3>
<div style="display: flex; flex-direction: column; gap: 8px;">
<button
on:click={handleFetchGraphToken}
disabled={loading}
style="background: #4f46e5; color: white; border: none; padding: 10px; border-radius: 6px; font-weight: bold; font-size: 14px; cursor: pointer; opacity: {loading ? 0.6 : 1}; width: 100%;"
>
{loading ? 'Loading...' : 'Fetch Token'}
</button>
<button
on:click={handleRefreshGraphToken}
disabled={loading}
style="background: #4f46e5; color: white; border: none; padding: 10px; border-radius: 6px; font-weight: bold; font-size: 14px; cursor: pointer; opacity: {loading ? 0.6 : 1}; width: 100%;"
>
{loading ? 'Loading...' : 'Refresh'}
</button>
</div>
</div>
<!-- PocketBase Token -->
<div style="background: rgba(71, 85, 105, 0.3); border: 1px solid #475569; padding: 12px; border-radius: 8px;">
<h3 style="font-size: 16px; font-weight: bold; margin: 0 0 12px 0;">PocketBase Token</h3>
<div style="display: flex; flex-direction: column; gap: 8px;">
<button
on:click={handleValidatePBToken}
disabled={loading}
style="background: #059669; color: white; border: none; padding: 10px; border-radius: 6px; font-weight: bold; font-size: 14px; cursor: pointer; opacity: {loading ? 0.6 : 1}; width: 100%;"
>
{loading ? 'Loading...' : 'Validate'}
</button>
{#if pbTokenValid}
<p style="color: #4ade80; font-size: 12px; margin: 0;">✓ Token is valid</p>
{/if}
</div>
</div>
</div>
<!-- Compare Tokens -->
<div style="background: rgba(71, 85, 105, 0.3); border: 1px solid #475569; padding: 12px; border-radius: 8px; margin-bottom: 16px;">
<button
on:click={handleCompareTokens}
disabled={loading}
style="background: #9333ea; color: white; border: none; padding: 12px; border-radius: 6px; font-weight: bold; font-size: 14px; cursor: pointer; opacity: {loading ? 0.6 : 1}; width: 100%;"
>
{loading ? 'Loading...' : 'Compare Tokens'}
</button>
</div>
<!-- Output -->
{#if graphToken}
<div style="background: rgba(71, 85, 105, 0.3); border: 1px solid #475569; padding: 12px; border-radius: 8px; margin-bottom: 16px;">
<h3 style="font-size: 16px; font-weight: bold; margin: 0 0 12px 0;">Graph Token Response</h3>
<pre style="background: #0f172a; padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 11px; color: #cbd5e1; max-height: 200px; white-space: pre-wrap; word-wrap: break-word; margin: 0;">{graphToken}</pre>
</div>
{/if}
{#if comparisonData}
<div style="background: rgba(71, 85, 105, 0.3); border: 1px solid #475569; padding: 12px; border-radius: 8px;">
<h3 style="font-size: 16px; font-weight: bold; margin: 0 0 12px 0;">Token Comparison</h3>
<pre style="background: #0f172a; padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 11px; color: #cbd5e1; max-height: 200px; white-space: pre-wrap; word-wrap: break-word; margin: 0;">{comparisonData}</pre>
</div>
{/if}
</div>
</div>
@@ -0,0 +1,83 @@
import { json, type RequestHandler } from '@sveltejs/kit';
import { BackendAuth } from '$lib/auth/backend';
const backendAuth = new BackendAuth(
{
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
process.env.PB_DB
);
export const POST: RequestHandler = async ({ request }) => {
try {
const body = await request.json();
const pbToken = body.pbToken;
// Get Graph token
const { token: graphToken, expiresOnISO: graphExpires } = await backendAuth.graphTokenManager.getTokenWithExpiry();
if (!graphToken) {
return json({ success: false, error: 'Failed to acquire Graph token' }, { status: 500 });
}
// Decode both tokens to compare
const decodeJWT = (token: string) => {
try {
const parts = token.split('.');
if (parts.length !== 3) return null;
return JSON.parse(Buffer.from(parts[1], 'base64').toString());
} catch {
return null;
}
};
const graphPayload = decodeJWT(graphToken);
// Validate PB token to get actual user
let authenticatedUser = 'Unknown';
if (pbToken) {
try {
const isValid = await backendAuth.pbValidator.validateUserToken(pbToken);
if (isValid) {
const userRecord = await backendAuth.pbValidator.getUserRecord(pbToken);
authenticatedUser = userRecord?.email || userRecord?.id || 'Unknown';
}
} catch {
authenticatedUser = 'Token invalid or expired';
}
}
return json({
success: true,
comparison: {
graph_token: {
issuer: graphPayload?.iss || 'N/A',
audience: graphPayload?.aud || 'N/A',
app: graphPayload?.app_displayname || 'N/A',
scopes: graphPayload?.scp || 'N/A',
issued_at: graphPayload?.iat ? new Date(graphPayload.iat * 1000).toISOString() : 'N/A',
expires_at: graphPayload?.exp ? new Date(graphPayload.exp * 1000).toISOString() : 'N/A',
type: 'Microsoft Graph Token (Backend/App-only)',
source: '@azure/msal-node',
purpose: 'Backend API calls to Microsoft Graph',
},
pocketbase_token: {
note: 'User token from PocketBase OAuth2',
type: 'PocketBase User Token (Frontend/User)',
source: 'PocketBase OAuth2 flow',
purpose: 'User authentication and authorization',
authenticated_user: authenticatedUser,
},
are_different: true,
explanation: 'These are completely different tokens from different systems: Graph token is for app-to-app Microsoft API access, PocketBase token is for user authentication in the application.',
},
});
} catch (error) {
return json(
{ success: false, error: error instanceof Error ? error.message : 'Failed to compare tokens' },
{ status: 500 }
);
}
};
@@ -0,0 +1,14 @@
import { json } from '@sveltejs/kit';
import { AuthConfigManager } from '$lib/auth/backend';
export async function GET() {
try {
const config = AuthConfigManager.getFrontendConfig();
return json(config);
} catch (error) {
return json(
{ error: 'Failed to get config', message: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
}
@@ -0,0 +1,54 @@
import { json, type RequestHandler } from '@sveltejs/kit';
import { BackendAuth, AuthConfigManager } from '$lib/auth/backend';
const backendAuth = new BackendAuth(
{
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
process.env.PB_DB
);
export const GET: RequestHandler = async () => {
try {
const { token, expiresOnISO } = await backendAuth.graphTokenManager.getTokenWithExpiry();
if (!token) {
return json({ success: false, error: 'Failed to acquire Graph token' }, { status: 500 });
}
// Decode JWT to show token details (for verification)
const parts = token.split('.');
let payload: any = {};
if (parts.length === 3) {
try {
const decoded = JSON.parse(Buffer.from(parts[1], 'base64').toString());
payload = {
aud: decoded.aud,
iss: decoded.iss,
scp: decoded.scp,
app_displayname: decoded.app_displayname,
iat: new Date(decoded.iat * 1000).toISOString(),
exp: new Date(decoded.exp * 1000).toISOString(),
};
} catch (e) {
// Silent fail - just show truncated token
}
}
return json({
success: true,
token: token.substring(0, 50) + '...',
fullToken: token, // Include full token for testing
expiresOn: expiresOnISO,
isValid: backendAuth.graphTokenManager.isTokenValid(),
tokenDetails: Object.keys(payload).length > 0 ? payload : 'Could not decode',
});
} catch (error) {
return json(
{ success: false, error: error instanceof Error ? error.message : 'Failed to acquire token' },
{ status: 500 }
);
}
};
@@ -0,0 +1,28 @@
import { json, type RequestHandler } from '@sveltejs/kit';
import { BackendAuth } from '$lib/auth/backend';
const backendAuth = new BackendAuth(
{
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
process.env.PB_DB
);
export const POST: RequestHandler = async () => {
try {
backendAuth.graphTokenManager.clearCache();
const { token, expiresOnISO } = await backendAuth.graphTokenManager.getTokenWithExpiry();
return json({
success: true,
refreshed: true,
expiresOn: expiresOnISO,
});
} catch (error) {
return json(
{ success: false, error: error instanceof Error ? error.message : 'Refresh failed' },
{ status: 500 }
);
}
};
@@ -0,0 +1,39 @@
import { json, type RequestHandler } from '@sveltejs/kit';
import { BackendAuth } from '$lib/auth/backend';
const backendAuth = new BackendAuth(
{
clientId: process.env.CLIENT_ID,
tenantId: process.env.TENANT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
process.env.PB_DB
);
export const POST: RequestHandler = async ({ request }) => {
try {
const body = await request.json();
const token = body.pbToken;
if (!token) {
return json({ valid: false, error: 'No token provided' }, { status: 400 });
}
const isValid = await backendAuth.pbValidator.validateUserToken(token);
const user = isValid ? await backendAuth.pbValidator.getUserRecord(token) : null;
return json({
valid: isValid,
user: user ? {
id: user.id,
email: user.email,
name: user.name,
} : null,
});
} catch (error) {
return json(
{ valid: false, error: error instanceof Error ? error.message : 'Validation failed' },
{ status: 500 }
);
}
};
+3
View File
@@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:
+16
View File
@@ -0,0 +1,16 @@
import adapter from '@sveltejs/adapter-node';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),
// LOCKED: Always use port 3005 for Mode1Svelte service
// 3006 = Orchestrator, 3005 = Mode service
// https://ji-test.ccllc.pro proxies to these ports
paths: {
base: process.env.BASE_PATH || ''
}
}
};
export default config;
+8
View File
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {},
},
plugins: [],
};
+20
View File
@@ -0,0 +1,20 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}
+11 -13
View File
@@ -1,17 +1,15 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
build: { plugins: [sveltekit()],
outDir: 'frontend/dist', build: {
emptyOutDir: true, // Ensure asset paths are relative, not absolute
lib: { // This fixes proxy routing issues
entry: 'frontend/auth.ts', rollupOptions: {
name: 'AuthModule', output: {
fileName: () => 'auth.js', assetFileNames: 'assets/[name]-[hash][extname]'
formats: ['umd'] }
} }
}, }
server: {
middlewareMode: true
}
}); });
+1 -1
View File
@@ -1 +1 @@
Mode6Test Mode1Svelte
+33 -13
View File
@@ -82,20 +82,40 @@ async function startMode(mode: string): Promise<boolean> {
console.log(`[ModeSwitch] Starting ${mode} (${folderName}/) on port ${SHARED_PORT}...`); console.log(`[ModeSwitch] Starting ${mode} (${folderName}/) on port ${SHARED_PORT}...`);
// Determine server path - all modes use backend/server.ts // Mode1Svelte uses Node.js adapter with build/index.js
const serverPath = 'backend/server.ts'; if (mode === 'Mode1Svelte') {
const buildPath = `${modePath}/build`;
if (!fs.existsSync(buildPath)) {
console.error(`Build directory not found for Mode1Svelte. Run: cd ${modePath} && npm run build`);
return false;
}
// Start the mode's backend server // Start with Node.js
currentProcess = Bun.spawn({ currentProcess = Bun.spawn({
cmd: [process.execPath, 'run', serverPath], cmd: ['node', 'build/index.js'],
cwd: modePath, cwd: modePath,
env: { env: {
...process.env, // Inherit all env vars (secrets, etc.) ...process.env,
PORT: String(SHARED_PORT), // Force mode service to bind to shared port PORT: String(SHARED_PORT),
}, },
stdout: 'pipe', stdout: 'pipe',
stderr: 'pipe', stderr: 'pipe',
}); });
} else {
// Other modes (Mode1, Mode6Test) use Bun with backend/server.ts
const serverPath = 'backend/server.ts';
currentProcess = Bun.spawn({
cmd: [process.execPath, 'run', serverPath],
cwd: modePath,
env: {
...process.env,
PORT: String(SHARED_PORT),
},
stdout: 'pipe',
stderr: 'pipe',
});
}
currentMode = mode; currentMode = mode;
writeActiveMode(mode); writeActiveMode(mode);