Files
dockhand/lib/stores/auth.ts
Jarek Krochmalski 62e3c6439e Initial commit
2025-12-28 21:16:03 +01:00

210 lines
5.1 KiB
TypeScript

import { writable, derived } from 'svelte/store';
export interface Permissions {
containers: string[];
images: string[];
volumes: string[];
networks: string[];
stacks: string[];
environments: string[];
registries: string[];
notifications: string[];
configsets: string[];
settings: string[];
users: string[];
git: string[];
license: string[];
audit_logs: string[];
activity: string[];
schedules: string[];
}
export interface AuthUser {
id: number;
username: string;
email?: string;
displayName?: string;
avatar?: string;
isAdmin: boolean;
provider: 'local' | 'ldap' | 'oidc';
permissions: Permissions;
}
export interface AuthState {
user: AuthUser | null;
loading: boolean;
authEnabled: boolean;
authenticated: boolean;
}
function createAuthStore() {
const { subscribe, set, update } = writable<AuthState>({
user: null,
loading: true,
authEnabled: false,
authenticated: false
});
return {
subscribe,
/**
* Check current session status
* Should be called on app init
*/
async check() {
update(state => ({ ...state, loading: true }));
try {
const response = await fetch('/api/auth/session');
const data = await response.json();
if (data.error) {
set({
user: null,
loading: false,
authEnabled: false,
authenticated: false
});
return;
}
set({
user: data.user || null,
loading: false,
authEnabled: data.authEnabled,
authenticated: data.authenticated
});
} catch {
set({
user: null,
loading: false,
authEnabled: false,
authenticated: false
});
}
},
/**
* Login with username and password
*/
async login(username: string, password: string, mfaToken?: string, provider: string = 'local'): Promise<{
success: boolean;
error?: string;
requiresMfa?: boolean;
}> {
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, mfaToken, provider })
});
const data = await response.json();
if (!response.ok) {
return { success: false, error: data.error || 'Login failed' };
}
if (data.requiresMfa) {
return { success: true, requiresMfa: true };
}
if (data.success && data.user) {
// Refresh session to get full user with permissions
await this.check();
return { success: true };
}
return { success: false, error: 'Login failed' };
} catch (error) {
return { success: false, error: 'Network error' };
}
},
/**
* Logout and clear session
*/
async logout() {
try {
await fetch('/api/auth/logout', { method: 'POST' });
} finally {
set({
user: null,
loading: false,
authEnabled: true, // Keep authEnabled as we know it was on
authenticated: false
});
}
},
/**
* Check if user has a specific permission
* When auth is disabled, returns true (full access)
*/
hasPermission(user: AuthUser | null, authEnabled: boolean, resource: keyof Permissions, action: string): boolean {
// If auth is disabled, everything is allowed
if (!authEnabled) return true;
// If no user and auth is enabled, deny
if (!user) return false;
// Admins can do anything
if (user.isAdmin) return true;
// Check specific permission
const permissions = user.permissions[resource];
return permissions?.includes(action) ?? false;
}
};
}
export const authStore = createAuthStore();
// Derived store for easy permission checking
export const canAccess = derived(authStore, ($auth) => {
return (resource: keyof Permissions, action: string): boolean => {
// If auth is disabled, everything is allowed
if (!$auth.authEnabled) return true;
// If not authenticated and auth is enabled, deny
if (!$auth.authenticated || !$auth.user) return false;
// Admins can do anything
if ($auth.user.isAdmin) return true;
// Check specific permission
const permissions = $auth.user.permissions?.[resource];
return permissions?.includes(action) ?? false;
};
});
// Derived store to check if user has ANY permission for a resource
// Used for menu visibility - show menu if user has any access to that resource
export const hasAnyAccess = derived(authStore, ($auth) => {
return (resource: keyof Permissions): boolean => {
// If auth is disabled, everything is allowed
if (!$auth.authEnabled) return true;
// If not authenticated and auth is enabled, deny
if (!$auth.authenticated || !$auth.user) return false;
// Admins can do anything
if ($auth.user.isAdmin) return true;
// Check if user has ANY permission for this resource
const permissions = $auth.user.permissions?.[resource];
return permissions && permissions.length > 0;
};
});
// Derived store for whether auth is required for the current session
export const requiresAuth = derived(authStore, ($auth) => {
return $auth.authEnabled && !$auth.authenticated;
});
// Derived store for admin check - true if auth disabled OR user is admin
export const isAdmin = derived(authStore, ($auth) => {
if (!$auth.authEnabled) return true;
return $auth.user?.isAdmin ?? false;
});