Files
cmdb-insight/frontend/src/stores/authStore.ts
Bert Hausmans ea1c84262c Add OAuth 2.0 authentication support for Jira Data Center
- Add OAuth 2.0 configuration options in backend env.ts
- Create authService.ts for OAuth flow, token management, and sessions
- Create auth.ts routes for login, callback, logout, and user info
- Update JiraAssets service to use user tokens when OAuth is enabled
- Add cookie-parser for session handling
- Create Login.tsx component with Jira OAuth login button
- Add authStore.ts (Zustand) for frontend auth state management
- Update App.tsx to show login page when OAuth is enabled
- Add user menu with logout functionality
- Document OAuth setup in CLAUDE.md

Supports two modes:
1. Service Account: Uses JIRA_PAT for all requests (default)
2. OAuth 2.0: Each user authenticates with their Jira credentials
2026-01-06 15:40:52 +01:00

151 lines
3.6 KiB
TypeScript

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export interface User {
accountId: string;
displayName: string;
emailAddress?: string;
avatarUrl?: string;
}
interface AuthConfig {
oauthEnabled: boolean;
serviceAccountEnabled: boolean;
jiraHost: string;
}
interface AuthState {
// State
user: User | null;
isAuthenticated: boolean;
authMethod: 'oauth' | 'service-account' | null;
isLoading: boolean;
error: string | null;
config: AuthConfig | null;
// Actions
setUser: (user: User | null, method: 'oauth' | 'service-account' | null) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
setConfig: (config: AuthConfig) => void;
logout: () => Promise<void>;
checkAuth: () => Promise<void>;
fetchConfig: () => Promise<void>;
}
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:3001';
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
// Initial state
user: null,
isAuthenticated: false,
authMethod: null,
isLoading: true,
error: null,
config: null,
// Actions
setUser: (user, method) => set({
user,
isAuthenticated: !!user,
authMethod: method,
error: null,
}),
setLoading: (loading) => set({ isLoading: loading }),
setError: (error) => set({ error, isLoading: false }),
setConfig: (config) => set({ config }),
logout: async () => {
try {
await fetch(`${API_BASE}/api/auth/logout`, {
method: 'POST',
credentials: 'include',
});
} catch (error) {
console.error('Logout error:', error);
}
set({
user: null,
isAuthenticated: false,
authMethod: null,
error: null,
});
},
checkAuth: async () => {
set({ isLoading: true });
try {
const response = await fetch(`${API_BASE}/api/auth/me`, {
credentials: 'include',
});
if (!response.ok) {
throw new Error('Auth check failed');
}
const data = await response.json();
if (data.authenticated) {
set({
user: data.user,
isAuthenticated: true,
authMethod: data.authMethod,
isLoading: false,
error: null,
});
} else {
set({
user: null,
isAuthenticated: false,
authMethod: null,
isLoading: false,
});
}
} catch (error) {
console.error('Auth check error:', error);
set({
user: null,
isAuthenticated: false,
authMethod: null,
isLoading: false,
error: error instanceof Error ? error.message : 'Authentication check failed',
});
}
},
fetchConfig: async () => {
try {
const response = await fetch(`${API_BASE}/api/auth/config`, {
credentials: 'include',
});
if (response.ok) {
const config = await response.json();
set({ config });
}
} catch (error) {
console.error('Failed to fetch auth config:', error);
}
},
}),
{
name: 'auth-storage',
partialize: (state) => ({
// Only persist non-sensitive data
authMethod: state.authMethod,
}),
}
)
);
// Helper to get login URL
export function getLoginUrl(): string {
return `${API_BASE}/api/auth/login`;
}