From 5a4a3c51e98f4883c394733bfebaa9c8447c52e0 Mon Sep 17 00:00:00 2001 From: aewing Date: Thu, 22 Jan 2026 05:53:15 +0000 Subject: [PATCH] Add Mode1Svelte - independent copy of Mode1 for future SvelteKit integration - Created Mode1Svelte as complete standalone project - Identical to Mode1 currently (Hono backend + Vite frontend) - Separate auth-module copy (no dependencies on Mode1) - Updated orchestrator to default to Mode1Svelte - Both Mode1 and Mode1Svelte are independent and interchangeable via ModeSwitch - Ready for gradual Svelte/SvelteKit migration while maintaining API compatibility --- Mode1Svelte/README.md | 158 ++++++++ Mode1Svelte/auth-module/README.md | 248 +++++++++++++ Mode1Svelte/auth-module/backend.ts | 204 +++++++++++ Mode1Svelte/auth-module/frontend.ts | 250 +++++++++++++ Mode1Svelte/auth-module/types.ts | 62 ++++ Mode1Svelte/backend/server.ts | 226 ++++++++++++ Mode1Svelte/bun.lock | 226 ++++++++++++ Mode1Svelte/frontend/auth.ts | 3 + Mode1Svelte/frontend/index.html | 545 ++++++++++++++++++++++++++++ Mode1Svelte/package.json | 26 ++ Mode1Svelte/vite.config.ts | 17 + ModeSwitch/backend/orchestrator.ts | 3 +- 12 files changed, 1967 insertions(+), 1 deletion(-) create mode 100644 Mode1Svelte/README.md create mode 100644 Mode1Svelte/auth-module/README.md create mode 100644 Mode1Svelte/auth-module/backend.ts create mode 100644 Mode1Svelte/auth-module/frontend.ts create mode 100644 Mode1Svelte/auth-module/types.ts create mode 100644 Mode1Svelte/backend/server.ts create mode 100644 Mode1Svelte/bun.lock create mode 100644 Mode1Svelte/frontend/auth.ts create mode 100644 Mode1Svelte/frontend/index.html create mode 100644 Mode1Svelte/package.json create mode 100644 Mode1Svelte/vite.config.ts diff --git a/Mode1Svelte/README.md b/Mode1Svelte/README.md new file mode 100644 index 0000000..fa8fb0a --- /dev/null +++ b/Mode1Svelte/README.md @@ -0,0 +1,158 @@ +# Mode1Svelte - Auth Module Test Suite + +Self-contained test environment for the auth-module. This version is identical to Mode1 currently, serving as a foundation for future Svelte integration. + +## Structure + +``` +Mode1Svelte/ +├── 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 + +✓ **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 + - Token comparison (Graph vs PocketBase) + +## Running Mode1Svelte + +### Via Orchestrator (recommended) + +```bash +# Switch to Mode1Svelte +curl -X POST http://localhost:3006/switch/Mode1Svelte + +# Check status +curl http://localhost:3006/status +``` + +### Direct + +```bash +cd Mode1Svelte +bun install +PORT=3005 bun run backend/server.ts +``` + +Then visit: `http://localhost:3005` + +## Future: Svelte Integration + +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 +# Switch to Mode1 +curl -X POST http://localhost:3006/switch/Mode1 + +# Check status +curl http://localhost:3006/status +``` + +### Direct + +```bash +# Install dependencies (already done) +bun install + +# Start server +bun run backend/server.ts + +# Access dashboard +open http://localhost:3005 +``` + +## API Endpoints + +### Configuration +- `GET /api/auth/config` - Get frontend configuration + +### Graph Token Management +- `GET /api/auth/graph/token` - Get current Graph token (with expiration) +- `POST /api/auth/refresh-graph` - Force refresh Graph token + +### PocketBase Validation +- `POST /api/auth/validate-pb-token` - Validate a PocketBase token + +### Health +- `GET /health` - Health check + +## Environment Variables + +Uses `/home/admin/secrets/.env` via auth-module: +- `CLIENT_ID` - Microsoft application ID +- `TENANT_ID` - Azure tenant ID +- `CLIENT_SECRET` - Microsoft client secret +- `PB_URL` - PocketBase frontend URL +- `PB_DB` - PocketBase backend URL + +## Dependencies + +All dependencies are installed locally in `node_modules/`: +- `hono` - Web framework +- `pocketbase` - PocketBase client +- `@azure/msal-node` - Microsoft authentication +- `dotenv` - Environment variable loading + +## Notes + +- This Mode1 is **completely self-contained** with its own auth-module copy +- No dependencies on root auth-module +- Safe to modify and test independently +- All secrets come from `/home/admin/secrets/.env` diff --git a/Mode1Svelte/auth-module/README.md b/Mode1Svelte/auth-module/README.md new file mode 100644 index 0000000..2d55281 --- /dev/null +++ b/Mode1Svelte/auth-module/README.md @@ -0,0 +1,248 @@ +# 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 + + + + +
+
+``` + +### 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 diff --git a/Mode1Svelte/auth-module/backend.ts b/Mode1Svelte/auth-module/backend.ts new file mode 100644 index 0000000..a28027f --- /dev/null +++ b/Mode1Svelte/auth-module/backend.ts @@ -0,0 +1,204 @@ +import { config } from 'dotenv'; +import { ConfidentialClientApplication } from '@azure/msal-node'; +import PocketBase from 'pocketbase'; +import { GraphTokenCache, BackendAuthConfig } from './types'; + +// Load environment variables from shared secrets directory +config({ path: '/home/admin/secrets/.env' }); + +/** + * Configuration Manager + * Exposes frontend-safe configuration loaded from environment + */ +export class AuthConfigManager { + /** + * Get frontend configuration (safe to expose to browser) + */ + static getFrontendConfig() { + return { + pbUrl: process.env.PB_URL!, + provider: 'microsoft', + collection: 'Users', + }; + } + + /** + * Get all backend secrets (never expose to frontend) + */ + static getBackendConfig() { + return { + clientId: process.env.CLIENT_ID!, + tenantId: process.env.TENANT_ID!, + clientSecret: process.env.CLIENT_SECRET!, + pbDb: process.env.PB_DB!, + }; + } +} + +/** + * Microsoft Graph Token Management (Backend) + * Handles token acquisition and caching for backend Graph API calls + */ +export class GraphTokenManager { + private cca: ConfidentialClientApplication; + private cache: GraphTokenCache | null = null; + private config: Required; + + constructor(config: BackendAuthConfig) { + this.config = { + clientId: config.clientId || process.env.CLIENT_ID || '', + tenantId: config.tenantId || process.env.TENANT_ID || '', + clientSecret: config.clientSecret || process.env.CLIENT_SECRET || '', + }; + + this.cca = new ConfidentialClientApplication({ + auth: { + clientId: this.config.clientId, + authority: `https://login.microsoftonline.com/${this.config.tenantId}`, + clientSecret: this.config.clientSecret, + }, + }); + } + + /** + * Get Graph token (from cache or acquire new) + */ + async getToken(): Promise { + const now = Date.now(); + + // Check cache validity (with 60s buffer for expiration) + if (this.cache && this.cache.expiresOn - 60000 > now) { + return this.cache.token; + } + + try { + const result = await this.cca.acquireTokenByClientCredential({ + scopes: ['https://graph.microsoft.com/.default'], + }); + + if (!result?.accessToken) { + throw new Error('Failed to acquire Graph token'); + } + + const expiresOn = result.expiresOn + ? new Date(result.expiresOn).getTime() + : now + 55 * 60 * 1000; // Default 55 minutes + + this.cache = { + token: result.accessToken, + expiresOn, + }; + + return result.accessToken; + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + throw new Error(`Failed to acquire Graph token: ${message}`); + } + } + + /** + * Get token with expiration info + */ + async getTokenWithExpiry(): Promise<{ token: string; expiresOnISO: string }> { + const token = await this.getToken(); + const expiresOn = this.cache?.expiresOn || Date.now(); + return { + token, + expiresOnISO: new Date(expiresOn).toISOString(), + }; + } + + /** + * Check if token is cached and valid + */ + isTokenValid(): boolean { + if (!this.cache) return false; + const now = Date.now(); + return this.cache.expiresOn - 60000 > now; + } + + /** + * Clear cache (force refresh on next call) + */ + clearCache(): void { + this.cache = null; + } +} + +/** + * PocketBase Token Validation (Backend) + * Validates and uses user PocketBase tokens + */ +export class PocketBaseValidator { + private pb: PocketBase; + + constructor(pbUrl?: string) { + this.pb = new PocketBase(pbUrl || process.env.PB_DB!); + } + + /** + * Set user token and validate it + */ + async validateUserToken(token: string): Promise { + try { + this.pb.authStore.save(token, null); + await this.pb.collection('Users').authRefresh(); + return true; + } catch (error) { + console.error('Token validation failed:', error instanceof Error ? error.message : error); + return false; + } + } + + /** + * Get user record from token + */ + async getUserRecord(token: string): Promise { + try { + this.pb.authStore.save(token, null); + const record = this.pb.authStore.record || this.pb.authStore.model; + return record; + } catch (error) { + console.error('Failed to get user record:', error); + return null; + } + } + + /** + * Get PocketBase instance + */ + getPocketBase(): PocketBase { + return this.pb; + } +} + +/** + * Combined backend auth manager + */ +export class BackendAuth { + graphTokenManager: GraphTokenManager; + pbValidator: PocketBaseValidator; + + constructor(config: BackendAuthConfig, pbUrl?: string) { + this.graphTokenManager = new GraphTokenManager(config); + this.pbValidator = new PocketBaseValidator(pbUrl); + } + + /** + * Middleware to validate PocketBase token in requests + * Usage: app.use('/*', (c, next) => backendAuth.validateTokenMiddleware(c, next)) + */ + async validateTokenMiddleware(c: any, next: any): Promise { + const token = c.req.header('Authorization')?.replace('Bearer ', '') || + (await c.req.json().catch(() => ({})))?.pbToken; + + if (token) { + const isValid = await this.pbValidator.validateUserToken(token); + if (!isValid) { + return c.json({ error: 'Invalid token' }, 401); + } + } + + return next(); + } +} diff --git a/Mode1Svelte/auth-module/frontend.ts b/Mode1Svelte/auth-module/frontend.ts new file mode 100644 index 0000000..650805a --- /dev/null +++ b/Mode1Svelte/auth-module/frontend.ts @@ -0,0 +1,250 @@ +import PocketBase from 'pocketbase'; +import { AuthConfig, AuthState, AuthCallbacks } from './types'; + +/** + * PocketBase OAuth2 Frontend Module + * Handles user authentication and token management + */ +export class PocketBaseAuth { + private pb: PocketBase; + private config: Required; + private callbacks: AuthCallbacks; + private state: AuthState; + + constructor(config: AuthConfig, callbacks?: Partial) { + this.config = { + pbUrl: config.pbUrl!, + collection: config.collection || 'Users', + provider: config.provider || 'microsoft', + loginContainerId: config.loginContainerId || 'loginContainer', + userDisplayNameId: config.userDisplayNameId || 'userDisplayName', + userEmailId: config.userEmailId || 'userEmailValue', + loginBtnId: config.loginBtnId || 'loginBtn', + loginErrorId: config.loginErrorId || 'loginError', + }; + + this.callbacks = { + onAuthSuccess: callbacks?.onAuthSuccess || (() => {}), + onAuthFailure: callbacks?.onAuthFailure || (() => {}), + onTokenUpdate: callbacks?.onTokenUpdate || (() => {}), + onUiUpdate: callbacks?.onUiUpdate || (() => {}), + }; + + this.pb = new PocketBase(this.config.pbUrl); + this.state = { + isAuthenticated: false, + user: null, + token: null, + }; + + this.init(); + } + + /** + * Initialize auth module + */ + private init(): void { + this.updateAuthUI(); + if (this.pb.authStore.isValid && this.pb.authStore.token) { + this.ensureUserLogged(); + } + this.setupLoginButton(); + } + + /** + * Setup login button click handler + */ + private setupLoginButton(): void { + const loginBtn = document.getElementById(this.config.loginBtnId); + if (!loginBtn) { + console.warn(`Login button with id "${this.config.loginBtnId}" not found`); + return; + } + + loginBtn.addEventListener('click', async (e) => { + e.preventDefault(); + await this.handleLogin(); + }); + } + + /** + * Handle login button click + */ + private async handleLogin(): Promise { + const loginBtn = document.getElementById(this.config.loginBtnId) as HTMLButtonElement; + const loginError = document.getElementById(this.config.loginErrorId) as HTMLElement; + + if (!loginBtn || !loginError) return; + + loginBtn.disabled = true; + loginBtn.textContent = 'Checking session...'; + loginError.classList.add('hidden'); + + try { + // Try to use existing token first + const hadToken = await this.ensureUserLogged(); + if (hadToken) { + this.resetLoginButton(); + return; + } + + // Otherwise perform OAuth + loginBtn.textContent = 'Logging in...'; + const authData = await this.pb.collection(this.config.collection).authWithOAuth2({ + provider: this.config.provider, + }); + + this.updateState(authData); + this.updateAuthUI(); + this.callbacks.onAuthSuccess?.(this.state); + + console.log('✓ Logged in with', this.config.provider); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + console.error('Login failed:', message); + loginError.textContent = `Login failed: ${message}`; + loginError.classList.remove('hidden'); + this.callbacks.onAuthFailure?.(error); + } finally { + this.resetLoginButton(); + } + } + + /** + * Ensure user is logged in (check existing token) + */ + async ensureUserLogged(): Promise { + if (!this.pb.authStore.isValid || !this.pb.authStore.token) { + return false; + } + + try { + const refresh = await this.pb.collection(this.config.collection).authRefresh(); + this.updateState(refresh); + this.updateAuthUI(); + this.callbacks.onTokenUpdate?.(this.state); + console.log('✓ Token refreshed'); + return true; + } catch (error) { + console.warn('Existing token invalid, clearing auth'); + this.pb.authStore.clear(); + this.updateState(null); + this.updateAuthUI(); + return false; + } + } + + /** + * Update internal auth state + */ + private updateState(data: any): void { + if (!data) { + this.state = { + isAuthenticated: false, + user: null, + token: null, + }; + return; + } + + const record = data.record || this.pb.authStore.record || this.pb.authStore.model; + const meta = data.meta || {}; + const model = this.pb.authStore.model; + + this.state = { + isAuthenticated: true, + user: { + id: record?.id || model?.id || 'unknown', + name: record?.name || model?.name || meta?.name || record?.email || model?.email || 'Unknown User', + email: record?.email || model?.email || meta?.email || '(no email)', + }, + token: this.pb.authStore.token, + }; + } + + /** + * Update UI based on auth state + */ + updateAuthUI(): void { + const loginContainer = document.getElementById(this.config.loginContainerId); + const userDisplayName = document.getElementById(this.config.userDisplayNameId); + const userEmail = document.getElementById(this.config.userEmailId); + + if (!loginContainer) return; + + if (this.pb.authStore.isValid) { + loginContainer.classList.add('hidden'); + if (this.state.user) { + if (userDisplayName) userDisplayName.textContent = this.state.user.name; + if (userEmail) userEmail.textContent = this.state.user.email; + } + } else { + loginContainer.classList.remove('hidden'); + const loginError = document.getElementById(this.config.loginErrorId); + if (loginError) loginError.classList.add('hidden'); + } + + this.callbacks.onUiUpdate?.(this.state); + } + + /** + * Check token status (for verification/display) + */ + async checkTokenStatus(): Promise<{ pbToken: boolean; tokenExpiry?: string }> { + const pbToken = !!this.pb.authStore.token; + return { pbToken }; + } + + /** + * Get current auth state + */ + getAuthState(): AuthState { + return { ...this.state }; + } + + /** + * Get PocketBase instance (for direct usage if needed) + */ + getPocketBase(): PocketBase { + return this.pb; + } + + /** + * Logout + */ + logout(): void { + this.pb.authStore.clear(); + this.updateState(null); + this.updateAuthUI(); + console.log('✓ Logged out'); + } + + /** + * Reset login button to initial state + */ + private resetLoginButton(): void { + const loginBtn = document.getElementById(this.config.loginBtnId) as HTMLButtonElement; + if (loginBtn) { + loginBtn.disabled = false; + loginBtn.textContent = 'Login with Microsoft'; + } + } +} + +/** + * Quick init function - fetches config from backend + */ +export async function initPocketBaseAuth( + backendUrl: string, + callbacks?: Partial +): Promise { + // Fetch frontend config from backend + const response = await fetch(`${backendUrl}/api/auth/config`); + if (!response.ok) { + throw new Error('Failed to fetch auth configuration from backend'); + } + const config = await response.json(); + + const auth = new PocketBaseAuth(config, callbacks); + return auth; +} diff --git a/Mode1Svelte/auth-module/types.ts b/Mode1Svelte/auth-module/types.ts new file mode 100644 index 0000000..b320f76 --- /dev/null +++ b/Mode1Svelte/auth-module/types.ts @@ -0,0 +1,62 @@ +/** + * Frontend Auth Configuration + */ +export interface AuthConfig { + pbUrl: string; // PocketBase URL (required) + collection?: string; + provider?: string; + loginContainerId?: string; + userDisplayNameId?: string; + userEmailId?: string; + loginBtnId?: string; + loginErrorId?: string; +} + +/** + * Frontend configuration provided by backend + */ +export interface FrontendConfig { + pbUrl: string; + provider?: string; + collection?: string; +} + +/** + * Backend Auth Configuration + */ +export interface BackendAuthConfig { + clientId?: string; + tenantId?: string; + clientSecret?: string; +} + +/** + * Auth state object + */ +export interface AuthState { + isAuthenticated: boolean; + user: { + id: string; + name: string; + email: string; + } | null; + token: string | null; +} + +/** + * Graph token cache object + */ +export interface GraphTokenCache { + token: string; + expiresOn: number; +} + +/** + * Auth event callbacks + */ +export interface AuthCallbacks { + onAuthSuccess?: (state: AuthState) => void; + onAuthFailure?: (error: any) => void; + onTokenUpdate?: (state: AuthState) => void; + onUiUpdate?: (state: AuthState) => void; +} diff --git a/Mode1Svelte/backend/server.ts b/Mode1Svelte/backend/server.ts new file mode 100644 index 0000000..a8bf8d3 --- /dev/null +++ b/Mode1Svelte/backend/server.ts @@ -0,0 +1,226 @@ +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, +}; diff --git a/Mode1Svelte/bun.lock b/Mode1Svelte/bun.lock new file mode 100644 index 0000000..9091d9e --- /dev/null +++ b/Mode1Svelte/bun.lock @@ -0,0 +1,226 @@ +{ + "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=="], + } +} diff --git a/Mode1Svelte/frontend/auth.ts b/Mode1Svelte/frontend/auth.ts new file mode 100644 index 0000000..cb198e7 --- /dev/null +++ b/Mode1Svelte/frontend/auth.ts @@ -0,0 +1,3 @@ +// Frontend entry point - exports auth module for browser +export { PocketBaseAuth, initPocketBaseAuth } from '../auth-module/frontend'; +export type { AuthConfig, AuthState, AuthCallbacks } from '../auth-module/types'; diff --git a/Mode1Svelte/frontend/index.html b/Mode1Svelte/frontend/index.html new file mode 100644 index 0000000..c55e615 --- /dev/null +++ b/Mode1Svelte/frontend/index.html @@ -0,0 +1,545 @@ + + + + + + Mode1 - Auth Module Test + + + +
+

🔐 Auth Module Test Suite (Mode1)

+ +
+ +
+

+ + Frontend: PocketBase Auth +

+ +
+ Tests OAuth2 login, token refresh, and user state management. +
+ +
+ + +
+ + +
+
+
+ + +
+

+ + Backend: Graph Token & PB Validation +

+ +
+ Tests Microsoft Graph token acquisition, caching, and PocketBase token validation. +
+ +
+ + +
+ + + +
+
+
+
+ + +
+

+ + Job Info (First 4 Records) +

+ +
+ Fetch and display first 4 records from Job_Info_Prod collection. +
+ + + +
+
+
+
+ + + + + +
+
+ + + + + diff --git a/Mode1Svelte/package.json b/Mode1Svelte/package.json new file mode 100644 index 0000000..1c1fd7e --- /dev/null +++ b/Mode1Svelte/package.json @@ -0,0 +1,26 @@ +{ + "name": "mode1-auth-test", + "version": "1.0.0", + "type": "module", + "private": true, + "scripts": { + "start": "bun run backend/server.ts", + "dev": "bun --watch backend/server.ts", + "build": "vite build", + "prebuild": "vite build" + }, + "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" + } +} diff --git a/Mode1Svelte/vite.config.ts b/Mode1Svelte/vite.config.ts new file mode 100644 index 0000000..486b3db --- /dev/null +++ b/Mode1Svelte/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + build: { + outDir: 'frontend/dist', + emptyOutDir: true, + lib: { + entry: 'frontend/auth.ts', + name: 'AuthModule', + fileName: () => 'auth.js', + formats: ['umd'] + } + }, + server: { + middlewareMode: true + } +}); diff --git a/ModeSwitch/backend/orchestrator.ts b/ModeSwitch/backend/orchestrator.ts index 9bea8c6..5c86f6d 100644 --- a/ModeSwitch/backend/orchestrator.ts +++ b/ModeSwitch/backend/orchestrator.ts @@ -11,10 +11,11 @@ const ORCHESTRATOR_PORT = 3006; // Orchestrator API runs here // Mode folder mappings - only Mode6Test exists currently const MODE_FOLDERS: Record = { 'Mode1': 'Mode1', + 'Mode1Svelte': 'Mode1Svelte', 'Mode6Test': 'Mode6Test', }; -let currentMode: string = 'Mode6Test'; +let currentMode: string = 'Mode1Svelte'; let currentProcess: any = null; // Helper: Read active mode from config file