You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
4.5 KiB

using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using OAuthServer.Services;
namespace OAuthServer.Controllers;
[ApiController]
[Route("oauth")]
public class OAuthController(
ILogger<OAuthController> logger,
JwtService jwt,
IDataProtectionProvider dataProtectionProvider
) : ControllerBase {
private readonly Dictionary<string, string> _clients = new() {
{ "lmao", "yeet" },
};
[Authorize]
[HttpGet("authorize")]
// ReSharper disable InconsistentNaming
public ActionResult Authorize(
[Required, Url] string redirect_uri,
string? response_type,
string? client_id,
string? state,
string? nonce
) {
if (string.IsNullOrEmpty(response_type) || string.IsNullOrEmpty(client_id) || string.IsNullOrEmpty(state)) {
return Redirect($"{redirect_uri}?error=invalid_request");
}
if (response_type != "code") {
return Redirect($"{redirect_uri}?error=unsupported_response_type&state={state}");
}
if (!_clients.ContainsKey(client_id)) {
logger.LogInformation("Unknown client id");
return Redirect($"{redirect_uri}?error=unauthorized_client&state={state}");
}
IDataProtector protector = dataProtectionProvider.CreateProtector("oauth");
CodeObject codeObject = new CodeObject(
ClientId: client_id,
RedirectUri: redirect_uri,
Expiry: DateTime.UtcNow.AddMinutes(5),
Nonce: nonce
);
string code = protector.Protect(JsonSerializer.Serialize(codeObject));
return Redirect($"{redirect_uri}?code={code}&state={state}");
}
public record GenerateTokenRequest(
string? grant_type,
string? code,
string? redirect_uri,
string? client_id,
string? client_secret
);
[HttpPost("token")]
[Consumes("application/x-www-form-urlencoded")]
public ActionResult GenerateToken([FromForm] GenerateTokenRequest request) {
if (string.IsNullOrEmpty(request.grant_type) ||
string.IsNullOrEmpty(request.code) ||
string.IsNullOrEmpty(request.redirect_uri) ||
string.IsNullOrEmpty(request.client_id) ||
string.IsNullOrEmpty(request.client_secret)) {
return BadRequest(new { error = "invalid_request" });
}
if (request.grant_type != "authorization_code") {
return BadRequest(new { error = "unsupported_grant_type" });
}
if (!_clients.TryGetValue(request.client_id, out string? clientSecret)) {
logger.LogInformation("Unknown client id");
return BadRequest(new { error = "unauthorized_client" });
}
if (request.client_secret != clientSecret) {
logger.LogInformation("Invalid client secret");
return BadRequest(new { error = "unauthorized_client" });
}
IDataProtector protector = dataProtectionProvider.CreateProtector("oauth");
CodeObject? codeObject;
try {
codeObject = JsonSerializer.Deserialize<CodeObject>(protector.Unprotect(request.code));
} catch (Exception) {
return BadRequest(new { error = "invalid_request" });
}
if (codeObject == null) {
return BadRequest(new { error = "invalid_request" });
}
if (codeObject.ClientId != request.client_id || codeObject.RedirectUri != request.redirect_uri) {
return BadRequest(new { error = "invalid_request" });
}
if (DateTime.UtcNow > codeObject.Expiry) {
logger.LogInformation("Expired token");
return BadRequest(new { error = "invalid_grant" });
}
string token = jwt.GenerateToken(codeObject.ClientId, codeObject.Nonce);
Response.Headers.Append("Cache-Control", "no-store");
Response.Headers.Append("Pragma", "no-cache");
return Ok(new { access_token = token, token_type = "bearer", id_token = token });
}
[Authorize]
[HttpGet("user")]
public ActionResult UserInfo() {
return Ok(new {
sub = "248289761001",
name = "Jane Doe",
// given_name = "Jane",
// family_name = "Doe",
// preferred_username = "j.doe",
email = "janedoe@example.com",
});
}
private record CodeObject(string ClientId, string RedirectUri, DateTime Expiry, string? Nonce);
}