mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-23 09:03:15 -04:00
Merge pull request #26 from lanedirt/25-add-versioning-to-webapi-project
Add versioning to webapi project
This commit is contained in:
@@ -120,5 +120,8 @@ dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_coalesce_expression = false:suggestion
|
||||
dotnet_style_null_propagation = false:suggestion
|
||||
|
||||
# IDE0046: Convert to conditional expression
|
||||
dotnet_diagnostic.IDE0046.severity = silent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
@@ -18,6 +18,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace AliasVault.Api.Controllers;
|
||||
using System.Globalization;
|
||||
using AliasDb;
|
||||
using AliasVault.Shared.Models.WebApi;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -21,6 +22,7 @@ using Service = AliasVault.Shared.Models.WebApi.Service;
|
||||
/// </summary>
|
||||
/// <param name="context">DbContext instance.</param>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
[ApiVersion("1")]
|
||||
public class AliasController(AliasDbContext context, UserManager<IdentityUser> userManager) : AuthenticatedRequestController(userManager)
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using AliasDb;
|
||||
using AliasVault.Shared.Models;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
@@ -26,8 +27,9 @@ using Microsoft.IdentityModel.Tokens;
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
/// <param name="signInManager">SignInManager instance.</param>
|
||||
/// <param name="configuration">IConfiguration instance.</param>
|
||||
[Route("api/[controller]")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
public class AuthController(AliasDbContext context, UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IConfiguration configuration) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
@@ -175,7 +177,7 @@ public class AuthController(AliasDbContext context, UserManager<IdentityUser> us
|
||||
issuer: configuration["Jwt:Issuer"] ?? string.Empty,
|
||||
audience: configuration["Jwt:Issuer"] ?? string.Empty,
|
||||
claims: claims,
|
||||
expires: DateTime.Now.AddMinutes(30),
|
||||
expires: DateTime.Now.AddSeconds(15),
|
||||
signingCredentials: creds);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
|
||||
@@ -15,7 +15,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
/// Base controller for requests that require authentication.
|
||||
/// </summary>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
[Route("api/[controller]")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class AuthenticatedRequestController(UserManager<IdentityUser> userManager) : ControllerBase
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace AliasVault.Api.Controllers;
|
||||
|
||||
using AliasGenerators.Identity;
|
||||
using AliasGenerators.Identity.Implementations;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -15,6 +16,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
/// Controller for identity generation.
|
||||
/// </summary>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
[ApiVersion("1")]
|
||||
public class IdentityController(UserManager<IdentityUser> userManager) : AuthenticatedRequestController(userManager)
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
using System.Data.Common;
|
||||
using System.Text;
|
||||
using AliasDb;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Data.Sqlite;
|
||||
@@ -26,7 +27,6 @@ builder.Services.AddLogging(logging =>
|
||||
logging.AddFilter("Microsoft.AspNetCore.Identity.UserManager", LogLevel.Error);
|
||||
});
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddSingleton<DbConnection>(container =>
|
||||
{
|
||||
var configFile = new ConfigurationBuilder()
|
||||
@@ -99,7 +99,19 @@ builder.Services.AddCors(options =>
|
||||
});
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddApiVersioning(options =>
|
||||
{
|
||||
options.DefaultApiVersion = new ApiVersion(1, 0);
|
||||
options.AssumeDefaultVersionWhenUnspecified = true;
|
||||
options.ReportApiVersions = true;
|
||||
})
|
||||
.AddApiExplorer(options =>
|
||||
{
|
||||
options.GroupNameFormat = "'v'VVV";
|
||||
options.SubstituteApiVersionInUrl = true;
|
||||
});
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "AliasVault API", Version = "v1" });
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@page "/user/login"
|
||||
@attribute [AllowAnonymous]
|
||||
@layout Auth.Layout.MainLayout
|
||||
|
||||
@inject HttpClient Http
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
@@ -71,7 +70,7 @@
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Http.PostAsJsonAsync("api/Auth/login", _loginModel);
|
||||
var result = await Http.PostAsJsonAsync("api/v1/Auth/login", _loginModel);
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Http.PostAsJsonAsync("api/Auth/register", _registerModel);
|
||||
var result = await Http.PostAsJsonAsync("api/v1/Auth/register", _registerModel);
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
|
||||
@@ -16,23 +16,15 @@ using Blazored.LocalStorage;
|
||||
/// This service is responsible for handling authentication-related operations such as refreshing tokens,
|
||||
/// storing tokens, and revoking tokens.
|
||||
/// </summary>
|
||||
public class AuthService
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="AuthService"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="httpClient">The HTTP client.</param>
|
||||
/// <param name="localStorage">The local storage service.</param>
|
||||
public class AuthService(HttpClient httpClient, ILocalStorageService localStorage)
|
||||
{
|
||||
private const string AccessTokenKey = "token";
|
||||
private const string RefreshTokenKey = "refreshToken";
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILocalStorageService _localStorage;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The HTTP client.</param>
|
||||
/// <param name="localStorage">The local storage service.</param>
|
||||
public AuthService(HttpClient httpClient, ILocalStorageService localStorage)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_localStorage = localStorage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the access token asynchronously.
|
||||
@@ -44,14 +36,14 @@ public class AuthService
|
||||
var accessToken = await GetAccessTokenAsync();
|
||||
var refreshToken = await GetRefreshTokenAsync();
|
||||
var tokenInput = new TokenModel { Token = accessToken, RefreshToken = refreshToken };
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "api/Auth/refresh")
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/Auth/refresh")
|
||||
{
|
||||
Content = JsonContent.Create(tokenInput),
|
||||
};
|
||||
|
||||
// Add the X-Ignore-Failure header to the request so any failure does not trigger another refresh token request.
|
||||
request.Headers.Add("X-Ignore-Failure", "true");
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var response = await httpClient.SendAsync(request);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -77,7 +69,7 @@ public class AuthService
|
||||
/// <returns>The stored access token.</returns>
|
||||
public async Task<string> GetAccessTokenAsync()
|
||||
{
|
||||
return await _localStorage.GetItemAsStringAsync(AccessTokenKey) ?? string.Empty;
|
||||
return await localStorage.GetItemAsStringAsync(AccessTokenKey) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,7 +79,7 @@ public class AuthService
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task StoreAccessTokenAsync(string newToken)
|
||||
{
|
||||
await _localStorage.SetItemAsStringAsync(AccessTokenKey, newToken);
|
||||
await localStorage.SetItemAsStringAsync(AccessTokenKey, newToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -96,7 +88,7 @@ public class AuthService
|
||||
/// <returns>The stored refresh token.</returns>
|
||||
public async Task<string> GetRefreshTokenAsync()
|
||||
{
|
||||
return await _localStorage.GetItemAsStringAsync(RefreshTokenKey) ?? string.Empty;
|
||||
return await localStorage.GetItemAsStringAsync(RefreshTokenKey) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,7 +98,7 @@ public class AuthService
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task StoreRefreshTokenAsync(string newToken)
|
||||
{
|
||||
await _localStorage.SetItemAsStringAsync(RefreshTokenKey, newToken);
|
||||
await localStorage.SetItemAsStringAsync(RefreshTokenKey, newToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -115,8 +107,8 @@ public class AuthService
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task RemoveTokensAsync()
|
||||
{
|
||||
await _localStorage.RemoveItemAsync(AccessTokenKey);
|
||||
await _localStorage.RemoveItemAsync(RefreshTokenKey);
|
||||
await localStorage.RemoveItemAsync(AccessTokenKey);
|
||||
await localStorage.RemoveItemAsync(RefreshTokenKey);
|
||||
|
||||
// If the remote call fails we catch the exception and ignore it.
|
||||
// This is because the user is already logged out and we don't want to trigger another refresh token request.
|
||||
@@ -142,13 +134,13 @@ public class AuthService
|
||||
RefreshToken = await GetRefreshTokenAsync(),
|
||||
};
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "api/Auth/revoke")
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/Auth/revoke")
|
||||
{
|
||||
Content = JsonContent.Create(tokenInput),
|
||||
};
|
||||
|
||||
// Add the X-Ignore-Failure header to the request so any failure does not trigger another refresh token request.
|
||||
request.Headers.Add("X-Ignore-Failure", "true");
|
||||
await _httpClient.SendAsync(request);
|
||||
await httpClient.SendAsync(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ public class AliasService(HttpClient httpClient)
|
||||
/// <returns>Identity object.</returns>
|
||||
public async Task<Identity> GenerateRandomIdentityAsync()
|
||||
{
|
||||
var identity = await httpClient.GetFromJsonAsync<Identity>("api/Identity/generate");
|
||||
if (identity == null)
|
||||
var identity = await httpClient.GetFromJsonAsync<Identity>("api/v1/Identity/generate");
|
||||
if (identity is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to generate random identity.");
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class AliasService(HttpClient httpClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
var returnObject = await httpClient.PutAsJsonAsync<Alias>("api/Alias", aliasObject);
|
||||
var returnObject = await httpClient.PutAsJsonAsync<Alias>("api/v1/Alias", aliasObject);
|
||||
return await returnObject.Content.ReadFromJsonAsync<Guid>();
|
||||
}
|
||||
catch
|
||||
@@ -59,7 +59,7 @@ public class AliasService(HttpClient httpClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
var returnObject = await httpClient.PostAsJsonAsync<Alias>("api/Alias/" + id, aliasObject);
|
||||
var returnObject = await httpClient.PostAsJsonAsync<Alias>("api/v1/Alias/" + id, aliasObject);
|
||||
return await returnObject.Content.ReadFromJsonAsync<Guid>();
|
||||
}
|
||||
catch
|
||||
@@ -77,7 +77,7 @@ public class AliasService(HttpClient httpClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.GetFromJsonAsync<Alias>("api/Alias/" + aliasId);
|
||||
return await httpClient.GetFromJsonAsync<Alias>("api/v1/Alias/" + aliasId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -93,7 +93,7 @@ public class AliasService(HttpClient httpClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.GetFromJsonAsync<List<AliasListEntry>>("api/Alias/items");
|
||||
return await httpClient.GetFromJsonAsync<List<AliasListEntry>>("api/v1/Alias/items");
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -111,7 +111,7 @@ public class AliasService(HttpClient httpClient)
|
||||
// Delete from webapi.
|
||||
try
|
||||
{
|
||||
await httpClient.DeleteAsync("api/Alias/" + id);
|
||||
await httpClient.DeleteAsync("api/v1/Alias/" + id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user