Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix login fwlite web #1338

Merged
merged 3 commits into from
Jan 6, 2025
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
6 changes: 6 additions & 0 deletions backend/FwLite/FwLiteShared/Auth/AuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public async Task SignInWebView(LexboxServer server)
options.Value.AfterLoginWebView?.Invoke();
}

[JSInvokable]
public bool UseSystemWebView()
{
return options.Value.SystemWebViewLogin;
}

public async Task<string> SignInWebApp(LexboxServer server, string returnUrl)
{
var result = await clientFactory.GetClient(server).SignIn(returnUrl);
Expand Down
49 changes: 27 additions & 22 deletions backend/FwLite/FwLiteShared/Auth/OAuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,35 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await foreach (var loginRequest in _requestChannel.Reader.ReadAllAsync(stoppingToken))
{
//this sits here and waits for AcquireAuthorizationCodeAsync to finish, meanwhile the uri passed in to that method is sent back to the caller of SubmitLoginRequest
//which then redirects the browser to that uri, once it's done it's sent back and calls FinishLoginRequest, which sends it's uri to OAuthLoginRequest
//which causes AcquireAuthorizationCodeAsync to return

try
{
//todo we can get stuck here if the user doesn't complete the login, this basically bricks the login at the moment. We need a timeout or something
//step 2
var result = await loginRequest.Application.AcquireTokenInteractive(OAuthClient.DefaultScopes)
.WithCustomWebUi(loginRequest)
.ExecuteAsync(stoppingToken);
//step 7, causes step 8 to resume
loginRequest.SetAuthenticationResult(result);
}
catch (Exception e)
{
logger.LogError(e, "Error getting token");
loginRequest.SetException(e);
}

if (loginRequest.State is not null)
_oAuthLoginRequests.Remove(loginRequest.State);
//don't await, otherwise we'll block the channel reader and only 1 login will be processed at a time
//cancel the login after 5 minutes, otherwise it'll probably hang forever and abandoned requests will never be cleaned up
_ = Task.Run(() => StartLogin(loginRequest, stoppingToken.Merge(new CancellationTokenSource(TimeSpan.FromMinutes(5)).Token)), stoppingToken);
}
}

private async Task StartLogin(OAuthLoginRequest loginRequest, CancellationToken stoppingToken)
{
//this sits here and waits for AcquireAuthorizationCodeAsync to finish, meanwhile the uri passed in to that method is sent back to the caller of SubmitLoginRequest
//which then redirects the browser to that uri, once it's done it's sent back and calls FinishLoginRequest, which sends it's uri to OAuthLoginRequest
//which causes AcquireAuthorizationCodeAsync to return
try
{
//step 2
var result = await loginRequest.Application.AcquireTokenInteractive(OAuthClient.DefaultScopes)
.WithCustomWebUi(loginRequest)
.ExecuteAsync(stoppingToken);
//step 7, causes step 8 to resume
loginRequest.SetAuthenticationResult(result);
}
catch (Exception e)
{
logger.LogError(e, "Error getting token");
loginRequest.SetException(e);
}

if (loginRequest.State is not null)
_oAuthLoginRequests.Remove(loginRequest.State);
}
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions backend/FwLite/FwLiteWeb/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>
<link rel="icon" type="image/png" href="_content/FwLiteShared/favicon.png"/>
<HeadOutlet @rendermode="InteractiveServer"/>
<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)"/>
<script>
window.invokeOnWindow = function (methodName, args) {
if (!(methodName in window)) {
Expand All @@ -20,7 +20,7 @@
</head>

<body>
<Routes @rendermode="InteractiveServer"/>
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)"/>
<script src="_framework/blazor.web.js"></script>
</body>

Expand Down
10 changes: 3 additions & 7 deletions backend/FwLite/FwLiteWeb/Routes/AuthRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,12 @@ public static IEndpointConventionBuilder MapAuthRoutes(this WebApplication app)
async (AuthService authService, string authority, IOptions<AuthConfig> options, [FromHeader] string referer) =>
{
var returnUrl = new Uri(referer).PathAndQuery;
//todo blazor, once we're using blazor this endpoint will only be used for non webview logins
if (options.Value.SystemWebViewLogin)
{
await authService.SignInWebView(options.Value.GetServerByAuthority(authority));
return Results.Redirect(returnUrl);
}
else
{
return Results.Redirect(await authService.SignInWebApp(options.Value.GetServerByAuthority(authority), returnUrl));
throw new NotSupportedException("System web view login is not supported for this endpoint");
}

return Results.Redirect(await authService.SignInWebApp(options.Value.GetServerByAuthority(authority), returnUrl));
});
group.MapGet("/oauth-callback",
async (OAuthService oAuthService, HttpContext context) =>
Expand Down
2 changes: 1 addition & 1 deletion frontend/viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"dev": "vite build -m web-component --watch",
"lexbox-dev": "vite build -m web-component",
"build": "vite build -m web-component",
"build-app": "vite build --emptyOutDir",
"build-app": "vite build",
"preview": "vite preview",
"test": "vitest run",
"test:ui": "vitest --ui",
Expand Down
31 changes: 5 additions & 26 deletions frontend/viewer/src/HomeView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import {onMount} from 'svelte';
import {useAuthService, useImportFwdataService, useProjectsService} from './lib/services/service-provider';
import type {ILexboxServer, IServerStatus} from '$lib/dotnet-types';
import LoginButton from '$lib/auth/LoginButton.svelte';

const projectsService = useProjectsService();
const authService = useAuthService();
Expand Down Expand Up @@ -86,27 +87,9 @@
throw error;
});

let loadingServer: string = '';
async function login(server: ILexboxServer) {
loadingServer = server.authority;
try {
await authService.signInWebView(server);
await fetchRemoteProjects();
serversStatus = await authService.servers();
} finally {
loadingServer = '';
}
}

async function logout(server: ILexboxServer) {
loadingServer = server.authority;
try {
await authService.logout(server);
await fetchRemoteProjects();
serversStatus = await authService.servers();
} finally {
loadingServer = '';
}
async function refreshProjectsAndServers() {
await fetchRemoteProjects();
serversStatus = await authService.servers();
}


Expand Down Expand Up @@ -268,11 +251,7 @@
{#if status.loggedInAs}
<p class="mr-2 px-2 py-1 text-sm border rounded-full">{status.loggedInAs}</p>
{/if}
{#if status.loggedIn}
<Button loading={loadingServer === server.authority} variant="fill" color="primary" on:click={() => logout(server)} icon={mdiLogout}>Logout</Button>
{:else}
<Button loading={loadingServer === server.authority} variant="fill-light" color="primary" on:click={() => login(server)} icon={mdiLogin}>Login</Button>
{/if}
<LoginButton {server} isLoggedIn={status.loggedIn} on:status={() => refreshProjectsAndServers()} />
</div>
{@const serverProjects = remoteProjects[server.authority]?.filter(p => p.crdt) ?? []}
{#each serverProjects as project}
Expand Down
79 changes: 79 additions & 0 deletions frontend/viewer/src/lib/auth/LoginButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<script context="module" lang="ts">
import type {IAuthService} from '$lib/dotnet-types';
import {type Readable, writable, type Writable} from 'svelte/store';

let shouldUseSystemWebViewStore: Writable<boolean> | undefined = undefined;

function useSystemWebView(authService: IAuthService): Readable<boolean> {
if (shouldUseSystemWebViewStore) return shouldUseSystemWebViewStore;
shouldUseSystemWebViewStore = writable(true);
void authService.useSystemWebView().then(r => shouldUseSystemWebViewStore!.set(r));
return shouldUseSystemWebViewStore;
}
</script>

<script lang="ts">
import {mdiLogin, mdiLogout} from '@mdi/js';
import {Button} from 'svelte-ux';
import type {ILexboxServer} from '$lib/dotnet-types';
import {useAuthService} from '$lib/services/service-provider';
import {createEventDispatcher} from 'svelte';

const authService = useAuthService();
const shouldUseSystemWebView = useSystemWebView(authService);
const dispatch = createEventDispatcher<{
status: 'logged-in' | 'logged-out'
}>();
export let isLoggedIn: boolean;
export let server: ILexboxServer;
let loading = false;


async function login(server: ILexboxServer) {
loading = true;
try {
await authService.signInWebView(server);
dispatch('status', 'logged-in');
} finally {
loading = false;
}
}

async function logout(server: ILexboxServer) {
loading = true;
try {
await authService.logout(server);
dispatch('status', 'logged-out');
} finally {
loading = false;
}
}
</script>

{#if isLoggedIn}
<Button {loading}
variant="fill"
color="primary"
on:click={() => logout(server)}
icon={mdiLogout}>
Logout
</Button>
{:else}
{#if $shouldUseSystemWebView}
<Button {loading}
variant="fill-light"
color="primary"
on:click={() => login(server)}
icon={mdiLogin}>
Login
</Button>
{:else}
<Button {loading}
variant="fill-light"
color="primary"
href="/api/auth/login/{server.id}"
icon={mdiLogin}>
Login
</Button>
{/if}
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface IAuthService
{
servers() : Promise<IServerStatus[]>;
signInWebView(server: ILexboxServer) : Promise<void>;
useSystemWebView() : Promise<boolean>;
signInWebApp(server: ILexboxServer, returnUrl: string) : Promise<string>;
logout(server: ILexboxServer) : Promise<void>;
getLoggedInName(server: ILexboxServer) : Promise<string>;
Expand Down
Loading