Add Mode1 auth testing environment with token comparison verification
- Created Mode1 as self-contained test environment - Added auth-module copy with self-contained configuration - Implemented backend server with Graph token and PocketBase validation endpoints - Added frontend test dashboard with token comparison functionality - Fixed TypeScript compatibility issues (replaced import.meta.dir with __dirname) - Implemented actual PB token validation in comparison endpoint (no placeholders) - All endpoints tested and working with real tokens
This commit is contained in:
@@ -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
|
||||
<!-- 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
|
||||
Reference in New Issue
Block a user