Skip to content

Commit c110bd8

Browse files
authored
Merge pull request #3295 from humanprotocol/develop
Release 2025-04-24
2 parents 74455d8 + 9bd1048 commit c110bd8

142 files changed

Lines changed: 2155 additions & 2129 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/apps/human-app/frontend/src/api/api-client.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/apps/human-app/frontend/src/api/api-paths.ts

Lines changed: 0 additions & 102 deletions
This file was deleted.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { jwtDecode } from 'jwt-decode';
2+
import { type SignInDto } from '@/modules/signin/worker/schemas';
3+
import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider';
4+
import {
5+
type AuthTokensSuccessResponse,
6+
authTokensSuccessResponseSchema,
7+
} from '@/shared/schemas';
8+
import { type BrowserAuthProvider } from '@/shared/types/browser-auth-provider';
9+
import { type HttpApiClient } from './http-api-client';
10+
import { commonApiPaths } from './common-api-paths';
11+
12+
export interface AuthProvider {
13+
getAccessToken: () => Promise<string | null>;
14+
refreshAccessToken: () => Promise<void>;
15+
}
16+
17+
const apiPaths = {
18+
worker: {
19+
signIn: {
20+
path: '/auth/signin',
21+
},
22+
},
23+
} as const;
24+
25+
export class AuthService implements AuthProvider {
26+
private readonly browserAuthProvider: BrowserAuthProvider =
27+
browserAuthProvider;
28+
29+
private static refreshPromise: Promise<AuthTokensSuccessResponse | null> | null =
30+
null;
31+
32+
constructor(private readonly httpClient: HttpApiClient) {}
33+
34+
async signIn(data: SignInDto): Promise<void> {
35+
const res = await this.httpClient.post<AuthTokensSuccessResponse>(
36+
apiPaths.worker.signIn.path,
37+
{
38+
successSchema: authTokensSuccessResponseSchema,
39+
body: data,
40+
}
41+
);
42+
43+
this.browserAuthProvider.signIn(res, 'web2');
44+
}
45+
46+
async getAccessToken(): Promise<string | null> {
47+
const accessToken = this.browserAuthProvider.getAccessToken();
48+
49+
if (!accessToken) {
50+
return null;
51+
}
52+
53+
const decodedToken = jwtDecode<{ exp: number }>(accessToken);
54+
const currentTime = Math.floor(Date.now() / 1000);
55+
const tokenExpiration = decodedToken.exp;
56+
57+
if (tokenExpiration && tokenExpiration - currentTime < 30) {
58+
await this.refreshAccessToken();
59+
60+
const newAccessToken = this.browserAuthProvider.getAccessToken();
61+
62+
if (!newAccessToken) {
63+
return null;
64+
}
65+
66+
return newAccessToken;
67+
}
68+
69+
return accessToken;
70+
}
71+
72+
async refreshAccessToken(): Promise<void> {
73+
const authType = this.browserAuthProvider.getAuthType();
74+
75+
if (!authType) {
76+
throw new Error('Auth type not found');
77+
}
78+
79+
if (!AuthService.refreshPromise) {
80+
AuthService.refreshPromise = this.fetchTokenRefresh();
81+
}
82+
83+
const tokens = await AuthService.refreshPromise;
84+
85+
AuthService.refreshPromise = null;
86+
87+
if (tokens === null) {
88+
throw new Error('Failed to refresh access token');
89+
}
90+
91+
browserAuthProvider.signIn(tokens, authType);
92+
}
93+
94+
private async fetchTokenRefresh(): Promise<AuthTokensSuccessResponse | null> {
95+
const refreshToken = this.browserAuthProvider.getRefreshToken();
96+
97+
if (!refreshToken) {
98+
return null;
99+
}
100+
101+
try {
102+
const response = await this.httpClient.post<AuthTokensSuccessResponse>(
103+
commonApiPaths.auth.refresh.path,
104+
{
105+
body: {
106+
// eslint-disable-next-line camelcase
107+
refresh_token: refreshToken,
108+
},
109+
}
110+
);
111+
112+
return response;
113+
} catch (error) {
114+
return null;
115+
}
116+
}
117+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { env } from '@/shared/env';
2+
import {
3+
HttpApiClient,
4+
humanAppApiClient,
5+
type RequestConfig,
6+
} from './http-api-client';
7+
import { AuthService, type AuthProvider } from './auth-service';
8+
9+
export class AuthorizedHttpApiClient extends HttpApiClient {
10+
constructor(
11+
baseUrl: string,
12+
private readonly authProvider: AuthProvider
13+
) {
14+
super(baseUrl);
15+
}
16+
17+
protected async makeRequest<T = unknown>(
18+
method: string,
19+
path: string,
20+
config: RequestConfig
21+
): Promise<T> {
22+
const token = await this.authProvider.getAccessToken();
23+
24+
const _config = {
25+
...config,
26+
headers: {
27+
...config.headers,
28+
Authorization: `Bearer ${token}`,
29+
},
30+
};
31+
32+
return super.makeRequest(method, path, _config);
33+
}
34+
}
35+
36+
export const authService = new AuthService(humanAppApiClient);
37+
38+
export const authorizedHumanAppApiClient = new AuthorizedHttpApiClient(
39+
env.VITE_API_URL,
40+
authService
41+
);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const commonApiPaths = {
2+
auth: {
3+
refresh: {
4+
path: '/auth/refresh',
5+
},
6+
},
7+
} as const;

packages/apps/human-app/frontend/src/api/fetch-refresh-token.ts

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)