mirror of
https://onedev.site.tesses.net/tytd2025
synced 2026-06-01 18:05:32 +00:00
Add login support and oobe
This commit is contained in:
@@ -17,23 +17,305 @@ var TYTDResources = [
|
||||
var times=1;
|
||||
|
||||
class TYTDApp {
|
||||
private OOBE_STATE = {
|
||||
tag = "UnknownPC",
|
||||
pollHours = 3,
|
||||
pollMinutes = 0,
|
||||
pollSeconds = 0,
|
||||
enablePlugins = true
|
||||
};
|
||||
|
||||
private TYTD;
|
||||
|
||||
public TYTDApp()
|
||||
{
|
||||
|
||||
var tytdfs = new SubdirFilesystem(FS.Local, "TYTD");
|
||||
this.TYTD = new TYTD.Downloader(tytdfs,FS.MakeFull("TYTD"));
|
||||
var tytdfs = new SubdirFilesystem(FS.Local, GetTYTDDir());
|
||||
this.TYTD = new TYTD.Downloader(tytdfs,FS.MakeFull(GetTYTDDir()));
|
||||
this.TYTD.Start();
|
||||
|
||||
}
|
||||
|
||||
public Handle(ctx)
|
||||
{
|
||||
if(ctx.Path == "/api/v1/login")
|
||||
{
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
const req=ctx.ReadJson();
|
||||
const result = this.TYTD.Login(req.username, req.password);
|
||||
if(result)
|
||||
{
|
||||
ctx.SendJson({
|
||||
success = true,
|
||||
token = result
|
||||
});
|
||||
}
|
||||
else {
|
||||
ctx.SendJson({
|
||||
success=false
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ctx.StatusCode=307;
|
||||
ctx.SendRedirect("/login");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(ctx.Path == "/login")
|
||||
{
|
||||
var incorrect=false;
|
||||
const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/";
|
||||
const username = ctx.QueryParams.TryGetFirst("username") ?? "";
|
||||
const password = ctx.QueryParams.TryGetFirst("password") ?? "";
|
||||
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
const result = this.TYTD.Login(username, password);
|
||||
if(result)
|
||||
{
|
||||
ctx.StatusCode = 303;
|
||||
ctx.WithHeader("Set-Cookie",$"Session={result}; SameSite=Strict").SendRedirect("/");
|
||||
return true;
|
||||
}
|
||||
else incorrect=true;
|
||||
}
|
||||
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Login(redirect, incorrect));
|
||||
return true;
|
||||
}
|
||||
const loggedIn = this.TYTD.IsLoggedIn(ctx);
|
||||
if(!loggedIn)
|
||||
{
|
||||
each(var file : TYTDResources)
|
||||
{
|
||||
if(ctx.Path == file.path)
|
||||
{
|
||||
ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ctx.StatusCode = 307;
|
||||
ctx.SendRedirect($"/login?redirect={Net.Http.UrlEncode(ctx.Path)}");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if(this.TYTD.Config.OobeState == "oobe")
|
||||
{
|
||||
each(var file : TYTDResources)
|
||||
{
|
||||
if(ctx.Path == file.path)
|
||||
{
|
||||
ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(ctx.Path == "/oobe")
|
||||
{
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
const from = ctx.QueryParams.TryGetFirstInt("from");
|
||||
switch(from)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
const enablePlugins = ctx.QueryParams.GetFirstBoolean("enablePlugins");
|
||||
const tag = ctx.QueryParams.TryGetFirst("tag") ?? "UnknownPC";
|
||||
const pollHours = ctx.QueryParams.TryGetFirstInt("pollHours") ?? 3;
|
||||
const pollMinutes = ctx.QueryParams.TryGetFirstInt("pollMinutes") ?? 0;
|
||||
const pollSeconds = ctx.QueryParams.TryGetFirstInt("pollSeconds") ?? 0;
|
||||
|
||||
|
||||
this.OOBE_STATE.tag = tag;
|
||||
this.OOBE_STATE.pollHours = pollHours;
|
||||
this.OOBE_STATE.pollMinutes = pollMinutes;
|
||||
|
||||
this.OOBE_STATE.pollSeconds = pollSeconds;
|
||||
|
||||
this.OOBE_STATE.enablePlugins = enablePlugins;
|
||||
|
||||
const page = this.OOBE_STATE.enablePlugins ? "/oobe?page=2" : "/oobe?page=3";
|
||||
ctx.StatusCode = 303;
|
||||
ctx.SendRedirect(page);
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
|
||||
const createUser = ctx.QueryParams.GetFirstBoolean("user");
|
||||
|
||||
var seconds = this.OOBE_STATE.pollSeconds;
|
||||
seconds += this.OOBE_STATE.pollMinutes * 60;
|
||||
seconds += this.OOBE_STATE.pollHours * 3600;
|
||||
this.TYTD.Config.TYTDTag = this.OOBE_STATE.tag;
|
||||
this.TYTD.Config.BellTimer = seconds;
|
||||
this.TYTD.Config.EnablePlugins = this.OOBE_STATE.enablePlugins;
|
||||
this.TYTD.Config.OobeState = "welcome";
|
||||
this.TYTD.SaveConfig();
|
||||
|
||||
|
||||
if(createUser)
|
||||
{
|
||||
|
||||
ctx.StatusCode = 303;
|
||||
ctx.SendRedirect("/newuser");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
||||
ctx.StatusCode = 303;
|
||||
ctx.SendRedirect("/");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
ctx.WithMimeType("text/html").SendText(Components.ShellSimple("First Setup", <h1>Malformed data</h1>));
|
||||
return true;
|
||||
}
|
||||
const page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
switch(page)
|
||||
{
|
||||
case 1:
|
||||
ctx.WithMimeType("text/html").SendText(Pages.OobePage1(this.OOBE_STATE));
|
||||
break;
|
||||
case 2:
|
||||
ctx.WithMimeType("text/html").SendText(Pages.OobePage2(this.TYTD,ctx));
|
||||
break;
|
||||
case 3:
|
||||
ctx.WithMimeType("text/html").SendText(Pages.OobePage3());
|
||||
break;
|
||||
default:
|
||||
ctx.WithMimeType("text/html").SendText(Components.ShellSimple("First Setup", <h1>Setup page not found</h1>));
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if(ctx.Path != "/package-manage" && ctx.Path != "/api/v1/plugin-thumbnail.png")
|
||||
{
|
||||
ctx.StatusCode = 307;
|
||||
ctx.SendRedirect($"/oobe?page=1");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(ctx.Path == "/welcome")
|
||||
{
|
||||
const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/settings";
|
||||
if(ctx.QueryParams.GetFirstBoolean("closePopup"))
|
||||
{
|
||||
if(this.TYTD.Config.OobeState == "welcome")
|
||||
{
|
||||
this.TYTD.Config.OobeState = "finished";
|
||||
this.TYTD.SaveConfig();
|
||||
}
|
||||
ctx.StatusCode = 307;
|
||||
ctx.SendRedirect(redirect);
|
||||
|
||||
return true;
|
||||
}
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Welcome(redirect));
|
||||
return true;
|
||||
}
|
||||
if(this.TYTD.Config.OobeState == "welcome")
|
||||
{
|
||||
each(var file : TYTDResources)
|
||||
{
|
||||
if(ctx.Path == file.path)
|
||||
{
|
||||
ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(ctx.Path != "/newuser")
|
||||
{
|
||||
|
||||
ctx.StatusCode = 307;
|
||||
ctx.SendRedirect($"/welcome?redirect={Net.Http.UrlEncode(ctx.Path)}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(ctx.Path == "/")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/passwd")
|
||||
{
|
||||
var incorrect=false;
|
||||
|
||||
const password = ctx.QueryParams.TryGetFirst("password") ?? "";
|
||||
const newpassword = ctx.QueryParams.TryGetFirst("newpassword") ?? "";
|
||||
|
||||
const confirm = ctx.QueryParams.TryGetFirst("newpassword") ?? "";
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
this.TYTD.Passwd();
|
||||
}
|
||||
|
||||
ctx.WithMimeType("text/html").SendText(Pages.ChangePassword(redirect, incorrect));
|
||||
}
|
||||
else if(ctx.Path == "/newuser")
|
||||
{
|
||||
var error = null;
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
const username = ctx.QueryParams.TryGetFirst("username");
|
||||
const password = ctx.QueryParams.TryGetFirst("password");
|
||||
const confirm = ctx.QueryParams.TryGetFirst("confirm");
|
||||
const isAdmin = ctx.QueryParams.GetFirstBoolean("isAdmin");
|
||||
const canUsePlugins = ctx.QueryParams.GetFirstBoolean("canUsePlugins");
|
||||
const canManagePlugins = ctx.QueryParams.GetFirstBoolean("canManagePlugins");
|
||||
const canDownloadDB = ctx.QueryParams.GetFirstBoolean("canDownloadDB");
|
||||
const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/";
|
||||
var flags = 0;
|
||||
if(isAdmin) flags |= UserFlags.AdminFlag;
|
||||
if(canUsePlugins) flags |= UserFlags.PluginFlag;
|
||||
if(canManagePlugins) flags |= UserFlags.ManagePluginFlag;
|
||||
if(canDownloadDB) flags |= UserFlags.DatabaseFlag;
|
||||
|
||||
|
||||
if(password != confirm)
|
||||
{
|
||||
error = "Passwords do not match";
|
||||
}
|
||||
else if(!TypeIsString(password)) {
|
||||
error = "Password is not defined";
|
||||
|
||||
}
|
||||
else if(!TypeIsString(username)) {
|
||||
error = "Username not defined";
|
||||
}
|
||||
else {
|
||||
error = this.TYTD.CreateAccount(ctx, username, password, flags);
|
||||
}
|
||||
|
||||
if(!TypeIsString(error))
|
||||
{
|
||||
ctx.StatusCode=303;
|
||||
ctx.SendRedirect(redirect);
|
||||
}
|
||||
}
|
||||
ctx.WithMimeType("text/html").SendText(Pages.NewUser(error));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/logout")
|
||||
{
|
||||
this.TYTD.Logout(ctx);
|
||||
ctx.StatusCode = 307;
|
||||
ctx.SendRedirect("/");
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Api());
|
||||
@@ -150,7 +432,11 @@ class TYTDApp {
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/database.db")
|
||||
{
|
||||
this.TYTD.SendDatabase(ctx);
|
||||
|
||||
if(!this.TYTD.SendDatabase(ctx))
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized","<h1>You are not authorized to download the database</h1>",3,/"api/v1/database.db"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/personal")
|
||||
@@ -270,6 +556,12 @@ class TYTDApp {
|
||||
ctx.WithMimeType("text/html").SendText(Components.Subscribe(this.TYTD,id));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/whoami")
|
||||
{
|
||||
const row = this.TYTD.WhoAmI(ctx);
|
||||
ctx.WithMimeType("text/html").SendText(Pages.WhoAmI(row));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/edit-personal-description")
|
||||
{
|
||||
var name = ctx.QueryParams.TryGetFirst("name");
|
||||
@@ -299,8 +591,98 @@ class TYTDApp {
|
||||
}
|
||||
else if(ctx.Path == "/settings")
|
||||
{
|
||||
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Settings(this.TYTD,ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/edituser")
|
||||
{
|
||||
if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx)))
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,3));
|
||||
return true;
|
||||
}
|
||||
const user = ctx.QueryParams.TryGetFirst("user");
|
||||
|
||||
if(TypeIsString(user))
|
||||
{
|
||||
var userObj = null;
|
||||
const whoami = this.TYTD.WhoAmI(ctx);
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
if(whoami.username != user)
|
||||
{
|
||||
const isAdmin = ctx.QueryParams.GetFirstBoolean("isAdmin");
|
||||
const canUsePlugins = ctx.QueryParams.GetFirstBoolean("canUsePlugins");
|
||||
const canManagePlugins = ctx.QueryParams.GetFirstBoolean("canManagePlugins");
|
||||
const canDownloadDB = ctx.QueryParams.GetFirstBoolean("canDownloadDB");
|
||||
var flags = 0;
|
||||
if(isAdmin) flags |= UserFlags.AdminFlag;
|
||||
if(canUsePlugins) flags |= UserFlags.PluginFlag;
|
||||
if(canManagePlugins) flags |= UserFlags.ManagePluginFlag;
|
||||
if(canDownloadDB) flags |= UserFlags.DatabaseFlag;
|
||||
|
||||
|
||||
this.TYTD.Mutex.Lock();
|
||||
const db = this.TYTD.OpenDB();
|
||||
Sqlite.Exec(db, $"UPDATE users SET flags = {flags} WHERE username = {Sqlite.Escape(user)}");
|
||||
Sqlite.Close(db);
|
||||
this.TYTD.Mutex.Unlock();
|
||||
}
|
||||
}
|
||||
if(ctx.Method == "DELETE")
|
||||
{
|
||||
if(whoami.username != user)
|
||||
{
|
||||
this.TYTD.Mutex.Lock();
|
||||
const db = this.TYTD.OpenDB();
|
||||
Sqlite.Exec(db,$"DELETE FROM users WHERE username = {Sqlite.Escape(user)}");
|
||||
Sqlite.Close(db);
|
||||
this.TYTD.Mutex.Unlock();
|
||||
ctx.StatusCode = 200;
|
||||
ctx.WithHeader("HX-Location", "/edituser");
|
||||
ctx.SendText("Redirect");
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.StatusCode = 401;
|
||||
ctx.WriteHeaders();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(whoami.username != user)
|
||||
{
|
||||
this.TYTD.Mutex.Lock();
|
||||
const db = this.TYTD.OpenDB();
|
||||
userObj = Sqlite.Exec(db,$"SELECT * FROM users WHERE username = {Sqlite.Escape(user)}");
|
||||
Sqlite.Close(db);
|
||||
this.TYTD.Mutex.Unlock();
|
||||
if(TypeIsList(userObj) && userObj.Length == 1) userObj = userObj[0];
|
||||
else userObj=null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ctx.WithMimeType("text/html").SendText(Pages.EditUser(userObj));
|
||||
}
|
||||
else {
|
||||
ctx.WithMimeType("text/html").SendText(Pages.EditUserList(this.TYTD, ctx));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/admin")
|
||||
{
|
||||
if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx)))
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,3));
|
||||
return true;
|
||||
}
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
|
||||
/*
|
||||
hours
|
||||
minutes
|
||||
@@ -324,7 +706,7 @@ class TYTDApp {
|
||||
this.TYTD.SaveConfig();
|
||||
}
|
||||
}
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Settings(this.TYTD,ctx));
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Admin(this.TYTD,ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/watch" || ctx.Path == "/video")
|
||||
@@ -427,11 +809,13 @@ class TYTDApp {
|
||||
}
|
||||
else if(ctx.Path == "/plugins-download")
|
||||
{
|
||||
if(!UserFlags.CanManagePlugins(this.TYTD.IsLoggedIn(ctx))) return false;
|
||||
ctx.WithMimeType("text/html").SendText(Pages.DownloadPlugins(this.TYTD,ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/package-manage" && ctx.Method == "POST")
|
||||
{
|
||||
if(!UserFlags.CanManagePlugins(this.TYTD.IsLoggedIn(ctx))) return false;
|
||||
var data = {
|
||||
name = ctx.QueryParams.TryGetFirst("name"),
|
||||
version = ctx.QueryParams.TryGetFirst("version"),
|
||||
@@ -502,7 +886,12 @@ class TYTDApp {
|
||||
}
|
||||
else if(ctx.Path.StartsWith("/plugin/"))
|
||||
{
|
||||
if(UserFlags.CanUsePlugins(this.TYTD.IsLoggedIn(ctx)))
|
||||
return this.TYTD.Servers.Handle(ctx);
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
each(var file : TYTDResources)
|
||||
@@ -523,8 +912,53 @@ class TYTDApp {
|
||||
}
|
||||
}
|
||||
|
||||
func GetTYTDDir()
|
||||
{
|
||||
const dir = Env["TYTDDIR"];
|
||||
if(TypeIsString(dir) && dir.Length > 0)
|
||||
return dir;
|
||||
return (Env.Videos / "TYTD2025").ToString();
|
||||
}
|
||||
|
||||
func WebAppMain(args)
|
||||
{
|
||||
if(args.Length == 2 && args[1] == "change-password")
|
||||
{
|
||||
Console.Write("Username: ");
|
||||
const username = Console.ReadLine();
|
||||
Console.Write("Password: ");
|
||||
const echo = Console.Echo;
|
||||
Console.Echo = false;
|
||||
const password = Console.ReadLine();
|
||||
Console.WriteLine();
|
||||
Console.Write("Confirm: ");
|
||||
const confirm = Console.ReadLine();
|
||||
Console.WriteLine();
|
||||
Console.Echo = true;
|
||||
|
||||
if(password != confirm)
|
||||
{
|
||||
Console.WriteLine("Passwords do not match!");
|
||||
return null;
|
||||
}
|
||||
|
||||
const salt = Crypto.RandomBytes(32, "TYTD2025");
|
||||
const hash = Crypto.PBKDF2(password, salt, UserFlags.ITTR,64,384);
|
||||
FS.Local.CreateDirectory(GetTYTDDir());
|
||||
const db = Sqlite.Open(GetTYTDDir()/"tytd.db");
|
||||
|
||||
|
||||
const res = Sqlite.Exec(db, $"UPDATE users SET password_hash = {Sqlite.Escape(Crypto.Base64Encode(hash))}, password_salt = {Sqlite.Escape(Crypto.Base64Encode(salt))} WHERE username = {Sqlite.Escape(username)};");
|
||||
if(TypeIsList(res))
|
||||
{
|
||||
Console.WriteLine("Changed password successfully");
|
||||
}
|
||||
else {
|
||||
Console.WriteLine($"Failed to change password: {res}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return new TYTDApp();
|
||||
}
|
||||
func main(args)
|
||||
|
||||
Reference in New Issue
Block a user