Skip to content

Commit 752f4fa

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fix/ssr-throttle-token
2 parents 6bae6d2 + aaeb9cb commit 752f4fa

36 files changed

Lines changed: 421 additions & 135 deletions

CHANGELOG

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO.
44

5+
26.4.0 (2026-02-26)
6+
===================
7+
8+
* Search and fetch funder ids from ROR instead of CrossRef
9+
10+
26.3.1 (2026-02-25)
11+
===================
12+
13+
* Hotfix to prevent 403 error when fetching metadata from causing a redirect
14+
15+
26.3.0 (2026-02-24)
16+
===================
17+
18+
* FAIR Signposting
19+
20+
26.2.1 (2026-02-03)
21+
===================
22+
23+
* Hotfix for navigation translations and contributor search
24+
525
26.2.0 (2026-01-29)
626
===================
727

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "osf",
3-
"version": "26.2.0",
3+
"version": "26.4.0",
44
"scripts": {
55
"ng": "ng",
66
"analyze-bundle": "ng build --configuration=analyze-bundle && source-map-explorer dist/**/*.js --no-border-checks",

src/app/core/components/nav-menu/nav-menu.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ export class NavMenuComponent {
4444
private readonly isAuthenticated = select(UserSelectors.isAuthenticated);
4545
private readonly currentResource = select(CurrentResourceSelectors.getCurrentResource);
4646
private readonly provider = select(ProviderSelectors.getCurrentProvider);
47+
private readonly translationsReady = toSignal(this.translateService.stream('navigation.overview'));
4748

4849
readonly actions = createDispatchMap({ getResourceDetails: GetResourceDetails });
4950

5051
readonly mainMenuItems = computed(() => {
52+
this.translationsReady();
5153
const isAuthenticated = this.isAuthenticated();
5254
const filtered = filterMenuItems(MENU_ITEMS, isAuthenticated);
5355

src/app/core/interceptors/auth.interceptor.spec.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import { authInterceptor } from './auth.interceptor';
1414

1515
describe('authInterceptor', () => {
1616
let cookieService: CookieService;
17-
let mockHandler: jest.Mock;
17+
let cookieServiceMock: { get: jest.Mock };
1818

1919
const setup = (platformId = 'browser', environmentOverrides: Partial<EnvironmentModel> = {}) => {
20+
cookieServiceMock = { get: jest.fn() };
21+
2022
TestBed.configureTestingModule({
2123
providers: [
22-
MockProvider(CookieService, { get: jest.fn() }),
24+
MockProvider(CookieService, cookieServiceMock),
2325
MockProvider(PLATFORM_ID, platformId),
2426
MockProvider(ENVIRONMENT, { throttleToken: '', ...environmentOverrides } as EnvironmentModel),
2527
],
@@ -29,7 +31,6 @@ describe('authInterceptor', () => {
2931
};
3032

3133
beforeEach(() => {
32-
mockHandler = jest.fn();
3334
jest.clearAllMocks();
3435
});
3536

@@ -41,13 +42,13 @@ describe('authInterceptor', () => {
4142
};
4243

4344
const createHandler = () => {
44-
const handler = mockHandler.mockReturnValue(of({}));
45+
const handler = jest.fn().mockReturnValue(of({}));
4546
return handler;
4647
};
4748

48-
it('should skip CrossRef funders API requests', () => {
49+
it('should skip ROR funders API requests', () => {
4950
setup();
50-
const request = createRequest('/api.crossref.org/funders/10.13039/100000001');
51+
const request = createRequest('https://api.ror.org/v2');
5152
const handler = createHandler();
5253

5354
runInInjectionContext(TestBed, () => authInterceptor(request, handler));
@@ -110,7 +111,7 @@ describe('authInterceptor', () => {
110111

111112
it('should add CSRF token and withCredentials in browser platform', () => {
112113
setup();
113-
jest.spyOn(cookieService, 'get').mockReturnValue('csrf-token-123');
114+
cookieServiceMock.get.mockReturnValue('csrf-token-123');
114115

115116
const request = createRequest('/api/v2/projects/');
116117
const handler = createHandler();
@@ -126,7 +127,7 @@ describe('authInterceptor', () => {
126127

127128
it('should not add CSRF token when not available in browser platform', () => {
128129
setup();
129-
jest.spyOn(cookieService, 'get').mockReturnValue('');
130+
cookieServiceMock.get.mockReturnValue('');
130131

131132
const request = createRequest('/api/v2/projects/');
132133
const handler = createHandler();

src/app/core/interceptors/auth.interceptor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import { inject, PLATFORM_ID } from '@angular/core';
88

99
import { ENVIRONMENT } from '@core/provider/environment.provider';
1010

11+
import { environment } from 'src/environments/environment';
12+
1113
export const authInterceptor: HttpInterceptorFn = (
1214
req: HttpRequest<unknown>,
1315
next: HttpHandlerFn
1416
): Observable<HttpEvent<unknown>> => {
15-
if (req.url.includes('/api.crossref.org/funders')) {
17+
if (req.url.startsWith(environment.funderApiUrl)) {
1618
return next(req);
1719
}
1820

src/app/core/interceptors/error.interceptor.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { MockProvider } from 'ng-mocks';
22

33
import { throwError } from 'rxjs';
44

5-
import { HttpContext, HttpErrorResponse, HttpRequest } from '@angular/common/http';
5+
import { HttpContext, HttpErrorResponse, HttpHeaders, HttpRequest } from '@angular/common/http';
66
import { runInInjectionContext } from '@angular/core';
77
import { TestBed } from '@angular/core/testing';
88
import { Router } from '@angular/router';
@@ -271,4 +271,25 @@ describe('errorInterceptor', () => {
271271
});
272272
});
273273
});
274+
275+
it('should not navigate for 403 errors when X-No-Auth-Redirect header is true', () => {
276+
const error = new HttpErrorResponse({
277+
error: {},
278+
status: 403,
279+
url: '/metadata/abcde/?format=google-dataset-json-ld',
280+
headers: new HttpHeaders({ 'X-No-Auth-Redirect': 'true' }),
281+
});
282+
const request = createRequest();
283+
284+
runInInjectionContext(TestBed, () => {
285+
const result = errorInterceptor(request, createErrorHandler(error));
286+
result.subscribe({
287+
error: () => {
288+
expect(router.navigate).not.toHaveBeenCalled();
289+
expect(loaderService.hide).toHaveBeenCalled();
290+
expect(toastService.showError).not.toHaveBeenCalled();
291+
},
292+
});
293+
});
294+
});
274295
});

src/app/core/interceptors/error.interceptor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ export const errorInterceptor: HttpInterceptorFn = (req, next) => {
6363

6464
if (error.status === 403) {
6565
const requestAccessRegex = /\/v2\/nodes\/[^/]+\/requests\/?$/i;
66-
if (error.url && requestAccessRegex.test(error.url)) {
66+
67+
if (error.url && (requestAccessRegex.test(error.url) || req.headers.has('X-No-Auth-Redirect'))) {
6768
loaderService.hide();
6869
return throwError(() => error);
6970
}

src/app/core/interceptors/view-only.interceptor.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,14 @@ describe('viewOnlyInterceptor', () => {
113113
it('should not modify funders API requests even when view-only param exists', () => {
114114
jest.spyOn(viewOnlyHelper, 'getViewOnlyParam').mockReturnValue('funder123');
115115

116-
const request = createRequest('/api.crossref.org/funders/10.13039/100000001');
116+
const request = createRequest('https://api.ror.org/v2');
117117
const handler = createHandler();
118118

119119
runInInjectionContext(TestBed, () => viewOnlyInterceptor(request, handler));
120120

121121
expect(handler).toHaveBeenCalledTimes(1);
122122
const modifiedRequest = handler.mock.calls[0][0];
123-
expect(modifiedRequest.url).toBe('/api.crossref.org/funders/10.13039/100000001');
123+
expect(modifiedRequest.url).toBe('https://api.ror.org/v2');
124124
});
125125

126126
it('should handle requests to other external APIs normally', () => {

src/app/core/interceptors/view-only.interceptor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { Router } from '@angular/router';
66

77
import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
88

9+
import { environment } from 'src/environments/environment';
10+
911
export const viewOnlyInterceptor: HttpInterceptorFn = (
1012
req: HttpRequest<unknown>,
1113
next: HttpHandlerFn
@@ -15,7 +17,7 @@ export const viewOnlyInterceptor: HttpInterceptorFn = (
1517

1618
const viewOnlyParam = viewOnlyHelper.getViewOnlyParam(router);
1719

18-
if (!req.url.includes('/api.crossref.org/funders') && viewOnlyParam) {
20+
if (!req.url.startsWith(environment.funderApiUrl) && viewOnlyParam) {
1921
if (req.url.includes('view_only=')) {
2022
return next(req);
2123
}

0 commit comments

Comments
 (0)