Skip to content

Commit a415a08

Browse files
authored
Merge pull request #1072 from CenterForOpenScience/feature/pbs-26-19
PBS-26-19 / ang 26.21 - PB&S
2 parents 9f0d3b1 + a3ed3a3 commit a415a08

58 files changed

Lines changed: 2134 additions & 988 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.

‎package-lock.json‎

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

‎package.json‎

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,28 @@
3131
},
3232
"private": true,
3333
"dependencies": {
34-
"@angular/animations": "^21.2.19",
34+
"@angular/animations": "^21.2.25",
3535
"@angular/cdk": "^21.2.14",
36-
"@angular/common": "^21.2.19",
37-
"@angular/compiler": "^21.2.19",
38-
"@angular/core": "^21.2.19",
39-
"@angular/forms": "^21.2.19",
40-
"@angular/platform-browser": "^21.2.19",
41-
"@angular/platform-browser-dynamic": "^21.2.19",
42-
"@angular/platform-server": "^21.2.19",
43-
"@angular/router": "^21.2.19",
44-
"@angular/ssr": "^21.2.20",
36+
"@angular/common": "^21.2.25",
37+
"@angular/compiler": "^21.2.25",
38+
"@angular/core": "^21.2.25",
39+
"@angular/forms": "^21.2.25",
40+
"@angular/platform-browser": "^21.2.25",
41+
"@angular/platform-browser-dynamic": "^21.2.25",
42+
"@angular/platform-server": "^21.2.25",
43+
"@angular/router": "^21.2.25",
44+
"@angular/ssr": "^21.2.24",
4545
"@centerforopenscience/markdown-it-atrules": "^0.1.2",
4646
"@citation-js/core": "^0.7.21",
4747
"@citation-js/plugin-csl": "^0.7.21",
48-
"@fortawesome/fontawesome-free": "^7.2.0",
48+
"@fortawesome/fontawesome-free": "^7.3.1",
4949
"@mdit/plugin-img-size": "0.23.1",
50-
"@newrelic/browser-agent": "^1.310.1",
50+
"@newrelic/browser-agent": "^1.323.0",
5151
"@ngx-translate/core": "^17.0.0",
5252
"@ngx-translate/http-loader": "^17.0.0",
5353
"@ngxs/store": "^21.0.0",
5454
"@primeuix/themes": "^2.0.3",
55-
"@sentry/angular": "^10.42.0",
55+
"@sentry/angular": "^10.75.3",
5656
"@traptitech/markdown-it-katex": "^3.6.0",
5757
"ace-builds": "1.43.6",
5858
"angular-google-tag-manager": "^1.13.0",
@@ -70,42 +70,45 @@
7070
"ngx-markdown-editor": "^5.3.4",
7171
"primeflex": "^4.0.0",
7272
"primeicons": "^7.0.0",
73-
"primeng": "^21.1.3",
73+
"primeng": "^21.1.10",
7474
"rxjs": "~7.8.0",
7575
"tslib": "^2.3.0"
7676
},
7777
"devDependencies": {
78-
"@angular-eslint/eslint-plugin": "^21.3.0",
79-
"@angular-eslint/eslint-plugin-template": "^21.3.0",
80-
"@angular-eslint/template-parser": "^21.3.0",
81-
"@angular/build": "^21.2.20",
82-
"@angular/cli": "^21.2.20",
83-
"@angular/compiler-cli": "^21.2.19",
84-
"@commitlint/cli": "^20.4.3",
85-
"@commitlint/config-conventional": "^20.4.3",
78+
"@angular-eslint/eslint-plugin": "^21.4.0",
79+
"@angular-eslint/eslint-plugin-template": "^21.4.0",
80+
"@angular-eslint/template-parser": "^21.4.0",
81+
"@angular/build": "^21.2.24",
82+
"@angular/cli": "^21.2.24",
83+
"@angular/compiler-cli": "^21.2.25",
84+
"@commitlint/cli": "^20.5.3",
85+
"@commitlint/config-conventional": "^20.5.3",
8686
"@eslint/js": "^10.0.1",
8787
"@types/express": "^5.0.1",
8888
"@types/gapi": "^0.0.47",
8989
"@types/gapi.auth2": "^0.0.61",
9090
"@types/markdown-it": "^14.1.2",
9191
"@types/node": "^20.17.19",
92-
"@vitest/coverage-v8": "^4.1.9",
93-
"angular-eslint": "^21.3.0",
92+
"@vitest/coverage-v8": "^4.1.11",
93+
"angular-eslint": "^21.4.0",
9494
"angularx-qrcode": "^21.0.4",
95-
"eslint": "^10.0.2",
95+
"eslint": "^10.11.0",
9696
"eslint-config-prettier": "^10.1.8",
9797
"eslint-plugin-prettier": "^5.5.5",
9898
"eslint-plugin-simple-import-sort": "^12.1.1",
9999
"eslint-plugin-unused-imports": "^4.4.1",
100100
"husky": "^9.1.7",
101101
"jsdom": "^29.1.1",
102102
"lint-staged": "^16.3.2",
103-
"ng-mocks": "^14.15.3",
103+
"ng-mocks": "^14.18.1",
104104
"prettier": "3.8.1",
105105
"source-map-explorer": "^2.5.3",
106106
"typescript": "~5.9.3",
107-
"typescript-eslint": "^8.56.1",
108-
"vitest": "^4.1.9"
107+
"typescript-eslint": "^8.71.0",
108+
"vitest": "^4.1.11"
109+
},
110+
"overrides": {
111+
"piscina": "5.3.2"
109112
},
110113
"lint-staged": {
111114
"**/*.{ts,html,scss}": [

‎src/app/app.component.html‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
<section class="maintenance-overlay font-bold text-xl flex flex-column align-items-center justify-content-center">
77
<p>{{ 'maintenance.title' | translate }}</p>
88
<p>{{ 'maintenance.message' | translate }}</p>
9+
<p>
10+
{{ 'maintenance.details' | translate }}
11+
<a href="https://status.cos.io/" target="_blank" rel="noopener noreferrer">https://status.cos.io/</a>
12+
</p>
913
</section>
1014
}

‎src/app/app.component.scss‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.maintenance-overlay {
22
position: fixed;
33
inset: 0;
4-
z-index: 2000;
4+
z-index: 3100;
55
background: var(--white);
66
}

‎src/app/app.routes.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ export const routes: Routes = [
6767
(mod) => mod.ChooseRepositoryComponent
6868
),
6969
},
70+
{
71+
path: 'resend',
72+
loadComponent: () =>
73+
import('./features/auth/pages/resend/resend-confirmation.component').then(
74+
(mod) => mod.ResendConfirmationComponent
75+
),
76+
data: { skipBreadcrumbs: true },
77+
},
7078
{
7179
path: 'search',
7280
loadComponent: () => import('./features/search/search.component').then((mod) => mod.SearchComponent),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export enum MaintenanceStatus {
2+
Active = 'active',
3+
Inactive = 'inactive',
4+
Unknown = 'unknown',
5+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { CookieService } from 'ngx-cookie-service';
2+
import { MockProvider } from 'ng-mocks';
3+
4+
import { PLATFORM_ID } from '@angular/core';
5+
import { TestBed } from '@angular/core/testing';
6+
7+
import { ENVIRONMENT } from '@core/provider/environment.provider';
8+
import { WINDOW } from '@core/provider/window.provider';
9+
import { JsonApiService } from '@osf/shared/services/json-api.service';
10+
import { LoaderService } from '@osf/shared/services/loader.service';
11+
12+
import { CookieServiceMock } from '@testing/providers/cookie-service.mock';
13+
import { JsonApiServiceMock } from '@testing/providers/json-api.service.mock';
14+
import { LoaderServiceMock } from '@testing/providers/loader-service.mock';
15+
import { provideMockStore } from '@testing/providers/store-provider.mock';
16+
17+
import { AuthService } from './auth.service';
18+
19+
describe('AuthService', () => {
20+
const webUrl = 'https://web.test';
21+
const casUrl = 'https://cas.test';
22+
const origin = 'https://osf.test';
23+
24+
let service: AuthService;
25+
let loaderService: LoaderServiceMock;
26+
let locationMock: { href: string; pathname: string; origin: string };
27+
28+
function setup(overrides: { pathname?: string; href?: string; isBrowser?: boolean } = {}) {
29+
const pathname = overrides.pathname ?? '/dashboard';
30+
locationMock = {
31+
href: overrides.href ?? `${origin}${pathname}`,
32+
pathname,
33+
origin,
34+
};
35+
36+
loaderService = new LoaderServiceMock();
37+
38+
TestBed.configureTestingModule({
39+
providers: [
40+
AuthService,
41+
provideMockStore(),
42+
MockProvider(JsonApiService, JsonApiServiceMock.simple()),
43+
MockProvider(CookieService, CookieServiceMock.simple()),
44+
MockProvider(LoaderService, loaderService),
45+
MockProvider(WINDOW, { location: locationMock } as Window),
46+
MockProvider(ENVIRONMENT, { webUrl, casUrl, apiDomainUrl: 'https://api.test' }),
47+
MockProvider(PLATFORM_ID, overrides.isBrowser === false ? 'server' : 'browser'),
48+
],
49+
});
50+
51+
service = TestBed.inject(AuthService);
52+
}
53+
54+
function nextFromServiceParam(href: string): string | null {
55+
const serviceParam = new URL(href).searchParams.get('service');
56+
return serviceParam ? new URL(serviceParam).searchParams.get('next') : null;
57+
}
58+
59+
it('should use the current page as next when signing in from a regular page', () => {
60+
const href = `${origin}/project/abc`;
61+
setup({ pathname: '/project/abc', href });
62+
63+
service.navigateToSignIn();
64+
65+
expect(loaderService.show).toHaveBeenCalled();
66+
expect(nextFromServiceParam(locationMock.href)).toBe(href);
67+
});
68+
69+
it('should use home as next when signing in from reset password', () => {
70+
setup({
71+
pathname: '/resetpassword/user-1/token-1',
72+
href: `${origin}/resetpassword/user-1/token-1`,
73+
});
74+
75+
service.navigateToSignIn();
76+
77+
expect(nextFromServiceParam(locationMock.href)).toBe(`${webUrl}/`);
78+
});
79+
80+
it('should use home as next when signing in from forgot password with orcid', () => {
81+
setup({ pathname: '/forgotpassword', href: `${origin}/forgotpassword` });
82+
83+
service.navigateToOrcidSignIn();
84+
85+
expect(new URL(locationMock.href).searchParams.get('next')).toBe(`${webUrl}/`);
86+
});
87+
88+
it('should use home as next when signing in from register with institution', () => {
89+
setup({ pathname: '/register', href: `${origin}/register` });
90+
91+
service.navigateToInstitutionSignIn();
92+
93+
expect(new URL(locationMock.href).searchParams.get('next')).toBe(`${webUrl}/`);
94+
});
95+
96+
it('should not redirect to sign in when not in the browser', () => {
97+
setup({ isBrowser: false, href: `${origin}/dashboard` });
98+
99+
service.navigateToSignIn();
100+
101+
expect(loaderService.show).not.toHaveBeenCalled();
102+
expect(locationMock.href).toBe(`${origin}/dashboard`);
103+
});
104+
});

‎src/app/core/services/auth.service.ts‎

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { inject, Injectable, PLATFORM_ID } from '@angular/core';
77

88
import { SignUpModel } from '@core/models/sign-up.model';
99
import { ENVIRONMENT } from '@core/provider/environment.provider';
10+
import { WINDOW } from '@core/provider/window.provider';
1011
import { ClearCurrentUser } from '@osf/core/store/user';
1112
import { urlParam } from '@osf/shared/helpers/url-param.helper';
1213
import { JsonApiService } from '@osf/shared/services/json-api.service';
@@ -20,6 +21,7 @@ export class AuthService {
2021
private readonly cookieService = inject(CookieService);
2122
private readonly loaderService = inject(LoaderService);
2223
private readonly environment = inject(ENVIRONMENT);
24+
private readonly window = inject(WINDOW);
2325
private readonly platformId = inject(PLATFORM_ID);
2426
private readonly actions = createDispatchMap({ clearCurrentUser: ClearCurrentUser });
2527

@@ -43,12 +45,12 @@ export class AuthService {
4345
this.loaderService.show();
4446

4547
const serviceUrl = new URL(`${this.webUrl}/login`);
46-
serviceUrl.searchParams.set('next', window.location.href);
48+
serviceUrl.searchParams.set('next', this.getPostLoginRedirectUrl());
4749

4850
const loginUrl = new URL(`${this.casUrl}/login`);
4951
loginUrl.searchParams.set('service', serviceUrl.toString());
5052

51-
window.location.href = loginUrl.toString();
53+
this.window.location.href = loginUrl.toString();
5254
}
5355

5456
navigateToOrcidSignIn(): void {
@@ -59,10 +61,10 @@ export class AuthService {
5961
const loginUrl = `${this.casUrl}/login?${urlParam({
6062
redirectOrcid: 'true',
6163
service: `${this.webUrl}/login`,
62-
next: window.location.href,
64+
next: this.getPostLoginRedirectUrl(),
6365
})}`;
6466

65-
window.location.href = loginUrl;
67+
this.window.location.href = loginUrl;
6668
}
6769

6870
navigateToInstitutionSignIn(): void {
@@ -73,10 +75,10 @@ export class AuthService {
7375
const loginUrl = `${this.casUrl}/login?${urlParam({
7476
campaign: 'institution',
7577
service: `${this.webUrl}/login`,
76-
next: window.location.href,
78+
next: this.getPostLoginRedirectUrl(),
7779
})}`;
7880

79-
window.location.href = loginUrl;
81+
this.window.location.href = loginUrl;
8082
}
8183

8284
logout(nextUrl?: string): void {
@@ -85,7 +87,7 @@ export class AuthService {
8587

8688
if (isPlatformBrowser(this.platformId)) {
8789
this.cookieService.deleteAll();
88-
window.location.href = `${this.webUrl}/logout/?next=${encodeURIComponent(nextUrl || `${window.location.origin}/`)}`;
90+
this.window.location.href = `${this.webUrl}/logout/?next=${encodeURIComponent(nextUrl || `${this.window.location.origin}/`)}`;
8991
}
9092
}
9193

@@ -117,4 +119,28 @@ export class AuthService {
117119

118120
return this.jsonApiService.post(baseUrl, body);
119121
}
122+
123+
resendConfirmationUrl(email: string) {
124+
const baseUrl = `${this.apiUrl}/resend_confirmation/`;
125+
const body = {
126+
data: {
127+
attributes: {
128+
email: email,
129+
},
130+
},
131+
};
132+
133+
return this.jsonApiService.post(baseUrl, body);
134+
}
135+
136+
private getPostLoginRedirectUrl(): string {
137+
const pathname = this.window.location.pathname;
138+
const excludedPaths = ['/resetpassword', '/forgotpassword', '/register', '/resend'];
139+
140+
if (excludedPaths.some((path) => pathname === path || pathname.startsWith(`${path}/`))) {
141+
return `${this.webUrl}/`;
142+
}
143+
144+
return this.window.location.href;
145+
}
120146
}

0 commit comments

Comments
 (0)