Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions src/SIL.XForge.Scripture/ClientApp/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
{ provide: ErrorReportingService, useMock: mockedErrorReportingService },
{ provide: BreakpointObserver, useClass: TestBreakpointObserver },
{ provide: DialogService, useMock: mockedDialogService },
provideNoopAnimations()

Check warning on line 126 in src/SIL.XForge.Scripture/ClientApp/src/app/app.component.spec.ts

View workflow job for this annotation

GitHub Actions / Lint and Prettier (22.13.0, 11.11.0)

`provideNoopAnimations` is deprecated. 20.2 Use `animate.enter` or `animate.leave` instead. Intent to remove in v23
]
}));

Expand Down Expand Up @@ -255,6 +255,18 @@
verify(mockedAuthService.updateInterfaceLanguage(anything())).never();
}));

it('pulls the user profile from Auth0 if the user is not present in the realtime service', fakeAsync(() => {
const env = new TestEnvironment();
when(mockedAuthService.isNewlyLoggedIn).thenResolve(true);
env.setCurrentUser('missing_user_id');
env.navigate(['/projects', 'project01']);
env.init();

tick();
env.fixture.detectChanges();
verify(mockedAuthService.pullAuthUserProfile()).once();
}));

it('sets user locale when stored locale does not match the browsing session', fakeAsync(() => {
const env = new TestEnvironment();
when(mockedAuthService.isNewlyLoggedIn).thenResolve(true);
Expand Down
8 changes: 7 additions & 1 deletion src/SIL.XForge.Scripture/ClientApp/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,14 @@ export class AppComponent extends DataLoadingComponent implements OnInit, OnDest
});
}

// If the user is logged in, but they do not have user profile, pull it from Auth0
const isLoggedIn = await this.authService.isLoggedIn;
if (isLoggedIn && this.currentUserDoc.data == null) {
await this.authService.pullAuthUserProfile();
}

// Set the locale to the Auth0 user profile on first login
const languageTag: string | undefined = this.currentUserDoc.data!.interfaceLanguage;
const languageTag: string | undefined = this.currentUserDoc.data?.interfaceLanguage;
if (
isNewlyLoggedIn &&
languageTag != null &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ export const KNOWN_ERROR_CODES: ObjectPaths<typeof en.join>[] = [
'key_already_used',
'key_expired',
'max_users_reached',
'project_link_is_invalid',
'role_not_found',
'project_link_is_invalid'
'user_missing'
];

@Component({
Expand Down
11 changes: 11 additions & 0 deletions src/SIL.XForge.Scripture/ClientApp/src/app/join/join.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum ShareKeys {
InvalidShareKey = 'invalid_share_key',
KeyAlreadyUsed = 'key_already_used',
MaxUsersReached = 'max_users_reached',
UserMissing = 'user_missing',
Valid = 'valid'
}

Expand Down Expand Up @@ -114,6 +115,9 @@ const meta: Meta = {
when(mockedSFProjectService.onlineJoinWithShareKey(ShareKeys.InvalidShareKey)).thenThrow(
new CommandError(CommandErrorCode.Forbidden, 'project_link_is_invalid')
);
when(mockedSFProjectService.onlineJoinWithShareKey(ShareKeys.UserMissing)).thenThrow(
new CommandError(CommandErrorCode.NotFound, 'user_missing')
);
if (context.args.shareKey === ShareKeys.Valid) {
when(mockedAuthService.tryTransparentAuthentication()).thenCall(() => {
when(mockedAuthService.isLoggedIn).thenResolve(true);
Expand Down Expand Up @@ -180,3 +184,10 @@ export const DialogKeyAlreadyUsed: Story = {
loggedIn: true
}
};

export const DialogUserMissing: Story = {
args: {
shareKey: ShareKeys.UserMissing,
loggedIn: true
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
"please_connect_to_use_link": "Please connect to the internet to join a project using a link.",
"project_link_is_invalid": "The project link is invalid. Please contact the person who sent you the link and ask for a new one.",
"role_not_found": "The role you were assigned on this project is no longer available. Please contact the person who sent you the link and ask for a new one.",
"user_missing": "Your user account could not be found. Please log out join using the link again.",
"your_name": "Name"
},
"dialog": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@

get currentUserRoles(): SystemRole[] {
// Provide compatibility with login session predating role array support
const role = this.localSettings.get<SystemRole | undefined>(ROLE_SETTING);

Check warning on line 142 in src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.ts

View workflow job for this annotation

GitHub Actions / Lint and Prettier (22.13.0, 11.11.0)

`ROLE_SETTING` is deprecated. This value is deprecated, but maintained to ensure compatibility with older login sessions
if (role != null) {
return [role];
} else {
Expand Down Expand Up @@ -283,6 +283,10 @@
}
}

pullAuthUserProfile(): Promise<void> {
return this.commandService.onlineInvoke(USERS_URL, 'pullAuthUserProfile');
}

requestParatextCredentialUpdate(cancelCallback?: () => void): void {
void this.dialogService
.confirm('warnings.paratext_credentials_expired', 'warnings.logout')
Expand Down Expand Up @@ -323,7 +327,7 @@
this.localSettings.set(cacheKey.toKey(), cacheEntry);
await this.localLogIn(authResponse.access_token, authResponse.id_token, authResponse.expires_in);
await this.remoteStore.init(() => this.getAccessToken());
await this.commandService.onlineInvoke(USERS_URL, 'pullAuthUserProfile');
await this.pullAuthUserProfile();
this._loggedInState$.next({ loggedIn: true, newlyLoggedIn: true, anonymousUser: true });
return true;
}
Expand Down Expand Up @@ -483,7 +487,7 @@
await this.localLogIn(authDetails.token.access_token, authDetails.token.id_token, authDetails.token.expires_in);
await this.remoteStore.init(() => this.getAccessToken());
try {
await this.commandService.onlineInvoke(USERS_URL, 'pullAuthUserProfile');
await this.pullAuthUserProfile();
} catch (err) {
// Display error dialog to pause login loop.
// Error details will be sent to Bugsnag and logged to the console.
Expand Down Expand Up @@ -620,7 +624,7 @@
this.localSettings.set(ID_TOKEN_SETTING, idToken);
this.localSettings.set(EXPIRES_AT_SETTING, expiresAt);
this.localSettings.set(USER_ID_SETTING, userId);
this.localSettings.remove(ROLE_SETTING);

Check warning on line 627 in src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.ts

View workflow job for this annotation

GitHub Actions / Lint and Prettier (22.13.0, 11.11.0)

`ROLE_SETTING` is deprecated. This value is deprecated, but maintained to ensure compatibility with older login sessions
this.localSettings.set(ROLES_SETTING, typeof role === 'string' ? [role] : role || []);
this.scheduleRenewal();
this.bugsnagService.leaveBreadcrumb(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class SFUserProjectsService {
/** Updates our provided set of SF project docs for the current user based on the userdoc's list of SF projects the
* user is on. */
private async updateProjectList(userDoc: UserDoc): Promise<void> {
const currentProjectIds = userDoc.data!.sites[environment.siteId].projects;
const currentProjectIds: string[] = userDoc.data?.sites[environment.siteId].projects ?? [];

let removedProjectsCount = 0;
for (const [id, projectDoc] of this._projectDocs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,9 +474,6 @@ public async Task<IRpcMethodResult> InvitedUsers(string projectId)
}
}

[Obsolete("Only here for old clients that still call it. Removed 2023-04.")]
public async Task<IRpcMethodResult> CheckLinkSharing(string shareKey) => Ok(await JoinWithShareKey(shareKey));

public async Task<IRpcMethodResult> JoinWithShareKey(string shareKey)
{
try
Expand All @@ -494,16 +491,12 @@ public async Task<IRpcMethodResult> JoinWithShareKey(string shareKey)
catch (Exception)
{
_exceptionHandler.RecordEndpointInfoForException(
new Dictionary<string, string> { { "method", "CheckLinkSharing" }, { "shareKey", shareKey } }
new Dictionary<string, string> { { "method", "JoinWithShareKey" }, { "shareKey", shareKey } }
);
throw;
}
}

[Obsolete("New endpoints only require the share key. Old clients would still provide the projectId")]
public async Task<IRpcMethodResult> CheckLinkSharing(string projectId, string shareKey) =>
await CheckLinkSharing(shareKey);

public IRpcMethodResult IsSourceProject(string projectId) => Ok(projectService.IsSourceProject(projectId));

public async Task<IRpcMethodResult> LinkSharingKey(
Expand Down
5 changes: 5 additions & 0 deletions src/SIL.XForge.Scripture/Services/SFProjectService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,11 @@ public async Task<string> JoinWithShareKeyAsync(string curUserId, string shareKe
}

IDocument<User> userDoc = await conn.FetchAsync<User>(curUserId);
if (!userDoc.IsLoaded)
{
throw new DataNotFoundException("user_missing");
}

// Attempt to get the role for the user from the Paratext registry
Attempt<string> attempt = await TryGetProjectRoleAsync(project, curUserId);
if (attempt.TryResult(out string projectRole))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,13 @@ public void JoinWithShareKeyAsync_LinkSharingDisabledAndUserOnProject_Success()
Assert.DoesNotThrowAsync(() => env.Service.JoinWithShareKeyAsync(User02, "abcd"));
}

[Test]
public void JoinWithShareKeyAsync_MissingUser()
{
var env = new TestEnvironment();
Assert.ThrowsAsync<DataNotFoundException>(() => env.Service.JoinWithShareKeyAsync("missing_user_id", "abcd"));
}

[Test]
public void JoinWithShareKeyAsync_LinkSharingDisabledAndUserNotOnProject_Forbidden()
{
Expand Down
Loading