Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1a6ce9d
fix(ssr): added source maps for clear error messages
nsemets Mar 18, 2026
d9cef38
fix(preprints): fixed withdrawn preprint issue
nsemets Mar 18, 2026
47270f1
Merge pull request #911 from nsemets/hotfix/ssr-fixes
adlius Mar 18, 2026
6a4ccb1
feat(release): Bump version no. Add CHANGELOG
adlius Mar 18, 2026
ffa0cd0
Merge branch 'hotfix/26.5.1'
adlius Mar 18, 2026
3666b51
Merge tag '26.5.1' into develop
adlius Mar 18, 2026
4909e37
Merge branch 'develop' into feature/pbs-26-2
adlius Mar 26, 2026
474c08f
Merge pull request #920 from CenterForOpenScience/feature/pbs-26-2
adlius Mar 26, 2026
0a15cbc
[ENG-10695] Non-spam deleted items showing as spam (#921)
nsemets Mar 26, 2026
5e1c3da
fix(preprint): fixed merge resolution (#922)
nsemets Mar 26, 2026
c59746c
feat(release): Bump version no. Add CHANGELOG
adlius Mar 26, 2026
3df1d46
Merge branch 'release/26.6.0'
adlius Mar 26, 2026
a0dd217
Merge tag '26.6.0' into develop
adlius Mar 26, 2026
4df7520
revert(metadata): Prevent redirect on 403 error
futa-ikeda Mar 26, 2026
18b11c3
Merge pull request #923 from futa-ikeda/hotfix/metadata-records-service
adlius Mar 26, 2026
2bc8ac2
feat(hotfix): Bump version no. Add CHANGELOG
adlius Mar 26, 2026
731e0d8
Merge branch 'hotfix/26.6.1'
adlius Mar 26, 2026
94ba3cf
Merge tag '26.6.1' into develop
adlius Mar 26, 2026
f9da526
[ENG-10063] orcid integration  (#939)
futa-ikeda Apr 8, 2026
e3933bd
Update chagne log and bump version
cslzchen Apr 8, 2026
92af918
Merge branch 'release/26.7.0'
cslzchen Apr 8, 2026
ee958a1
Merge tag '26.7.0' into develop
cslzchen Apr 8, 2026
fcab7d9
Merge remote-tracking branch 'upstream/develop' into fix/merge-conflicts
nsemets Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

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

26.7.0 (2026-04-08)
===================

* ORCiD Integration Project - FE Part

26.6.1 (2026-03-26)
===================

* Hotfix to prevent redirect on 403 error

26.6.0 (2026-03-26)
===================

* Misc. improvements and bug fixes

26.5.1 (2026-03-18)
===================

* Hotfix for Angular SSR

26.5.0 (2026-02-26)
===================

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"ssr": {
"entry": "src/server.ts"
},
"sourceMap": true,
"budgets": [
{
"type": "initial",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "osf",
"version": "26.5.0",
"version": "26.7.0",
"scripts": {
"ng": "ng",
"analyze-bundle": "ng build --configuration=analyze-bundle && source-map-explorer dist/**/*.js --no-border-checks",
Expand Down Expand Up @@ -30,7 +30,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": {
Expand Down
4 changes: 2 additions & 2 deletions src/app/core/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 || '/')}`;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/features/preprints/services/preprints.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { provideRouter } 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 { 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';
Expand Down Expand Up @@ -93,7 +94,7 @@ describe('ProfileInformationComponent', () => {
external_identity: {
ORCID: {
id: '0000-0002-1825-0097',
status: 'verified',
status: ExternalIdentityStatus.VERIFIED,
},
},
} as UserModel);
Expand All @@ -107,7 +108,7 @@ describe('ProfileInformationComponent', () => {
external_identity: {
ORCID: {
id: '0000-0002-1825-0097',
status: 'pending',
status: ExternalIdentityStatus.LINK,
},
},
} as UserModel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div class="flex flex-column row-gap-4 border-1 border-round-lg grey-border-color p-3 md:p-4 xl:p-5">
<div class="flex flex-row justify-content-between">
<h2>
{{ 'settings.profileSettings.social.labels.authenticatedIdentity' | translate }}
</h2>
</div>
<div class="flex flex-column row-gap-4 w-full md:flex-row md:align-items-end md:column-gap-3">
<div class="w-full md:w-12">
@if (existingOrcid()) {
<div class="flex flex-row align-items-center gap-2">
<img ngSrc="assets/icons/colored/orcid.svg" width="16" height="16" alt="orcid" />
<a class="font-bold" [href]="orcidUrl()"> {{ orcidUrl() }} </a>
<p-button
icon="fas fa-times"
class="w-6 md:w-auto"
severity="danger"
variant="text"
[pTooltip]="'settings.profileSettings.social.disconnectOrcid' | translate"
[ariaLabel]="'settings.profileSettings.social.disconnectOrcid' | translate"
(onClick)="disconnectOrcid()"
>
</p-button>
</div>
} @else {
<img ngSrc="assets/images/integrations/orcid-logotype.png" width="130" height="40" alt="orcid" />
<p class="mt-2" [innerHTML]="'settings.profileSettings.social.orcidDescription' | translate"></p>
<p class="mt-2 font-bold">{{ 'settings.profileSettings.social.orcidWarning' | translate }}</p>
<div class="mt-2">
<p-button
class="w-6 md:w-auto"
[label]="'settings.profileSettings.social.connectOrcid' | translate"
severity="secondary"
(onClick)="connectOrcid()"
>
</p-button>
</div>
}
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { Store } from '@ngxs/store';

import { MockProvider } from 'ng-mocks';

import { Mock } from 'vitest';

import { signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ENVIRONMENT } from '@core/provider/environment.provider';
import { AuthService } from '@core/services/auth.service';
import { AccountSettingsSelectors } from '@osf/features/settings/account-settings/store/account-settings.selectors';
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 { provideOSFCore } from '@testing/osf.testing.provider';
import { AuthServiceMock, AuthServiceMockType } from '@testing/providers/auth-service.mock';
import {
CustomConfirmationServiceMock,
CustomConfirmationServiceMockType,
} from '@testing/providers/custom-confirmation-provider.mock';
import { LoaderServiceMock } from '@testing/providers/loader-service.mock';
import { provideMockStore } from '@testing/providers/store-provider.mock';
import { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock';

import { DeleteExternalIdentity, GetExternalIdentities } from '../../../account-settings/store';

import { AuthenticatedIdentityComponent } from './authenticated-identity.component';

describe('AuthenticatedIdentityComponent', () => {
let component: AuthenticatedIdentityComponent;
let fixture: ComponentFixture<AuthenticatedIdentityComponent>;
let store: Store;
let customConfirmationServiceMock: CustomConfirmationServiceMockType;
let loaderServiceMock: LoaderServiceMock;
let toastServiceMock: ToastServiceMockType;
let authServiceMock: AuthServiceMockType;

const mockExternalIdentities = signal([
{
id: 'ORCID',
externalId: '0001-0002-0003-0004',
status: ExternalIdentityStatus.VERIFIED,
},
]);

beforeEach(() => {
customConfirmationServiceMock = CustomConfirmationServiceMock.simple();
loaderServiceMock = new LoaderServiceMock();
toastServiceMock = ToastServiceMock.simple();
authServiceMock = AuthServiceMock.simple();

TestBed.configureTestingModule({
imports: [AuthenticatedIdentityComponent],
providers: [
provideOSFCore(),
MockProvider(ENVIRONMENT, { webUrl: 'http://localhost:4200', casUrl: 'http://localhost:8080' }),
MockProvider(AuthService, authServiceMock),
MockProvider(LoaderService, loaderServiceMock),
MockProvider(CustomConfirmationService, customConfirmationServiceMock),
MockProvider(ToastService, toastServiceMock),
provideMockStore({
signals: [
{
selector: AccountSettingsSelectors.getExternalIdentities,
value: mockExternalIdentities,
},
],
}),
],
});

store = TestBed.inject(Store);
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();
});

it('should dispatch get external identities on init', () => {
expect(store.dispatch).toHaveBeenCalledWith(new GetExternalIdentities());
});

it('should delete ORCID and show success when confirmation is accepted', () => {
(store.dispatch as Mock).mockClear();
component.disconnectOrcid();

const { onConfirm } = customConfirmationServiceMock.confirmDelete.mock.calls[0][0];
onConfirm();

expect(loaderServiceMock.show).toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalledWith(new DeleteExternalIdentity('ORCID'));
expect(toastServiceMock.showSuccess).toHaveBeenCalledWith(
'settings.accountSettings.connectedIdentities.successDelete'
);
expect(loaderServiceMock.hide).toHaveBeenCalled();
});

it('should not delete ORCID when confirmation is not accepted', () => {
(store.dispatch as Mock).mockClear();
component.disconnectOrcid();

expect(loaderServiceMock.show).not.toHaveBeenCalled();
expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(DeleteExternalIdentity));
expect(toastServiceMock.showSuccess).not.toHaveBeenCalled();
expect(loaderServiceMock.hide).not.toHaveBeenCalled();
});

it('should redirect to CAS login with ORCID redirect and social tab destination', () => {
component.connectOrcid();

expect(authServiceMock.logout).toHaveBeenCalledTimes(1);

const logoutUrl = authServiceMock.logout.mock.calls[0][0] as string;
const casUrl = new URL(logoutUrl);

expect(casUrl.origin).toBe('http://localhost:8080');
expect(casUrl.pathname).toBe('/login');
expect(casUrl.searchParams.get('redirectOrcid')).toBe('true');
expect(casUrl.searchParams.get('service')).toBe('http://localhost:4200/login');

const nextParam = casUrl.searchParams.get('next');
expect(nextParam).toBeTruthy();

const finalDestination = new URL(decodeURIComponent(nextParam as string));
expect(finalDestination.origin).toBe('http://localhost:4200');
expect(finalDestination.pathname).toBe('/settings/profile');
expect(finalDestination.searchParams.get('tab')).toBe('2');
});
});
Loading
Loading