Scaffold NewApproach: Full-stack Bun+Hono+TypeScript+TailwindCSS project
- Create modular project structure with src/backend, src/frontend, public - Hono server with routing, static file serving, and API endpoints - TailwindCSS styling pipeline with Tailwind CLI - Vanilla JS frontend (extensible for frameworks) - Complete TypeScript configuration and type checking - Auth module as self-contained submodule - Ready for development: bun run dev (port 3000) - All dependencies installed and tested Also: - Lock main branch with read-only settings in VS Code - Create .github/copilot-instructions.md with Monica persona - Initialize session logging system Status: Project ready for feature development
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
// Auth Module: Universal, pattern-based token management
|
||||
//
|
||||
// How it works:
|
||||
// - Manages tokens for multiple auth patterns (PocketBase, Microsoft Graph, etc.)
|
||||
// - Stores tokens in localStorage under standardized keys
|
||||
// - Supports composition of patterns (e.g., 'pb+graph' enables both)
|
||||
// - All operations are synchronous and immutable
|
||||
//
|
||||
// Usage:
|
||||
// Auth.configure('pb+graph').then(() => {
|
||||
// const token = Auth.getToken('pb-user');
|
||||
// const graphToken = Auth.getToken('graph-user');
|
||||
// })
|
||||
//
|
||||
// Token types available:
|
||||
// - pb-user: PocketBase user token
|
||||
// - pb-agent: PocketBase service account token
|
||||
// - graph-user: Microsoft Graph delegated token
|
||||
// - graph-agent: Microsoft Graph app-only token
|
||||
//
|
||||
// Dependencies:
|
||||
// - PocketBase (optional, for pb tokens): window.PocketBase
|
||||
// - localStorage: browser API for token persistence
|
||||
//
|
||||
// Gotchas:
|
||||
// - Tokens are stored in plaintext in localStorage (security consideration)
|
||||
// - Pattern must be set before accessing tokens
|
||||
// - All token types must be explicitly enabled via configure()
|
||||
|
||||
type TokenType = 'pb-user' | 'pb-agent' | 'graph-user' | 'graph-agent';
|
||||
|
||||
interface TokenConfig {
|
||||
[key in TokenType]: string;
|
||||
}
|
||||
|
||||
interface AuthConfig {
|
||||
pbUrl?: string;
|
||||
graphApiUrl?: string;
|
||||
}
|
||||
|
||||
const TOKEN_STORAGE_KEYS: TokenConfig = {
|
||||
'pb-user': 'auth:pb-user',
|
||||
'pb-agent': 'auth:pb-agent',
|
||||
'graph-user': 'auth:graph-user',
|
||||
'graph-agent': 'auth:graph-agent'
|
||||
};
|
||||
|
||||
class AuthManager {
|
||||
private pattern: TokenType[] = [];
|
||||
private pbInstance: any = null;
|
||||
private config: AuthConfig = {};
|
||||
|
||||
async configure(patternString: string, config?: AuthConfig): Promise<void> {
|
||||
this.pattern = this.parsePattern(patternString);
|
||||
this.config = config || {};
|
||||
|
||||
if (this.pattern.some(t => t.startsWith('pb'))) {
|
||||
await this.initPocketBase();
|
||||
}
|
||||
}
|
||||
|
||||
private parsePattern(pattern: string): TokenType[] {
|
||||
return pattern
|
||||
.split('+')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean) as TokenType[];
|
||||
}
|
||||
|
||||
private async initPocketBase(): Promise<void> {
|
||||
if (this.pbInstance) return;
|
||||
|
||||
const pbUrl = this.config.pbUrl || 'http://127.0.0.1:8090';
|
||||
const PocketBase = (window as any).PocketBase;
|
||||
|
||||
if (!PocketBase) {
|
||||
throw new Error('PocketBase library not loaded. Include PocketBase script before Auth initialization.');
|
||||
}
|
||||
|
||||
this.pbInstance = new PocketBase(pbUrl);
|
||||
}
|
||||
|
||||
getToken(type: TokenType): string | null {
|
||||
const key = TOKEN_STORAGE_KEYS[type];
|
||||
if (!key) return null;
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
|
||||
setToken(type: TokenType, token: string | null): void {
|
||||
const key = TOKEN_STORAGE_KEYS[type];
|
||||
if (!key) return;
|
||||
|
||||
if (token) {
|
||||
localStorage.setItem(key, token);
|
||||
} else {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
clearToken(type: TokenType): void {
|
||||
this.setToken(type, null);
|
||||
}
|
||||
|
||||
clearAllTokens(): void {
|
||||
Object.keys(TOKEN_STORAGE_KEYS).forEach(key => {
|
||||
localStorage.removeItem(TOKEN_STORAGE_KEYS[key as TokenType]);
|
||||
});
|
||||
}
|
||||
|
||||
isConfigured(): boolean {
|
||||
return this.pattern.length > 0;
|
||||
}
|
||||
|
||||
getEnabledTypes(): TokenType[] {
|
||||
return [...this.pattern];
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
const Auth = new AuthManager();
|
||||
|
||||
// Make available globally for browser environments
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).Auth = Auth;
|
||||
}
|
||||
|
||||
export default Auth;
|
||||
export { TokenType, AuthManager };
|
||||
Reference in New Issue
Block a user