mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-06 21:29:05 +00:00
Initial commit
This commit is contained in:
209
lib/stores/auth.ts
Normal file
209
lib/stores/auth.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
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;
|
||||
});
|
||||
Reference in New Issue
Block a user