✨ Self-contained oidc receiver page
This commit is contained in:
parent
70aeb5e0cb
commit
c806c5d139
@ -205,7 +205,7 @@ public class ConnectionController(
|
||||
// Login existing user
|
||||
var session = await auth.CreateSessionAsync(connection.Account, clock.GetCurrentInstant());
|
||||
var token = auth.CreateToken(session);
|
||||
return Redirect($"/?token={token}");
|
||||
return Redirect($"/auth/token?token={token}");
|
||||
}
|
||||
|
||||
// Register new user
|
||||
@ -228,7 +228,7 @@ public class ConnectionController(
|
||||
|
||||
var loginSession = await auth.CreateSessionAsync(account, clock.GetCurrentInstant());
|
||||
var loginToken = auth.CreateToken(loginSession);
|
||||
return Redirect($"/?token={loginToken}");
|
||||
return Redirect($"/auth/token?token={loginToken}");
|
||||
}
|
||||
|
||||
private static async Task<OidcCallbackData> ExtractCallbackData(HttpRequest request)
|
||||
|
@ -11,9 +11,18 @@ namespace DysonNetwork.Sphere.Auth.OpenId;
|
||||
/// <summary>
|
||||
/// Base service for OpenID Connect authentication providers
|
||||
/// </summary>
|
||||
public abstract class OidcService(IConfiguration configuration, IHttpClientFactory httpClientFactory, AppDatabase db)
|
||||
public abstract class OidcService
|
||||
{
|
||||
protected readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
|
||||
protected readonly IConfiguration _configuration;
|
||||
protected readonly IHttpClientFactory _httpClientFactory;
|
||||
protected readonly AppDatabase _db;
|
||||
|
||||
protected OidcService(IConfiguration configuration, IHttpClientFactory httpClientFactory, AppDatabase db)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this provider
|
||||
@ -47,9 +56,9 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto
|
||||
{
|
||||
return new ProviderConfiguration
|
||||
{
|
||||
ClientId = configuration[$"Oidc:{ConfigSectionName}:ClientId"] ?? "",
|
||||
ClientSecret = configuration[$"Oidc:{ConfigSectionName}:ClientSecret"] ?? "",
|
||||
RedirectUri = configuration["BaseUrl"] + "/auth/callback/" + ProviderName
|
||||
ClientId = _configuration[$"Oidc:{ConfigSectionName}:ClientId"] ?? "",
|
||||
ClientSecret = _configuration[$"Oidc:{ConfigSectionName}:ClientSecret"] ?? "",
|
||||
RedirectUri = _configuration["BaseUrl"] + "/auth/callback/" + ProviderName
|
||||
};
|
||||
}
|
||||
|
||||
@ -58,7 +67,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto
|
||||
/// </summary>
|
||||
protected async Task<OidcDiscoveryDocument?> GetDiscoveryDocumentAsync()
|
||||
{
|
||||
var client = httpClientFactory.CreateClient();
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
var response = await client.GetAsync(DiscoveryEndpoint);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<OidcDiscoveryDocument>();
|
||||
@ -78,7 +87,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto
|
||||
throw new InvalidOperationException("Token endpoint not found in discovery document");
|
||||
}
|
||||
|
||||
var client = httpClientFactory.CreateClient();
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
var content = new FormUrlEncodedContent(BuildTokenRequestParameters(code, config, codeVerifier));
|
||||
|
||||
var response = await client.PostAsync(discoveryDocument.TokenEndpoint, content);
|
||||
@ -169,7 +178,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto
|
||||
)
|
||||
{
|
||||
// Create or update the account connection
|
||||
var connection = await db.AccountConnections
|
||||
var connection = await _db.AccountConnections
|
||||
.FirstOrDefaultAsync(c => c.Provider == ProviderName &&
|
||||
c.ProvidedIdentifier == userInfo.UserId &&
|
||||
c.AccountId == account.Id
|
||||
@ -186,7 +195,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto
|
||||
LastUsedAt = SystemClock.Instance.GetCurrentInstant(),
|
||||
AccountId = account.Id
|
||||
};
|
||||
await db.AccountConnections.AddAsync(connection);
|
||||
await _db.AccountConnections.AddAsync(connection);
|
||||
}
|
||||
|
||||
// Create a challenge that's already completed
|
||||
@ -206,7 +215,7 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto
|
||||
UserAgent = request.Request.Headers.UserAgent,
|
||||
};
|
||||
|
||||
await db.AuthChallenges.AddAsync(challenge);
|
||||
await _db.AuthChallenges.AddAsync(challenge);
|
||||
|
||||
// Create a session
|
||||
var session = new Session
|
||||
@ -217,8 +226,8 @@ public abstract class OidcService(IConfiguration configuration, IHttpClientFacto
|
||||
Challenge = challenge
|
||||
};
|
||||
|
||||
await db.AuthSessions.AddAsync(session);
|
||||
await db.SaveChangesAsync();
|
||||
await _db.AuthSessions.AddAsync(session);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return session;
|
||||
}
|
||||
|
50
DysonNetwork.Sphere/Pages/Auth/Token.cshtml
Normal file
50
DysonNetwork.Sphere/Pages/Auth/Token.cshtml
Normal file
@ -0,0 +1,50 @@
|
||||
@page "/auth/token"
|
||||
@model DysonNetwork.Sphere.Pages.Auth.TokenModel
|
||||
@{
|
||||
ViewData["Title"] = "Authentication Successful";
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
||||
<div class="h-full flex items-center justify-center">
|
||||
<div class="max-w-lg w-full mx-auto p-6 text-center">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Authentication Successful</h1>
|
||||
<p class="mb-6 text-gray-900 dark:text-white">You can now close this window and return to the application.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
(function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const token = urlParams.get('token');
|
||||
|
||||
if (token) {
|
||||
console.log("Authentication token received.");
|
||||
|
||||
// For WebView2/UWP apps that can handle window.external.notify
|
||||
if (window.external && typeof window.external.notify === 'function') {
|
||||
try {
|
||||
window.external.notify(token);
|
||||
console.log("Token sent via window.external.notify.");
|
||||
return; // Exit after successful notification
|
||||
} catch (e) {
|
||||
console.error("Failed to send token via window.external.notify:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// For mobile apps that use custom URI schemes
|
||||
try {
|
||||
const customSchemeUrl = `dyson://auth?token=${encodeURIComponent(token)}`;
|
||||
window.location.href = customSchemeUrl;
|
||||
console.log("Attempting to redirect to custom scheme:", customSchemeUrl);
|
||||
} catch (e) {
|
||||
console.error("Failed to redirect to custom scheme:", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error("Authentication token not found in URL.");
|
||||
document.querySelector('p').innerText = "Authentication failed: No token was provided.";
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
}
|
11
DysonNetwork.Sphere/Pages/Auth/Token.cshtml.cs
Normal file
11
DysonNetwork.Sphere/Pages/Auth/Token.cshtml.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace DysonNetwork.Sphere.Pages.Auth
|
||||
{
|
||||
public class TokenModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user