Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
84 changes: 84 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* This is intended to be a basic starting point for linting in your app.
* It relies on recommended configs out of the box for simplicity, but you can
* and should modify this configuration to best suit your team's needs.
*/

/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
commonjs: true,
es6: true,
},
ignorePatterns: ["!**/.server", "!**/.client"],

// Base config
extends: ["eslint:recommended"],

overrides: [
// React
{
files: ["**/*.{js,jsx,ts,tsx}"],
plugins: ["react", "jsx-a11y"],
extends: [
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
],
settings: {
react: {
version: "detect",
},
formComponents: ["Form"],
linkComponents: [
{ name: "Link", linkAttribute: "to" },
{ name: "NavLink", linkAttribute: "to" },
],
"import/resolver": {
typescript: {},
},
},
},

// Typescript
{
files: ["**/*.{ts,tsx}"],
plugins: ["@typescript-eslint", "import"],
parser: "@typescript-eslint/parser",
settings: {
"import/internal-regex": "^~/",
"import/resolver": {
node: {
extensions: [".ts", ".tsx"],
},
typescript: {
alwaysTryTypes: true,
},
},
},
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
],
},

// Node
{
files: [".eslintrc.cjs"],
env: {
node: true,
},
},
],
};
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules

/.cache
/build
.env
297 changes: 297 additions & 0 deletions AUTH-MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
# Authentication System Migration - NextJS to Remix

## Overview

The authentication system has been successfully migrated from NextJS to Remix, following Remix best practices and maintaining the same API structure and protobuf usage. The new system provides better security, performance, and developer experience.

## Key Changes

### 1. **Server-Side Session Management**
- **Before (NextJS)**: Client-side cookie management with `getCookie()`, `setCookie()`
- **After (Remix)**: Server-side session storage with HTTP-only cookies
- **Benefits**: Better security, no client-side token exposure, automatic CSRF protection

### 2. **Data Fetching Pattern**
- **Before (NextJS)**: Client-side API calls with `useEffect` and state management
- **After (Remix)**: Server-side loaders and actions with automatic revalidation
- **Benefits**: Better performance, SEO, and user experience

### 3. **Form Handling**
- **Before (NextJS)**: Manual form state and submission handling
- **After (Remix)**: Progressive enhancement with `<Form>` component and fetchers
- **Benefits**: Works without JavaScript, better accessibility, automatic loading states

## File Structure

```
app/
├── lib/
│ ├── auth.server.ts # Server-side auth utilities
│ └── auth-utils.tsx # Client-side auth utilities and guards
├── components/auth/
│ ├── auth-provider.tsx # Auth context provider (Remix-optimized)
│ ├── login-form.tsx # Login form component
│ └── user-menu.tsx # User menu with logout
├── routes/
│ ├── auth.login.tsx # Login action route
│ ├── auth.register.tsx # Registration action route
│ ├── auth.logout.tsx # Logout action route
│ └── login.tsx # Login page
├── services/
│ └── user.ts # User service functions
└── root.tsx # Updated with AuthProvider
```

## Usage Examples

### 1. **Using the Auth Context**

```tsx
import { useAuth } from '~/components/auth/auth-provider';

function MyComponent() {
const { user, isAuthenticated, login, logout } = useAuth();

if (!isAuthenticated()) {
return <div>Please log in</div>;
}

return <div>Welcome, {user?.email}!</div>;
}
```

### 2. **Protecting Routes (Server-Side)**

```tsx
// In your route loader
import { requireAuth } from '~/lib/auth.server';

export async function loader({ request }: LoaderFunctionArgs) {
const authSession = await requireAuth(request); // Throws redirect if not authenticated

// User is authenticated, proceed with loading data
const data = await getProtectedData(authSession.token);
return json({ data });
}
```

### 3. **Protecting Components (Client-Side)**

```tsx
import { AuthGuard } from '~/lib/auth-utils';

function ProtectedContent() {
return (
<AuthGuard fallback={<div>Please log in to see this content</div>}>
<div>Secret content here!</div>
</AuthGuard>
);
}
```

### 4. **Using Higher-Order Component**

```tsx
import { withAuth } from '~/lib/auth-utils';

const ProtectedComponent = withAuth(function MyComponent() {
return <div>This component requires authentication</div>;
});
```

### 5. **Manual Login/Logout**

```tsx
import { useAuth } from '~/components/auth/auth-provider';

function AuthButtons() {
const { login, logout, isAuthenticated } = useAuth();

if (isAuthenticated()) {
return <button onClick={logout}>Logout</button>;
}

return (
<button onClick={() => login('email@example.com', 'password')}>
Login
</button>
);
}
```

## API Integration

The auth system maintains the same API client structure:

```tsx
// Server-side (in loaders/actions)
import { apiClient } from '~/lib/api-client';

export async function loader({ request }: LoaderFunctionArgs) {
const user = await apiClient.getCurrentUser(request.headers);
return json({ user });
}

// Client-side (in components)
import { useAuth } from '~/components/auth/auth-provider';

function UserProfile() {
const { user } = useAuth(); // User data from server
return <div>{user?.email}</div>;
}
```

## Environment Variables

Add to your `.env` file:

```env
# Required for session encryption
SESSION_SECRET=your-super-secret-session-key-change-in-production

# API URLs (same as before)
NEXT_PUBLIC_API_URL=https://api.sunnah.dev
INTERNAL_API_URL=http://backend:8080
```

## Migration Benefits

### 1. **Security Improvements**
- HTTP-only cookies prevent XSS attacks
- Server-side session validation
- Automatic CSRF protection
- No token exposure to client-side JavaScript

### 2. **Performance Improvements**
- Server-side rendering with user data
- Automatic data revalidation
- Progressive enhancement
- Smaller client-side bundles

### 3. **Developer Experience**
- Simpler state management
- Built-in loading and error states
- Type-safe throughout
- Better debugging with Remix DevTools

### 4. **User Experience**
- Faster page loads
- Works without JavaScript
- Better accessibility
- Automatic form validation

## Backward Compatibility

The auth system maintains the same interface as the NextJS version:

- Same `User` and `UserSettings` types from protobuf
- Same API client methods
- Same authentication flow
- Same error handling patterns

## Testing

```tsx
// Test authenticated components
import { render } from '@testing-library/react';
import { AuthProvider } from '~/components/auth/auth-provider';

function renderWithAuth(component: React.ReactElement, user?: User) {
return render(
<AuthProvider initialUser={user}>
{component}
</AuthProvider>
);
}

test('shows user menu when authenticated', () => {
const mockUser = { id: '1', email: 'test@example.com' };
renderWithAuth(<UserMenu />, mockUser);
// Test assertions...
});
```

## Common Patterns

### 1. **Conditional Rendering Based on Auth**

```tsx
function Navigation() {
const { isAuthenticated } = useAuth();

return (
<nav>
<Link to="/">Home</Link>
{isAuthenticated() ? (
<>
<Link to="/profile">Profile</Link>
<UserMenu />
</>
) : (
<Link to="/login">Login</Link>
)}
</nav>
);
}
```

### 2. **Loading States**

```tsx
function UserProfile() {
const { user, isLoading } = useAuth();

if (isLoading) {
return <div>Loading...</div>;
}

if (!user) {
return <div>Not authenticated</div>;
}

return <div>Welcome, {user.email}!</div>;
}
```

### 3. **Error Handling**

```tsx
function LoginForm() {
const { error } = useAuth();

return (
<form>
{/* Form fields */}
{error && (
<div className="error">
{error}
</div>
)}
</form>
);
}
```

## Next Steps

1. **Add OAuth Support**: Extend the auth routes to handle OAuth providers
2. **Add Password Reset**: Create routes for password reset flow
3. **Add Email Verification**: Handle email verification process
4. **Add Role-Based Access**: Extend permissions system for different user roles
5. **Add Session Management**: Add ability to view/revoke active sessions

## Troubleshooting

### Common Issues

1. **Session Secret**: Make sure `SESSION_SECRET` is set in production
2. **API URLs**: Ensure `INTERNAL_API_URL` is correctly configured for server-side requests
3. **Cookie Settings**: Verify cookie settings match your domain configuration
4. **Type Errors**: Ensure protobuf types are properly imported and used

### Debug Tips

1. Use Remix DevTools to inspect loader data
2. Check browser Network tab for auth requests
3. Verify session cookies in browser DevTools
4. Check server logs for authentication errors
Loading
Loading