void.cat/VoidCat/Services/Users/Auth/GenericOAuth2Service.cs

133 lines
3.9 KiB
C#
Raw Normal View History

2022-09-08 09:41:31 +00:00
using Newtonsoft.Json;
2022-09-08 12:38:32 +00:00
using VoidCat.Model;
2022-09-08 09:41:31 +00:00
using VoidCat.Model.User;
using VoidCat.Services.Abstractions;
namespace VoidCat.Services.Users.Auth;
/// <summary>
/// Generic base class for OAuth2 code grant flow
/// </summary>
2022-09-08 12:38:32 +00:00
public abstract class GenericOAuth2Service : IOAuthProvider
2022-09-08 09:41:31 +00:00
{
2022-09-08 12:38:32 +00:00
private readonly Uri _uri;
2022-09-08 09:41:31 +00:00
private readonly HttpClient _client;
2022-09-08 12:38:32 +00:00
protected GenericOAuth2Service(HttpClient client, VoidSettings settings)
2022-09-08 09:41:31 +00:00
{
2022-09-08 12:38:32 +00:00
_uri = settings.SiteUrl;
2022-09-08 09:41:31 +00:00
_client = client;
}
/// <inheritdoc />
public abstract string Id { get; }
/// <inheritdoc />
public Uri Authorize()
{
var ub = new UriBuilder(AuthorizeEndpoint)
{
Query = string.Join("&", BuildAuthorizeQuery().Select(a => $"{a.Key}={Uri.EscapeDataString(a.Value)}"))
};
return ub.Uri;
}
/// <inheritdoc />
public async ValueTask<UserAuthToken> GetToken(string code)
{
var form = new FormUrlEncodedContent(BuildTokenQuery(code));
var rsp = await _client.PostAsync(TokenEndpoint, form);
var json = await rsp.Content.ReadAsStringAsync();
if (!rsp.IsSuccessStatusCode)
{
throw new InvalidOperationException($"Failed to get token from provider: {Id}, response: {json}");
}
2022-09-08 12:38:32 +00:00
var dto = JsonConvert.DeserializeObject<OAuthAccessToken>(json);
2022-09-08 09:41:31 +00:00
return TransformDto(dto!);
}
/// <inheritdoc />
public abstract ValueTask<InternalUser?> GetUserDetails(UserAuthToken token);
/// <summary>
/// Build query args for authorize
/// </summary>
/// <returns></returns>
2022-09-08 12:38:32 +00:00
protected virtual Dictionary<string, string> BuildAuthorizeQuery()
=> new()
{
{"response_type", "code"},
{"client_id", Details.ClientId!},
{"scope", string.Join(" ", Scopes)},
{"prompt", "none"},
{"redirect_uri", new Uri(_uri, $"/auth/{Id}/token").ToString()}
};
2022-09-08 09:41:31 +00:00
/// <summary>
/// Build query args for token generation
/// </summary>
/// <returns></returns>
2022-09-08 12:38:32 +00:00
protected virtual Dictionary<string, string> BuildTokenQuery(string code)
=> new()
{
{"client_id", Details.ClientId!},
{"client_secret", Details.ClientSecret!},
{"grant_type", "authorization_code"},
{"code", code},
{"redirect_uri", new Uri(_uri, $"/auth/{Id}/token").ToString()}
};
2022-09-08 09:41:31 +00:00
/// <summary>
/// Authorize url for this service
/// </summary>
protected abstract Uri AuthorizeEndpoint { get; }
/// <summary>
/// Generate token url for this service
/// </summary>
protected abstract Uri TokenEndpoint { get; }
2022-09-08 12:38:32 +00:00
/// <summary>
/// OAuth client details
/// </summary>
protected abstract OAuthDetails Details { get; }
/// <summary>
/// OAuth scopes
/// </summary>
protected abstract string[] Scopes { get; }
/// <summary>
/// Transform DTO to <see cref="UserAuthToken"/>
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
protected virtual UserAuthToken TransformDto(OAuthAccessToken dto)
{
return new()
{
Id = Guid.NewGuid(),
Provider = Id,
AccessToken = dto.AccessToken,
Expires = DateTime.UtcNow.AddSeconds(dto.ExpiresIn),
TokenType = dto.TokenType,
RefreshToken = dto.RefreshToken,
Scope = dto.Scope
};
}
protected class OAuthAccessToken
{
[JsonProperty("access_token")] public string AccessToken { get; init; }
[JsonProperty("expires_in")] public int ExpiresIn { get; init; }
[JsonProperty("token_type")] public string TokenType { get; init; }
[JsonProperty("refresh_token")] public string RefreshToken { get; init; }
[JsonProperty("scope")] public string Scope { get; init; }
}
2022-09-08 09:41:31 +00:00
}