From 1a6ce9d77d60de3fc988d50f304b235f816525df Mon Sep 17 00:00:00 2001 From: nsemets Date: Wed, 18 Mar 2026 11:03:16 +0200 Subject: [PATCH 01/10] fix(ssr): added source maps for clear error messages --- Dockerfile | 2 +- angular.json | 1 + package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f487a8ccb..f169b2f08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /app RUN npm prune --omit=dev --no-audit --no-fund EXPOSE 4000 ENV PORT=4000 -CMD ["node", "dist/osf/server/server.mjs"] +CMD ["node", "--enable-source-maps", "dist/osf/server/server.mjs"] # Static dist artifact stage FROM node:22-alpine AS dist diff --git a/angular.json b/angular.json index 1457228db..4b3ad245c 100644 --- a/angular.json +++ b/angular.json @@ -96,6 +96,7 @@ "ssr": { "entry": "src/server.ts" }, + "sourceMap": true, "budgets": [ { "type": "initial", diff --git a/package.json b/package.json index 6649d8f60..42e7b2f2b 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "test:check-coverage-thresholds": "node .github/scripts/check-coverage-thresholds.js", "test:display": "node .github/counter/counter.test.display.js", "watch": "ng build --watch --configuration development", - "serve:ssr:osf": "node dist/osf/server/server.mjs" + "serve:ssr:osf": "node --enable-source-maps dist/osf/server/server.mjs" }, "private": true, "dependencies": { From d9cef3890c831893914646d6419d660ebf36b36d Mon Sep 17 00:00:00 2001 From: nsemets Date: Wed, 18 Mar 2026 16:12:30 +0200 Subject: [PATCH 02/10] fix(preprints): fixed withdrawn preprint issue --- .../preprint-tombstone.component.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts index 2023c666e..740dfdabb 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts +++ b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts @@ -6,8 +6,18 @@ import { Card } from 'primeng/card'; import { Skeleton } from 'primeng/skeleton'; import { Tag } from 'primeng/tag'; -import { DatePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, effect, inject, input, OnDestroy, output } from '@angular/core'; +import { DatePipe, isPlatformBrowser } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + computed, + effect, + inject, + input, + OnDestroy, + output, + PLATFORM_ID, +} from '@angular/core'; import { Router } from '@angular/router'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; @@ -57,6 +67,8 @@ export class PreprintTombstoneComponent implements OnDestroy { }); private router = inject(Router); + private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); + preprintVersionSelected = output(); preprintProvider = input.required(); @@ -91,7 +103,9 @@ export class PreprintTombstoneComponent implements OnDestroy { } ngOnDestroy(): void { - this.actions.resetContributorsState(); + if (this.isBrowser) { + this.actions.resetContributorsState(); + } } tagClicked(tag: string) { From 6a4ccb19b2337d3bdc0536a1344991900ba35ee1 Mon Sep 17 00:00:00 2001 From: Yuhuai Liu Date: Wed, 18 Mar 2026 11:37:34 -0400 Subject: [PATCH 03/10] feat(release): Bump version no. Add CHANGELOG --- CHANGELOG | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9758ee36b..e99662e0e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,11 @@ We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO. +26.5.1 (2026-03-18) +=================== + +* Hotfix for Angular SSR + 26.5.0 (2026-02-26) =================== diff --git a/package.json b/package.json index 42e7b2f2b..c22afbbf3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osf", - "version": "26.5.0", + "version": "26.5.1", "scripts": { "ng": "ng", "analyze-bundle": "ng build --configuration=analyze-bundle && source-map-explorer dist/**/*.js --no-border-checks", From 0a15cbc4be34cb53aaaebc076a8aa3ed6f646d85 Mon Sep 17 00:00:00 2001 From: nsemets Date: Thu, 26 Mar 2026 15:32:35 +0200 Subject: [PATCH 04/10] [ENG-10695] Non-spam deleted items showing as spam (#921) - Ticket: [ENG-10695] - Feature flag: n/a ## Summary of Changes 1. Changed check to flagged content. --- src/app/features/preprints/services/preprints.service.ts | 2 +- src/app/shared/services/resource.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index 0b037250a..d9edd0e91 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -100,7 +100,7 @@ export class PreprintsService { .pipe( map((response) => PreprintsMapper.fromPreprintWithEmbedsJsonApi(response)), catchError((error) => { - if (error.status === 410) { + if (error.error?.errors?.[0]?.meta?.flagged_content) { this.router.navigate(['/spam-content']); } return throwError(() => error); diff --git a/src/app/shared/services/resource.service.ts b/src/app/shared/services/resource.service.ts index 604199658..76fda4a0b 100644 --- a/src/app/shared/services/resource.service.ts +++ b/src/app/shared/services/resource.service.ts @@ -63,7 +63,7 @@ export class ResourceGuidService { ), finalize(() => this.loaderService.hide()), catchError((error) => { - if (error.status === 410) { + if (error.error?.errors?.[0]?.meta?.flagged_content) { this.router.navigate(['/spam-content']); } return throwError(() => error); From 5e1c3da05e784b7c4cbff42136090f0379219a12 Mon Sep 17 00:00:00 2001 From: nsemets Date: Thu, 26 Mar 2026 16:07:27 +0200 Subject: [PATCH 05/10] fix(preprint): fixed merge resolution (#922) --- .../preprint-tombstone/preprint-tombstone.component.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts index a8022b125..b1f94e7b8 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts +++ b/src/app/features/preprints/components/preprint-details/preprint-tombstone/preprint-tombstone.component.ts @@ -68,11 +68,6 @@ export class PreprintTombstoneComponent implements OnDestroy { fetchSubjects: FetchSelectedSubjects, loadMoreBibliographicContributors: LoadMoreBibliographicContributors, }); - private router = inject(Router); - - private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); - - preprintVersionSelected = output(); readonly preprint = select(PreprintSelectors.getPreprint); readonly isPreprintLoading = select(PreprintSelectors.isPreprintLoading); From c59746cc6c27244eb490f6ec6c50af443cedda37 Mon Sep 17 00:00:00 2001 From: Yuhuai Liu Date: Thu, 26 Mar 2026 13:50:24 -0400 Subject: [PATCH 06/10] feat(release): Bump version no. Add CHANGELOG --- CHANGELOG | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e99662e0e..fea43c2ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,11 @@ We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO. +26.6.0 (2026-03-26) +=================== + +* Misc. improvements and bug fixes + 26.5.1 (2026-03-18) =================== diff --git a/package.json b/package.json index c22afbbf3..226fb92b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osf", - "version": "26.5.1", + "version": "26.6.0", "scripts": { "ng": "ng", "analyze-bundle": "ng build --configuration=analyze-bundle && source-map-explorer dist/**/*.js --no-border-checks", From 4df75208c6cddef9f8434bd296f1b859f3191de1 Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Thu, 26 Mar 2026 15:24:26 -0400 Subject: [PATCH 07/10] revert(metadata): Prevent redirect on 403 error --- src/app/shared/services/metadata-records.service.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/shared/services/metadata-records.service.ts b/src/app/shared/services/metadata-records.service.ts index 993ece886..911d17f76 100644 --- a/src/app/shared/services/metadata-records.service.ts +++ b/src/app/shared/services/metadata-records.service.ts @@ -1,4 +1,4 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { ENVIRONMENT } from '@core/provider/environment.provider'; @@ -18,6 +18,9 @@ export class MetadataRecordsService { getMetadataRecord(osfid: string, format: MetadataRecordFormat) { const url = `${this.webUrl}/metadata/${osfid}/?format=${format}`; - return this.http.get(url, { responseType: 'text' }); + return this.http.get(url, { + responseType: 'text', + headers: new HttpHeaders({ 'X-No-Auth-Redirect': 'true' }), + }); } } From 2bc8ac23796e1d34d871ff72c28032c655728065 Mon Sep 17 00:00:00 2001 From: Yuhuai Liu Date: Thu, 26 Mar 2026 16:41:52 -0400 Subject: [PATCH 08/10] feat(hotfix): Bump version no. Add CHANGELOG --- CHANGELOG | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fea43c2ad..93c377589 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,11 @@ We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO. +26.6.1 (2026-03-26) +=================== + +* Hotfix to prevent redirect on 403 error + 26.6.0 (2026-03-26) =================== diff --git a/package.json b/package.json index 226fb92b3..581e678ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osf", - "version": "26.6.0", + "version": "26.6.1", "scripts": { "ng": "ng", "analyze-bundle": "ng build --configuration=analyze-bundle && source-map-explorer dist/**/*.js --no-border-checks", From f9da526f0ca3073f3f61c2cbae32b4853791f806 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:46:18 -0400 Subject: [PATCH 09/10] =?UTF-8?q?[ENG-10063]=20orcid=20integration=C2=A0?= =?UTF-8?q?=20(#939)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(profile-settings): Add query-param to specify a tab (#906) * [ENG-10584][ENG-10585] Allow users to disconnect existing orcid in social tab (#912) * feat(settings): Allow users to disconnect orcid in social tab * feat(settings): Add dummy connect button when no orcid is associated with user * chore(settings): move authenticated identity to own component * refactor(settings): Implement CR suggestions; Update tests * refactor(settings): Update authenticated identity test * refactor(settings): Update authenticated identity test to use OSFTestingModule * feat(settings): Allow user to connect ORCID in profile settings page (#918) * [ENG-10684] Update Authenticated Identity section (#924) * feat(settings): update authenticated identity section * style(settings): Update styles * refactor(settings): Update Authenticated Identity section * chore(settings): Update Authenticated identity section language (#930) * fix(settings): Update connectOrcid to properly logging user out (#934) --- src/app/core/services/auth.service.ts | 4 +- .../profile-information.component.ts | 3 +- .../authenticated-identity.component.html | 40 ++++++++ .../authenticated-identity.component.scss | 0 .../authenticated-identity.component.spec.ts | 71 ++++++++++++++ .../authenticated-identity.component.ts | 92 ++++++++++++++++++ .../components/social/social.component.html | 2 + .../social/social.component.spec.ts | 8 +- .../components/social/social.component.ts | 7 +- .../profile-settings.component.spec.ts | 15 ++- .../profile-settings.component.ts | 19 +++- .../enums/external-identity-status.enum.ts | 5 + .../models/user/external-identity.model.ts | 4 +- src/assets/i18n/en.json | 5 + .../images/integrations/orcid-logotype.png | Bin 0 -> 14455 bytes 15 files changed, 265 insertions(+), 10 deletions(-) create mode 100644 src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.html create mode 100644 src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.scss create mode 100644 src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.spec.ts create mode 100644 src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.ts create mode 100644 src/app/shared/enums/external-identity-status.enum.ts create mode 100644 src/assets/images/integrations/orcid-logotype.png diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index b51cb58b9..074e4d732 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -73,13 +73,13 @@ export class AuthService { window.location.href = loginUrl; } - logout(): void { + logout(nextUrl?: string): void { this.loaderService.show(); this.actions.clearCurrentUser(); if (isPlatformBrowser(this.platformId)) { this.cookieService.deleteAll(); - window.location.href = `${this.webUrl}/logout/?next=${encodeURIComponent('/')}`; + window.location.href = `${this.webUrl}/logout/?next=${encodeURIComponent(nextUrl || '/')}`; } } diff --git a/src/app/features/profile/components/profile-information/profile-information.component.ts b/src/app/features/profile/components/profile-information/profile-information.component.ts index da555cac9..68b9edf81 100644 --- a/src/app/features/profile/components/profile-information/profile-information.component.ts +++ b/src/app/features/profile/components/profile-information/profile-information.component.ts @@ -10,6 +10,7 @@ import { RouterLink } from '@angular/router'; import { EducationHistoryComponent } from '@osf/shared/components/education-history/education-history.component'; import { EmploymentHistoryComponent } from '@osf/shared/components/employment-history/employment-history.component'; import { SOCIAL_LINKS } from '@osf/shared/constants/social-links.const'; +import { ExternalIdentityStatus } from '@osf/shared/enums/external-identity-status.enum'; import { IS_MEDIUM } from '@osf/shared/helpers/breakpoints.tokens'; import { Institution } from '@osf/shared/models/institutions/institutions.model'; import { UserModel } from '@osf/shared/models/user/user.model'; @@ -50,7 +51,7 @@ export class ProfileInformationComponent { orcidId = computed(() => { const orcid = this.currentUser()?.external_identity?.ORCID; - return orcid?.status?.toUpperCase() === 'VERIFIED' ? orcid.id : undefined; + return orcid?.status?.toUpperCase() === ExternalIdentityStatus.VERIFIED ? orcid.id : undefined; }); toProfileSettings() { diff --git a/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.html b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.html new file mode 100644 index 000000000..ecd67c295 --- /dev/null +++ b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.html @@ -0,0 +1,40 @@ +
+
+

+ {{ 'settings.profileSettings.social.labels.authenticatedIdentity' | translate }} +

+
+
+
+ @if (existingOrcid()) { + + } @else { + orcid +

+

{{ 'settings.profileSettings.social.orcidWarning' | translate }}

+
+ + +
+ } +
+
+
diff --git a/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.scss b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.spec.ts b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.spec.ts new file mode 100644 index 000000000..5f2583462 --- /dev/null +++ b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.spec.ts @@ -0,0 +1,71 @@ +import { MockProvider } from 'ng-mocks'; + +import { signal } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AccountSettingsSelectors } from '@osf/features/settings/account-settings/store/account-settings.selectors'; +import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service'; + +import { AuthenticatedIdentityComponent } from './authenticated-identity.component'; + +import { OSFTestingModule } from '@testing/osf.testing.module'; +import { + CustomConfirmationServiceMock, + CustomConfirmationServiceMockType, +} from '@testing/providers/custom-confirmation-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; + +describe('AuthenticatedIdentityComponent', () => { + let component: AuthenticatedIdentityComponent; + let fixture: ComponentFixture; + let customConfirmationServiceMock: CustomConfirmationServiceMockType; + + const mockExternalIdentities = signal([ + { + id: 'ORCID', + externalId: '0001-0002-0003-0004', + status: 'VERIFIED', + }, + ]); + + beforeEach(async () => { + customConfirmationServiceMock = CustomConfirmationServiceMock.simple(); + await TestBed.configureTestingModule({ + imports: [AuthenticatedIdentityComponent, OSFTestingModule], + providers: [ + MockProvider(CustomConfirmationService, customConfirmationServiceMock), + provideMockStore({ + signals: [ + { + selector: AccountSettingsSelectors.getExternalIdentities, + value: mockExternalIdentities, + }, + ], + }), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(AuthenticatedIdentityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show existing user ORCID when present in external identities', () => { + expect(component.existingOrcid()).toEqual('0001-0002-0003-0004'); + expect(component.orcidUrl()).toEqual('https://orcid.org/0001-0002-0003-0004'); + component.disconnectOrcid(); + expect(customConfirmationServiceMock.confirmDelete).toHaveBeenCalled(); + }); + + it('should show connect button when no existing ORCID is present in external identities', () => { + mockExternalIdentities.set([]); + fixture.detectChanges(); + + expect(component.existingOrcid()).toBeUndefined(); + expect(component.orcidUrl()).toBeNull(); + }); +}); diff --git a/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.ts b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.ts new file mode 100644 index 000000000..77619339e --- /dev/null +++ b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.ts @@ -0,0 +1,92 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Button } from 'primeng/button'; +import { Tooltip } from 'primeng/tooltip'; + +import { finalize } from 'rxjs'; + +import { NgOptimizedImage } from '@angular/common'; +import { ChangeDetectionStrategy, Component, computed, inject, OnInit } from '@angular/core'; + +import { ENVIRONMENT } from '@core/provider/environment.provider'; +import { AuthService } from '@core/services/auth.service'; +import { ExternalIdentityStatus } from '@osf/shared/enums/external-identity-status.enum'; +import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service'; +import { LoaderService } from '@osf/shared/services/loader.service'; +import { ToastService } from '@osf/shared/services/toast.service'; + +import { + AccountSettingsSelectors, + DeleteExternalIdentity, + GetExternalIdentities, +} from '../../../account-settings/store'; +import { ProfileSettingsTabOption } from '../../enums'; + +@Component({ + selector: 'osf-authenticated-identity', + imports: [NgOptimizedImage, Button, Tooltip, TranslatePipe], + templateUrl: './authenticated-identity.component.html', + styleUrl: './authenticated-identity.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AuthenticatedIdentityComponent implements OnInit { + private readonly authService = inject(AuthService); + private readonly environment = inject(ENVIRONMENT); + private readonly customConfirmationService = inject(CustomConfirmationService); + private readonly toastService = inject(ToastService); + private readonly loaderService = inject(LoaderService); + + private readonly ORCID_PROVIDER = 'ORCID'; + + ngOnInit() { + this.actions.getExternalIdentities(); + } + + readonly actions = createDispatchMap({ + deleteExternalIdentity: DeleteExternalIdentity, + getExternalIdentities: GetExternalIdentities, + }); + + readonly externalIdentities = select(AccountSettingsSelectors.getExternalIdentities); + + readonly orcidUrl = computed(() => { + return this.existingOrcid() ? `https://orcid.org/${this.existingOrcid()}` : null; + }); + + readonly existingOrcid = computed( + (): string | undefined => + this.externalIdentities()?.find((i) => i.id === 'ORCID' && i.status === ExternalIdentityStatus.VERIFIED) + ?.externalId + ); + + disconnectOrcid(): void { + this.customConfirmationService.confirmDelete({ + headerKey: 'settings.accountSettings.connectedIdentities.deleteDialog.header', + messageParams: { name: this.ORCID_PROVIDER }, + messageKey: 'settings.accountSettings.connectedIdentities.deleteDialog.message', + onConfirm: () => { + this.loaderService.show(); + this.actions + .deleteExternalIdentity(this.ORCID_PROVIDER) + .pipe(finalize(() => this.loaderService.hide())) + .subscribe(() => this.toastService.showSuccess('settings.accountSettings.connectedIdentities.successDelete')); + }, + }); + } + + connectOrcid(): void { + const webUrl = this.environment.webUrl; + const casUrl = this.environment.casUrl; + const finalDestination = new URL(`${webUrl}/settings/profile`); + finalDestination.searchParams.set('tab', ProfileSettingsTabOption.Social.toString()); + const casLoginUrl = new URL(`${casUrl}/login`); + casLoginUrl.search = new URLSearchParams({ + redirectOrcid: 'true', + service: `${webUrl}/login`, + next: encodeURIComponent(finalDestination.toString()), + }).toString(); + this.authService.logout(casLoginUrl.toString()); + } +} diff --git a/src/app/features/settings/profile-settings/components/social/social.component.html b/src/app/features/settings/profile-settings/components/social/social.component.html index e34553fe1..777d4fd88 100644 --- a/src/app/features/settings/profile-settings/components/social/social.component.html +++ b/src/app/features/settings/profile-settings/components/social/social.component.html @@ -1,3 +1,5 @@ + +