Compare commits
4 Commits
02bfbabcfc
...
595ade2d79
Author | SHA1 | Date | |
---|---|---|---|
|
595ade2d79 | ||
|
8ad863b781 | ||
|
46de83dc42 | ||
|
edc195b234 |
@ -1,7 +1,9 @@
|
|||||||
using Api.DBAccess;
|
using Api.DBAccess;
|
||||||
using Api.Models;
|
using Api.Models;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
using RabbitMQ.Client.Events;
|
using RabbitMQ.Client.Events;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
@ -11,34 +13,27 @@ namespace Api.AMQP
|
|||||||
{
|
{
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly DbAccess _dbAccess;
|
private readonly DbAccess _dbAccess;
|
||||||
|
private IConnection _conn;
|
||||||
|
private IChannel _channel;
|
||||||
|
private ConnectionFactory _factory;
|
||||||
|
private string _queue;
|
||||||
|
|
||||||
public AMQPPublisher(IConfiguration configuration, DbAccess dbAccess)
|
public AMQPPublisher(IConfiguration configuration, DbAccess dbAccess)
|
||||||
{
|
{
|
||||||
_dbAccess = dbAccess;
|
_dbAccess = dbAccess;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
_factory = new ConnectionFactory();
|
||||||
|
_queue = "temperature-limits";
|
||||||
|
|
||||||
|
InitFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle_Push_Device_Limits()
|
public async Task Handle_Push_Device_Limits()
|
||||||
{
|
{
|
||||||
var factory = new ConnectionFactory();
|
|
||||||
var queue = "temperature-limits";
|
|
||||||
|
|
||||||
factory.UserName = _configuration["AMQP:username"];
|
|
||||||
factory.Password = _configuration["AMQP:password"];
|
|
||||||
factory.HostName = _configuration["AMQP:host"];
|
|
||||||
factory.Port = Convert.ToInt32(_configuration["AMQP:port"]);
|
|
||||||
|
|
||||||
// Connecting to our rabbitmq and after that it create's a channel where you can connect to a queue
|
|
||||||
var conn = await factory.CreateConnectionAsync();
|
|
||||||
Console.WriteLine("AMQPClient connected");
|
|
||||||
var channel = await conn.CreateChannelAsync();
|
|
||||||
|
|
||||||
// Here we connect to the queue through the channel that got created earlier
|
|
||||||
await channel.QueueDeclareAsync(queue: queue, durable: false, exclusive: false, autoDelete: false);
|
|
||||||
Console.WriteLine($"{queue} connected");
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
await Connect();
|
||||||
|
|
||||||
// Publishes all devices limits
|
// Publishes all devices limits
|
||||||
var devices = _dbAccess.ReadDevices();
|
var devices = _dbAccess.ReadDevices();
|
||||||
foreach (var device in devices)
|
foreach (var device in devices)
|
||||||
@ -49,33 +44,21 @@ namespace Api.AMQP
|
|||||||
deviceLimit.TempLow = device.TempLow;
|
deviceLimit.TempLow = device.TempLow;
|
||||||
string message = JsonSerializer.Serialize(deviceLimit);
|
string message = JsonSerializer.Serialize(deviceLimit);
|
||||||
var body = Encoding.UTF8.GetBytes(message);
|
var body = Encoding.UTF8.GetBytes(message);
|
||||||
await channel.BasicPublishAsync(exchange: string.Empty, routingKey: queue, body: body);
|
await _channel.BasicPublishAsync(exchange: string.Empty, routingKey: _queue, body: body);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short delay before disconnecting from rabbitMQ
|
// Short delay before disconnecting from rabbitMQ
|
||||||
await Task.Delay(10000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
// Disconnecting from rabbitMQ to save resources
|
// Disconnecting from rabbitMQ to save resources
|
||||||
await channel.CloseAsync();
|
await Dispose();
|
||||||
Console.WriteLine($"{queue} disconnected");
|
|
||||||
await conn.CloseAsync();
|
|
||||||
Console.WriteLine("AMQPClient disconnected");
|
|
||||||
await channel.DisposeAsync();
|
|
||||||
await conn.DisposeAsync();
|
|
||||||
// 1 hour delay
|
// 1 hour delay
|
||||||
await Task.Delay(3600000);
|
await Task.Delay(3600000);
|
||||||
|
|
||||||
// Creating a new connection to rabbitMQ
|
await Connect();
|
||||||
conn = await factory.CreateConnectionAsync();
|
|
||||||
Console.WriteLine("AMQPClient connected");
|
|
||||||
channel = await conn.CreateChannelAsync();
|
|
||||||
|
|
||||||
// Here we connect to the queue through the channel that got created earlier
|
|
||||||
await channel.QueueDeclareAsync(queue: queue, durable: false, exclusive: false, autoDelete: false);
|
|
||||||
Console.WriteLine($"{queue} connected");
|
|
||||||
|
|
||||||
// Here all messages is consumed so the queue is empty
|
// Here all messages is consumed so the queue is empty
|
||||||
var consumer = new AsyncEventingBasicConsumer(channel);
|
var consumer = new AsyncEventingBasicConsumer(_channel);
|
||||||
consumer.ReceivedAsync += (model, ea) =>
|
consumer.ReceivedAsync += (model, ea) =>
|
||||||
{
|
{
|
||||||
Console.WriteLine("Emptying queue");
|
Console.WriteLine("Emptying queue");
|
||||||
@ -84,9 +67,45 @@ namespace Api.AMQP
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Consumes the data in the queue
|
// Consumes the data in the queue
|
||||||
await channel.BasicConsumeAsync(queue, true, consumer);
|
await _channel.BasicConsumeAsync(_queue, true, consumer);
|
||||||
|
|
||||||
|
// Short delay before disconnecting from rabbitMQ
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disconnects from rabbitMQ
|
||||||
|
private async Task<bool> Dispose()
|
||||||
|
{
|
||||||
|
await _channel.CloseAsync();
|
||||||
|
await _conn.CloseAsync();
|
||||||
|
await _channel.DisposeAsync();
|
||||||
|
await _conn.DisposeAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connects to rabbitMQ
|
||||||
|
private async Task<bool> Connect()
|
||||||
|
{
|
||||||
|
// Creating a new connection to rabbitMQ
|
||||||
|
_conn = await _factory.CreateConnectionAsync();
|
||||||
|
Console.WriteLine("AMQPClient connected");
|
||||||
|
_channel = await _conn.CreateChannelAsync();
|
||||||
|
|
||||||
|
// Here we connect to the queue through the channel that got created earlier
|
||||||
|
await _channel.QueueDeclareAsync(queue: _queue, durable: false, exclusive: false, autoDelete: false);
|
||||||
|
Console.WriteLine($"{_queue} connected");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The info for the factory
|
||||||
|
private void InitFactory()
|
||||||
|
{
|
||||||
|
_factory.UserName = _configuration["AMQP:username"];
|
||||||
|
_factory.Password = _configuration["AMQP:password"];
|
||||||
|
_factory.HostName = _configuration["AMQP:host"];
|
||||||
|
_factory.Port = Convert.ToInt32(_configuration["AMQP:port"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using RabbitMQ.Client;
|
|||||||
using RabbitMQ.Client.Events;
|
using RabbitMQ.Client.Events;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
namespace Api.AMQPReciever
|
namespace Api.AMQPReciever
|
||||||
{
|
{
|
||||||
@ -11,34 +12,27 @@ namespace Api.AMQPReciever
|
|||||||
{
|
{
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly DbAccess _dbAccess;
|
private readonly DbAccess _dbAccess;
|
||||||
|
private IConnection _conn;
|
||||||
|
private IChannel _channel;
|
||||||
|
private ConnectionFactory _factory;
|
||||||
|
private string _queue;
|
||||||
|
|
||||||
public AMQPReciever(IConfiguration configuration, DbAccess dbAccess)
|
public AMQPReciever(IConfiguration configuration, DbAccess dbAccess)
|
||||||
{
|
{
|
||||||
_dbAccess = dbAccess;
|
_dbAccess = dbAccess;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
_factory = new ConnectionFactory();
|
||||||
|
_queue = "temperature-logs";
|
||||||
|
|
||||||
|
InitFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle_Received_Application_Message()
|
public async Task Handle_Received_Application_Message()
|
||||||
{
|
{
|
||||||
var factory = new ConnectionFactory();
|
await Connect();
|
||||||
var queue = "temperature-logs";
|
|
||||||
|
|
||||||
factory.UserName = _configuration["AMQP:username"];
|
|
||||||
factory.Password = _configuration["AMQP:password"];
|
|
||||||
factory.HostName = _configuration["AMQP:host"];
|
|
||||||
factory.Port = Convert.ToInt32(_configuration["AMQP:port"]);
|
|
||||||
|
|
||||||
// Connecting to our rabbitmq and after that it create's a channel where you can connect to a queue
|
|
||||||
using var conn = await factory.CreateConnectionAsync();
|
|
||||||
Console.WriteLine("AMQPClient connected");
|
|
||||||
using var channel = await conn.CreateChannelAsync();
|
|
||||||
|
|
||||||
// Here we connect to the queue through the channel that got created earlier
|
|
||||||
await channel.QueueDeclareAsync(queue: queue, durable: false, exclusive: false, autoDelete: false);
|
|
||||||
Console.WriteLine($"{queue} connected");
|
|
||||||
|
|
||||||
// Everytime a message is recieved from the queue it goes into this consumer.ReceivedAsync
|
// Everytime a message is recieved from the queue it goes into this consumer.ReceivedAsync
|
||||||
var consumer = new AsyncEventingBasicConsumer(channel);
|
var consumer = new AsyncEventingBasicConsumer(_channel);
|
||||||
consumer.ReceivedAsync += (model, ea) =>
|
consumer.ReceivedAsync += (model, ea) =>
|
||||||
{
|
{
|
||||||
Console.WriteLine("Received application message.");
|
Console.WriteLine("Received application message.");
|
||||||
@ -73,9 +67,44 @@ namespace Api.AMQPReciever
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Consumes the data in the queue
|
// Consumes the data in the queue
|
||||||
await channel.BasicConsumeAsync(queue, true, consumer);
|
await _channel.BasicConsumeAsync(_queue, true, consumer);
|
||||||
|
|
||||||
while (true);
|
while (true);
|
||||||
|
|
||||||
|
await Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnects from rabbitMQ
|
||||||
|
private async Task<bool> Dispose()
|
||||||
|
{
|
||||||
|
await _channel.CloseAsync();
|
||||||
|
await _conn.CloseAsync();
|
||||||
|
await _channel.DisposeAsync();
|
||||||
|
await _conn.DisposeAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connects to rabbitMQ
|
||||||
|
private async Task<bool> Connect()
|
||||||
|
{
|
||||||
|
// Creating a new connection to rabbitMQ
|
||||||
|
_conn = await _factory.CreateConnectionAsync();
|
||||||
|
Console.WriteLine("AMQPClient connected");
|
||||||
|
_channel = await _conn.CreateChannelAsync();
|
||||||
|
|
||||||
|
// Here we connect to the queue through the channel that got created earlier
|
||||||
|
await _channel.QueueDeclareAsync(queue: _queue, durable: false, exclusive: false, autoDelete: false);
|
||||||
|
Console.WriteLine($"{_queue} connected");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The info for the factory
|
||||||
|
private void InitFactory()
|
||||||
|
{
|
||||||
|
_factory.UserName = _configuration["AMQP:username"];
|
||||||
|
_factory.Password = _configuration["AMQP:password"];
|
||||||
|
_factory.HostName = _configuration["AMQP:host"];
|
||||||
|
_factory.Port = Convert.ToInt32(_configuration["AMQP:port"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace Api.BusinessLogic
|
|||||||
|
|
||||||
string salt = Guid.NewGuid().ToString();
|
string salt = Guid.NewGuid().ToString();
|
||||||
string hashedPassword = ComputeHash(user.Password, SHA256.Create(), salt);
|
string hashedPassword = ComputeHash(user.Password, SHA256.Create(), salt);
|
||||||
|
|
||||||
user.Salt = salt;
|
user.Salt = salt;
|
||||||
user.Password = hashedPassword;
|
user.Password = hashedPassword;
|
||||||
|
|
||||||
@ -74,7 +74,9 @@ namespace Api.BusinessLogic
|
|||||||
if (user.Password == hashedPassword)
|
if (user.Password == hashedPassword)
|
||||||
{
|
{
|
||||||
var token = GenerateJwtToken(user);
|
var token = GenerateJwtToken(user);
|
||||||
return new OkObjectResult(new { token, user.UserName, user.Id });
|
user.RefreshToken = Guid.NewGuid().ToString();
|
||||||
|
_dbAccess.UpdatesRefreshToken(user.RefreshToken, user.Id);
|
||||||
|
return new OkObjectResult(new { token, user.UserName, user.Id, refreshToken = user.RefreshToken });
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ConflictObjectResult(new { message = "Invalid password" });
|
return new ConflictObjectResult(new { message = "Invalid password" });
|
||||||
@ -119,6 +121,13 @@ namespace Api.BusinessLogic
|
|||||||
return await _dbAccess.DeleteUser(userId);
|
return await _dbAccess.DeleteUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> RefreshToken(string refreshToken)
|
||||||
|
{
|
||||||
|
User user = await _dbAccess.ReadUser(refreshToken);
|
||||||
|
if (user == null) { return new ConflictObjectResult(new { message = "Could not match refreshtoken" }); }
|
||||||
|
return new OkObjectResult(GenerateJwtToken(user));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a hash from a salt and input using the algorithm that is provided
|
/// Generates a hash from a salt and input using the algorithm that is provided
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -51,5 +51,11 @@ namespace Api.Controllers
|
|||||||
return await _userLogic.DeleteUser(userId);
|
return await _userLogic.DeleteUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("RefreshToken")]
|
||||||
|
public async Task<IActionResult> RefreshToken(string refreshToken)
|
||||||
|
{
|
||||||
|
return await _userLogic.RefreshToken(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,20 @@ namespace Api.DBAccess
|
|||||||
return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
|
return await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a user according to refreshToken
|
||||||
|
public async Task<User> ReadUser(string refreshToken)
|
||||||
|
{
|
||||||
|
return await _context.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void UpdatesRefreshToken(string refreshToken, int userId)
|
||||||
|
{
|
||||||
|
var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
|
||||||
|
|
||||||
|
user.RefreshToken = refreshToken;
|
||||||
|
user.RefreshTokenExpiresAt = DateTime.Now.AddDays(7);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the user in the database
|
/// Updates the user in the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
138
backend/Api/Migrations/20250327084557_AddedRefreshTokenToUser.Designer.cs
generated
Normal file
138
backend/Api/Migrations/20250327084557_AddedRefreshTokenToUser.Designer.cs
generated
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Api;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Api.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(DBContext))]
|
||||||
|
[Migration("20250327084557_AddedRefreshTokenToUser")]
|
||||||
|
partial class AddedRefreshTokenToUser
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.3");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Api.Models.Device", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ReferenceId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<double>("TempHigh")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("TempLow")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<int?>("UserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Devices");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Api.Models.TemperatureLogs", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("DeviceId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("TempHigh")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("TempLow")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("Temperature")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId");
|
||||||
|
|
||||||
|
b.ToTable("TemperatureLogs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Api.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RefreshTokenExpiresAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Salt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Api.Models.Device", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Api.Models.User", null)
|
||||||
|
.WithMany("Devices")
|
||||||
|
.HasForeignKey("UserId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Api.Models.TemperatureLogs", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Api.Models.Device", null)
|
||||||
|
.WithMany("Logs")
|
||||||
|
.HasForeignKey("DeviceId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Api.Models.Device", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Logs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Api.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Devices");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Api.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedRefreshTokenToUser : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "RefreshToken",
|
||||||
|
table: "Users",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "RefreshTokenExpiresAt",
|
||||||
|
table: "Users",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "RefreshToken",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "RefreshTokenExpiresAt",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -88,6 +88,12 @@ namespace Api.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RefreshTokenExpiresAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Salt")
|
b.Property<string>("Salt")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
public string? Salt { get; set; }
|
public string? Salt { get; set; }
|
||||||
|
|
||||||
|
public string? RefreshToken { get; set; }
|
||||||
|
|
||||||
|
public DateTime RefreshTokenExpiresAt { get; set; }
|
||||||
|
|
||||||
public List<Device>? Devices { get; set; }
|
public List<Device>? Devices { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,29 @@ namespace Api
|
|||||||
services.AddSwaggerGen(c =>
|
services.AddSwaggerGen(c =>
|
||||||
{
|
{
|
||||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
|
||||||
|
|
||||||
|
// Configure Swagger to use Bearer token authentication
|
||||||
|
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Description = "JWT Authorization header using the Bearer scheme",
|
||||||
|
Type = SecuritySchemeType.Http,
|
||||||
|
Scheme = "bearer"
|
||||||
|
});
|
||||||
|
|
||||||
|
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||||
|
{
|
||||||
|
{
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = "Bearer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new string[] { }
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user