Files
bolt.diy/app/lib/hooks/useAuth.ts
Nirmal Arya 1687d812bf feat: implement GitHub OIDC authentication
- Add GitHub OAuth 2.0 authentication flow
- Create authentication routes: /auth/login, /auth/callback, /auth/logout
- Implement OAuth server utilities with proper session management
- Add authentication hooks and client-side state management
- Update header with login/logout controls and user menu
- Create user profile page with GitHub integration
- Add environment variables for GitHub OAuth configuration
- Include comprehensive documentation for setup and usage
- Enhance profile store with authentication state
- Add authentication status API endpoint

Closes: GitHub authentication implementation
2025-05-31 15:40:32 -04:00

135 lines
3.6 KiB
TypeScript

import { useEffect, useState } from 'react';
import { useNavigate, useLocation } from '@remix-run/react';
import {
profileStore,
setAuthenticatedUser,
clearAuthState,
isAuthenticated as getIsAuthenticated,
getGitHubUser
} from '~/lib/stores/profile';
import { useStore } from '@nanostores/react';
import type { GitHubUser } from '~/lib/auth/github-oauth.server';
// Define the API response type for auth status
interface AuthStatusResponse {
isAuthenticated: boolean;
user?: GitHubUser;
tokenStatus?: {
hasToken: boolean;
};
error?: string;
}
/**
* Hook to manage authentication state and sync between server and client
*/
export function useAuth() {
const navigate = useNavigate();
const location = useLocation();
const profile = useStore(profileStore);
const [isLoading, setIsLoading] = useState(true);
const [serverChecked, setServerChecked] = useState(false);
// Sync server authentication state with client on initial load
useEffect(() => {
const checkServerAuth = async () => {
try {
setIsLoading(true);
// Fetch authentication status from server
const response = await fetch('/api/auth/status');
if (response.ok) {
const data = await response.json() as AuthStatusResponse;
if (data.isAuthenticated && data.user) {
// Update client state with server auth data
setAuthenticatedUser(data.user);
} else if (profile.isAuthenticated) {
// Clear client state if server says not authenticated
clearAuthState();
}
} else {
// Handle error - assume not authenticated
if (profile.isAuthenticated) {
clearAuthState();
}
}
} catch (error) {
console.error('Error checking authentication status:', error);
} finally {
setIsLoading(false);
setServerChecked(true);
}
};
// Only check server auth once
if (!serverChecked) {
checkServerAuth();
}
}, [serverChecked, profile.isAuthenticated]);
/**
* Navigate to login page
*/
const login = (redirectTo?: string) => {
const searchParams = new URLSearchParams();
if (redirectTo || location.pathname !== '/login') {
searchParams.set('redirectTo', redirectTo || location.pathname);
}
const searchParamsString = searchParams.toString();
navigate(`/auth/login${searchParamsString ? `?${searchParamsString}` : ''}`);
};
/**
* Navigate to logout page
*/
const logout = (redirectTo?: string) => {
const searchParams = new URLSearchParams();
if (redirectTo) {
searchParams.set('redirectTo', redirectTo);
}
const searchParamsString = searchParams.toString();
navigate(`/auth/logout${searchParamsString ? `?${searchParamsString}` : ''}`);
};
/**
* Check if user needs to authenticate for a protected resource
* and redirect to login if needed
*/
const requireAuth = (redirectIfNotAuth: boolean = true) => {
if (!isLoading && !profile.isAuthenticated && redirectIfNotAuth) {
login();
return false;
}
return profile.isAuthenticated;
};
return {
// Authentication state
isAuthenticated: profile.isAuthenticated,
isLoading,
// User data
user: profile.isAuthenticated ? {
username: profile.username,
avatar: profile.avatar,
bio: profile.bio,
githubUser: profile.github,
lastLogin: profile.lastLogin
} : null,
// GitHub specific data
githubUser: getGitHubUser(),
// Actions
login,
logout,
requireAuth,
};
}