diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/Elements.Backend/Controllers/AuthController.cs | 106 | ||||
| -rw-r--r-- | backend/Elements.Backend/Controllers/UserController.cs | 41 | ||||
| -rw-r--r-- | backend/Elements.Backend/Controllers/WeatherForecastController.cs | 32 | ||||
| -rw-r--r-- | backend/Elements.Backend/Elements.Backend.csproj | 41 | ||||
| -rw-r--r-- | backend/Elements.Backend/Program.cs | 53 | ||||
| -rw-r--r-- | backend/Elements.Backend/WeatherForecast.cs | 12 | ||||
| -rw-r--r-- | backend/Elements.Data/ApplicationContextFactory.cs | 15 | ||||
| -rw-r--r-- | backend/Elements.Data/ApplicationDbContext.cs | 9 | ||||
| -rw-r--r-- | backend/Elements.Data/Elements.Data.csproj | 5 | ||||
| -rw-r--r-- | backend/Elements.Data/Migrations/20231020142907_Initial.Designer.cs | 80 | ||||
| -rw-r--r-- | backend/Elements.Data/Migrations/20231020142907_Initial.cs | 64 | ||||
| -rw-r--r-- | backend/Elements.Data/Migrations/ApplicationDbContextModelSnapshot.cs | 77 | ||||
| -rw-r--r-- | backend/Elements.Data/Models/Element.cs | 13 | ||||
| -rw-r--r-- | backend/Elements.Data/Models/User.cs | 9 |
14 files changed, 484 insertions, 73 deletions
diff --git a/backend/Elements.Backend/Controllers/AuthController.cs b/backend/Elements.Backend/Controllers/AuthController.cs new file mode 100644 index 0000000..56e7c3b --- /dev/null +++ b/backend/Elements.Backend/Controllers/AuthController.cs @@ -0,0 +1,106 @@ +using System.Runtime.Serialization; +using System.Security.Claims; +using System.Text.Json; +using Elements.Data; +using Elements.Data.Models; +using Google.Apis.Auth; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Elements.Backend.Controllers; + +[ApiController] +[Route("[controller]/[action]")] +public class AuthController : ControllerBase +{ + private readonly IConfiguration _config; + private readonly ApplicationDbContext _dbContext; + + public AuthController(IConfiguration config, ApplicationDbContext dbContext) + { + _config = config; + _dbContext = dbContext; + } + + public class LoginModel + { + public required string GoogleToken { get; init; } + } + + [HttpPost] + public async Task<IActionResult> Login([FromBody] LoginModel model) + { + GoogleJsonWebSignature.Payload? payload = await VerifyGoogleIdToken(model.GoogleToken); + if (payload == null) + return Unauthorized(); + + User? user = await _dbContext.Users.SingleOrDefaultAsync(u => u.GoogleId == payload.Subject); + if (user != null) + { + //Check if user's name changed and update if it did + if (user.Name != payload.Name) + user.Name = payload.Name; + } + else + { + user = new User() + { + Name = payload.Name, + GoogleId = payload.Subject, + Elements = new List<Element>() + }; + + await _dbContext.Users.AddAsync(user); + } + + await _dbContext.SaveChangesAsync(); + + List<Claim> claims = new() + { + new Claim("id", user.Id.ToString()), + new Claim(ClaimTypes.Role, "User") + }; + ClaimsIdentity claimsIdentity = new(claims, CookieAuthenticationDefaults.AuthenticationScheme); + + AuthenticationProperties authProperties = new() + { + IsPersistent = true, + AllowRefresh = true + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + + var response = new + { + Id = user.Id.ToString() + }; + + return Ok(JsonSerializer.Serialize(response)); + } + + [HttpPost] + public async Task<IActionResult> Logout() + { + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + return Ok(); + } + + private async Task<GoogleJsonWebSignature.Payload?> VerifyGoogleIdToken(string token) + { + try + { + GoogleJsonWebSignature.Payload? payload = await GoogleJsonWebSignature.ValidateAsync(token); + return payload; + } + catch (InvalidJwtException) + { + return null; + } + } +}
\ No newline at end of file diff --git a/backend/Elements.Backend/Controllers/UserController.cs b/backend/Elements.Backend/Controllers/UserController.cs new file mode 100644 index 0000000..bde93aa --- /dev/null +++ b/backend/Elements.Backend/Controllers/UserController.cs @@ -0,0 +1,41 @@ +using System.Security.Claims; +using System.Text.Json; +using Elements.Data; +using Elements.Data.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Elements.Backend.Controllers; + +[ApiController] +[Route("[controller]/[action]")] +public class UserController: ControllerBase +{ + private readonly ApplicationDbContext _dbContext; + + public UserController(ApplicationDbContext dbContext) + { + _dbContext = dbContext; + } + + [HttpGet] + [Authorize] + [Route("/user/{id:int}")] + public async Task<IActionResult> Users(int id) + { + IEnumerable<Claim> claims = User.Claims; + string? currentUserId = claims.FirstOrDefault(claim => claim.Type == "id")?.Value; + if (currentUserId == null) + return StatusCode(StatusCodes.Status500InternalServerError); + if (currentUserId != id.ToString()) + return Unauthorized(); + + User? user = await _dbContext.Users.FirstOrDefaultAsync(user => user.Id == id); + if (user == null) + return StatusCode(StatusCodes.Status500InternalServerError); + + string userJson = JsonSerializer.Serialize(user); + return Ok(userJson); + } +}
\ No newline at end of file diff --git a/backend/Elements.Backend/Controllers/WeatherForecastController.cs b/backend/Elements.Backend/Controllers/WeatherForecastController.cs deleted file mode 100644 index 9a70e36..0000000 --- a/backend/Elements.Backend/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc;
-
-namespace Elements.Backend.Controllers;
-
-[ApiController]
-[Route("[controller]")]
-public class WeatherForecastController : ControllerBase
-{
- private static readonly string[] Summaries = new[]
- {
- "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
- };
-
- private readonly ILogger<WeatherForecastController> _logger;
-
- public WeatherForecastController(ILogger<WeatherForecastController> logger)
- {
- _logger = logger;
- }
-
- [HttpGet(Name = "GetWeatherForecast")]
- public IEnumerable<WeatherForecast> Get()
- {
- return Enumerable.Range(1, 5).Select(index => new WeatherForecast
- {
- Date = DateOnly.FromDateTime(DateTime.Now),
- TemperatureC = Random.Shared.Next(-20, 55),
- Summary = Summaries[Random.Shared.Next(Summaries.Length)]
- })
- .ToArray();
- }
-}
diff --git a/backend/Elements.Backend/Elements.Backend.csproj b/backend/Elements.Backend/Elements.Backend.csproj index 0c664a6..787d048 100644 --- a/backend/Elements.Backend/Elements.Backend.csproj +++ b/backend/Elements.Backend/Elements.Backend.csproj @@ -1,18 +1,27 @@ -<Project Sdk="Microsoft.NET.Sdk.Web">
-
- <PropertyGroup>
- <TargetFramework>net7.0</TargetFramework>
- <Nullable>enable</Nullable>
- <ImplicitUsings>enable</ImplicitUsings>
- </PropertyGroup>
-
- <ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
- <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
- </ItemGroup>
-
+<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>net7.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>enable</ImplicitUsings> + <UserSecretsId>dae266ca-b349-4b87-a992-5470f9dd635d</UserSecretsId> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Google.Apis.Auth" Version="1.62.1" /> + <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="7.0.12" /> + <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.12"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.12" /> + <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.10" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> + </ItemGroup> + <ItemGroup> <ProjectReference Include="..\Elements.Data\Elements.Data.csproj" /> - </ItemGroup>
-
-</Project>
+ </ItemGroup> + +</Project> diff --git a/backend/Elements.Backend/Program.cs b/backend/Elements.Backend/Program.cs index 15eacee..50e5255 100644 --- a/backend/Elements.Backend/Program.cs +++ b/backend/Elements.Backend/Program.cs @@ -1,23 +1,54 @@ +using System.Text.Json;
+using Elements.Data;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.Google;
+using Microsoft.EntityFrameworkCore;
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
+builder.Services.AddRouting(options => options.LowercaseUrls = true);
+builder.Services.AddControllers().AddJsonOptions(options =>
+{
+ options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+});
-builder.Services.AddControllers();
-// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
-
-var app = builder.Build();
-
-// Configure the HTTP request pipeline.
-if (app.Environment.IsDevelopment())
+if (builder.Environment.IsDevelopment())
{
- app.UseSwagger();
- app.UseSwaggerUI();
+ builder.Services.AddDbContext<ApplicationDbContext>(options =>
+ {
+ options.UseSqlite("Data Source=elements.db");
+ });
}
+builder.Services
+.AddAuthentication(options =>
+{
+ options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
+})
+.AddCookie(options =>
+{
+ options.Events = new()
+ {
+ OnRedirectToLogin = (ctx) =>
+ {
+ ctx.Response.StatusCode = 401;
+ return Task.CompletedTask;
+ },
+ OnRedirectToAccessDenied = (ctx) =>
+ {
+ ctx.Response.StatusCode = 401;
+ return Task.CompletedTask;
+ }
+
+ };
+});
+
+var app = builder.Build();
+
app.UseHttpsRedirection();
+app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
diff --git a/backend/Elements.Backend/WeatherForecast.cs b/backend/Elements.Backend/WeatherForecast.cs deleted file mode 100644 index 7c8962e..0000000 --- a/backend/Elements.Backend/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Elements.Backend;
-
-public class WeatherForecast
-{
- public DateOnly Date { get; set; }
-
- public int TemperatureC { get; set; }
-
- public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-
- public string? Summary { get; set; }
-}
diff --git a/backend/Elements.Data/ApplicationContextFactory.cs b/backend/Elements.Data/ApplicationContextFactory.cs new file mode 100644 index 0000000..b8e4ebd --- /dev/null +++ b/backend/Elements.Data/ApplicationContextFactory.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Elements.Data; + +public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext> +{ + public ApplicationDbContext CreateDbContext(string[] args) + { + DbContextOptionsBuilder<ApplicationDbContext> optionsBuilder = new(); + optionsBuilder.UseSqlite("Data Source=elements.db"); + + return new ApplicationDbContext(optionsBuilder.Options); + } +}
\ No newline at end of file diff --git a/backend/Elements.Data/ApplicationDbContext.cs b/backend/Elements.Data/ApplicationDbContext.cs index 619719a..a912cc4 100644 --- a/backend/Elements.Data/ApplicationDbContext.cs +++ b/backend/Elements.Data/ApplicationDbContext.cs @@ -1,7 +1,14 @@ -using Microsoft.EntityFrameworkCore;
+using Elements.Data.Models;
+using Microsoft.EntityFrameworkCore;
namespace Elements.Data;
public class ApplicationDbContext : DbContext
{
+ public DbSet<Element> Elements { get; set; }
+ public DbSet<User> Users { get; set; }
+ public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
+ {
+
+ }
}
diff --git a/backend/Elements.Data/Elements.Data.csproj b/backend/Elements.Data/Elements.Data.csproj index 408fea2..2fb1f63 100644 --- a/backend/Elements.Data/Elements.Data.csproj +++ b/backend/Elements.Data/Elements.Data.csproj @@ -7,6 +7,11 @@ </PropertyGroup>
<ItemGroup> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.12"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.12" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.12" /> </ItemGroup>
diff --git a/backend/Elements.Data/Migrations/20231020142907_Initial.Designer.cs b/backend/Elements.Data/Migrations/20231020142907_Initial.Designer.cs new file mode 100644 index 0000000..1272d62 --- /dev/null +++ b/backend/Elements.Data/Migrations/20231020142907_Initial.Designer.cs @@ -0,0 +1,80 @@ +// <auto-generated /> +using Elements.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elements.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20231020142907_Initial")] + partial class Initial + { + /// <inheritdoc /> + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.12"); + + modelBuilder.Entity("Elements.Data.Models.Element", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<int>("State") + .HasColumnType("INTEGER"); + + b.Property<int>("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Elements"); + }); + + modelBuilder.Entity("Elements.Data.Models.User", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("GoogleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Elements.Data.Models.Element", b => + { + b.HasOne("Elements.Data.Models.User", null) + .WithMany("Elements") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Elements.Data.Models.User", b => + { + b.Navigation("Elements"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Elements.Data/Migrations/20231020142907_Initial.cs b/backend/Elements.Data/Migrations/20231020142907_Initial.cs new file mode 100644 index 0000000..8257e5b --- /dev/null +++ b/backend/Elements.Data/Migrations/20231020142907_Initial.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Elements.Data.Migrations +{ + /// <inheritdoc /> + public partial class Initial : Migration + { + /// <inheritdoc /> + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column<int>(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + GoogleId = table.Column<string>(type: "TEXT", nullable: false), + Name = table.Column<string>(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Elements", + columns: table => new + { + Id = table.Column<int>(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column<int>(type: "INTEGER", nullable: false), + Name = table.Column<string>(type: "TEXT", nullable: false), + State = table.Column<int>(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Elements", x => x.Id); + table.ForeignKey( + name: "FK_Elements_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Elements_UserId", + table: "Elements", + column: "UserId"); + } + + /// <inheritdoc /> + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Elements"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/backend/Elements.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/backend/Elements.Data/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..b713511 --- /dev/null +++ b/backend/Elements.Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,77 @@ +// <auto-generated /> +using Elements.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Elements.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.12"); + + modelBuilder.Entity("Elements.Data.Models.Element", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<int>("State") + .HasColumnType("INTEGER"); + + b.Property<int>("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Elements"); + }); + + modelBuilder.Entity("Elements.Data.Models.User", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("GoogleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Elements.Data.Models.Element", b => + { + b.HasOne("Elements.Data.Models.User", null) + .WithMany("Elements") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Elements.Data.Models.User", b => + { + b.Navigation("Elements"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Elements.Data/Models/Element.cs b/backend/Elements.Data/Models/Element.cs index 2f602e5..08f25a8 100644 --- a/backend/Elements.Data/Models/Element.cs +++ b/backend/Elements.Data/Models/Element.cs @@ -1,5 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + namespace Elements.Data.Models; +public enum ElementState { + HasColor, + HasIcon +} + public class Element { - + public int Id {get; init;} + public required int UserId { get; init; } + public required string Name {get; init;} + public required ElementState State {get; init;} }
\ No newline at end of file diff --git a/backend/Elements.Data/Models/User.cs b/backend/Elements.Data/Models/User.cs new file mode 100644 index 0000000..b44a1e2 --- /dev/null +++ b/backend/Elements.Data/Models/User.cs @@ -0,0 +1,9 @@ +namespace Elements.Data.Models; + +public class User +{ + public int Id { get; init; } + public required string GoogleId { get; init; } + public required string Name { get; set; } + public required ICollection<Element> Elements { get; set; } +}
\ No newline at end of file |
