diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
index f495692..4dc945a 100644
--- a/.idea/dataSources.xml
+++ b/.idea/dataSources.xml
@@ -5,7 +5,7 @@
<driver-ref>mongo.4</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.dbschema.MongoJdbcDriver</jdbc-driver>
- <jdbc-url>mongodb://localhost:27017/nodejs</jdbc-url>
+ <jdbc-url>mongodb://localhost:27017/nodejs?authSource=admin</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<driver-properties>
<property name="authSource" value="admin" />
diff --git a/README.md b/README.md
index fa2e942..bca91cb 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,8 @@ Environment variables:
| Name | Default | Description |
| ---- | ------- | ----------- |
| `PORT` | `3000` | The port the server will run on. |
-| `LOG_REQUESTS` | `-` | Requests to log to the console by status, `-` to invert. |
| `DATABASE_SECRET_PATH` | ` ` | The path to the mongodb connection string. |
| `JWT_SECRET_PATH` | ` ` | The path to the JWT secret certificate. |
+| `LOG_REQUESTS` | `-` | Requests to log to the console by status, `-` to invert. |
| `LOG_QUERIES` | `false` | Whether to enable mongoose debug logs |
| `LOG_AUTH` | `false` | Whether to enable authentication debug logs |
diff --git a/src/api/routes/auth/deviceRoutes.js b/src/api/routes/auth/deviceRoutes.js
index 551252b..023d02d 100644
--- a/src/api/routes/auth/deviceRoutes.js
+++ b/src/api/routes/auth/deviceRoutes.js
@@ -1,7 +1,11 @@
-import { registerUser } from '#db/index.js';
-import { RegisterDto } from '#dto/index.js';
import { requireAuth } from '#api/middlewares/index.js';
import { RouteMethod } from '#api/RouteDescription.js';
+import { SafeNSoundError } from '#util/error.js';
+import Joi from 'joi';
+
+const deviceUpdateSchema = Joi.object({
+ name: Joi.string().optional().max(100)
+});
/**
* @type {RouteDescription}
@@ -13,9 +17,79 @@ export const getDevicesRoute = {
middlewares: [requireAuth],
description: 'Get all devices registered to the user',
async method(req, res) {
- const data = await RegisterDto.create(req.body);
- const registerResult = await registerUser(data);
- res.send(registerResult);
+ res.send(req.user.devices);
+ }
+ })
+ }
+};
+
+/**
+ * @type {RouteDescription}
+ */
+export const manageDeviceRoute = {
+ path: '/auth/devices/:id',
+ methods: {
+ get: new RouteMethod({
+ middlewares: [requireAuth],
+ description: 'Get user device by ID',
+ async method(req, res) {
+ const device = req.user.devices.find(
+ device => device.id === req.params.id
+ );
+ if (!device) {
+ res.status(404).send(
+ new SafeNSoundError({
+ errCode: 'ENTITY_NOT_FOUND',
+ message: 'Device not found'
+ })
+ );
+ return;
+ }
+ res.send(device);
+ }
+ }),
+ delete: new RouteMethod({
+ middlewares: [requireAuth],
+ description: 'Delete user device by ID',
+ async method(req, res) {
+ const deviceIndex = req.user.devices.findIndex(
+ device => device.id === req.params.id
+ );
+ if (deviceIndex === -1) {
+ res.status(404).send(
+ new SafeNSoundError({
+ errCode: 'ENTITY_NOT_FOUND',
+ message: 'Device not found'
+ })
+ );
+ return;
+ }
+ req.user.devices.splice(deviceIndex, 1);
+ await req.user.save();
+ res.status(204).send();
+ }
+ }),
+ patch: new RouteMethod({
+ middlewares: [requireAuth],
+ description: 'Update user device by ID',
+ async method(req, res) {
+ const device = req.user.devices.find(
+ device => device.id === req.params.id
+ );
+ if (!device) {
+ res.status(404).send(
+ new SafeNSoundError({
+ errCode: 'ENTITY_NOT_FOUND',
+ message: 'Device not found'
+ })
+ );
+ return;
+ }
+ if (req.body.name) {
+ device.name = req.body.name;
+ }
+ await req.user.save();
+ res.send(device);
}
})
}
diff --git a/testFrontend/SafeNSound.Frontend/Pages/Auth.razor b/testFrontend/SafeNSound.Frontend/Pages/Auth.razor
index 5540f02..20625f7 100644
--- a/testFrontend/SafeNSound.Frontend/Pages/Auth.razor
+++ b/testFrontend/SafeNSound.Frontend/Pages/Auth.razor
@@ -3,13 +3,13 @@
<h1>Auth</h1>
<u>User:</u><br/>
<span>Username (L?, R): </span>
-<FancyTextBox @bind-Value="@Username"/><br/>
+<FancyTextBox @bind-Value="@AuthData.Username"/><br/>
<span>Email (L? R): </span>
-<FancyTextBox @bind-Value="@Email"/><br/>
+<FancyTextBox @bind-Value="@AuthData.Email"/><br/>
<span>Password (L, R): </span>
-<FancyTextBox @bind-Value="@Password" IsPassword="true"/><br/>
+<FancyTextBox @bind-Value="@AuthData.Password" IsPassword="true"/><br/>
<span>Type (R): </span>
-<FancyTextBox @bind-Value="@UserType"/><span> (one of user|monitor|admin)</span><br/>
+<FancyTextBox @bind-Value="@AuthData.UserType"/><span> (one of user|monitor|admin)</span><br/>
<LinkButton OnClick="@Randomise">Randomise</LinkButton>
<LinkButton OnClick="@Register">Register</LinkButton>
<LinkButton OnClick="@Login">Login</LinkButton>
@@ -24,6 +24,20 @@
<LinkButton OnClick="@GetAssignedUsers">Get</LinkButton>
<LinkButton OnClick="@AddAssignedUser">Add</LinkButton>
<LinkButton OnClick="@RemoveAssignedUser">Remove</LinkButton>
+<br/><br/>
+
+<u>Devices:</u><br/>
+@if (CurrentDevice is not null) {
+ <span>Device ID: @CurrentDevice.Id</span><br/>
+ <span>Log in date: @CurrentDevice.CreatedAt</span><br/>
+ <span>Last seen: @CurrentDevice.LastSeen</span><br/>
+ <span>Device name: </span>
+ <FancyTextBox @bind-Value="@CurrentDevice.Name"/><br/>
+ <LinkButton OnClick="@GetDevice">Get</LinkButton>
+ <LinkButton OnClick="@UpdateDevice">Update</LinkButton>
+ <LinkButton OnClick="@DeleteDevice">Delete</LinkButton>
+ <LinkButton OnClick="@GetAllDevices">Get all</LinkButton>
+}
@if (Exception != null) {
<div class="alert alert-danger">
@@ -44,10 +58,15 @@
}
@code {
- private string Username { get; set; } = string.Empty;
- private string Email { get; set; } = string.Empty;
- private string Password { get; set; } = string.Empty;
- private string UserType { get; set; } = string.Empty;
+
+ private RegisterDto AuthData { get; set; } = new() {
+ Username = string.Empty,
+ UserType = string.Empty,
+ Email = String.Empty,
+ Password = string.Empty
+ };
+
+ private DeviceDto? CurrentDevice { get; set; }
private string TargetUserId { get; set; } = string.Empty;
@@ -55,10 +74,10 @@
private object? Result { get; set; }
private async Task Randomise() {
- Username = Guid.NewGuid().ToString();
- Email = Guid.NewGuid() + "@example.com";
- Password = Guid.NewGuid().ToString();
- UserType = Random.Shared.GetItems(["user", "monitor", "admin"], 1)[0];
+ AuthData.Username = Guid.NewGuid().ToString();
+ AuthData.Email = Guid.NewGuid() + "@example.com";
+ AuthData.Password = Guid.NewGuid().ToString();
+ AuthData.UserType = Random.Shared.GetItems(["user", "monitor", "admin"], 1)[0];
StateHasChanged();
}
@@ -67,10 +86,10 @@
Exception = null;
try {
await Authentication.Register(new() {
- Username = Username,
- Password = Password,
- Email = Email,
- UserType = UserType
+ Username = AuthData.Username,
+ Password = AuthData.Password,
+ Email = AuthData.Email,
+ UserType = AuthData.UserType
});
}
catch (Exception ex) {
@@ -86,11 +105,14 @@
try {
AuthResult result;
Result = result = await Authentication.Login(new() {
- Username = Username,
- Password = Password,
- Email = Email
+ Username = AuthData.Username,
+ Password = AuthData.Password,
+ Email = AuthData.Email
});
App.Client = new SafeNSoundClient(Config, result.AccessToken);
+ CurrentDevice = await App.Client.GetDevice(
+ (await App.Client.WhoAmI()).DeviceId
+ );
}
catch (Exception ex) {
Exception = ex;
@@ -104,9 +126,9 @@
Exception = null;
try {
await Authentication.Delete(new() {
- Username = Username,
- Password = Password,
- Email = Email
+ Username = AuthData.Username,
+ Password = AuthData.Password,
+ Email = AuthData.Email
});
}
catch (Exception ex) {
@@ -125,6 +147,7 @@
catch (Exception ex) {
Exception = ex;
}
+
StateHasChanged();
}
@@ -137,6 +160,7 @@
catch (Exception ex) {
Exception = ex;
}
+
StateHasChanged();
}
@@ -150,6 +174,7 @@
catch (Exception ex) {
Exception = ex;
}
+
StateHasChanged();
}
@@ -163,6 +188,7 @@
catch (Exception ex) {
Exception = ex;
}
+
StateHasChanged();
}
@@ -185,6 +211,28 @@
catch (Exception ex) {
Exception = ex;
}
+
+ StateHasChanged();
+ }
+
+ private async Task GetDevice() {
+ Result = CurrentDevice = await App.Client!.GetDevice(CurrentDevice!.Id!);
+ StateHasChanged();
+ }
+
+ private async Task UpdateDevice() {
+ await App.Client!.UpdateDevice(CurrentDevice!.Id!, new() {
+ Name = CurrentDevice.Name
+ });
+ await GetDevice();
+ }
+
+ private async Task DeleteDevice() {
+ await App.Client!.DeleteDevice(CurrentDevice!.Id!);
+ }
+
+ private async Task GetAllDevices() {
+ Result = await App.Client!.GetDevices();
StateHasChanged();
}
diff --git a/testFrontend/SafeNSound.Sdk/SafeNSoundClient.cs b/testFrontend/SafeNSound.Sdk/SafeNSoundClient.cs
index 8291178..9ea8073 100644
--- a/testFrontend/SafeNSound.Sdk/SafeNSoundClient.cs
+++ b/testFrontend/SafeNSound.Sdk/SafeNSoundClient.cs
@@ -4,24 +4,20 @@ using System.Text.Json.Serialization;
namespace SafeNSound.Sdk;
-public class SafeNSoundClient(SafeNSoundConfiguration config, string accessToken)
-{
- public WrappedHttpClient HttpClient { get; } = new()
- {
+public class SafeNSoundClient(SafeNSoundConfiguration config, string accessToken) {
+ public WrappedHttpClient HttpClient { get; } = new() {
BaseAddress = new Uri(config.BaseUri),
- DefaultRequestHeaders =
- {
+ DefaultRequestHeaders = {
Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken)
}
};
-
- public async Task<WhoAmI> WhoAmI()
- {
+
+ public async Task<WhoAmI> WhoAmI() {
var res = await HttpClient.GetAsync("/auth/whoami");
res.EnsureSuccessStatusCode();
return (await res.Content.ReadFromJsonAsync<WhoAmI>())!;
}
-
+
#region Alarm
public async Task<AlarmDto> GetAlarm(string userId = "@me") {
@@ -29,25 +25,21 @@ public class SafeNSoundClient(SafeNSoundConfiguration config, string accessToken
res.EnsureSuccessStatusCode();
return (await res.Content.ReadFromJsonAsync<AlarmDto>())!;
}
-
+
public async Task SetAlarm(AlarmDto alarm, string userId = "@me") {
var res = await HttpClient.PutAsJsonAsync("/alarm/@me", alarm);
res.EnsureSuccessStatusCode();
}
-
+
public async Task DeleteAlarm(string userId = "@me") {
var res = await HttpClient.DeleteAsync($"/alarm/{userId}");
res.EnsureSuccessStatusCode();
}
-
-
#endregion
#region Budget
-
-
#endregion
public async Task<Dictionary<string, AlarmDto>> GetAllAlarms() {
@@ -89,13 +81,48 @@ public class SafeNSoundClient(SafeNSoundConfiguration config, string accessToken
var res = await HttpClient.PostAsync("/admin/monitorAllUsers", null);
res.EnsureSuccessStatusCode();
}
-}
+ public async Task<List<DeviceDto>> GetDevices() {
+ var res = await HttpClient.GetAsync("/auth/devices");
+ res.EnsureSuccessStatusCode();
+ return (await res.Content.ReadFromJsonAsync<List<DeviceDto>>())!;
+ }
+
+ public async Task<DeviceDto> GetDevice(string deviceId) {
+ var res = await HttpClient.GetAsync($"/auth/devices/{deviceId}");
+ res.EnsureSuccessStatusCode();
+ return (await res.Content.ReadFromJsonAsync<DeviceDto>())!;
+ }
+
+ public async Task DeleteDevice(string deviceId) {
+ var res = await HttpClient.DeleteAsync($"/auth/devices/{deviceId}");
+ res.EnsureSuccessStatusCode();
+ }
+
+ public async Task UpdateDevice(string deviceId, DeviceDto device) {
+ var res = await HttpClient.PatchAsJsonAsync($"/auth/devices/{deviceId}", device);
+ res.EnsureSuccessStatusCode();
+ }
+}
public class AlarmDto {
[JsonPropertyName("reason")]
public required string Reason { get; set; }
-
+
[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; set; }
+}
+
+public class DeviceDto {
+ [JsonPropertyName("_id")]
+ public string? Id { get; set; }
+
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("createdAt")]
+ public DateTime CreatedAt { get; set; }
+
+ [JsonPropertyName("lastSeen")]
+ public DateTime LastSeen { get; set; }
}
\ No newline at end of file
|