Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,36 @@ public void AnyOtherFailure_KeepsCachedCredentials(Exception e)

outcome.Should().Be(OAuthClient.SilentAuthFailureOutcome.KeepCachedCredentials);
}

[Fact]
public void OidcFetchFailure_IsOffline()
{
//the real-world offline error: MSAL fails to fetch the OIDC config, wrapping the connection failure
var e = new MsalServiceException("oidc_failure", "Failed to retrieve OIDC configuration",
new HttpRequestException("Connection failure"));

var result = OAuthClient.ClassifyInteractiveLoginFailure(e);

result.Should().Be(LoginResult.Offline);
}

[Fact]
public void UserCancel_IsCancelled()
{
//a cancel is a cancel regardless of connectivity; offline-with-warm-cache also lands here
var e = new MsalClientException(MsalError.AuthenticationCanceledError, "User canceled authentication");

var result = OAuthClient.ClassifyInteractiveLoginFailure(e);

result.Should().Be(LoginResult.Cancelled);
}

[Fact]
public void UnexpectedFailure_IsNotClassified_SoCallerRethrows()
{
var result = OAuthClient.ClassifyInteractiveLoginFailure(
new InvalidOperationException("something unexpected"));

result.Should().BeNull();
}
}
36 changes: 31 additions & 5 deletions backend/FwLite/FwLiteShared/Auth/AuthService.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
using System.Text.Json.Serialization;
using FwLiteShared.Projects;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;

namespace FwLiteShared.Auth;

public record ServerStatus(string DisplayName, bool LoggedIn, string? LoggedInAs, LexboxServer Server);
public class AuthService(LexboxProjectService lexboxProjectService, OAuthClientFactory clientFactory, IOptions<AuthConfig> options)

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum LoginResult
{
Success,
Offline,
Cancelled,
}

public class AuthService(
LexboxProjectService lexboxProjectService,
OAuthClientFactory clientFactory,
ILogger<AuthService> logger,
IOptions<AuthConfig> options)
{
[JSInvokable]
public async Task<ServerStatus[]> Servers()
Expand All @@ -21,11 +36,22 @@ public async Task<ServerStatus[]> Servers()
}

[JSInvokable]
public async Task SignInWebView(LexboxServer server)
public async Task<LoginResult> SignInWebView(LexboxServer server)
{
var result = await clientFactory.GetClient(server).SignIn(string.Empty);//does nothing here
if (!result.HandledBySystemWebView) throw new InvalidOperationException("Sign in not handled by system web view");
options.Value.AfterLoginWebView?.Invoke();
try
{
var result = await clientFactory.GetClient(server).SignIn(string.Empty);//does nothing here
if (!result.HandledBySystemWebView) throw new InvalidOperationException("Sign in not handled by system web view");
options.Value.AfterLoginWebView?.Invoke();
return LoginResult.Success;
}
catch (Exception e)
{
var classified = OAuthClient.ClassifyInteractiveLoginFailure(e);
if (classified is null) throw;
logger.LogInformation(e, "Web view sign in did not complete: {LoginResult}", classified);
return classified.Value;
}
}

[JSInvokable]
Expand Down
8 changes: 8 additions & 0 deletions backend/FwLite/FwLiteShared/Auth/OAuthClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,14 @@ internal enum SilentAuthFailureOutcome
_ => SilentAuthFailureOutcome.KeepCachedCredentials,
};

internal static LoginResult? ClassifyInteractiveLoginFailure(Exception e) => e switch
{
MsalServiceException { InnerException: HttpRequestException } => LoginResult.Offline,
HttpRequestException or OperationCanceledException => LoginResult.Offline,
MsalClientException { ErrorCode: MsalError.AuthenticationCanceledError } => LoginResult.Cancelled,
_ => null,
};

public async Task<string?> GetCurrentName()
{
var auth = await GetAuth();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ private static void ConfigureFwLiteSharedTypes(ConfigurationBuilder builder)
builder.ExportAsEnum<ProjectRole>().UseString();
builder.ExportAsEnum<MorphTypeKind>().UseString();
builder.ExportAsEnum<SyncStatus>().UseString();
builder.ExportAsEnum<LoginResult>().UseString();
builder.ExportAsEnum<DownloadProjectByCodeResult>().UseString();
builder.ExportAsEnum<SyncJobStatusEnum>().UseString();
builder.ExportAsEnum<ViewBase>().UseString();
Expand Down
25 changes: 23 additions & 2 deletions frontend/viewer/src/lib/auth/LoginButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
<script lang="ts">
import * as ResponsiveMenu from '$lib/components/responsive-menu';
import type {ILexboxServer} from '$lib/dotnet-types';
import {LoginResult} from '$lib/dotnet-types';
import {useAuthService} from '$lib/services/service-provider';
import {Button} from '$lib/components/ui/button';
import {AppNotification} from '$lib/notifications/notifications';
Comment thread
myieye marked this conversation as resolved.
import {openUrl} from '$lib/services/url-opener';

const authService = useAuthService();
const shouldUseSystemWebView = useSystemWebView(authService);
Expand All @@ -40,8 +43,25 @@
async function login(server: ILexboxServer) {
loading = true;
try {
await authService.signInWebView(server);
statusChange('logged-in');
const result = await authService.signInWebView(server);

if (result === LoginResult.Success) {
statusChange('logged-in');
} else if (result === LoginResult.Offline) {
AppNotification.displayAction(
$t`You appear to be offline. Can you connect to ${server.displayName}?`,
{
label: $t`Open in browser`,
callback: () => {
void openUrl(server.authority);
return {dismiss: true};
},
},
{type: 'warning'},
);
} else {
AppNotification.display($t`Login cancelled.`, {type: 'warning', timeout: 'short'});
}
} finally {
loading = false;
}
Expand All @@ -56,6 +76,7 @@
loading = false;
}
}

</script>

{#if status.loggedIn}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
// the code is regenerated.

import type {IServerStatus} from './IServerStatus';
import type {LoginResult} from './LoginResult';
import type {ILexboxServer} from './ILexboxServer';

export interface IAuthService
{
servers() : Promise<IServerStatus[]>;
signInWebView(server: ILexboxServer) : Promise<void>;
signInWebView(server: ILexboxServer) : Promise<LoginResult>;
useSystemWebView() : Promise<boolean>;
logout(server: ILexboxServer) : Promise<void>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
// This code was generated by a Reinforced.Typings tool.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.

export enum LoginResult {
Success = "Success",
Offline = "Offline",
Cancelled = "Cancelled"
}
/* eslint-enable */
1 change: 1 addition & 0 deletions frontend/viewer/src/lib/dotnet-types/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './generated-types/FwLiteShared/Auth/IAuthService';
export * from './generated-types/FwLiteShared/Auth/ILexboxServer';
export * from './generated-types/FwLiteShared/Auth/IServerStatus';
export * from './generated-types/FwLiteShared/Auth/LoginResult';
export * from './generated-types/FwLiteShared/Projects/ICombinedProjectsService';
export * from './generated-types/FwLiteShared/Projects/IProjectModel';
export * from './generated-types/FwLiteShared/Projects/IServerProjects';
Expand Down
15 changes: 15 additions & 0 deletions frontend/viewer/src/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,11 @@ msgstr "Local only"
msgid "Login"
msgstr "Login"

#. Toast shown when the user closes or cancels the login dialog before signing in.
#: src/lib/auth/LoginButton.svelte
msgid "Login cancelled."
msgstr "Login cancelled."

#. Default button label on the login button when not yet authenticated. Clicking opens the login flow for a server.
#: src/lib/auth/LoginButton.svelte
#: src/lib/auth/LoginButton.svelte
Expand Down Expand Up @@ -1409,6 +1414,11 @@ msgstr "Open"
msgid "Open Data Directory"
msgstr "Open Data Directory"

#. Action button on the offline-login warning toast; opens the server's site in a browser so the user can check the connection.
#: src/lib/auth/LoginButton.svelte
msgid "Open in browser"
msgstr "Open in browser"

#. Button label
#: src/lib/components/OpenInFieldWorksButton.svelte
msgid "Open in FieldWorks"
Expand Down Expand Up @@ -2166,6 +2176,11 @@ msgstr "Writing systems"
msgid "Writing Systems"
msgstr "Writing Systems"

#. Warning toast shown when a login attempt can't connect to the server. {0} is the server name (e.g. "Lexbox").
#: src/lib/auth/LoginButton.svelte
msgid "You appear to be offline. Can you connect to {0}?"
msgstr "You appear to be offline. Can you connect to {0}?"

#. Status message when no updates are available
#: src/lib/updates/UpdateDialogContent.svelte
msgid "You are running the latest version."
Expand Down
14 changes: 14 additions & 0 deletions frontend/viewer/src/locales/es.po
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,11 @@ msgstr "Sólo local"
msgid "Login"
msgstr "Inicio de sesión"

#. Toast shown when the user closes or cancels the login dialog before signing in.
#: src/lib/auth/LoginButton.svelte
msgid "Login cancelled."
msgstr ""

#. Default button label on the login button when not yet authenticated. Clicking opens the login flow for a server.
#: src/lib/auth/LoginButton.svelte
#: src/lib/auth/LoginButton.svelte
Expand Down Expand Up @@ -1414,6 +1419,11 @@ msgstr "Abrir"
msgid "Open Data Directory"
msgstr "Directorio de datos abiertos"

#. Action button on the offline-login warning toast; opens the server's site in a browser so the user can check the connection.
#: src/lib/auth/LoginButton.svelte
msgid "Open in browser"
msgstr ""

#. Button label
#: src/lib/components/OpenInFieldWorksButton.svelte
msgid "Open in FieldWorks"
Expand Down Expand Up @@ -2171,6 +2181,10 @@ msgstr "Sistemas de escritura"
msgid "Writing Systems"
msgstr "Sistemas de escritura"

#: src/lib/auth/LoginButton.svelte
msgid "You appear to be offline. Can you connect to {0}?"
msgstr ""

#. Status message when no updates are available
#: src/lib/updates/UpdateDialogContent.svelte
msgid "You are running the latest version."
Expand Down
14 changes: 14 additions & 0 deletions frontend/viewer/src/locales/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,11 @@ msgstr "Uniquement au niveau local"
msgid "Login"
msgstr "Connexion"

#. Toast shown when the user closes or cancels the login dialog before signing in.
#: src/lib/auth/LoginButton.svelte
msgid "Login cancelled."
msgstr ""

#. Default button label on the login button when not yet authenticated. Clicking opens the login flow for a server.
#: src/lib/auth/LoginButton.svelte
#: src/lib/auth/LoginButton.svelte
Expand Down Expand Up @@ -1414,6 +1419,11 @@ msgstr "Ouvert"
msgid "Open Data Directory"
msgstr "Répertoire des données ouvertes"

#. Action button on the offline-login warning toast; opens the server's site in a browser so the user can check the connection.
#: src/lib/auth/LoginButton.svelte
msgid "Open in browser"
msgstr ""

#. Button label
#: src/lib/components/OpenInFieldWorksButton.svelte
msgid "Open in FieldWorks"
Expand Down Expand Up @@ -2171,6 +2181,10 @@ msgstr "Systèmes d'écriture"
msgid "Writing Systems"
msgstr "Systèmes d'écriture"

#: src/lib/auth/LoginButton.svelte
msgid "You appear to be offline. Can you connect to {0}?"
msgstr ""

#. Status message when no updates are available
#: src/lib/updates/UpdateDialogContent.svelte
msgid "You are running the latest version."
Expand Down
14 changes: 14 additions & 0 deletions frontend/viewer/src/locales/id.po
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,11 @@ msgstr "Hanya lokal"
msgid "Login"
msgstr "Masuk"

#. Toast shown when the user closes or cancels the login dialog before signing in.
#: src/lib/auth/LoginButton.svelte
msgid "Login cancelled."
msgstr ""

#. Default button label on the login button when not yet authenticated. Clicking opens the login flow for a server.
#: src/lib/auth/LoginButton.svelte
#: src/lib/auth/LoginButton.svelte
Expand Down Expand Up @@ -1414,6 +1419,11 @@ msgstr "Buka"
msgid "Open Data Directory"
msgstr "Direktori Data Terbuka"

#. Action button on the offline-login warning toast; opens the server's site in a browser so the user can check the connection.
#: src/lib/auth/LoginButton.svelte
msgid "Open in browser"
msgstr ""

#. Button label
#: src/lib/components/OpenInFieldWorksButton.svelte
msgid "Open in FieldWorks"
Expand Down Expand Up @@ -2171,6 +2181,10 @@ msgstr "Sistem penulisan"
msgid "Writing Systems"
msgstr "Sistem Penulisan"

#: src/lib/auth/LoginButton.svelte
msgid "You appear to be offline. Can you connect to {0}?"
msgstr ""

#. Status message when no updates are available
#: src/lib/updates/UpdateDialogContent.svelte
msgid "You are running the latest version."
Expand Down
14 changes: 14 additions & 0 deletions frontend/viewer/src/locales/ko.po
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,11 @@ msgstr "로컬 전용"
msgid "Login"
msgstr "로그인"

#. Toast shown when the user closes or cancels the login dialog before signing in.
#: src/lib/auth/LoginButton.svelte
msgid "Login cancelled."
msgstr ""

#. Default button label on the login button when not yet authenticated. Clicking opens the login flow for a server.
#: src/lib/auth/LoginButton.svelte
#: src/lib/auth/LoginButton.svelte
Expand Down Expand Up @@ -1414,6 +1419,11 @@ msgstr "열기"
msgid "Open Data Directory"
msgstr "오픈 데이터 디렉터리"

#. Action button on the offline-login warning toast; opens the server's site in a browser so the user can check the connection.
#: src/lib/auth/LoginButton.svelte
msgid "Open in browser"
msgstr ""

#. Button label
#: src/lib/components/OpenInFieldWorksButton.svelte
msgid "Open in FieldWorks"
Expand Down Expand Up @@ -2171,6 +2181,10 @@ msgstr "쓰기 시스템"
msgid "Writing Systems"
msgstr "쓰기 시스템"

#: src/lib/auth/LoginButton.svelte
msgid "You appear to be offline. Can you connect to {0}?"
msgstr ""

#. Status message when no updates are available
#: src/lib/updates/UpdateDialogContent.svelte
msgid "You are running the latest version."
Expand Down
Loading
Loading