233 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using Microsoft.AspNetCore.Mvc;
 | 
						|
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
						|
using DysonNetwork.Sphere.Auth.OidcProvider.Services;
 | 
						|
using System.ComponentModel.DataAnnotations;
 | 
						|
using DysonNetwork.Shared.Models;
 | 
						|
using DysonNetwork.Sphere.Auth.OidcProvider.Responses;
 | 
						|
using DysonNetwork.Sphere.Developer;
 | 
						|
 | 
						|
namespace DysonNetwork.Sphere.Pages.Auth;
 | 
						|
 | 
						|
public class AuthorizeModel(OidcProviderService oidcService, IConfiguration configuration) : PageModel
 | 
						|
{
 | 
						|
    [BindProperty(SupportsGet = true)] public string? ReturnUrl { get; set; }
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true, Name = "client_id")]
 | 
						|
    [Required(ErrorMessage = "The client_id parameter is required")]
 | 
						|
    public string? ClientIdString { get; set; }
 | 
						|
 | 
						|
    public Guid ClientId { get; set; }
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true, Name = "response_type")]
 | 
						|
    public string ResponseType { get; set; } = "code";
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true, Name = "redirect_uri")]
 | 
						|
    public string? RedirectUri { get; set; }
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true)] public string? Scope { get; set; }
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true)] public string? State { get; set; }
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true)] public string? Nonce { get; set; }
 | 
						|
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true, Name = "code_challenge")]
 | 
						|
    public string? CodeChallenge { get; set; }
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true, Name = "code_challenge_method")]
 | 
						|
    public string? CodeChallengeMethod { get; set; }
 | 
						|
 | 
						|
    [BindProperty(SupportsGet = true, Name = "response_mode")]
 | 
						|
    public string? ResponseMode { get; set; }
 | 
						|
 | 
						|
    public string? AppName { get; set; }
 | 
						|
    public string? AppLogo { get; set; }
 | 
						|
    public string? AppUri { get; set; }
 | 
						|
    public string[]? RequestedScopes { get; set; }
 | 
						|
 | 
						|
    public async Task<IActionResult> OnGetAsync()
 | 
						|
    {
 | 
						|
        // First check if user is authenticated
 | 
						|
        if (HttpContext.Items["CurrentUser"] is not DysonNetwork.Shared.Models.Account currentUser)
 | 
						|
        {
 | 
						|
            var returnUrl = Uri.EscapeDataString($"{Request.Path}{Request.QueryString}");
 | 
						|
            return RedirectToPage("/Auth/Login", new { returnUrl });
 | 
						|
        }
 | 
						|
 | 
						|
        // Validate client_id
 | 
						|
        if (string.IsNullOrEmpty(ClientIdString) || !Guid.TryParse(ClientIdString, out var clientId))
 | 
						|
        {
 | 
						|
            ModelState.AddModelError("client_id", "Invalid client_id format");
 | 
						|
            return BadRequest("Invalid client_id format");
 | 
						|
        }
 | 
						|
 | 
						|
        ClientId = clientId;
 | 
						|
 | 
						|
        // Get client info
 | 
						|
        var client = await oidcService.FindClientByIdAsync(ClientId);
 | 
						|
        if (client == null)
 | 
						|
        {
 | 
						|
            ModelState.AddModelError("client_id", "Client not found");
 | 
						|
            return NotFound("Client not found");
 | 
						|
        }
 | 
						|
        
 | 
						|
        var config = client.OauthConfig;
 | 
						|
        if (config is null)
 | 
						|
        {
 | 
						|
            ModelState.AddModelError("client_id", "Client was not available for use OAuth / OIDC");
 | 
						|
            return BadRequest("Client was not enabled for OAuth / OIDC");
 | 
						|
        }
 | 
						|
 | 
						|
        // Validate redirect URI for non-Developing apps
 | 
						|
        if (client.Status != CustomAppStatus.Developing)
 | 
						|
        {
 | 
						|
            if (!string.IsNullOrEmpty(RedirectUri) && !(config.RedirectUris?.Contains(RedirectUri) ?? false))
 | 
						|
            {
 | 
						|
                return BadRequest(new ErrorResponse
 | 
						|
                {
 | 
						|
                    Error = "invalid_request",
 | 
						|
                    ErrorDescription = "Invalid redirect_uri"
 | 
						|
                });
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // Check for an existing valid session
 | 
						|
        var existingSession = await oidcService.FindValidSessionAsync(currentUser.Id, clientId);
 | 
						|
        if (existingSession != null)
 | 
						|
        {
 | 
						|
            // Auto-approve since valid session exists
 | 
						|
            return await HandleApproval(currentUser, client, existingSession);
 | 
						|
        }
 | 
						|
 | 
						|
        // Show authorization page
 | 
						|
        var baseUrl = configuration["BaseUrl"];
 | 
						|
        AppName = client.Name;
 | 
						|
        AppLogo = client.Picture is not null ? $"{baseUrl}/files/{client.Picture.Id}" : null;
 | 
						|
        AppUri = config.ClientUri;
 | 
						|
        RequestedScopes = (Scope ?? "openid profile").Split(' ').Distinct().ToArray();
 | 
						|
 | 
						|
        return Page();
 | 
						|
    }
 | 
						|
 | 
						|
    private async Task<IActionResult> HandleApproval(DysonNetwork.Shared.Models.Account currentUser, CustomApp client, Session? existingSession = null)
 | 
						|
    {
 | 
						|
        if (string.IsNullOrEmpty(RedirectUri))
 | 
						|
        {
 | 
						|
            ModelState.AddModelError("redirect_uri", "No redirect_uri provided");
 | 
						|
            return BadRequest("No redirect_uri provided");
 | 
						|
        }
 | 
						|
 | 
						|
        string authCode;
 | 
						|
        
 | 
						|
        if (existingSession != null)
 | 
						|
        {
 | 
						|
            // Reuse existing session
 | 
						|
            authCode = await oidcService.GenerateAuthorizationCodeForReuseSessionAsync(
 | 
						|
                session: existingSession,
 | 
						|
                clientId: ClientId,
 | 
						|
                redirectUri: RedirectUri,
 | 
						|
                scopes: Scope?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? [],
 | 
						|
                codeChallenge: CodeChallenge,
 | 
						|
                codeChallengeMethod: CodeChallengeMethod,
 | 
						|
                nonce: Nonce
 | 
						|
            );
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            // Create a new session (existing flow)
 | 
						|
            authCode = await oidcService.GenerateAuthorizationCodeAsync(
 | 
						|
                clientId: ClientId,
 | 
						|
                userId: currentUser.Id,
 | 
						|
                redirectUri: RedirectUri,
 | 
						|
                scopes: Scope?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? [],
 | 
						|
                codeChallenge: CodeChallenge,
 | 
						|
                codeChallengeMethod: CodeChallengeMethod,
 | 
						|
                nonce: Nonce
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        // Build the redirect URI with the authorization code
 | 
						|
        var redirectUriBuilder = new UriBuilder(RedirectUri);
 | 
						|
        var query = System.Web.HttpUtility.ParseQueryString(redirectUriBuilder.Query);
 | 
						|
        query["code"] = authCode;
 | 
						|
        if (!string.IsNullOrEmpty(State))
 | 
						|
            query["state"] = State;
 | 
						|
        if (!string.IsNullOrEmpty(Scope))
 | 
						|
            query["scope"] = Scope;
 | 
						|
        redirectUriBuilder.Query = query.ToString();
 | 
						|
 | 
						|
        return Redirect(redirectUriBuilder.ToString());
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IActionResult> OnPostAsync(bool allow)
 | 
						|
    {
 | 
						|
        if (HttpContext.Items["CurrentUser"] is not DysonNetwork.Shared.Models.Account currentUser) return Unauthorized();
 | 
						|
 | 
						|
        // First, validate the client ID
 | 
						|
        if (string.IsNullOrEmpty(ClientIdString) || !Guid.TryParse(ClientIdString, out var clientId))
 | 
						|
        {
 | 
						|
            ModelState.AddModelError("client_id", "Invalid client_id format");
 | 
						|
            return BadRequest("Invalid client_id format");
 | 
						|
        }
 | 
						|
 | 
						|
        ClientId = clientId;
 | 
						|
 | 
						|
        // Check if a client exists
 | 
						|
        var client = await oidcService.FindClientByIdAsync(ClientId);
 | 
						|
        if (client == null)
 | 
						|
        {
 | 
						|
            ModelState.AddModelError("client_id", "Client not found");
 | 
						|
            return NotFound("Client not found");
 | 
						|
        }
 | 
						|
 | 
						|
        if (!allow)
 | 
						|
        {
 | 
						|
            // User denied the authorization request
 | 
						|
            if (string.IsNullOrEmpty(RedirectUri))
 | 
						|
                return BadRequest("No redirect_uri provided");
 | 
						|
 | 
						|
            var deniedUriBuilder = new UriBuilder(RedirectUri);
 | 
						|
            var deniedQuery = System.Web.HttpUtility.ParseQueryString(deniedUriBuilder.Query);
 | 
						|
            deniedQuery["error"] = "access_denied";
 | 
						|
            deniedQuery["error_description"] = "The user denied the authorization request";
 | 
						|
            if (!string.IsNullOrEmpty(State)) deniedQuery["state"] = State;
 | 
						|
            deniedUriBuilder.Query = deniedQuery.ToString();
 | 
						|
 | 
						|
            return Redirect(deniedUriBuilder.ToString());
 | 
						|
        }
 | 
						|
 | 
						|
        // User approved the request
 | 
						|
        if (string.IsNullOrEmpty(RedirectUri))
 | 
						|
        {
 | 
						|
            ModelState.AddModelError("redirect_uri", "No redirect_uri provided");
 | 
						|
            return BadRequest("No redirect_uri provided");
 | 
						|
        }
 | 
						|
 | 
						|
        // Generate authorization code
 | 
						|
        var authCode = await oidcService.GenerateAuthorizationCodeAsync(
 | 
						|
            clientId: ClientId,
 | 
						|
            userId: currentUser.Id,
 | 
						|
            redirectUri: RedirectUri,
 | 
						|
            scopes: Scope?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>(),
 | 
						|
            codeChallenge: CodeChallenge,
 | 
						|
            codeChallengeMethod: CodeChallengeMethod,
 | 
						|
            nonce: Nonce);
 | 
						|
 | 
						|
        // Build the redirect URI with the authorization code
 | 
						|
        var redirectUri = new UriBuilder(RedirectUri);
 | 
						|
        var query = System.Web.HttpUtility.ParseQueryString(redirectUri.Query);
 | 
						|
 | 
						|
        // Add the authorization code
 | 
						|
        query["code"] = authCode;
 | 
						|
 | 
						|
        // Add state if provided (for CSRF protection)
 | 
						|
        if (!string.IsNullOrEmpty(State))
 | 
						|
            query["state"] = State;
 | 
						|
 | 
						|
        // Set the query string
 | 
						|
        redirectUri.Query = query.ToString();
 | 
						|
 | 
						|
        // Redirect back to the client with the authorization code
 | 
						|
        return Redirect(redirectUri.ToString());
 | 
						|
    }
 | 
						|
} |