mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-16 00:44:36 +01:00
add basic admin panel
This commit is contained in:
90
nksrv/AdminApiController.cs
Normal file
90
nksrv/AdminApiController.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using EmbedIO;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using nksrv.Utils;
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace nksrv
|
||||
{
|
||||
public class AdminApiController : WebApiController
|
||||
{
|
||||
public static Dictionary<string, User> AdminAuthTokens = new();
|
||||
private static MD5 md5 = MD5.Create();
|
||||
[Route(HttpVerbs.Any, "/login")]
|
||||
public async Task Login()
|
||||
{
|
||||
var c = await HttpContext.GetRequestFormDataAsync();
|
||||
var username = c["username"];
|
||||
var password = c["password"];
|
||||
|
||||
if (HttpContext.Request.HttpMethod != "POST")
|
||||
{
|
||||
await HttpContext.SendStringAsync(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/www/admin/index.html").Replace("<errormsg/>", ""), "text/html", Encoding.Unicode);
|
||||
return;
|
||||
}
|
||||
|
||||
User? user = null;
|
||||
bool nullusernames = false;
|
||||
if (username != null && password != null)
|
||||
{
|
||||
var passwordHash = Convert.ToHexString(md5.ComputeHash(Encoding.ASCII.GetBytes(password))).ToLower();
|
||||
foreach (var item in JsonDb.Instance.Users)
|
||||
{
|
||||
if (item.Username == username)
|
||||
{
|
||||
if (item.Password.ToLower() == passwordHash)
|
||||
{
|
||||
user = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nullusernames = true;
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
if (nullusernames == false)
|
||||
{
|
||||
await HttpContext.SendStringAsync((string)File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/www/admin/index.html").Replace("<errormsg/>", "Incorrect username or password"), "text/html", Encoding.Unicode);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await HttpContext.SendStringAsync((string)File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/www/admin/index.html").Replace("<errormsg/>", "Please enter a username or password"), "text/html", Encoding.Unicode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user.IsAdmin)
|
||||
{
|
||||
Response.Headers.Add("Set-Cookie", "token=" + CreateAuthToken(user) + ";path=/");
|
||||
HttpContext.Redirect("/admin/", 301);
|
||||
}
|
||||
else
|
||||
{
|
||||
await HttpContext.SendStringAsync((string)File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/www/admin/index.html").Replace("<errormsg/>", "User does not have admin panel access."), "text/html", Encoding.Unicode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string CreateAuthToken(User user)
|
||||
{
|
||||
var tok = RandomString(128);
|
||||
AdminAuthTokens.Add(tok, user);
|
||||
return tok;
|
||||
}
|
||||
|
||||
public static string RandomString(int length)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
return new string(Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[new Random().Next(s.Length)]).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ using Newtonsoft.Json.Linq;
|
||||
using Swan;
|
||||
using Google.Api;
|
||||
using nksrv.StaticInfo;
|
||||
using EmbedIO.WebApi;
|
||||
|
||||
namespace nksrv
|
||||
{
|
||||
@@ -60,7 +61,10 @@ namespace nksrv
|
||||
.WithModule(new ActionModule("/media/", HttpVerbs.Any, HandleAsset))
|
||||
.WithModule(new ActionModule("/PC/", HttpVerbs.Any, HandleAsset))
|
||||
.WithModule(new ActionModule("/$batch", HttpVerbs.Any, HandleBatchRequests))
|
||||
.WithModule(new ActionModule("/nikke_launcher", HttpVerbs.Any, HandleLauncherUI));
|
||||
.WithStaticFolder("/nikke_launcher", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "www", "launcher"), true)
|
||||
.WithStaticFolder("/admin/assets/", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "www", "admin", "assets"), true)
|
||||
.WithModule(new ActionModule("/admin", HttpVerbs.Any, HandleAdminRequest))
|
||||
.WithWebApi("/adminapi", m => m.WithController(typeof(AdminApiController)));
|
||||
|
||||
// Listen for state changes.
|
||||
//server.StateChanged += (s, e) => $"WebServer New State - {e.NewState}".Info();
|
||||
@@ -68,28 +72,59 @@ namespace nksrv
|
||||
return server;
|
||||
}
|
||||
|
||||
private static async Task HandleLauncherUI(IHttpContext ctx)
|
||||
private static async Task HandleAdminRequest(IHttpContext context)
|
||||
{
|
||||
await ctx.SendStringAsync(@"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Private Nikke Server Launcher</title>
|
||||
<style>
|
||||
* {
|
||||
color:white;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>What's new in Nikke Private Server<h1>
|
||||
<p>This is the inital release, only story mode works except that you can't collect items and a few other things don't work.</p>
|
||||
<p>In order to level up characters, you manually have to edit db.json</p>
|
||||
</body>
|
||||
</html>
|
||||
", "text/html", Encoding.UTF8);
|
||||
//check if user is logged in
|
||||
if (context.Request.Cookies["token"] == null && context.Request.Url.PathAndQuery != "/api/login")
|
||||
{
|
||||
context.Redirect("/adminapi/login");
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if authenticated correctly
|
||||
User? currentUser = null;
|
||||
if (context.Request.Url.PathAndQuery != "/api/login")
|
||||
{
|
||||
//verify token
|
||||
foreach (var item in AdminApiController.AdminAuthTokens)
|
||||
{
|
||||
if (item.Key == context.Request.Cookies["token"].Value)
|
||||
{
|
||||
currentUser = item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentUser == null)
|
||||
{
|
||||
context.Redirect("/adminapi/login");
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Request.Url.PathAndQuery == "/admin/")
|
||||
{
|
||||
context.Redirect("/admin/dashboard");
|
||||
}
|
||||
else if (context.Request.Url.PathAndQuery == "/admin/dashboard")
|
||||
{
|
||||
await context.SendStringAsync(ProcessAdminPage("dashbrd.html", currentUser), "text/html", Encoding.Unicode);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = 404;
|
||||
await context.SendStringAsync("404 not found", "text/html", Encoding.Unicode);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ProcessAdminPage(string pg, User? currentUser)
|
||||
{
|
||||
var pgContent = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "www", "admin", pg));
|
||||
var nav = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "www", "admin", "nav.html"));
|
||||
|
||||
//navbar
|
||||
pgContent = pgContent.Replace("{{navbar}}", nav);
|
||||
|
||||
return pgContent;
|
||||
}
|
||||
private static async Task HandleBatchRequests(IHttpContext ctx)
|
||||
{
|
||||
var theBytes = await PacketDecryption.DecryptOrReturnContentAsync(ctx);
|
||||
|
||||
@@ -78,6 +78,7 @@ namespace nksrv.Utils
|
||||
public string Nickname = "SomePlayer";
|
||||
public int ProfileIconId = 39900;
|
||||
public bool ProfileIconIsPrism = false;
|
||||
public bool IsAdmin = false;
|
||||
|
||||
// Game data
|
||||
public List<string> CompletedScenarios = [];
|
||||
|
||||
@@ -36,5 +36,8 @@
|
||||
<None Update="site.pfx">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="www\**\*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
23
nksrv/www/admin/assets/login.css
Normal file
23
nksrv/www/admin/assets/login.css
Normal file
@@ -0,0 +1,23 @@
|
||||
@ -0,0 +1,23 @@ html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: url('/admin/assets/login.jpg') no-repeat center center fixed;
|
||||
background-size: cover;
|
||||
/* Center child horizontally*/
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.LoginBox {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 20px 20px 20px 20px;
|
||||
}
|
||||
BIN
nksrv/www/admin/assets/login.jpg
Normal file
BIN
nksrv/www/admin/assets/login.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
239
nksrv/www/admin/assets/style.css
Normal file
239
nksrv/www/admin/assets/style.css
Normal file
@@ -0,0 +1,239 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin: 5px 0px 5px 0;
|
||||
}
|
||||
|
||||
|
||||
/* Tabs */
|
||||
/* Style the tab */
|
||||
.tab {
|
||||
float: left;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f1f1f1;
|
||||
width: 30%;
|
||||
height: 1000px;
|
||||
}
|
||||
|
||||
/* Style the buttons inside the tab */
|
||||
.tab button {
|
||||
display: block;
|
||||
background-color: inherit;
|
||||
color: black;
|
||||
padding: 22px 16px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
/* Change background color of buttons on hover */
|
||||
.tab button:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
/* Create an active/current "tab button" class */
|
||||
.tab button.active {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
/* Style the tab content */
|
||||
.tabcontent {
|
||||
float: left;
|
||||
padding: 5px 5px 5px 5px;
|
||||
border: 1px solid #ccc;
|
||||
width: 70%;
|
||||
border-left: none;
|
||||
height: 1000px;
|
||||
}
|
||||
|
||||
.navbar2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: var(--main-nav-light-bg);
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
float: left;
|
||||
display: block;
|
||||
color: var(--main-nav-light-color);
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navbar-item:hover {
|
||||
background-color: #029761;
|
||||
text-decoration: none;
|
||||
color: var(--main-nav-light-color);
|
||||
}
|
||||
|
||||
.navbar-item:not(.active):hover {
|
||||
background-color: var(--main-nav-light-hover);
|
||||
text-decoration: none;
|
||||
color: var(--main-nav-light-color);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #04AA6D;
|
||||
}
|
||||
|
||||
:root {
|
||||
--main-nav-dark-bg: #333;
|
||||
--main-nav-dark-color: white;
|
||||
--main-nav-dark-hover: #111;
|
||||
--main-nav-light-bg: #f1f1f1;
|
||||
--main-nav-light-color: black;
|
||||
--main-nav-light-hover: #dddddd;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
zonegroup {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
zonegroup p {
|
||||
display: flex;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
zonegroup input {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
.dropdown2 {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dropdown2 .dropbtn {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: black;
|
||||
padding: 14px 16px;
|
||||
background-color: inherit;
|
||||
font-family: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar a:hover, .dropdown2:hover .dropbtn, .dropbtn:focus {
|
||||
background-color: green;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f9f9f9;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: black;
|
||||
padding: 12px 16px 12px 7px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Leave this at the end */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: rgb(25, 25, 25);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.app-bar {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
/* For browsers that do not support gradients */
|
||||
background-color: #A3A6A9;
|
||||
background-image: linear-gradient(to right, #A3A6A9, #666765);
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: darkgray;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tab {
|
||||
background-color: rgb(36,36,36);
|
||||
}
|
||||
|
||||
.tab button {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tab button:hover {
|
||||
background-color: #666765;
|
||||
}
|
||||
|
||||
.tab button.active {
|
||||
background-color: darkgray;
|
||||
}
|
||||
|
||||
.navbar2 {
|
||||
background-color: var(--main-nav-dark-bg);
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
color: var(--main-nav-dark-color);
|
||||
}
|
||||
|
||||
.navbar-item:hover {
|
||||
background-color: #029761;
|
||||
text-decoration: none;
|
||||
color: var(--main-nav-dark-color);
|
||||
}
|
||||
|
||||
.navbar-item:not(.active):hover {
|
||||
background-color: var(--main-nav-dark-hover);
|
||||
text-decoration: none;
|
||||
color: var(--main-nav-dark-color);
|
||||
}
|
||||
|
||||
.modal {
|
||||
--bs-modal-bg: black;
|
||||
}
|
||||
|
||||
.dropdown2 .dropbtn {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
21
nksrv/www/admin/dashbrd.html
Normal file
21
nksrv/www/admin/dashbrd.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Security System Controller</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/admin/assets/style.css">
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
<body>
|
||||
{{navbar}}
|
||||
<div class="containter">
|
||||
<h1>Welcome to Nikke Private Server Admin Panel</h1>
|
||||
<p>There are no settings to display.</p>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
34
nksrv/www/admin/index.html
Normal file
34
nksrv/www/admin/index.html
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Security System Controller</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="/admin/assets/login.css">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
<body>
|
||||
<div class="LoginBox">
|
||||
<h1>Login</h1>
|
||||
<form action="/adminapi/login" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="UsernameBox" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="UsernameBox" name="username">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="PasswordBox" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="PasswordBox" name="password">
|
||||
</div>
|
||||
<errormsg/>
|
||||
<br />
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
nksrv/www/admin/nav.html
Normal file
3
nksrv/www/admin/nav.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div class="navbar2">
|
||||
<a href="/admin/dashboard" class="navbar-item">Overview</a>
|
||||
</div>
|
||||
16
nksrv/www/launcher/index.html
Normal file
16
nksrv/www/launcher/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Private Nikke Server Launcher</title>
|
||||
<style>
|
||||
* {
|
||||
color: white;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>What's new in Nikke Private Server - v0.1.1</h1>
|
||||
<p>Bug fixes</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user