Add basic authentication
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { makeApiUrl } from '.';
|
||||
|
||||
export const login = async (name: string, password: string): Promise<string> => {
|
||||
const response = await fetch(makeApiUrl('/login'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ name, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to login');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.token;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { makeApiUrl } from '.';
|
||||
import { fetchWithAuth } from '.';
|
||||
import type {
|
||||
CreateCustomHeadersRequest,
|
||||
CreateHeaderRequest,
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
} from './types/site';
|
||||
|
||||
export const getCustomHeaders = async (siteId: string): Promise<CustomHeaders[]> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/custom-headers`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/custom-headers`, {
|
||||
method: 'GET',
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -20,7 +20,7 @@ export const createCustomHeaders = async (
|
||||
siteId: string,
|
||||
data: CreateCustomHeadersRequest
|
||||
): Promise<CustomHeaders> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/custom-headers`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/custom-headers`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -38,7 +38,7 @@ export const updateCustomHeaders = async (
|
||||
groupId: string,
|
||||
data: CreateCustomHeadersRequest
|
||||
): Promise<CustomHeaders> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/custom-headers/${groupId}`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/custom-headers/${groupId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -52,7 +52,7 @@ export const updateCustomHeaders = async (
|
||||
};
|
||||
|
||||
export const deleteCustomHeaders = async (siteId: string, groupId: string): Promise<void> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/custom-headers/${groupId}`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/custom-headers/${groupId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -65,7 +65,7 @@ export const createHeader = async (
|
||||
groupId: string,
|
||||
data: CreateHeaderRequest
|
||||
): Promise<Header> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/custom-headers/${groupId}/headers`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/custom-headers/${groupId}/headers`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -83,7 +83,7 @@ export const updateHeader = async (
|
||||
headerId: string,
|
||||
data: CreateHeaderRequest
|
||||
): Promise<Header> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/headers/${headerId}`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/headers/${headerId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -97,7 +97,7 @@ export const updateHeader = async (
|
||||
};
|
||||
|
||||
export const deleteHeader = async (siteId: string, headerId: string): Promise<void> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/headers/${headerId}`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/headers/${headerId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { makeApiUrl } from '.';
|
||||
import { fetchWithAuth } from '.';
|
||||
import type { Deployment } from './types/deployments';
|
||||
|
||||
export const getDeploymentsForSite = async (
|
||||
siteId: string,
|
||||
limit: number = 100
|
||||
): Promise<Deployment[]> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/deployments?limit=${limit}`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/deployments?limit=${limit}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { makeApiUrl } from '.';
|
||||
import { fetchWithAuth } from '.';
|
||||
import type { CreateForwardRuleRequest, ForwardRule } from './types/site';
|
||||
|
||||
export const getForwardRules = async (siteId: string): Promise<ForwardRule[]> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/forward-rules`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/forward-rules`, {
|
||||
method: 'GET',
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -15,7 +15,7 @@ export const createForwardRule = async (
|
||||
siteId: string,
|
||||
data: CreateForwardRuleRequest
|
||||
): Promise<ForwardRule> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/forward-rules`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/forward-rules`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -33,7 +33,7 @@ export const updateForwardRule = async (
|
||||
ruleId: string,
|
||||
data: CreateForwardRuleRequest
|
||||
): Promise<ForwardRule> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/forward-rules/${ruleId}`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/forward-rules/${ruleId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -47,7 +47,7 @@ export const updateForwardRule = async (
|
||||
};
|
||||
|
||||
export const deleteForwardRule = async (siteId: string, ruleId: string): Promise<void> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${siteId}/forward-rules/${ruleId}`), {
|
||||
const response = await fetchWithAuth(`/sites/${siteId}/forward-rules/${ruleId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -5,3 +5,20 @@ export const makeApiUrl = (endpoint: string) => {
|
||||
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
||||
return `${base}${path}`;
|
||||
};
|
||||
|
||||
export const authHeaders = () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchWithAuth = (endpoint: string, options: RequestInit = {}) => {
|
||||
const headers = {
|
||||
...(options.headers || {}),
|
||||
...authHeaders(),
|
||||
} as Record<string, string>;
|
||||
return fetch(makeApiUrl(endpoint), { ...options, headers });
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { makeApiUrl } from '.';
|
||||
import { fetchWithAuth } from '.';
|
||||
import type {
|
||||
CreateSiteRequest,
|
||||
CreateSiteResponse,
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
} from './types/site';
|
||||
|
||||
export const getSites = async (): Promise<GetAllSitesResponse> => {
|
||||
const response = await fetch(makeApiUrl('/sites'), {
|
||||
const response = await fetchWithAuth('/sites', {
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.status === 404) {
|
||||
@@ -21,7 +21,7 @@ export const getSites = async (): Promise<GetAllSitesResponse> => {
|
||||
};
|
||||
|
||||
export const getSite = async (id: string): Promise<Site> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${id}`), {
|
||||
const response = await fetchWithAuth(`/sites/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -31,7 +31,7 @@ export const getSite = async (id: string): Promise<Site> => {
|
||||
};
|
||||
|
||||
export const createSite = async (data: CreateSiteRequest): Promise<CreateSiteResponse> => {
|
||||
const response = await fetch(makeApiUrl('/sites'), {
|
||||
const response = await fetchWithAuth('/sites', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -45,7 +45,7 @@ export const createSite = async (data: CreateSiteRequest): Promise<CreateSiteRes
|
||||
};
|
||||
|
||||
export const updateSite = async (id: string, data: Partial<CreateSiteRequest>): Promise<Site> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${id}`), {
|
||||
const response = await fetchWithAuth(`/sites/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -59,7 +59,7 @@ export const updateSite = async (id: string, data: Partial<CreateSiteRequest>):
|
||||
};
|
||||
|
||||
export const toggleSiteEnabled = async (id: string): Promise<ToggleSiteEnabledResponse> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${id}/enabled`), {
|
||||
const response = await fetchWithAuth(`/sites/${id}/enabled`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -72,7 +72,7 @@ export const toggleSiteEnabled = async (id: string): Promise<ToggleSiteEnabledRe
|
||||
};
|
||||
|
||||
export const deleteSite = async (id: string): Promise<void> => {
|
||||
const response = await fetch(makeApiUrl(`/sites/${id}`), {
|
||||
const response = await fetchWithAuth(`/sites/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { makeApiUrl } from '.';
|
||||
import { fetchWithAuth } from '.';
|
||||
import type { CreateUserRequest, UpdateUserRequest, User } from './types/user';
|
||||
|
||||
export const getUsers = async (): Promise<User[]> => {
|
||||
const response = await fetch(makeApiUrl('/users'), {
|
||||
const response = await fetchWithAuth('/users', {
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.status === 404) {
|
||||
@@ -15,7 +15,7 @@ export const getUsers = async (): Promise<User[]> => {
|
||||
};
|
||||
|
||||
export const getUserById = async (id: string): Promise<User> => {
|
||||
const response = await fetch(makeApiUrl(`/users/${id}`), {
|
||||
const response = await fetchWithAuth(`/users/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -25,7 +25,7 @@ export const getUserById = async (id: string): Promise<User> => {
|
||||
};
|
||||
|
||||
export const createUser = async (data: CreateUserRequest): Promise<User> => {
|
||||
const response = await fetch(makeApiUrl('/users'), {
|
||||
const response = await fetchWithAuth('/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -39,7 +39,7 @@ export const createUser = async (data: CreateUserRequest): Promise<User> => {
|
||||
};
|
||||
|
||||
export const updateUser = async (id: string, data: Partial<UpdateUserRequest>): Promise<User> => {
|
||||
const response = await fetch(makeApiUrl(`/users/${id}`), {
|
||||
const response = await fetchWithAuth(`/users/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -53,7 +53,7 @@ export const updateUser = async (id: string, data: Partial<UpdateUserRequest>):
|
||||
};
|
||||
|
||||
export const deleteUser = async (id: string): Promise<void> => {
|
||||
const response = await fetch(makeApiUrl(`/users/${id}`), {
|
||||
const response = await fetchWithAuth(`/users/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
||||
+31
-5
@@ -1,4 +1,4 @@
|
||||
import { BrowserRouter, Route, Routes } from 'react-router';
|
||||
import { BrowserRouter, Route, Routes, Navigate } from 'react-router';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import MainPage from './pages/Main/Main';
|
||||
@@ -8,6 +8,7 @@ import './index.css';
|
||||
import NewSite from './pages/NewSite/NewSite';
|
||||
import SiteOverview from './pages/SiteOverview/SiteOverview';
|
||||
import Users from './pages/Users/Users';
|
||||
import Login from './pages/Login/Login';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -16,10 +17,35 @@ createRoot(document.getElementById('root')!).render(
|
||||
<ThemeProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<MainPage />} />
|
||||
<Route path="/sites/new" element={<NewSite />} />
|
||||
<Route path="/sites/:id" element={<SiteOverview />} />
|
||||
<Route path="/users" element={<Users />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
!localStorage.getItem('token') ? <Navigate to="/login" /> : <MainPage />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/sites/new"
|
||||
element={
|
||||
!localStorage.getItem('token') ? <Navigate to="/login" /> : <NewSite />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/sites/:id"
|
||||
element={
|
||||
!localStorage.getItem('token') ? (
|
||||
<Navigate to="/login" />
|
||||
) : (
|
||||
<SiteOverview />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/users"
|
||||
element={
|
||||
!localStorage.getItem('token') ? <Navigate to="/login" /> : <Users />
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { login } from '../../api/auth.api';
|
||||
import { Input } from '../../components/ui/input';
|
||||
import { Button } from '../../components/ui/button';
|
||||
|
||||
const Login = () => {
|
||||
const [name, setName] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
try {
|
||||
const token = await login(name, password);
|
||||
localStorage.setItem('token', token);
|
||||
navigate('/');
|
||||
} catch (err) {
|
||||
setError('Invalid credentials');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto mt-20">
|
||||
<h1 className="text-2xl font-semibold mb-4">Sign in</h1>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm mb-1">Name</label>
|
||||
<Input value={name} onChange={(e) => setName(e.target.value)} required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm mb-1">Password</label>
|
||||
<Input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
<div>
|
||||
<Button type="submit">Sign in</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
Reference in New Issue
Block a user