feat: improve authentication and cookie management
- Updated Docker and Turbo configuration to include more environment variables - Modified API configuration to support dynamic cookie and CORS settings - Enhanced user authentication flow with optional device ID and automatic generation - Refactored login, register, and logout resolvers to handle device management - Updated GraphQL schema to make device ID optional - Improved web application logout and authentication handling - Simplified client-side GraphQL mutations for login and registration
This commit is contained in:
parent
d29d116214
commit
f9c6230101
15 changed files with 190 additions and 148 deletions
|
@ -1,6 +1,6 @@
|
|||
import { createSignal, Show } from 'solid-js';
|
||||
import { Provider } from '@urql/solid';
|
||||
import { client } from './lib/graphql-client';
|
||||
import { createSignal, Show, onMount } from 'solid-js';
|
||||
import { gql } from '@urql/core';
|
||||
import { createMutation } from '@urql/solid';
|
||||
import { LoginForm } from './components/login-form';
|
||||
import { RegisterForm } from './components/register-form';
|
||||
import { RoomList } from './components/room-list';
|
||||
|
@ -8,11 +8,18 @@ import { ChatRoom } from './components/chat-room';
|
|||
import { CreateRoom } from './components/create-room';
|
||||
import './App.css';
|
||||
|
||||
const LOGOUT_MUTATION = gql`
|
||||
mutation Logout {
|
||||
logout
|
||||
}
|
||||
`;
|
||||
|
||||
function App() {
|
||||
const [isAuthenticated, setIsAuthenticated] = createSignal(false);
|
||||
const [userId, setUserId] = createSignal('');
|
||||
const [selectedRoomId, setSelectedRoomId] = createSignal<string | null>(null);
|
||||
const [showRegister, setShowRegister] = createSignal(false);
|
||||
const [, logout] = createMutation(LOGOUT_MUTATION);
|
||||
|
||||
// Function to generate avatar URL
|
||||
const getUserAvatarUrl = (userId: string, size: number = 40) =>
|
||||
|
@ -30,19 +37,27 @@ function App() {
|
|||
};
|
||||
|
||||
// Call checkAuth on component mount
|
||||
checkAuth();
|
||||
onMount(() => {
|
||||
checkAuth();
|
||||
});
|
||||
|
||||
const handleLoginSuccess = (_: string, id: string) => {
|
||||
setIsAuthenticated(true);
|
||||
setUserId(id);
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('userId');
|
||||
setIsAuthenticated(false);
|
||||
setUserId('');
|
||||
setSelectedRoomId(null);
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await logout({});
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
} finally {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('userId');
|
||||
setIsAuthenticated(false);
|
||||
setUserId('');
|
||||
setSelectedRoomId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectRoom = (roomId: string) => {
|
||||
|
@ -54,81 +69,79 @@ function App() {
|
|||
};
|
||||
|
||||
return (
|
||||
<Provider value={client}>
|
||||
<div class='app'>
|
||||
<header class='app-header'>
|
||||
<h1>Unreal Chat</h1>
|
||||
{isAuthenticated() && (
|
||||
<div class='user-profile'>
|
||||
<img
|
||||
src={getUserAvatarUrl(userId())}
|
||||
alt='User avatar'
|
||||
class='user-avatar'
|
||||
/>
|
||||
<button class='logout-button' onClick={handleLogout}>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
<div class='app'>
|
||||
<header class='app-header'>
|
||||
<h1>Unreal Chat</h1>
|
||||
{isAuthenticated() && (
|
||||
<div class='user-profile'>
|
||||
<img
|
||||
src={getUserAvatarUrl(userId())}
|
||||
alt='User avatar'
|
||||
class='user-avatar'
|
||||
/>
|
||||
<button class='logout-button' onClick={handleLogout}>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<main class='app-main'>
|
||||
<Show
|
||||
when={isAuthenticated()}
|
||||
fallback={
|
||||
<div class='auth-container'>
|
||||
<div class='auth-tabs'>
|
||||
<button
|
||||
class={!showRegister() ? 'active' : ''}
|
||||
onClick={() => setShowRegister(false)}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
class={showRegister() ? 'active' : ''}
|
||||
onClick={() => setShowRegister(true)}
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={showRegister()}
|
||||
fallback={<LoginForm onLoginSuccess={handleLoginSuccess} />}
|
||||
<main class='app-main'>
|
||||
<Show
|
||||
when={isAuthenticated()}
|
||||
fallback={
|
||||
<div class='auth-container'>
|
||||
<div class='auth-tabs'>
|
||||
<button
|
||||
class={!showRegister() ? 'active' : ''}
|
||||
onClick={() => setShowRegister(false)}
|
||||
>
|
||||
<RegisterForm onRegisterSuccess={handleLoginSuccess} />
|
||||
</Show>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
class={showRegister() ? 'active' : ''}
|
||||
onClick={() => setShowRegister(true)}
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class='chat-container'>
|
||||
<aside class='sidebar'>
|
||||
<CreateRoom onRoomCreated={handleSelectRoom} />
|
||||
<RoomList
|
||||
onSelectRoom={handleSelectRoom}
|
||||
selectedRoomId={selectedRoomId() || undefined}
|
||||
/>
|
||||
</aside>
|
||||
|
||||
<main class='chat-main'>
|
||||
<Show when={selectedRoomId()}>
|
||||
<ChatRoom
|
||||
roomId={selectedRoomId() || ''}
|
||||
userId={userId() || ''}
|
||||
onLeaveRoom={handleLeaveRoom}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={!selectedRoomId()}>
|
||||
<div class='select-room-message'>
|
||||
Select a room to start chatting or create a new one
|
||||
</div>
|
||||
</Show>
|
||||
</main>
|
||||
<Show
|
||||
when={showRegister()}
|
||||
fallback={<LoginForm onLoginSuccess={handleLoginSuccess} />}
|
||||
>
|
||||
<RegisterForm onRegisterSuccess={handleLoginSuccess} />
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</main>
|
||||
</div>
|
||||
</Provider>
|
||||
}
|
||||
>
|
||||
<div class='chat-container'>
|
||||
<aside class='sidebar'>
|
||||
<CreateRoom onRoomCreated={handleSelectRoom} />
|
||||
<RoomList
|
||||
onSelectRoom={handleSelectRoom}
|
||||
selectedRoomId={selectedRoomId() || undefined}
|
||||
/>
|
||||
</aside>
|
||||
|
||||
<main class='chat-main'>
|
||||
<Show when={selectedRoomId()}>
|
||||
<ChatRoom
|
||||
roomId={selectedRoomId() || ''}
|
||||
userId={userId() || ''}
|
||||
onLeaveRoom={handleLeaveRoom}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={!selectedRoomId()}>
|
||||
<div class='select-room-message'>
|
||||
Select a room to start chatting or create a new one
|
||||
</div>
|
||||
</Show>
|
||||
</main>
|
||||
</div>
|
||||
</Show>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ import { createMutation } from '@urql/solid';
|
|||
const LOGIN_MUTATION = gql`
|
||||
mutation Login($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
token
|
||||
accessToken
|
||||
refreshToken
|
||||
user {
|
||||
id
|
||||
username
|
||||
|
@ -35,7 +36,10 @@ export function LoginForm(props: LoginFormProps) {
|
|||
}
|
||||
|
||||
try {
|
||||
const result = await login({ email: email(), password: password() });
|
||||
const result = await login({
|
||||
email: email(),
|
||||
password: password(),
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
setError(result.error.message);
|
||||
|
@ -43,10 +47,12 @@ export function LoginForm(props: LoginFormProps) {
|
|||
}
|
||||
|
||||
if (result.data?.login) {
|
||||
const { token, user } = result.data.login;
|
||||
localStorage.setItem('token', token);
|
||||
const { accessToken, user } = result.data.login;
|
||||
localStorage.setItem('token', accessToken);
|
||||
localStorage.setItem('userId', user.id);
|
||||
props.onLoginSuccess(token, user.id);
|
||||
props.onLoginSuccess(accessToken, user.id);
|
||||
} else {
|
||||
setError('Login failed: No data received from server');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
|
|
|
@ -5,7 +5,8 @@ import { createMutation } from '@urql/solid';
|
|||
const REGISTER_MUTATION = gql`
|
||||
mutation Register($email: String!, $username: String!, $password: String!) {
|
||||
register(email: $email, username: $username, password: $password) {
|
||||
token
|
||||
accessToken
|
||||
refreshToken
|
||||
user {
|
||||
id
|
||||
username
|
||||
|
@ -54,10 +55,12 @@ export function RegisterForm(props: RegisterFormProps) {
|
|||
}
|
||||
|
||||
if (result.data?.register) {
|
||||
const { token, user } = result.data.register;
|
||||
localStorage.setItem('token', token);
|
||||
const { accessToken, user } = result.data.register;
|
||||
localStorage.setItem('token', accessToken);
|
||||
localStorage.setItem('userId', user.id);
|
||||
props.onRegisterSuccess(token, user.id);
|
||||
props.onRegisterSuccess(accessToken, user.id);
|
||||
} else {
|
||||
setError('Registration failed: No data received from server');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
/* @refresh reload */
|
||||
import { render } from 'solid-js/web';
|
||||
import { Provider } from '@urql/solid';
|
||||
import { client } from './lib/graphql-client';
|
||||
import './index.css';
|
||||
import App from './App.tsx';
|
||||
|
||||
const root = document.getElementById('root');
|
||||
|
||||
render(() => <App />, root!);
|
||||
render(
|
||||
() => (
|
||||
<Provider value={client}>
|
||||
<App />
|
||||
</Provider>
|
||||
),
|
||||
root!
|
||||
);
|
||||
|
|
|
@ -10,8 +10,6 @@ const envSchema = z
|
|||
WS_URL: env.VITE_WS_URL,
|
||||
}));
|
||||
const { API_URL, WS_URL } = envSchema.parse(import.meta.env);
|
||||
console.log('Current API_URL', API_URL);
|
||||
console.log('Current WS_URL', WS_URL);
|
||||
|
||||
// Create a WebSocket client for GraphQL subscriptions
|
||||
const wsClient = createWsClient({
|
||||
|
@ -40,10 +38,9 @@ export const client = createClient({
|
|||
// For development, we'll add a simple header-based authentication
|
||||
fetchOptions: () => {
|
||||
const token = localStorage.getItem('token');
|
||||
const userId = localStorage.getItem('userId');
|
||||
return {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'user-id': userId || '',
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue