Skip to content

Commit

Permalink
feat: improve auth logic
Browse files Browse the repository at this point in the history
  • Loading branch information
olexh committed Dec 28, 2024
1 parent 894e225 commit 7d66ec2
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 78 deletions.
5 changes: 4 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
NEXT_PUBLIC_API_URL=https://api.hikka.io
API_URL=https://api.hikka.io
SITE_URL=http://localhost:3000
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_SITE_URL=http://localhost:3000
COOKIE_HTTP_ONLY=false
COOKIE_DOMAIN=localhost
NEXT_PUBLIC_DEV=true
5 changes: 4 additions & 1 deletion .env.production
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
NEXT_PUBLIC_API_URL=https://api.hikka.io
API_URL=http://backend:8000
SITE_URL=https://hikka.io
NEXT_PUBLIC_SITE_URL=https://hikka.io
NEXT_PUBLIC_SITE_URL=https://hikka.io
COOKIE_HTTP_ONLY=true
COOKIE_DOMAIN=.hikka.io
NEXT_PUBLIC_DEV=false
8 changes: 0 additions & 8 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ const nextConfig = {

return config;
},
async rewrites() {
return [
{
source: '/api/:path*',
destination: `${process.env.API_URL}/:path*`,
},
];
},
async redirects() {
return [
{
Expand Down
2 changes: 1 addition & 1 deletion src/services/api/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const config = {
baseAPI: process.env.API_URL || process.env.NEXT_PUBLIC_SITE_URL + '/api',
baseAPI: process.env.API_URL || process.env.NEXT_PUBLIC_API_URL,
config: {
headers: {
'Content-type': 'application/json',
Expand Down
158 changes: 102 additions & 56 deletions src/services/api/fetchRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { getCookie } from '@/utils/cookies';

import config from './config';

// Types
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';

export interface BaseFetchRequestProps<
TParams extends Record<string, any> | FormData =
| Record<string, any>
| FormData,
TParams = Record<string, any> | FormData,
> {
params?: TParams;
page?: number;
Expand All @@ -16,73 +17,118 @@ export interface BaseFetchRequestProps<
auth?: string;
}

export interface FetchRequestProps<
TParams extends Record<string, any> | FormData =
| Record<string, any>
| FormData,
> extends BaseFetchRequestProps<TParams> {
export interface FetchRequestProps<TParams = Record<string, any> | FormData>
extends BaseFetchRequestProps<TParams> {
path: string;
method: string;
method: HttpMethod;
signal?: AbortSignal;
}

// Utility functions
function buildPaginationParams({
page,
size,
}: Pick<BaseFetchRequestProps, 'page' | 'size'>): URLSearchParams {
const params = new URLSearchParams();
if (page) params.set('page', String(page));
if (size) params.set('size', String(size));
return params;
}

function buildQueryParams(
method: HttpMethod,
params?: Record<string, any>,
): string {
if (method.toLowerCase() !== 'get' || !params) return '';
return '&' + new URLSearchParams(params).toString();
}

function buildRequestBody(
method: HttpMethod,
params?: Record<string, any> | FormData,
isFormData = false,
): BodyInit | undefined {
if (method.toLowerCase() === 'get' || !params) return undefined;
return isFormData ? (params as FormData) : JSON.stringify(params);
}

async function buildHeaders(
options: Pick<BaseFetchRequestProps, 'captcha' | 'auth'>,
isFormData: boolean,
): Promise<HeadersInit> {
const headers: Record<string, string> = {
// Only include default content-type header if not FormData
...(isFormData ? {} : config.config.headers),
captcha: options.captcha || '',
};

// Add auth header for server-side requests
if (typeof window === 'undefined') {
headers.auth = (options.auth || (await getCookie('auth')))!;
} else {
if (process.env.NEXT_PUBLIC_DEV === 'true') {
headers.auth = options.auth || (await getCookie('auth'))!;
}
}

return headers;
}

async function handleResponse<TResponse>(
response: Response,
): Promise<TResponse> {
if (!response.ok) {
if (response.status >= 400 && response.status <= 499) {
throw await response.json();
}
console.error(response);
throw new Error(
`Request failed: ${response.status} ${response.statusText}`,
);
}

const contentType = response.headers.get('Content-Type') || '';
if (contentType.includes('application/json')) {
return response.json();
}
return response.text() as unknown as TResponse;
}

// Main fetch request function
export async function fetchRequest<TResponse>({
path,
method,
params,
page,
size,
captcha,
formData,
config: myConfig,
formData = false,
config: customConfig,
auth,
signal,
}: FetchRequestProps): Promise<TResponse> {
const paginationParams = new URLSearchParams({
...(page ? { page: String(page) } : {}),
...(size ? { size: String(size) } : {}),
}).toString();

const queryParams =
(method === 'get' &&
params &&
'&' +
new URLSearchParams(
params as Record<string, string>,
).toString()) ||
'';

const input = config.baseAPI + path + '?' + paginationParams + queryParams;

const res = await fetch(input, {
// Build URL
const paginationParams = buildPaginationParams({ page, size });
const queryParams = buildQueryParams(method, params as Record<string, any>);
const url = `${config.baseAPI}${path}?${paginationParams}${queryParams}`;

// Build request options
const headers = await buildHeaders({ captcha, auth }, formData);
const body = buildRequestBody(method, params, formData);

// Make request
const response = await fetch(url, {
method: method.toUpperCase(),
credentials: 'include',
method: method,
body:
method !== 'get' && params
? formData
? (params as FormData)
: JSON.stringify(params)
: undefined,
...(myConfig ? {} : { cache: 'no-store' }),
...config.config,
...myConfig,
headers: {
...(formData ? {} : config.config.headers),
captcha: captcha || '',
...(typeof window === 'undefined'
? { auth: auth || (await getCookie('auth')) }
: {}),
},
body,
cache: customConfig ? undefined : 'no-store',
...config.config, // Apply base config
...customConfig, // Apply custom config
headers, // Apply headers last to ensure proper overwrites
signal,
});

await handleError(res);

return await res.json();
return handleResponse<TResponse>(response);
}

async function handleError(response: Response) {
if (!response.ok) {
if (response.status >= 400 && response.status <= 499) {
throw await response.json();
}
throw new Error('Failed to fetch data');
}
}
export type { HttpMethod };
5 changes: 4 additions & 1 deletion src/utils/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import { cookies } from 'next/headers';

export async function setCookie(name: string, value: string) {
console.log(process.env.COOKIE_DOMAIN, process.env.COOKIE_HTTP_ONLY);

(await cookies()).set(name, value, {
maxAge: 30 * 24 * 60 * 60,
httpOnly: true,
httpOnly: process.env.COOKIE_HTTP_ONLY === 'true',
sameSite: 'lax',
domain: process.env.COOKIE_DOMAIN,
});
}

Expand Down
20 changes: 10 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7400,11 +7400,11 @@ __metadata:
remark-mentions: "npm:^1.1.0"
remark-parse: "npm:^11.0.0"
sharp: "npm:^0.33.5"
slate: "npm:^0.110.2"
slate: "npm:^0.112.0"
slate-dom: "npm:^0.111.0"
slate-history: "npm:^0.110.3"
slate-hyperscript: "npm:^0.100.0"
slate-react: "npm:^0.111.0"
slate-react: "npm:^0.112.0"
tailwind-gradient-mask-image: "npm:^1.2.0"
tailwind-merge: "npm:^2.5.4"
tailwindcss: "npm:3.4.14"
Expand Down Expand Up @@ -11165,9 +11165,9 @@ __metadata:
languageName: node
linkType: hard

"slate-react@npm:^0.111.0":
version: 0.111.0
resolution: "slate-react@npm:0.111.0"
"slate-react@npm:^0.112.0":
version: 0.112.0
resolution: "slate-react@npm:0.112.0"
dependencies:
"@juggle/resize-observer": "npm:^3.4.0"
direction: "npm:^1.0.4"
Expand All @@ -11181,18 +11181,18 @@ __metadata:
react-dom: ">=18.2.0"
slate: ">=0.99.0"
slate-dom: ">=0.110.2"
checksum: 10c0/9228ece95538b9286709ed08fe8d6f5b4669daf83655ab3eca93953f4c2eb8109ff7ba1af74561f6bb800dacbc41514cfc034888df020ac906cd285164f6735e
checksum: 10c0/2d9b88e68f9b6dd17a82551f0e68d30a80e42919076e057c7cb5d512a975ad8f641f575ea20cb64cd498016b23ffb467bb447946b88feee24c703777ef0ba79b
languageName: node
linkType: hard

"slate@npm:^0.110.2":
version: 0.110.2
resolution: "slate@npm:0.110.2"
"slate@npm:^0.112.0":
version: 0.112.0
resolution: "slate@npm:0.112.0"
dependencies:
immer: "npm:^10.0.3"
is-plain-object: "npm:^5.0.0"
tiny-warning: "npm:^1.0.3"
checksum: 10c0/90ed1b3c92898ccd4b3cb33216797acfb6eacbc2e60c7b8d8af4f02305195c3832a8cddb4651b10c4beb1da122bfaa42bb748e6464d75c8bfd545f017297041c
checksum: 10c0/e31dc1eb13c20505d243398bb91efeb8a8ef602eef6c8d4b55f616a39dd80ba4405916a4e40f30b0aa4ba8fbfd52a83508ecb947ad4b4cbc68d8e1f6b518eb96
languageName: node
linkType: hard

Expand Down

0 comments on commit 7d66ec2

Please sign in to comment.