Merge branch 'main' into Subscriptions
This commit is contained in:
@@ -21,7 +21,10 @@ public class ResourcesController : Controller
|
|||||||
{
|
{
|
||||||
if (name.Contains(".."))
|
if (name.Contains(".."))
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.PathTransversal, name);
|
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
|
||||||
|
{
|
||||||
|
x.Add<string>(name);
|
||||||
|
});
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
Moonlight/App/Models/Forms/NameModel.cs
Normal file
14
Moonlight/App/Models/Forms/NameModel.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
|
public class NameModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[MinLength(2, ErrorMessage = "Do you think, that works?")]
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MinLength(2, ErrorMessage = "Do you think, that works?")]
|
||||||
|
public string LastName { get; set; }
|
||||||
|
}
|
||||||
10
Moonlight/App/Models/Forms/PasswordModel.cs
Normal file
10
Moonlight/App/Models/Forms/PasswordModel.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
|
public class PasswordModel
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "You need to enter a password")]
|
||||||
|
[MinLength(8, ErrorMessage = "You need to enter a password with minimum 8 characters in lenght")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
7
Moonlight/App/Models/Log/LogData.cs
Normal file
7
Moonlight/App/Models/Log/LogData.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.App.Models.Log;
|
||||||
|
|
||||||
|
public class LogData
|
||||||
|
{
|
||||||
|
public Type Type { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
}
|
||||||
@@ -9,5 +9,6 @@ public enum UserStatus
|
|||||||
Warned,
|
Warned,
|
||||||
Banned,
|
Banned,
|
||||||
Disabled,
|
Disabled,
|
||||||
DataPending
|
DataPending,
|
||||||
|
PasswordPending
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,11 @@ public class DomainService
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.AddDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
|
await AuditLogService.Log(AuditLogType.AddDomainRecord, x =>
|
||||||
|
{
|
||||||
|
x.Add<Domain>(d.Id);
|
||||||
|
x.Add<DnsRecord>(dnsRecord.Name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
@@ -199,7 +203,11 @@ public class DomainService
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
|
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, x =>
|
||||||
|
{
|
||||||
|
x.Add<Domain>(d.Id);
|
||||||
|
x.Add<DnsRecord>(dnsRecord.Name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
@@ -210,7 +218,11 @@ public class DomainService
|
|||||||
await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id)
|
await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id)
|
||||||
);
|
);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DeleteDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
|
await AuditLogService.Log(AuditLogType.DeleteDomainRecord, x =>
|
||||||
|
{
|
||||||
|
x.Add<Domain>(d.Id);
|
||||||
|
x.Add<DnsRecord>(dnsRecord.Name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Domain EnsureData(Domain domain)
|
private Domain EnsureData(Domain domain)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
using Moonlight.App.Database.Entities.LogsEntries;
|
||||||
|
using Moonlight.App.Models.Log;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories.LogEntries;
|
using Moonlight.App.Repositories.LogEntries;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
@@ -19,16 +20,18 @@ public class AuditLogService
|
|||||||
HttpContextAccessor = httpContextAccessor;
|
HttpContextAccessor = httpContextAccessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Log(AuditLogType type, params object[] data)
|
public Task Log(AuditLogType type, Action<AuditLogParameters> data)
|
||||||
{
|
{
|
||||||
var ip = GetIp();
|
var ip = GetIp();
|
||||||
|
var al = new AuditLogParameters();
|
||||||
|
data(al);
|
||||||
|
|
||||||
var entry = new AuditLogEntry()
|
var entry = new AuditLogEntry()
|
||||||
{
|
{
|
||||||
Ip = ip,
|
Ip = ip,
|
||||||
Type = type,
|
Type = type,
|
||||||
System = false,
|
System = false,
|
||||||
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
|
JsonData = al.Build()
|
||||||
};
|
};
|
||||||
|
|
||||||
Repository.Add(entry);
|
Repository.Add(entry);
|
||||||
@@ -36,13 +39,16 @@ public class AuditLogService
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task LogSystem(AuditLogType type, params object[] data)
|
public Task LogSystem(AuditLogType type, Action<AuditLogParameters> data)
|
||||||
{
|
{
|
||||||
|
var al = new AuditLogParameters();
|
||||||
|
data(al);
|
||||||
|
|
||||||
var entry = new AuditLogEntry()
|
var entry = new AuditLogEntry()
|
||||||
{
|
{
|
||||||
Type = type,
|
Type = type,
|
||||||
System = true,
|
System = true,
|
||||||
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
|
JsonData = al.Build()
|
||||||
};
|
};
|
||||||
|
|
||||||
Repository.Add(entry);
|
Repository.Add(entry);
|
||||||
@@ -62,4 +68,23 @@ public class AuditLogService
|
|||||||
|
|
||||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AuditLogParameters
|
||||||
|
{
|
||||||
|
private List<LogData> Data = new List<LogData>();
|
||||||
|
|
||||||
|
public void Add<T>(object data)
|
||||||
|
{
|
||||||
|
Data.Add(new LogData()
|
||||||
|
{
|
||||||
|
Type = typeof(T),
|
||||||
|
Value = data.ToString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string Build()
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
using Moonlight.App.Database.Entities.LogsEntries;
|
||||||
|
using Moonlight.App.Models.Log;
|
||||||
using Moonlight.App.Repositories.LogEntries;
|
using Moonlight.App.Repositories.LogEntries;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -18,15 +19,17 @@ public class ErrorLogService
|
|||||||
HttpContextAccessor = httpContextAccessor;
|
HttpContextAccessor = httpContextAccessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Log(Exception exception, params object[] objects)
|
public Task Log(Exception exception, Action<ErrorLogParameters> data)
|
||||||
{
|
{
|
||||||
var ip = GetIp();
|
var ip = GetIp();
|
||||||
|
var al = new ErrorLogParameters();
|
||||||
|
data(al);
|
||||||
|
|
||||||
var entry = new ErrorLogEntry()
|
var entry = new ErrorLogEntry()
|
||||||
{
|
{
|
||||||
Ip = ip,
|
Ip = ip,
|
||||||
System = false,
|
System = false,
|
||||||
JsonData = !objects.Any() ? "" : JsonConvert.SerializeObject(objects),
|
JsonData = al.Build(),
|
||||||
Class = NameOfCallingClass(),
|
Class = NameOfCallingClass(),
|
||||||
Stacktrace = exception.ToStringDemystified()
|
Stacktrace = exception.ToStringDemystified()
|
||||||
};
|
};
|
||||||
@@ -36,12 +39,15 @@ public class ErrorLogService
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task LogSystem(Exception exception, params object[] objects)
|
public Task LogSystem(Exception exception, Action<ErrorLogParameters> data)
|
||||||
{
|
{
|
||||||
|
var al = new ErrorLogParameters();
|
||||||
|
data(al);
|
||||||
|
|
||||||
var entry = new ErrorLogEntry()
|
var entry = new ErrorLogEntry()
|
||||||
{
|
{
|
||||||
System = true,
|
System = true,
|
||||||
JsonData = !objects.Any() ? "" : JsonConvert.SerializeObject(objects),
|
JsonData = al.Build(),
|
||||||
Class = NameOfCallingClass(),
|
Class = NameOfCallingClass(),
|
||||||
Stacktrace = exception.ToStringDemystified()
|
Stacktrace = exception.ToStringDemystified()
|
||||||
};
|
};
|
||||||
@@ -87,4 +93,23 @@ public class ErrorLogService
|
|||||||
|
|
||||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ErrorLogParameters
|
||||||
|
{
|
||||||
|
private List<LogData> Data = new List<LogData>();
|
||||||
|
|
||||||
|
public void Add<T>(object data)
|
||||||
|
{
|
||||||
|
Data.Add(new LogData()
|
||||||
|
{
|
||||||
|
Type = typeof(T),
|
||||||
|
Value = data.ToString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string Build()
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
using Moonlight.App.Database.Entities.LogsEntries;
|
||||||
|
using Moonlight.App.Models.Log;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories.LogEntries;
|
using Moonlight.App.Repositories.LogEntries;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
@@ -17,16 +18,18 @@ public class SecurityLogService
|
|||||||
HttpContextAccessor = httpContextAccessor;
|
HttpContextAccessor = httpContextAccessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Log(SecurityLogType type, params object[] data)
|
public Task Log(SecurityLogType type, Action<SecurityLogParameters> data)
|
||||||
{
|
{
|
||||||
var ip = GetIp();
|
var ip = GetIp();
|
||||||
|
var al = new SecurityLogParameters();
|
||||||
|
data(al);
|
||||||
|
|
||||||
var entry = new SecurityLogEntry()
|
var entry = new SecurityLogEntry()
|
||||||
{
|
{
|
||||||
Ip = ip,
|
Ip = ip,
|
||||||
Type = type,
|
Type = type,
|
||||||
System = false,
|
System = false,
|
||||||
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
|
JsonData = al.Build()
|
||||||
};
|
};
|
||||||
|
|
||||||
Repository.Add(entry);
|
Repository.Add(entry);
|
||||||
@@ -34,13 +37,16 @@ public class SecurityLogService
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task LogSystem(SecurityLogType type, params object[] data)
|
public Task LogSystem(SecurityLogType type, Action<SecurityLogParameters> data)
|
||||||
{
|
{
|
||||||
|
var al = new SecurityLogParameters();
|
||||||
|
data(al);
|
||||||
|
|
||||||
var entry = new SecurityLogEntry()
|
var entry = new SecurityLogEntry()
|
||||||
{
|
{
|
||||||
Type = type,
|
Type = type,
|
||||||
System = true,
|
System = true,
|
||||||
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
|
JsonData = al.Build()
|
||||||
};
|
};
|
||||||
|
|
||||||
Repository.Add(entry);
|
Repository.Add(entry);
|
||||||
@@ -60,4 +66,24 @@ public class SecurityLogService
|
|||||||
|
|
||||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class SecurityLogParameters
|
||||||
|
{
|
||||||
|
private List<LogData> Data = new List<LogData>();
|
||||||
|
|
||||||
|
public void Add<T>(object data)
|
||||||
|
{
|
||||||
|
Data.Add(new LogData()
|
||||||
|
{
|
||||||
|
Type = typeof(T),
|
||||||
|
Value = data.ToString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string Build()
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,10 @@ public class OneTimeJwtService
|
|||||||
}
|
}
|
||||||
catch (SignatureVerificationException)
|
catch (SignatureVerificationException)
|
||||||
{
|
{
|
||||||
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, token);
|
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, x =>
|
||||||
|
{
|
||||||
|
x.Add<string>(token);
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@@ -96,7 +96,11 @@ public class ServerService
|
|||||||
Action = rawSignal
|
Action = rawSignal
|
||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.ChangePowerState, new[] { server.Uuid.ToString(), rawSignal });
|
await AuditLogService.Log(AuditLogType.ChangePowerState, x =>
|
||||||
|
{
|
||||||
|
x.Add<Server>(server.Uuid);
|
||||||
|
x.Add<PowerSignal>(rawSignal);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerBackup> CreateBackup(Server server)
|
public async Task<ServerBackup> CreateBackup(Server server)
|
||||||
@@ -126,7 +130,11 @@ public class ServerService
|
|||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.CreateBackup,
|
await AuditLogService.Log(AuditLogType.CreateBackup,
|
||||||
new[] { serverData.Uuid.ToString(), backup.Uuid.ToString() });
|
x =>
|
||||||
|
{
|
||||||
|
x.Add<Server>(server.Uuid);
|
||||||
|
x.Add<ServerBackup>(backup.Uuid);
|
||||||
|
});
|
||||||
|
|
||||||
return backup;
|
return backup;
|
||||||
}
|
}
|
||||||
@@ -164,7 +172,11 @@ public class ServerService
|
|||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.RestoreBackup,
|
await AuditLogService.Log(AuditLogType.RestoreBackup,
|
||||||
new[] { s.Uuid.ToString(), serverBackup.Uuid.ToString() });
|
x =>
|
||||||
|
{
|
||||||
|
x.Add<Server>(server.Uuid);
|
||||||
|
x.Add<ServerBackup>(serverBackup.Uuid);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
|
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
|
||||||
@@ -186,7 +198,11 @@ public class ServerService
|
|||||||
await MessageService.Emit("wings.backups.delete", backup);
|
await MessageService.Emit("wings.backups.delete", backup);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DeleteBackup,
|
await AuditLogService.Log(AuditLogType.DeleteBackup,
|
||||||
new[] { serverBackup.Uuid.ToString(), serverBackup.Uuid.ToString() });
|
x =>
|
||||||
|
{
|
||||||
|
x.Add<Server>(server.Uuid);
|
||||||
|
x.Add<ServerBackup>(backup.Uuid);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
|
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
|
||||||
@@ -200,7 +216,11 @@ public class ServerService
|
|||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DownloadBackup,
|
await AuditLogService.Log(AuditLogType.DownloadBackup,
|
||||||
new[] { serverBackup.Uuid.ToString(), serverBackup.Uuid.ToString() });
|
x =>
|
||||||
|
{
|
||||||
|
x.Add<Server>(server.Uuid);
|
||||||
|
x.Add<ServerBackup>(serverBackup.Uuid);
|
||||||
|
});
|
||||||
|
|
||||||
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
|
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
|
||||||
}
|
}
|
||||||
@@ -305,13 +325,20 @@ public class ServerService
|
|||||||
StartOnCompletion = false
|
StartOnCompletion = false
|
||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.CreateServer, newServerData.Uuid.ToString());
|
await AuditLogService.Log(AuditLogType.CreateServer, x =>
|
||||||
|
{
|
||||||
|
x.Add<Server>(newServerData.Uuid);
|
||||||
|
});
|
||||||
|
|
||||||
return newServerData;
|
return newServerData;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await ErrorLogService.Log(e, new[] { newServerData.Uuid.ToString(), node.Id.ToString() });
|
await ErrorLogService.Log(e, x =>
|
||||||
|
{
|
||||||
|
x.Add<Server>(newServerData.Uuid);
|
||||||
|
x.Add<Node>(node.Id);
|
||||||
|
});
|
||||||
|
|
||||||
ServerRepository.Delete(newServerData);
|
ServerRepository.Delete(newServerData);
|
||||||
|
|
||||||
@@ -325,7 +352,10 @@ public class ServerService
|
|||||||
|
|
||||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/reinstall", null);
|
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/reinstall", null);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.ReinstallServer, server.Uuid.ToString());
|
await AuditLogService.Log(AuditLogType.ReinstallServer, x =>
|
||||||
|
{
|
||||||
|
x.Add<Server>(server.Uuid);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
|
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
|
||||||
@@ -334,7 +364,10 @@ public class ServerService
|
|||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
{
|
{
|
||||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, serverId);
|
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
|
||||||
|
{
|
||||||
|
x.Add<int>(id);
|
||||||
|
});
|
||||||
throw new Exception("Server not found");
|
throw new Exception("Server not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,15 @@ public class IdentityService
|
|||||||
}
|
}
|
||||||
catch (SignatureVerificationException)
|
catch (SignatureVerificationException)
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, token);
|
await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, x =>
|
||||||
|
{
|
||||||
|
x.Add<string>(token);
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await ErrorLogService.Log(e);
|
await ErrorLogService.Log(e, x => {});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +133,7 @@ public class IdentityService
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await ErrorLogService.Log(e);
|
await ErrorLogService.Log(e, x => {});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.LogServices;
|
using Moonlight.App.Services.LogServices;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
@@ -51,7 +52,10 @@ public class TotpService
|
|||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.EnableTotp, user.Email);
|
await AuditLogService.Log(AuditLogType.EnableTotp, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(user.Email);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnforceTotpLogin()
|
public async Task EnforceTotpLogin()
|
||||||
@@ -70,7 +74,10 @@ public class TotpService
|
|||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DisableTotp, user.Email);
|
await AuditLogService.Log(AuditLogType.DisableTotp,x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(user.Email);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateSecret()
|
private string GenerateSecret()
|
||||||
|
|||||||
@@ -77,7 +77,10 @@ public class UserService
|
|||||||
});
|
});
|
||||||
|
|
||||||
await MailService.SendMail(user!, "register", values => {});
|
await MailService.SendMail(user!, "register", values => {});
|
||||||
await AuditLogService.Log(AuditLogType.Register, user.Email);
|
await AuditLogService.Log(AuditLogType.Register, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(user.Email);
|
||||||
|
});
|
||||||
|
|
||||||
return await GenerateToken(user);
|
return await GenerateToken(user);
|
||||||
}
|
}
|
||||||
@@ -91,7 +94,11 @@ public class UserService
|
|||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
|
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(email);
|
||||||
|
x.Add<string>(password);
|
||||||
|
});
|
||||||
throw new DisplayException("Email and password combination not found");
|
throw new DisplayException("Email and password combination not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +107,11 @@ public class UserService
|
|||||||
return user.TotpEnabled;
|
return user.TotpEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
|
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(email);
|
||||||
|
x.Add<string>(password);
|
||||||
|
});
|
||||||
throw new DisplayException("Email and password combination not found");;
|
throw new DisplayException("Email and password combination not found");;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,18 +136,28 @@ public class UserService
|
|||||||
|
|
||||||
if (totpCodeValid)
|
if (totpCodeValid)
|
||||||
{
|
{
|
||||||
await AuditLogService.Log(AuditLogType.Login, email);
|
await AuditLogService.Log(AuditLogType.Login, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(email);
|
||||||
|
});
|
||||||
return await GenerateToken(user, true);
|
return await GenerateToken(user, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
|
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(email);
|
||||||
|
x.Add<string>(password);
|
||||||
|
});
|
||||||
throw new DisplayException("2FA code invalid");
|
throw new DisplayException("2FA code invalid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await AuditLogService.Log(AuditLogType.Login, email);
|
await AuditLogService.Log(AuditLogType.Login, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(email);
|
||||||
|
});
|
||||||
return await GenerateToken(user!, true);
|
return await GenerateToken(user!, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +170,10 @@ public class UserService
|
|||||||
|
|
||||||
if (isSystemAction)
|
if (isSystemAction)
|
||||||
{
|
{
|
||||||
await AuditLogService.LogSystem(AuditLogType.ChangePassword, user.Email);
|
await AuditLogService.LogSystem(AuditLogType.ChangePassword, x=>
|
||||||
|
{
|
||||||
|
x.Add<User>(user.Email);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -160,7 +184,10 @@ public class UserService
|
|||||||
values.Add("Location", "In your walls");
|
values.Add("Location", "In your walls");
|
||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.ChangePassword, user.Email);
|
await AuditLogService.Log(AuditLogType.ChangePassword, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(user.Email);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,17 +197,27 @@ public class UserService
|
|||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, id);
|
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
|
||||||
|
{
|
||||||
|
x.Add<int>(id);
|
||||||
|
});
|
||||||
throw new Exception("Invalid username");
|
throw new Exception("Invalid username");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
|
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
|
||||||
{
|
{
|
||||||
await AuditLogService.LogSystem(AuditLogType.Login, user.Email);
|
await AuditLogService.LogSystem(AuditLogType.Login, x =>
|
||||||
|
{
|
||||||
|
x.Add<User>(user.Email);
|
||||||
|
});
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, new[] { id.ToString(), password });
|
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
|
||||||
|
{
|
||||||
|
x.Add<int>(id);
|
||||||
|
x.Add<string>(password);
|
||||||
|
});
|
||||||
throw new Exception("Invalid userid or password");
|
throw new Exception("Invalid userid or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +255,7 @@ public class UserService
|
|||||||
var newPassword = StringHelper.GenerateString(16);
|
var newPassword = StringHelper.GenerateString(16);
|
||||||
await ChangePassword(user, newPassword, true);
|
await ChangePassword(user, newPassword, true);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.PasswordReset);
|
await AuditLogService.Log(AuditLogType.PasswordReset, x => {});
|
||||||
|
|
||||||
await MailService.SendMail(user, "passwordReset", values =>
|
await MailService.SendMail(user, "passwordReset", values =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Log
|
||||||
|
|
||||||
@inject UserRepository UserRepository
|
@inject UserRepository UserRepository
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<div class="fs-5 fw-semibold mb-2">
|
<div class="fs-5 fw-semibold mb-2">
|
||||||
@if (User == null)
|
@if (User == null)
|
||||||
{
|
{
|
||||||
<TL>Password change for</TL> @(Data[0])
|
<TL>Password change for</TL> @(Data[0].Value)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -38,18 +39,18 @@
|
|||||||
public AuditLogEntry Entry { get; set; }
|
public AuditLogEntry Entry { get; set; }
|
||||||
|
|
||||||
private User? User;
|
private User? User;
|
||||||
private string[] Data;
|
private LogData[] Data;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
|
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
|
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Log
|
||||||
@using Moonlight.App.Repositories.Servers
|
@using Moonlight.App.Repositories.Servers
|
||||||
|
|
||||||
@inject ServerRepository ServerRepository
|
@inject ServerRepository ServerRepository
|
||||||
@@ -18,11 +19,11 @@
|
|||||||
<div class="fs-5 fw-semibold mb-2">
|
<div class="fs-5 fw-semibold mb-2">
|
||||||
@if (Server == null)
|
@if (Server == null)
|
||||||
{
|
{
|
||||||
<TL>Change power state for</TL> @(Data[0]) <TL>to</TL> @(Data[1])
|
<TL>Change power state for</TL> @(Data[0].Value) <TL>to</TL> @(Data[1].Value)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<TL>Change power state for</TL> <a href="/admin/servers/edit/@(Server.Id)">@(Server.Name)</a> <TL>to</TL> @(Data[1])
|
<TL>Change power state for</TL> <a href="/admin/servers/edit/@(Server.Id)">@(Server.Name)</a> <TL>to</TL> @(Data[1].Value)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center mt-1 fs-6">
|
<div class="d-flex align-items-center mt-1 fs-6">
|
||||||
@@ -38,18 +39,18 @@
|
|||||||
public AuditLogEntry Entry { get; set; }
|
public AuditLogEntry Entry { get; set; }
|
||||||
|
|
||||||
private Server? Server;
|
private Server? Server;
|
||||||
private string[] Data;
|
private LogData[] Data;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
|
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
Server = ServerRepository.Get().FirstOrDefault(x => x.Uuid == Guid.Parse(Data[0]));
|
Server = ServerRepository.Get().FirstOrDefault(x => x.Uuid == Guid.Parse(Data[0].Value));
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Log
|
||||||
|
|
||||||
@inject UserRepository UserRepository
|
@inject UserRepository UserRepository
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<div class="fs-5 fw-semibold mb-2">
|
<div class="fs-5 fw-semibold mb-2">
|
||||||
@if (User == null)
|
@if (User == null)
|
||||||
{
|
{
|
||||||
<TL>New login for</TL> @(Data[0])
|
<TL>New login for</TL> @(Data[0].Value)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -38,18 +39,18 @@
|
|||||||
public AuditLogEntry Entry { get; set; }
|
public AuditLogEntry Entry { get; set; }
|
||||||
|
|
||||||
private User? User;
|
private User? User;
|
||||||
private string[] Data;
|
private LogData[] Data;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
|
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
|
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Log
|
||||||
|
|
||||||
@inject UserRepository UserRepository
|
@inject UserRepository UserRepository
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<div class="fs-5 fw-semibold mb-2">
|
<div class="fs-5 fw-semibold mb-2">
|
||||||
@if (User == null)
|
@if (User == null)
|
||||||
{
|
{
|
||||||
<TL>Register for</TL> @(Data[0])
|
<TL>Register for</TL> @(Data[0].Value)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -38,18 +39,18 @@
|
|||||||
public AuditLogEntry Entry { get; set; }
|
public AuditLogEntry Entry { get; set; }
|
||||||
|
|
||||||
private User? User;
|
private User? User;
|
||||||
private string[] Data;
|
private LogData[] Data;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
|
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
|
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|||||||
64
Moonlight/Shared/Components/Auth/PasswordChangeView.razor
Normal file
64
Moonlight/Shared/Components/Auth/PasswordChangeView.razor
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Models.Forms
|
||||||
|
@using Moonlight.App.Services.Sessions
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Misc
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject IdentityService IdentityService
|
||||||
|
@inject UserService UserService
|
||||||
|
@inject UserRepository UserRepository
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<div class="d-flex flex-center">
|
||||||
|
<div class="card rounded-3 w-md-550px">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-center flex-column-fluid">
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
<SmartForm Model="Password" OnValidSubmit="DoChange">
|
||||||
|
<div class="text-center mt-3 mb-11">
|
||||||
|
<h1 class="text-dark fw-bolder mb-3">
|
||||||
|
<TL>Change your password</TL>
|
||||||
|
</h1>
|
||||||
|
<div class="text-gray-500 fw-semibold fs-6">
|
||||||
|
<TL>You need to change your password in order to use moonlight</TL>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3 mb-9">
|
||||||
|
<div class="col-md-9">
|
||||||
|
<InputText @bind-Value="Password.Password" type="password" placeholder="@(SmartTranslateService.Translate("New password"))" class="form-control bg-transparent"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<button type="submit" class="btn btn-primary float-end">
|
||||||
|
<TL>Change</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</SmartForm>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private PasswordModel Password = new();
|
||||||
|
private User User;
|
||||||
|
|
||||||
|
private async Task Load(LazyLoader loader)
|
||||||
|
{
|
||||||
|
User = await IdentityService.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DoChange()
|
||||||
|
{
|
||||||
|
await UserService.ChangePassword(User, Password.Password);
|
||||||
|
User.Status = UserStatus.Unverified;
|
||||||
|
UserRepository.Update(User);
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Moonlight/Shared/Components/Auth/UserDataSetView.razor
Normal file
67
Moonlight/Shared/Components/Auth/UserDataSetView.razor
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
@using Microsoft.AspNetCore.Components
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Forms
|
||||||
|
@using Moonlight.App.Models.Misc
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Sessions
|
||||||
|
|
||||||
|
@inject IdentityService IdentityService
|
||||||
|
@inject UserRepository UserRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<div class="d-flex flex-center">
|
||||||
|
<div class="card rounded-3 w-md-550px">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-center flex-column-fluid">
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
<SmartForm Model="Name" OnValidSubmit="SetName">
|
||||||
|
<div class="text-center mt-3 mb-11">
|
||||||
|
<h1 class="text-dark fw-bolder mb-3">
|
||||||
|
<TL>Enter your information</TL>
|
||||||
|
</h1>
|
||||||
|
<div class="text-gray-500 fw-semibold fs-6">
|
||||||
|
<TL>You need to enter your full name in order to use moonlight</TL>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col">
|
||||||
|
<InputText @bind-Value="Name.FirstName" type="text" placeholder="@(SmartTranslateService.Translate("First name"))" class="form-control bg-transparent"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<InputText @bind-Value="Name.LastName" type="text" placeholder="@(SmartTranslateService.Translate("Last name"))" class="form-control bg-transparent"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary float-end mt-3">
|
||||||
|
<TL>Change</TL>
|
||||||
|
</button>
|
||||||
|
</SmartForm>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private User User;
|
||||||
|
private NameModel Name = new ();
|
||||||
|
|
||||||
|
private async Task Load(LazyLoader loader)
|
||||||
|
{
|
||||||
|
User = await IdentityService.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetName()
|
||||||
|
{
|
||||||
|
User.FirstName = Name.FirstName;
|
||||||
|
User.LastName = Name.LastName;
|
||||||
|
User.Status = UserStatus.Unverified;
|
||||||
|
|
||||||
|
UserRepository.Update(User);
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ else
|
|||||||
{
|
{
|
||||||
receivedExceptions.Add(exception);
|
receivedExceptions.Add(exception);
|
||||||
|
|
||||||
await ErrorLogService.Log(exception);
|
await ErrorLogService.Log(exception, x => {});
|
||||||
|
|
||||||
await base.OnErrorAsync(exception);
|
await base.OnErrorAsync(exception);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,14 @@
|
|||||||
{
|
{
|
||||||
<DisabledAlert></DisabledAlert>
|
<DisabledAlert></DisabledAlert>
|
||||||
}
|
}
|
||||||
|
else if (User.Status == UserStatus.PasswordPending)
|
||||||
|
{
|
||||||
|
<PasswordChangeView></PasswordChangeView>
|
||||||
|
}
|
||||||
|
else if (User.Status == UserStatus.DataPending)
|
||||||
|
{
|
||||||
|
<UserDataSetView></UserDataSetView>
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@Body
|
@Body
|
||||||
|
|||||||
@@ -14,45 +14,53 @@
|
|||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex flex-column flex-xl-row p-7">
|
<div class="d-flex flex-column flex-xl-row p-5 pb-0">
|
||||||
<div class="flex-lg-row-fluid me-xl-15 mb-20 mb-xl-0">
|
<div class="flex-lg-row-fluid me-xl-15 mb-20 mb-xl-0">
|
||||||
<div class="mb-0">
|
<div class="mb-0">
|
||||||
<h1 class="text-dark mb-10">
|
<h1 class="text-dark mb-6">
|
||||||
<TL>Open tickets</TL>
|
<TL>Open tickets</TL>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="mb-10">
|
<div class="separator"></div>
|
||||||
|
<div class="mb-5">
|
||||||
@if (Users.Any())
|
@if (Users.Any())
|
||||||
{
|
{
|
||||||
foreach (var user in Users)
|
foreach (var user in Users)
|
||||||
{
|
{
|
||||||
<div class="d-flex mb-10">
|
<div class="d-flex mt-3 mb-3 ms-2 me-2">
|
||||||
<span class="svg-icon svg-icon-2x me-5 ms-n1 mt-2 svg-icon-success">
|
<table>
|
||||||
<i class="text-primary bx bx-md bx-message-dots"></i>
|
<tr>
|
||||||
</span>
|
<td rowspan="2">
|
||||||
|
<span class="svg-icon svg-icon-2x me-5 ms-n1 svg-icon-success">
|
||||||
<div class="d-flex flex-column">
|
<i class="text-primary bx bx-md bx-message-dots"></i>
|
||||||
<div class="d-flex align-items-center mb-2">
|
</span>
|
||||||
<a href="/admin/support/view/@(user.Id)" class="text-dark text-hover-primary fs-4 me-3 fw-semibold">
|
</td>
|
||||||
@(user.FirstName) @(user.LastName)
|
<td>
|
||||||
</a>
|
<a href="/admin/support/view/@(user.Id)" class="text-dark text-hover-primary fs-4 me-3 fw-semibold">
|
||||||
</div>
|
@(user.FirstName) @(user.LastName)
|
||||||
|
</a>
|
||||||
<span class="text-muted fw-semibold fs-6">
|
</td>
|
||||||
@{
|
</tr>
|
||||||
var lastMessage = MessageCache.ContainsKey(user) ? MessageCache[user] : null;
|
<tr>
|
||||||
}
|
<td>
|
||||||
|
<span class="text-muted fw-semibold fs-6">
|
||||||
@if (lastMessage == null)
|
@{
|
||||||
{
|
var lastMessage = MessageCache.ContainsKey(user) ? MessageCache[user] : null;
|
||||||
<TL>No message sent yet</TL>
|
}
|
||||||
}
|
|
||||||
else
|
@if (lastMessage == null)
|
||||||
{
|
{
|
||||||
@(lastMessage.Message)
|
<TL>No message sent yet</TL>
|
||||||
}
|
}
|
||||||
</span>
|
else
|
||||||
</div>
|
{
|
||||||
|
@(lastMessage.Message)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="separator"></div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -64,39 +72,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-column flex-lg-row-auto w-100 mw-lg-300px mw-xxl-350px">
|
|
||||||
|
|
||||||
<div class="card-rounded bg-primary bg-opacity-5 p-10 mb-15">
|
|
||||||
<h2 class="text-dark fw-bold mb-11">
|
|
||||||
<TL>Actions</TL>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-10">
|
|
||||||
<!--begin::Icon-->
|
|
||||||
<i class="bi bi-file-earmark-text text-primary fs-1 me-5"></i>
|
|
||||||
<!--end::SymIconbol-->
|
|
||||||
|
|
||||||
<!--begin::Info-->
|
|
||||||
<div class="d-flex flex-column">
|
|
||||||
<h5 class="text-gray-800 fw-bold">Project Briefing</h5>
|
|
||||||
|
|
||||||
<!--begin::Section-->
|
|
||||||
<div class="fw-semibold">
|
|
||||||
<!--begin::Desc-->
|
|
||||||
<span class="text-muted">Check out our</span>
|
|
||||||
<!--end::Desc-->
|
|
||||||
|
|
||||||
<!--begin::Link-->
|
|
||||||
<a href="#" class="link-primary">Support Policy</a>
|
|
||||||
<!--end::Link-->
|
|
||||||
</div>
|
|
||||||
<!--end::Section-->
|
|
||||||
</div>
|
|
||||||
<!--end::Info-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,11 +22,11 @@
|
|||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex flex-column flex-xl-row p-7">
|
<div class="d-flex flex-column flex-xl-row p-7">
|
||||||
<div class="flex-lg-row-fluid me-xl-15 mb-20 mb-xl-0">
|
<div class="flex-lg-row-fluid me-6 mb-20 mb-xl-0">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<LazyLoader Load="LoadMessages">
|
<LazyLoader Load="LoadMessages">
|
||||||
<div class="scroll-y me-n5 pe-5" style="max-height: 65vh; display: flex; flex-direction: column-reverse;">
|
<div class="scroll-y me-n5 pe-5" style="max-height: 55vh; display: flex; flex-direction: column-reverse;">
|
||||||
@foreach (var message in Messages)
|
@foreach (var message in Messages)
|
||||||
{
|
{
|
||||||
if (message.IsSystem || message.IsSupport)
|
if (message.IsSystem || message.IsSupport)
|
||||||
@@ -99,10 +99,10 @@
|
|||||||
@if (typingUsers.Any())
|
@if (typingUsers.Any())
|
||||||
{
|
{
|
||||||
<span class="mb-5 fs-5 d-flex flex-row">
|
<span class="mb-5 fs-5 d-flex flex-row">
|
||||||
<div class="wave me-3">
|
<div class="wave me-1">
|
||||||
<div class="dot"></div>
|
<div class="dot h-5px w-5px" style="margin-right: 1px;"></div>
|
||||||
<div class="dot"></div>
|
<div class="dot h-5px w-5px" style="margin-right: 1px;"></div>
|
||||||
<div class="dot"></div>
|
<div class="dot h-5px w-5px"></div>
|
||||||
</div>
|
</div>
|
||||||
@if (typingUsers.Length > 1)
|
@if (typingUsers.Length > 1)
|
||||||
{
|
{
|
||||||
@@ -118,52 +118,56 @@
|
|||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
<textarea @bind="Content" @oninput="OnTyping" class="form-control mb-3" rows="1" placeholder="Type a message">
|
|
||||||
</textarea>
|
|
||||||
<div class="d-flex flex-stack">
|
<div class="d-flex flex-stack">
|
||||||
<div class="d-flex align-items-center me-2">
|
<table class="w-100">
|
||||||
<button class="btn btn-sm btn-icon btn-active-light-primary me-1" type="button">
|
<tr>
|
||||||
<i class="bx bx-upload fs-3"></i>
|
<!--<td class="align-top">
|
||||||
</button>
|
<button class="btn btn-sm btn-icon btn-active-light-primary me-1" type="button">
|
||||||
</div>
|
<i class="bx bx-upload fs-3"></i>
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Send"))"
|
</button>
|
||||||
WorkingText="@(SmartTranslateService.Translate("Sending"))"
|
</td>-->
|
||||||
CssClasses="btn-primary"
|
<td class="w-100">
|
||||||
OnClick="Send">
|
<textarea @bind="Content" @oninput="OnTyping" class="form-control mb-3 form-control-flush" rows="1" placeholder="Type a message">
|
||||||
</WButton>
|
</textarea>
|
||||||
|
</td>
|
||||||
|
<td class="align-top">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Send"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Sending"))"
|
||||||
|
CssClasses="btn-primary ms-2"
|
||||||
|
OnClick="Send">
|
||||||
|
</WButton>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-column flex-lg-row-auto w-100 mw-lg-300px mw-xxl-350px">
|
<div class="flex-column flex-lg-row-auto w-100 mw-lg-300px mw-xxl-350px">
|
||||||
<div class="card p-10 mb-15">
|
<div class="card p-10 mb-15 pb-8">
|
||||||
<h2 class="text-dark fw-bold mb-11">
|
<h2 class="text-dark fw-bold mb-2">
|
||||||
<TL>User information</TL>
|
<TL>User information</TL>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-6">
|
<div class="d-flex align-items-center mb-1">
|
||||||
<spna class="fw-semibold text-gray-800 fs-5 m-0">
|
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||||
<TL>Firstname</TL>: @(User.FirstName)
|
<TL>Name</TL>: @(User.FirstName) @User.LastName
|
||||||
</spna>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center mb-6">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<spna class="fw-semibold text-gray-800 fs-5 m-0">
|
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||||
<TL>Lastname</TL>: @(User.LastName)
|
<TL>Email</TL>: <a href="/admin/users/view/@User.Id">@(User.Email)</a>
|
||||||
</spna>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center mb-6">
|
<div class="align-items-center mt-3">
|
||||||
<spna class="fw-semibold text-gray-800 fs-5 m-0">
|
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||||
<TL>Email</TL>: @(User.Email)
|
|
||||||
</spna>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center mb-6">
|
|
||||||
<spna class="fw-semibold text-gray-800 fs-5 m-0">
|
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Close ticket"))"
|
<WButton Text="@(SmartTranslateService.Translate("Close ticket"))"
|
||||||
WorkingText="@(SmartTranslateService.Translate("Closing"))"
|
WorkingText="@(SmartTranslateService.Translate("Closing"))"
|
||||||
CssClasses="btn-danger"
|
CssClasses="btn-danger float-end"
|
||||||
OnClick="CloseTicket">
|
OnClick="CloseTicket">
|
||||||
</WButton>
|
</WButton>
|
||||||
</spna>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
<div class="card mb-5 mb-xl-10">
|
<div class="card mb-5 mb-xl-10">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h3 class="fw-bold m-0 text-gray-800">Persönliche Daten</h3>
|
<h3 class="fw-bold m-0 text-gray-800">
|
||||||
|
<TL>Personal information</TL>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body border-top p-9">
|
<div class="card-body border-top p-9">
|
||||||
|
|||||||
@@ -236,7 +236,7 @@
|
|||||||
|
|
||||||
private async void Enable()
|
private async void Enable()
|
||||||
{
|
{
|
||||||
await AuditLogService.Log(AuditLogType.EnableTotp, "Totp enabled");
|
await AuditLogService.Log(AuditLogType.EnableTotp, x => x.Add<string>("Totp enabled"));
|
||||||
await TotpService.Enable();
|
await TotpService.Enable();
|
||||||
TotpEnabled = await TotpService.GetEnabled();
|
TotpEnabled = await TotpService.GetEnabled();
|
||||||
TotpSecret = await TotpService.GetSecret();
|
TotpSecret = await TotpService.GetSecret();
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
|
|
||||||
private async void Disable()
|
private async void Disable()
|
||||||
{
|
{
|
||||||
await AuditLogService.Log(AuditLogType.DisableTotp, "Totp disabled");
|
await AuditLogService.Log(AuditLogType.DisableTotp, x => x.Add<string>("Totp disabled"));
|
||||||
await TotpService.Disable();
|
await TotpService.Disable();
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@
|
|||||||
{
|
{
|
||||||
await UserService.ChangePassword(User, Password);
|
await UserService.ChangePassword(User, Password);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.PasswordChange, "The password has been set to a new one");
|
await AuditLogService.Log(AuditLogType.PasswordChange, x => x.Add<string>("The password has been set to a new one"));
|
||||||
|
|
||||||
// Reload to make the user login again
|
// Reload to make the user login again
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<LazyLoader Load="LoadMessages">
|
<LazyLoader Load="LoadMessages">
|
||||||
<div class="scroll-y me-n5 pe-5" style="max-height: 65vh; display: flex; flex-direction: column-reverse;">
|
<div class="scroll-y me-n5 pe-5" style="max-height: 55vh; display: flex; flex-direction: column-reverse;">
|
||||||
@foreach (var message in Messages)
|
@foreach (var message in Messages)
|
||||||
{
|
{
|
||||||
if (message.IsSystem || message.IsSupport)
|
if (message.IsSystem || message.IsSupport)
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<div class="d-flex justify-content-start mb-10 ">
|
<div class="d-flex justify-content-start mb-10 ">
|
||||||
<div class="d-flex flex-column align-items-start">
|
<div class="d-flex flex-column align-items-start">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<div class="symbol symbol-35px symbol-circle ">
|
<div class="symbol symbol-35px symbol-circle ">
|
||||||
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))">
|
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))">
|
||||||
</div>
|
</div>
|
||||||
<div class="ms-3">
|
<div class="ms-3">
|
||||||
@@ -104,10 +104,10 @@
|
|||||||
@if (typingUsers.Any())
|
@if (typingUsers.Any())
|
||||||
{
|
{
|
||||||
<span class="mb-5 fs-5 d-flex flex-row">
|
<span class="mb-5 fs-5 d-flex flex-row">
|
||||||
<div class="wave me-3">
|
<div class="wave me-1">
|
||||||
<div class="dot"></div>
|
<div class="dot h-5px w-5px" style="margin-right: 1px;"></div>
|
||||||
<div class="dot"></div>
|
<div class="dot h-5px w-5px" style="margin-right: 1px;"></div>
|
||||||
<div class="dot"></div>
|
<div class="dot h-5px w-5px"></div>
|
||||||
</div>
|
</div>
|
||||||
@if (typingUsers.Length > 1)
|
@if (typingUsers.Length > 1)
|
||||||
{
|
{
|
||||||
@@ -120,19 +120,27 @@
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
<textarea @bind="Content" @oninput="OnTyping" class="form-control mb-3" rows="1" placeholder="Type a message">
|
|
||||||
</textarea>
|
|
||||||
<div class="d-flex flex-stack">
|
<div class="d-flex flex-stack">
|
||||||
<div class="d-flex align-items-center me-2">
|
<table class="w-100">
|
||||||
<button class="btn btn-sm btn-icon btn-active-light-primary me-1" type="button">
|
<tr>
|
||||||
<i class="bx bx-upload fs-3"></i>
|
<!--<td class="align-top">
|
||||||
</button>
|
<button class="btn btn-sm btn-icon btn-active-light-primary me-1" type="button">
|
||||||
</div>
|
<i class="bx bx-upload fs-3"></i>
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Send"))"
|
</button>
|
||||||
WorkingText="@(SmartTranslateService.Translate("Sending"))"
|
</td>-->
|
||||||
CssClasses="btn-primary"
|
<td class="w-100">
|
||||||
OnClick="Send">
|
<textarea @bind="Content" @oninput="OnTyping" class="form-control mb-3 form-control-flush" rows="1" placeholder="Type a message">
|
||||||
</WButton>
|
</textarea>
|
||||||
|
</td>
|
||||||
|
<td class="align-top">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Send"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Sending"))"
|
||||||
|
CssClasses="btn-primary ms-2"
|
||||||
|
OnClick="Send">
|
||||||
|
</WButton>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -461,33 +461,4 @@ Add new limit;Add new limit
|
|||||||
Create subscription;Create subscription
|
Create subscription;Create subscription
|
||||||
Options;Options
|
Options;Options
|
||||||
Amount;Amount
|
Amount;Amount
|
||||||
Do you really want to delete it?;Do you really want to delete it?
|
Do you really want to delete it?;Do you really want to delete it?
|
||||||
Save subscription;Save subscription
|
|
||||||
Loading your subscription;Loading your subscription
|
|
||||||
Searching for deploy node;Searching for deploy node
|
|
||||||
Searching for available images;Searching for available images
|
|
||||||
Server details;Server details
|
|
||||||
Configure your server;Configure your server
|
|
||||||
Default;Default
|
|
||||||
No images available;No images available
|
|
||||||
You reached the maximum amount of servers for every image of your subscription;You reached the maximum amount of servers for every image of your subscription
|
|
||||||
No node found;No node found
|
|
||||||
No node found to deploy to found;No node found to deploy to found
|
|
||||||
You need to specify a server image;You need to specify a server image
|
|
||||||
CPU;CPU
|
|
||||||
Compress;Compress
|
|
||||||
Decompress;Decompress
|
|
||||||
Cleanup exception;Cleanup exception
|
|
||||||
Error creating server on wings;Error creating server on wings
|
|
||||||
Enter code;Enter code
|
|
||||||
Create code;Create code
|
|
||||||
Duration;Duration
|
|
||||||
Enter duration of subscription;Enter duration of subscription
|
|
||||||
Copied code to clipboard;Copied code to clipboard
|
|
||||||
Current subscription;Current subscription
|
|
||||||
7 + 7 =;7 + 7 =
|
|
||||||
1 + 8 =;1 + 8 =
|
|
||||||
8 + 4 =;8 + 4 =
|
|
||||||
8 + 1 =;8 + 1 =
|
|
||||||
1 + 1 =;1 + 1 =
|
|
||||||
6 + 6 =;6 + 6 =
|
|
||||||
53
Moonlight/resources/mail/passwordChange.html
Normal file
53
Moonlight/resources/mail/passwordChange.html
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Moonlight password change</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="background-color:#ffffff; padding: 45px 0 34px 0; border-radius: 24px; margin:40px auto; max-width: 600px;">
|
||||||
|
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" height="auto"
|
||||||
|
style="border-collapse:collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="center" style="text-align:center; padding-bottom: 10px">
|
||||||
|
<div style="text-align:center; margin:0 15px 34px 15px">
|
||||||
|
<div style="margin-bottom: 10px">
|
||||||
|
<a href="https://endelon-hosting.de" rel="noopener" target="_blank">
|
||||||
|
<img alt="Logo" src="https://moonlight.endelon-hosting.de/assets/media/logo/MoonFullText.png" style="height: 35px">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 14px; font-weight: 500; margin-bottom: 27px; font-family:Arial,Helvetica,sans-serif;">
|
||||||
|
<p style="margin-bottom:9px; color:#181C32; font-size: 22px; font-weight:700">Hey {{FirstName}}, your password has been changed</p>
|
||||||
|
<p style="margin-bottom:2px; color:#7E8299">If this was not you please contact us. Also here is the data we collected.</p>
|
||||||
|
<p style="margin-bottom:2px; color:#7E8299">IP: {{Ip}}</p>
|
||||||
|
<p style="margin-bottom:2px; color:#7E8299">Device: {{Device}}</p>
|
||||||
|
<p style="margin-bottom:2px; color:#7E8299">Location: {{Location}}</p>
|
||||||
|
</div>
|
||||||
|
<a href="https://moonlight.endelon-hosting.de" target="_blank"
|
||||||
|
style="background-color:#50cd89; border-radius:6px;display:inline-block; padding:11px 19px; color: #FFFFFF; font-size: 14px; font-weight:500;">Open Moonlight
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="center"
|
||||||
|
style="font-size: 13px; text-align:center; padding: 0 10px 10px 10px; font-weight: 500; color: #A1A5B7; font-family:Arial,Helvetica,sans-serif">
|
||||||
|
<p style="color:#181C32; font-size: 16px; font-weight: 600; margin-bottom:9px">You need help?</p>
|
||||||
|
<p style="margin-bottom:2px">We are happy to help!</p>
|
||||||
|
<p style="margin-bottom:4px">More information at
|
||||||
|
<a href="https://endelon.link/support" rel="noopener" target="_blank" style="font-weight: 600">endelon.link/support</a>.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="center"
|
||||||
|
style="font-size: 13px; padding:0 15px; text-align:center; font-weight: 500; color: #A1A5B7;font-family:Arial,Helvetica,sans-serif">
|
||||||
|
<p>Copyright 2023 Endelon Hosting </p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -39,6 +39,6 @@ div.wave .dot:nth-child(3) {
|
|||||||
transform: initial;
|
transform: initial;
|
||||||
}
|
}
|
||||||
30% {
|
30% {
|
||||||
transform: translateY(-15px);
|
transform: translateY(-8px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user