From bb3ac66beaf24d2a86fe8536f28d73dc76063ff3 Mon Sep 17 00:00:00 2001 From: chiku Date: Thu, 10 Jul 2025 16:31:50 +0900 Subject: [PATCH 1/3] fix(api-endpoint): correct the usage of api endpoint `/v2/users` --- .../profile-settings/services/profile-settings.api.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/features/settings/profile-settings/services/profile-settings.api.service.ts b/src/app/features/settings/profile-settings/services/profile-settings.api.service.ts index 4838927cf..3b0ef1cf7 100644 --- a/src/app/features/settings/profile-settings/services/profile-settings.api.service.ts +++ b/src/app/features/settings/profile-settings/services/profile-settings.api.service.ts @@ -15,7 +15,7 @@ export class ProfileSettingsApiService { patchUserSettings(userId: string, key: keyof ProfileSettingsStateModel, data: ProfileSettingsUpdate) { const patchedData = { [key]: data }; - return this.#jsonApiService.patch>(`${environment.apiUrl}users/${userId}/`, { + return this.#jsonApiService.patch>(`${environment.apiUrl}/users/${userId}/`, { data: { type: 'users', id: userId, attributes: patchedData }, }); } From 0b7a12076b988563322dd66118dffcfb0683cac1 Mon Sep 17 00:00:00 2001 From: chiku Date: Thu, 10 Jul 2025 15:23:28 +0900 Subject: [PATCH 2/3] fix(dev-server): adjust development server config for proxy --- angular.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/angular.json b/angular.json index d73854c98..56021424d 100644 --- a/angular.json +++ b/angular.json @@ -80,6 +80,7 @@ "namedChunks": true }, "development": { + "baseHref": "/angular_osf/assets/", "optimization": false, "extractLicenses": false, "sourceMap": true, @@ -101,6 +102,8 @@ }, "development": { "buildTarget": "osf:build:development", + "port": 4300, + "host": "0.0.0.0", "hmr": false } }, From 86943b740f4f2e0cd592a09ab111ddc7cf970738 Mon Sep 17 00:00:00 2001 From: chiku Date: Thu, 10 Jul 2025 16:52:13 +0900 Subject: [PATCH 3/3] feat(cookie-auth): enable cookie auth for development --- src/app/app.config.ts | 4 +- .../interceptors/cookie-auth.interceptor.ts | 37 +++++++++++++++ .../interceptors/hybrid-auth.interceptor.ts | 29 ++++++++++++ src/app/core/interceptors/index.ts | 2 + src/app/core/services/cookie.service.ts | 45 +++++++++++++++++++ src/app/core/services/index.ts | 1 + src/environments/environment.development.ts | 24 ++++++---- src/environments/environment.ts | 6 +++ 8 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 src/app/core/interceptors/cookie-auth.interceptor.ts create mode 100644 src/app/core/interceptors/hybrid-auth.interceptor.ts create mode 100644 src/app/core/services/cookie.service.ts diff --git a/src/app/app.config.ts b/src/app/app.config.ts index ab079babd..501b60992 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -15,7 +15,7 @@ import { STATES } from '@core/constants'; import { provideTranslation } from '@core/helpers'; import { GlobalErrorHandler } from './core/handlers'; -import { authInterceptor, errorInterceptor } from './core/interceptors'; +import { errorInterceptor, hybridAuthInterceptor } from './core/interceptors'; import CustomPreset from './core/theme/custom-preset'; import { routes } from './app.routes'; @@ -37,7 +37,7 @@ export const appConfig: ApplicationConfig = { }, }), provideAnimations(), - provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])), + provideHttpClient(withInterceptors([hybridAuthInterceptor, errorInterceptor])), importProvidersFrom(TranslateModule.forRoot(provideTranslation())), ConfirmationService, MessageService, diff --git a/src/app/core/interceptors/cookie-auth.interceptor.ts b/src/app/core/interceptors/cookie-auth.interceptor.ts new file mode 100644 index 000000000..98fea47cf --- /dev/null +++ b/src/app/core/interceptors/cookie-auth.interceptor.ts @@ -0,0 +1,37 @@ +import { HttpInterceptorFn } from '@angular/common/http'; +import { inject } from '@angular/core'; + +import { CookieService } from '../services/cookie.service'; + +import { environment } from 'src/environments/environment'; + +export const cookieAuthInterceptor: HttpInterceptorFn = (req, next) => { + if (!environment.cookieAuth.enabled) { + return next(req); + } + + const cookieService = inject(CookieService); + const csrfToken = cookieService.getCsrfToken(); + + const isOsfApiRequest = req.url.includes(environment.apiDomainUrl) || req.url.includes('localhost:8000'); + + if (isOsfApiRequest) { + const headers: Record = { + Accept: 'application/vnd.api+json; version=2.20', + 'Content-Type': 'application/vnd.api+json', + }; + + if (csrfToken) { + headers['X-CSRFToken'] = csrfToken; + } + + const authReq = req.clone({ + setHeaders: headers, + withCredentials: true, + }); + + return next(authReq); + } + + return next(req); +}; diff --git a/src/app/core/interceptors/hybrid-auth.interceptor.ts b/src/app/core/interceptors/hybrid-auth.interceptor.ts new file mode 100644 index 000000000..20d90a997 --- /dev/null +++ b/src/app/core/interceptors/hybrid-auth.interceptor.ts @@ -0,0 +1,29 @@ +import { HttpInterceptorFn } from '@angular/common/http'; + +import { cookieAuthInterceptor } from './cookie-auth.interceptor'; + +import { environment } from 'src/environments/environment'; + +export const hybridAuthInterceptor: HttpInterceptorFn = (req, next) => { + if (environment.cookieAuth.enabled) { + return cookieAuthInterceptor(req, next); + } + + console.warn('Using fallback token authentication - this should not happen in production!'); + + const authToken = 'UlO9O9GNKgVzJD7pUeY53jiQTKJ4U2znXVWNvh0KZQruoENuILx0IIYf9LoDz7Duq72EIm'; + + if (authToken) { + const authReq = req.clone({ + setHeaders: { + Authorization: `Bearer ${authToken}`, + Accept: 'application/vnd.api+json', + 'Content-Type': 'application/vnd.api+json', + }, + }); + + return next(authReq); + } + + return next(req); +}; diff --git a/src/app/core/interceptors/index.ts b/src/app/core/interceptors/index.ts index 830718180..62905e054 100644 --- a/src/app/core/interceptors/index.ts +++ b/src/app/core/interceptors/index.ts @@ -1,2 +1,4 @@ export * from './auth.interceptor'; +export * from './cookie-auth.interceptor'; export * from './error.interceptor'; +export * from './hybrid-auth.interceptor'; diff --git a/src/app/core/services/cookie.service.ts b/src/app/core/services/cookie.service.ts new file mode 100644 index 000000000..9ea34cf31 --- /dev/null +++ b/src/app/core/services/cookie.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class CookieService { + getCookie(name: string): string | null { + try { + if (!document.cookie) { + return null; + } + + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + + if (parts.length === 2) { + const cookieValue = parts.pop()?.split(';').shift(); + return cookieValue ? decodeURIComponent(cookieValue) : null; + } + + return null; + } catch (error) { + console.warn(`Failed to read cookie '${name}':`, error); + return null; + } + } + + getCsrfToken(): string | null { + return this.getCookie(environment.cookieAuth.csrfCookieName); + } + + hasSessionCookie(): boolean { + return this.getCookie('sessionid') !== null; + } + + clearAuthCookies(): void { + const cookiesToClear = ['sessionid', 'csrftoken', 'api-csrf']; + + cookiesToClear.forEach((name) => { + document.cookie = `${name}=; Max-Age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`; + }); + } +} diff --git a/src/app/core/services/index.ts b/src/app/core/services/index.ts index 9d432f280..cdbecdbbf 100644 --- a/src/app/core/services/index.ts +++ b/src/app/core/services/index.ts @@ -1,3 +1,4 @@ +export { CookieService } from './cookie.service'; export { JsonApiService } from './json-api.service'; export { RequestAccessService } from './request-access.service'; export { UserService } from './user.service'; diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts index bd43d69df..f4b150055 100644 --- a/src/environments/environment.development.ts +++ b/src/environments/environment.development.ts @@ -1,13 +1,19 @@ export const environment = { production: false, - webUrl: 'https://staging4.osf.io', - downloadUrl: 'https://staging4.osf.io/download', - apiUrl: 'https://api.staging4.osf.io/v2', - apiUrlV1: 'https://staging4.osf.io/api/v1', - apiDomainUrl: 'https://api.staging4.osf.io', + webUrl: 'http://localhost:8000', + downloadUrl: 'http://localhost:8000/download', + apiUrl: 'http://localhost:8000/v2', + apiUrlV1: 'https//localhost:8000/api/v1', + apiDomainUrl: 'http://localhost:8000', shareDomainUrl: 'https://staging-share.osf.io/trove', - addonsApiUrl: 'https://addons.staging4.osf.io/v1', - fileApiUrl: 'https://files.us.staging4.osf.io/v1', - baseResourceUri: 'https://staging4.osf.io/', - funderApiUrl: 'https://api.crossref.org/', + addonsApiUrl: 'http://localhost:8000/v1', + fileApiUrl: 'http://localhost:8000/v1', + baseResourceUri: 'http://localhost:8000/', + funderApiUrl: 'http://api.crossref.org/', + + cookieAuth: { + enabled: true, + csrfCookieName: 'api-csrf', + withCredentials: true, + }, }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index bd43d69df..373826d85 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -10,4 +10,10 @@ export const environment = { fileApiUrl: 'https://files.us.staging4.osf.io/v1', baseResourceUri: 'https://staging4.osf.io/', funderApiUrl: 'https://api.crossref.org/', + + cookieAuth: { + enabled: false, + csrfCookieName: 'api-csrf', + withCredentials: false, + }, };