✨ Complete oauth / oidc
This commit is contained in:
parent
217b434cc4
commit
0226bf8fa3
@ -73,7 +73,7 @@ public class AuthService(
|
|||||||
return totalRequiredSteps;
|
return totalRequiredSteps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Session> CreateSessionAsync(Account.Account account, Instant time)
|
public async Task<Session> CreateSessionForOidcAsync(Account.Account account, Instant time, Guid? customAppId = null)
|
||||||
{
|
{
|
||||||
var challenge = new Challenge
|
var challenge = new Challenge
|
||||||
{
|
{
|
||||||
@ -82,7 +82,7 @@ public class AuthService(
|
|||||||
UserAgent = HttpContext.Request.Headers.UserAgent,
|
UserAgent = HttpContext.Request.Headers.UserAgent,
|
||||||
StepRemain = 1,
|
StepRemain = 1,
|
||||||
StepTotal = 1,
|
StepTotal = 1,
|
||||||
Type = ChallengeType.Oidc
|
Type = customAppId is not null ? ChallengeType.OAuth : ChallengeType.Oidc
|
||||||
};
|
};
|
||||||
|
|
||||||
var session = new Session
|
var session = new Session
|
||||||
@ -90,7 +90,8 @@ public class AuthService(
|
|||||||
AccountId = account.Id,
|
AccountId = account.Id,
|
||||||
CreatedAt = time,
|
CreatedAt = time,
|
||||||
LastGrantedAt = time,
|
LastGrantedAt = time,
|
||||||
Challenge = challenge
|
Challenge = challenge,
|
||||||
|
AppId = customAppId
|
||||||
};
|
};
|
||||||
|
|
||||||
db.AuthChallenges.Add(challenge);
|
db.AuthChallenges.Add(challenge);
|
||||||
|
@ -10,6 +10,7 @@ using System.Text.Json.Serialization;
|
|||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Auth.OidcProvider.Controllers;
|
namespace DysonNetwork.Sphere.Auth.OidcProvider.Controllers;
|
||||||
|
|
||||||
@ -43,35 +44,67 @@ public class OidcProviderController(
|
|||||||
return BadRequest(new ErrorResponse
|
return BadRequest(new ErrorResponse
|
||||||
{ Error = "invalid_client", ErrorDescription = "Invalid client credentials" });
|
{ Error = "invalid_client", ErrorDescription = "Invalid client credentials" });
|
||||||
|
|
||||||
// Validate the authorization code
|
|
||||||
var authCode = await oidcService.ValidateAuthorizationCodeAsync(
|
|
||||||
request.Code ?? string.Empty,
|
|
||||||
request.ClientId.Value,
|
|
||||||
request.RedirectUri,
|
|
||||||
request.CodeVerifier
|
|
||||||
);
|
|
||||||
|
|
||||||
if (authCode == null)
|
|
||||||
{
|
|
||||||
logger.LogWarning(@"Invalid or expired authorization code: {Code}", request.Code);
|
|
||||||
return BadRequest(new ErrorResponse
|
|
||||||
{ Error = "invalid_grant", ErrorDescription = "Invalid or expired authorization code" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate tokens
|
// Generate tokens
|
||||||
var tokenResponse = await oidcService.GenerateTokenResponseAsync(
|
var tokenResponse = await oidcService.GenerateTokenResponseAsync(
|
||||||
clientId: request.ClientId.Value,
|
clientId: request.ClientId.Value,
|
||||||
scopes: authCode.Scopes,
|
authorizationCode: request.Code!,
|
||||||
authorizationCode: request.Code!
|
redirectUri: request.RedirectUri,
|
||||||
|
codeVerifier: request.CodeVerifier
|
||||||
);
|
);
|
||||||
|
|
||||||
return Ok(tokenResponse);
|
return Ok(tokenResponse);
|
||||||
}
|
}
|
||||||
|
case "refresh_token" when string.IsNullOrEmpty(request.RefreshToken):
|
||||||
|
return BadRequest(new ErrorResponse
|
||||||
|
{ Error = "invalid_request", ErrorDescription = "Refresh token is required" });
|
||||||
case "refresh_token":
|
case "refresh_token":
|
||||||
// Handle refresh token request
|
{
|
||||||
// In a real implementation, you would validate the refresh token
|
try
|
||||||
// and issue a new access token
|
{
|
||||||
return BadRequest(new ErrorResponse { Error = "unsupported_grant_type" });
|
// Decode the base64 refresh token to get the session ID
|
||||||
|
var sessionIdBytes = Convert.FromBase64String(request.RefreshToken);
|
||||||
|
var sessionId = new Guid(sessionIdBytes);
|
||||||
|
|
||||||
|
// Find the session and related data
|
||||||
|
var session = await oidcService.FindSessionByIdAsync(sessionId);
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
if (session?.App is null || session.ExpiredAt < now)
|
||||||
|
{
|
||||||
|
return BadRequest(new ErrorResponse
|
||||||
|
{
|
||||||
|
Error = "invalid_grant",
|
||||||
|
ErrorDescription = "Invalid or expired refresh token"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the client
|
||||||
|
var client = session.App;
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
return BadRequest(new ErrorResponse
|
||||||
|
{
|
||||||
|
Error = "invalid_client",
|
||||||
|
ErrorDescription = "Client not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new tokens
|
||||||
|
var tokenResponse = await oidcService.GenerateTokenResponseAsync(
|
||||||
|
clientId: session.AppId!.Value,
|
||||||
|
sessionId: session.Id
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(tokenResponse);
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
return BadRequest(new ErrorResponse
|
||||||
|
{
|
||||||
|
Error = "invalid_grant",
|
||||||
|
ErrorDescription = "Invalid refresh token format"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return BadRequest(new ErrorResponse { Error = "unsupported_grant_type" });
|
return BadRequest(new ErrorResponse { Error = "unsupported_grant_type" });
|
||||||
}
|
}
|
||||||
@ -79,7 +112,7 @@ public class OidcProviderController(
|
|||||||
|
|
||||||
[HttpGet("userinfo")]
|
[HttpGet("userinfo")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> UserInfo()
|
public async Task<IActionResult> GetUserInfo()
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser ||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser ||
|
||||||
HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized();
|
HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized();
|
||||||
@ -175,19 +208,35 @@ public class OidcProviderController(
|
|||||||
|
|
||||||
public class TokenRequest
|
public class TokenRequest
|
||||||
{
|
{
|
||||||
[JsonPropertyName("grant_type")] public string? GrantType { get; set; }
|
[JsonPropertyName("grant_type")]
|
||||||
|
[FromForm(Name = "grant_type")]
|
||||||
|
public string? GrantType { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("code")] public string? Code { get; set; }
|
[JsonPropertyName("code")]
|
||||||
|
[FromForm(Name = "code")]
|
||||||
|
public string? Code { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("redirect_uri")] public string? RedirectUri { get; set; }
|
[JsonPropertyName("redirect_uri")]
|
||||||
|
[FromForm(Name = "redirect_uri")]
|
||||||
|
public string? RedirectUri { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("client_id")] public Guid? ClientId { get; set; }
|
[JsonPropertyName("client_id")]
|
||||||
|
[FromForm(Name = "client_id")]
|
||||||
|
public Guid? ClientId { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("client_secret")] public string? ClientSecret { get; set; }
|
[JsonPropertyName("client_secret")]
|
||||||
|
[FromForm(Name = "client_secret")]
|
||||||
|
public string? ClientSecret { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("refresh_token")] public string? RefreshToken { get; set; }
|
[JsonPropertyName("refresh_token")]
|
||||||
|
[FromForm(Name = "refresh_token")]
|
||||||
|
public string? RefreshToken { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("scope")] public string? Scope { get; set; }
|
[JsonPropertyName("scope")]
|
||||||
|
[FromForm(Name = "scope")]
|
||||||
|
public string? Scope { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("code_verifier")] public string? CodeVerifier { get; set; }
|
[JsonPropertyName("code_verifier")]
|
||||||
|
[FromForm(Name = "code_verifier")]
|
||||||
|
public string? CodeVerifier { get; set; }
|
||||||
}
|
}
|
@ -13,6 +13,5 @@ public class AuthorizationCodeInfo
|
|||||||
public string? CodeChallenge { get; set; }
|
public string? CodeChallenge { get; set; }
|
||||||
public string? CodeChallengeMethod { get; set; }
|
public string? CodeChallengeMethod { get; set; }
|
||||||
public string? Nonce { get; set; }
|
public string? Nonce { get; set; }
|
||||||
public Instant Expiration { get; set; }
|
|
||||||
public Instant CreatedAt { get; set; }
|
public Instant CreatedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -53,34 +53,54 @@ public class OidcProviderService(
|
|||||||
|
|
||||||
public async Task<TokenResponse> GenerateTokenResponseAsync(
|
public async Task<TokenResponse> GenerateTokenResponseAsync(
|
||||||
Guid clientId,
|
Guid clientId,
|
||||||
string authorizationCode,
|
string? authorizationCode = null,
|
||||||
IEnumerable<string>? scopes = null
|
string? redirectUri = null,
|
||||||
|
string? codeVerifier = null,
|
||||||
|
Guid? sessionId = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var client = await FindClientByIdAsync(clientId);
|
var client = await FindClientByIdAsync(clientId);
|
||||||
if (client == null)
|
if (client == null)
|
||||||
throw new InvalidOperationException("Client not found");
|
throw new InvalidOperationException("Client not found");
|
||||||
|
|
||||||
var authCode = await ValidateAuthorizationCodeAsync(authorizationCode, clientId);
|
Session session;
|
||||||
|
var clock = SystemClock.Instance;
|
||||||
|
var now = clock.GetCurrentInstant();
|
||||||
|
|
||||||
|
List<string>? scopes = null;
|
||||||
|
if (authorizationCode != null)
|
||||||
|
{
|
||||||
|
// Authorization code flow
|
||||||
|
var authCode = await ValidateAuthorizationCodeAsync(authorizationCode, clientId, redirectUri, codeVerifier);
|
||||||
if (authCode is null) throw new InvalidOperationException("Invalid authorization code");
|
if (authCode is null) throw new InvalidOperationException("Invalid authorization code");
|
||||||
var account = await db.Accounts.Where(a => a.Id == authCode.AccountId).FirstOrDefaultAsync();
|
var account = await db.Accounts.Where(a => a.Id == authCode.AccountId).FirstOrDefaultAsync();
|
||||||
if (account is null) throw new InvalidOperationException("Account was not found");
|
if (account is null) throw new InvalidOperationException("Account was not found");
|
||||||
|
|
||||||
var clock = SystemClock.Instance;
|
session = await auth.CreateSessionForOidcAsync(account, now);
|
||||||
var now = clock.GetCurrentInstant();
|
scopes = authCode.Scopes;
|
||||||
var session = await auth.CreateSessionAsync(account, now);
|
}
|
||||||
|
else if (sessionId.HasValue)
|
||||||
|
{
|
||||||
|
// Refresh token flow
|
||||||
|
session = await FindSessionByIdAsync(sessionId.Value) ??
|
||||||
|
throw new InvalidOperationException("Invalid session");
|
||||||
|
|
||||||
|
// Verify the session is still valid
|
||||||
|
if (session.ExpiredAt < now)
|
||||||
|
throw new InvalidOperationException("Session has expired");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Either authorization code or session ID must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
var expiresIn = (int)_options.AccessTokenLifetime.TotalSeconds;
|
var expiresIn = (int)_options.AccessTokenLifetime.TotalSeconds;
|
||||||
var expiresAt = now.Plus(Duration.FromSeconds(expiresIn));
|
var expiresAt = now.Plus(Duration.FromSeconds(expiresIn));
|
||||||
|
|
||||||
// Generate access token
|
// Generate an access token
|
||||||
var accessToken = GenerateJwtToken(client, session, expiresAt, scopes);
|
var accessToken = GenerateJwtToken(client, session, expiresAt, scopes);
|
||||||
var refreshToken = GenerateRefreshToken(session);
|
var refreshToken = GenerateRefreshToken(session);
|
||||||
|
|
||||||
// In a real implementation, you would store the token in the database
|
|
||||||
// For this example, we'll just return the token without storing it
|
|
||||||
// as we don't have a dedicated OIDC token table
|
|
||||||
|
|
||||||
return new TokenResponse
|
return new TokenResponse
|
||||||
{
|
{
|
||||||
AccessToken = accessToken,
|
AccessToken = accessToken,
|
||||||
@ -105,8 +125,8 @@ public class OidcProviderService(
|
|||||||
var tokenDescriptor = new SecurityTokenDescriptor
|
var tokenDescriptor = new SecurityTokenDescriptor
|
||||||
{
|
{
|
||||||
Subject = new ClaimsIdentity([
|
Subject = new ClaimsIdentity([
|
||||||
new Claim(JwtRegisteredClaimNames.Sub, session.Id.ToString()),
|
new Claim(JwtRegisteredClaimNames.Sub, session.AccountId.ToString()),
|
||||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
new Claim(JwtRegisteredClaimNames.Jti, session.Id.ToString()),
|
||||||
new Claim(JwtRegisteredClaimNames.Iat, now.ToUnixTimeSeconds().ToString(),
|
new Claim(JwtRegisteredClaimNames.Iat, now.ToUnixTimeSeconds().ToString(),
|
||||||
ClaimValueTypes.Integer64),
|
ClaimValueTypes.Integer64),
|
||||||
new Claim("client_id", client.Id.ToString())
|
new Claim("client_id", client.Id.ToString())
|
||||||
@ -144,7 +164,7 @@ public class OidcProviderService(
|
|||||||
{
|
{
|
||||||
ValidateIssuer = true,
|
ValidateIssuer = true,
|
||||||
ValidIssuer = _options.IssuerUri,
|
ValidIssuer = _options.IssuerUri,
|
||||||
ValidateAudience = true,
|
ValidateAudience = false,
|
||||||
ValidateLifetime = true,
|
ValidateLifetime = true,
|
||||||
ClockSkew = TimeSpan.Zero
|
ClockSkew = TimeSpan.Zero
|
||||||
};
|
};
|
||||||
@ -166,9 +186,18 @@ public class OidcProviderService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Session?> FindSessionByIdAsync(Guid sessionId)
|
||||||
|
{
|
||||||
|
return await db.AuthSessions
|
||||||
|
.Include(s => s.Account)
|
||||||
|
.Include(s => s.Challenge)
|
||||||
|
.Include(s => s.App)
|
||||||
|
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
private static string GenerateRefreshToken(Session session)
|
private static string GenerateRefreshToken(Session session)
|
||||||
{
|
{
|
||||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(session.Id.ToString()));
|
return Convert.ToBase64String(session.Id.ToByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool VerifyHashedSecret(string secret, string hashedSecret)
|
private static bool VerifyHashedSecret(string secret, string hashedSecret)
|
||||||
@ -202,7 +231,6 @@ public class OidcProviderService(
|
|||||||
CodeChallenge = codeChallenge,
|
CodeChallenge = codeChallenge,
|
||||||
CodeChallengeMethod = codeChallengeMethod,
|
CodeChallengeMethod = codeChallengeMethod,
|
||||||
Nonce = nonce,
|
Nonce = nonce,
|
||||||
Expiration = now.Plus(Duration.FromTimeSpan(_options.AuthorizationCodeLifetime)),
|
|
||||||
CreatedAt = now
|
CreatedAt = now
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -214,7 +242,7 @@ public class OidcProviderService(
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AuthorizationCodeInfo?> ValidateAuthorizationCodeAsync(
|
private async Task<AuthorizationCodeInfo?> ValidateAuthorizationCodeAsync(
|
||||||
string code,
|
string code,
|
||||||
Guid clientId,
|
Guid clientId,
|
||||||
string? redirectUri = null,
|
string? redirectUri = null,
|
||||||
@ -230,17 +258,6 @@ public class OidcProviderService(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var clock = SystemClock.Instance;
|
|
||||||
var now = clock.GetCurrentInstant();
|
|
||||||
|
|
||||||
// Check if the code has expired
|
|
||||||
if (now > authCode.Expiration)
|
|
||||||
{
|
|
||||||
logger.LogWarning("Authorization code expired: {Code}", code);
|
|
||||||
await cache.RemoveAsync(cacheKey);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify client ID matches
|
// Verify client ID matches
|
||||||
if (authCode.ClientId != clientId)
|
if (authCode.ClientId != clientId)
|
||||||
{
|
{
|
||||||
|
@ -376,7 +376,7 @@ public class ConnectionController(
|
|||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
var loginSession = await auth.CreateSessionAsync(account, clock.GetCurrentInstant());
|
var loginSession = await auth.CreateSessionForOidcAsync(account, clock.GetCurrentInstant());
|
||||||
var loginToken = auth.CreateToken(loginSession);
|
var loginToken = auth.CreateToken(loginSession);
|
||||||
return Redirect($"/auth/token?token={loginToken}");
|
return Redirect($"/auth/token?token={loginToken}");
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using DysonNetwork.Sphere.Developer;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using Point = NetTopologySuite.Geometries.Point;
|
using Point = NetTopologySuite.Geometries.Point;
|
||||||
|
|
||||||
@ -17,6 +18,8 @@ public class Session : ModelBase
|
|||||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||||
public Guid ChallengeId { get; set; }
|
public Guid ChallengeId { get; set; }
|
||||||
public Challenge Challenge { get; set; } = null!;
|
public Challenge Challenge { get; set; } = null!;
|
||||||
|
public Guid? AppId { get; set; }
|
||||||
|
public CustomApp? App { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChallengeType
|
public enum ChallengeType
|
||||||
|
4014
DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.Designer.cs
generated
Normal file
4014
DysonNetwork.Sphere/Data/Migrations/20250629084150_AuthSessionWithApp.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AuthSessionWithApp : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "app_id",
|
||||||
|
table: "auth_sessions",
|
||||||
|
type: "uuid",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_auth_sessions_app_id",
|
||||||
|
table: "auth_sessions",
|
||||||
|
column: "app_id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "fk_auth_sessions_custom_apps_app_id",
|
||||||
|
table: "auth_sessions",
|
||||||
|
column: "app_id",
|
||||||
|
principalTable: "custom_apps",
|
||||||
|
principalColumn: "id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "fk_auth_sessions_custom_apps_app_id",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "ix_auth_sessions_app_id",
|
||||||
|
table: "auth_sessions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "app_id",
|
||||||
|
table: "auth_sessions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -978,6 +978,10 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("account_id");
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Guid?>("AppId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("app_id");
|
||||||
|
|
||||||
b.Property<Guid>("ChallengeId")
|
b.Property<Guid>("ChallengeId")
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("challenge_id");
|
.HasColumnName("challenge_id");
|
||||||
@ -1013,6 +1017,9 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.HasIndex("AccountId")
|
b.HasIndex("AccountId")
|
||||||
.HasDatabaseName("ix_auth_sessions_account_id");
|
.HasDatabaseName("ix_auth_sessions_account_id");
|
||||||
|
|
||||||
|
b.HasIndex("AppId")
|
||||||
|
.HasDatabaseName("ix_auth_sessions_app_id");
|
||||||
|
|
||||||
b.HasIndex("ChallengeId")
|
b.HasIndex("ChallengeId")
|
||||||
.HasDatabaseName("ix_auth_sessions_challenge_id");
|
.HasDatabaseName("ix_auth_sessions_challenge_id");
|
||||||
|
|
||||||
@ -3331,6 +3338,11 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("fk_auth_sessions_accounts_account_id");
|
.HasConstraintName("fk_auth_sessions_accounts_account_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AppId")
|
||||||
|
.HasConstraintName("fk_auth_sessions_custom_apps_app_id");
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge")
|
b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("ChallengeId")
|
.HasForeignKey("ChallengeId")
|
||||||
@ -3340,6 +3352,8 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
|
|
||||||
b.Navigation("Account");
|
b.Navigation("Account");
|
||||||
|
|
||||||
|
b.Navigation("App");
|
||||||
|
|
||||||
b.Navigation("Challenge");
|
b.Navigation("Challenge");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -148,9 +148,7 @@ public class AuthorizeModel(OidcProviderService oidcService) : PageModel
|
|||||||
|
|
||||||
// Add state if provided (for CSRF protection)
|
// Add state if provided (for CSRF protection)
|
||||||
if (!string.IsNullOrEmpty(State))
|
if (!string.IsNullOrEmpty(State))
|
||||||
{
|
|
||||||
query["state"] = State;
|
query["state"] = State;
|
||||||
}
|
|
||||||
|
|
||||||
// Set the query string
|
// Set the query string
|
||||||
redirectUri.Query = query.ToString();
|
redirectUri.Query = query.ToString();
|
||||||
|
@ -24,6 +24,7 @@ using NodaTime.Serialization.SystemTextJson;
|
|||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
|
using DysonNetwork.Sphere.Auth.OidcProvider.Options;
|
||||||
using DysonNetwork.Sphere.Auth.OidcProvider.Services;
|
using DysonNetwork.Sphere.Auth.OidcProvider.Services;
|
||||||
using DysonNetwork.Sphere.Connection.WebReader;
|
using DysonNetwork.Sphere.Connection.WebReader;
|
||||||
using DysonNetwork.Sphere.Developer;
|
using DysonNetwork.Sphere.Developer;
|
||||||
@ -235,6 +236,8 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<SafetyService>();
|
services.AddScoped<SafetyService>();
|
||||||
services.AddScoped<DiscoveryService>();
|
services.AddScoped<DiscoveryService>();
|
||||||
services.AddScoped<CustomAppService>();
|
services.AddScoped<CustomAppService>();
|
||||||
|
|
||||||
|
services.Configure<OidcProviderOptions>(configuration.GetSection("OidcProvider"));
|
||||||
services.AddScoped<OidcProviderService>();
|
services.AddScoped<OidcProviderService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
"PrivateKeyPath": "Keys/PrivateKey.pem",
|
"PrivateKeyPath": "Keys/PrivateKey.pem",
|
||||||
"AccessTokenLifetime": "01:00:00",
|
"AccessTokenLifetime": "01:00:00",
|
||||||
"RefreshTokenLifetime": "30.00:00:00",
|
"RefreshTokenLifetime": "30.00:00:00",
|
||||||
"AuthorizationCodeLifetime": "00:05:00",
|
"AuthorizationCodeLifetime": "00:30:00",
|
||||||
"RequireHttpsMetadata": true
|
"RequireHttpsMetadata": true
|
||||||
},
|
},
|
||||||
"Tus": {
|
"Tus": {
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5703920a18f94462b4354fab05326e6519a200_003F35_003F8536fc49_003FJsonSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5703920a18f94462b4354fab05326e6519a200_003F35_003F8536fc49_003FJsonSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtBearerExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff611a1225a3445458f2ca3f102eed5bdcd10_003F07_003F030df6ba_003FJwtBearerExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtBearerExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff611a1225a3445458f2ca3f102eed5bdcd10_003F07_003F030df6ba_003FJwtBearerExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtSecurityTokenHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F19f907d47c6f4a2ea68238bf22de133a16600_003Fa0_003F66d8c35f_003FJwtSecurityTokenHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtSecurityTokenHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F477051138f1f40de9077b7b1cdc55c6215fb0_003Ff5_003Fd716e016_003FJwtSecurityTokenHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtSecurityTokenHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F477051138f1f40de9077b7b1cdc55c6215fb0_003Ff5_003Fd716e016_003FJwtSecurityTokenHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKestrelServerLimits_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e2e5dfcafad4407b569dd5df56a2fbf274e00_003Fa4_003F39445f62_003FKestrelServerLimits_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKestrelServerLimits_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e2e5dfcafad4407b569dd5df56a2fbf274e00_003Fa4_003F39445f62_003FKestrelServerLimits_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKnownResamplers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003Fb3_003Fcdb3e080_003FKnownResamplers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKnownResamplers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003Fb3_003Fcdb3e080_003FKnownResamplers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
@ -60,6 +61,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFound_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff2c049af93e430aac427e8ff3cc9edd8763d5c9f006d7121ed1c5921585cba_003FNotFound_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFound_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff2c049af93e430aac427e8ff3cc9edd8763d5c9f006d7121ed1c5921585cba_003FNotFound_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANpgsqlEntityTypeBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fccb1faacaea4420db96b09857fc56178a1600_003Fd9_003F9acf9507_003FNpgsqlEntityTypeBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANpgsqlEntityTypeBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fccb1faacaea4420db96b09857fc56178a1600_003Fd9_003F9acf9507_003FNpgsqlEntityTypeBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3bef61b8a21d4c8e96872ecdd7782fa0e55000_003F79_003F4ab1c673_003FNullable_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F6a_003Fea17bf26_003FNullable_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F6a_003Fea17bf26_003FNullable_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOk_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003F3b_003F237bf104_003FOk_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOk_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003F3b_003F237bf104_003FOk_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user