Add login support and oobe

This commit is contained in:
2026-01-29 15:08:12 -06:00
parent c3ef57b8b6
commit 3919384b57
16 changed files with 1386 additions and 70 deletions

View File

@@ -1,4 +1,44 @@
//DO NOT ADD A FLAG WITH ONES BIT SET AS THIS IS
//TO DENOTE USER IS LOGGED IN
class UserFlags {
static getAdminFlag() 0b00000100;
static getPluginFlag() 0b00000010;
static getDatabaseFlag() 0b00001000;
static getManagePluginFlag() 0b00100000;
static IsAdmin(flags)
{
if(flags & UserFlags.AdminFlag) return true;
return false;
}
static CanDownloadDB(flags)
{
if(flags & UserFlags.AdminFlag) return true;
if(flags & UserFlags.DatabaseFlag) return true;
return false;
}
static CanCreateUsers(flags)
{
if(flags & UserFlags.AdminFlag) return true;
return false;
}
static CanUsePlugins(flags)
{
if(flags & UserFlags.AdminFlag) return true;
if(flags & UserFlags.ManagePluginFlag) return true;
if(flags & UserFlags.PluginFlag) return true;
return false;
}
static CanManagePlugins(flags)
{
if(flags & UserFlags.AdminFlag) return true;
if(flags & UserFlags.ManagePluginFlag) return true;
return false;
}
static getITTR() 35000;
}
class TYTD.Downloader {
/^
The storage vfs that TYTD accesses
^/
@@ -715,7 +755,8 @@ class TYTD.Downloader {
public Config = {
TYTDTag = "UnknownPC",
BellTimer = 10800,
EnablePlugins=true
EnablePlugins=true,
OobeState = "oobe"
};
public SaveConfig()
{
@@ -1255,6 +1296,8 @@ class TYTD.Downloader {
Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS personal_list_entries (id INTEGER PRIMARY KEY AUTOINCREMENT, listName TEXT, videoId TEXT);");
Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS plugin_settings (id INTEGER PRIMARY KEY AUTOINCREMENT, extension TEXT, key TEXT, value TEXT, UNIQUE(extension,key) ON CONFLICT REPLACE);");
Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS subscriptions (id INTEGER PRIMARY KEY AUTOINCREMENT, channelId TEXT UNIQUE ON CONFLICT REPLACE, bell TEXT);");
Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password_hash TEXT, password_salt TEXT, flags INTEGER);");
Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, accountId INTEGER, key TEXT UNIQUE);");
var config=Sqlite.Exec(db,"SELECT * FROM plugin_settings WHERE extension = '' AND key = 'settings';");
if(TypeOf(config) == "List" && config.Length>0)
{
@@ -1264,6 +1307,10 @@ class TYTD.Downloader {
}
}
this.Config.OobeState ??= "oobe";
Sqlite.Close(db);
this.Mutex.Unlock();
@@ -1718,11 +1765,219 @@ class TYTD.Downloader {
this.lastRequest = curRequest;
this.rlm.Unlock();
}
public GetSessionToken(ctx)
{
var cookie = ctx.RequestHeaders.TryGetFirst("Cookie");
if(TypeOf(cookie) == "String")
{
each(var part : cookie.Split("; "))
{
if(part.Length > 0)
{
var cookieKV = part.Split("=",true,2);
if(cookieKV.Length == 2 && cookieKV[0] == "Session")
{
return cookieKV[1];
}
}
}
}
var auth = ctx.RequestHeaders.TryGetFirst("Authorization");
if(TypeOf(auth) == "String")
{
auth=auth.Split(" ",true,2);
if(auth.Length < 2) return null;
if(auth[0] != "Bearer") return null;
return auth[1];
}
return null;
}
public Logout(ctx)
{
const token = GetSessionToken(ctx);
if(TypeIsString(token))
{
this.Mutex.Lock();
const db = this.OpenDB();
Sqlite.Exec(db, $"DELETE FROM sessions WHERE key = {Sqlite.Escape(token)};");
Sqlite.Close(db);
this.Mutex.Unlock();
}
}
public CreateAccount(ctx, username, password, flags)
{
var loggedIn = this.IsLoggedIn(ctx);
if(loggedIn == 0xFFFFFFFE) flags = 0xFFFFFFFE;
if(UserFlags.CanCreateUsers(loggedIn))
{
this.Mutex.Lock();
const db = this.OpenDB();
const salt = Crypto.RandomBytes(32, "TYTD2025");
const hash = Crypto.PBKDF2(password, salt, UserFlags.ITTR,64,384);
const resp = Sqlite.Exec(db, $"INSERT INTO users (username, password_salt, password_hash, flags) VALUES ({Sqlite.Escape(username)}, {Sqlite.Escape(Crypto.Base64Encode(salt))}, {Sqlite.Escape(Crypto.Base64Encode(hash))}, {flags});");
Sqlite.Close(db);
this.Mutex.Unlock();
if(TypeIsString(resp)) return resp;
}
else {
return "You are not authorized to create user accounts";
}
return null;
}
public IsLoggedIn(ctx)
{
this.Mutex.Lock();
const db = this.OpenDB();
const res=Sqlite.Exec(db, "SELECT COUNT(*) FROM users;");
var noAccounts=true;
if(TypeOf(res) == "List" && res.Length != 0)
{
if(res[0].["COUNT(*)"] != "0")
noAccounts=false;
}
if(noAccounts) {
Sqlite.Close(db);
this.Mutex.Unlock();
return 0xFFFFFFFE;
}
const sessionToken = this.GetSessionToken(ctx);
if(TypeIsString(sessionToken))
{
const res = Sqlite.Exec(db, $"SELECT * FROM sessions s INNER JOIN users u ON s.accountId = u.id WHERE key = {Sqlite.Escape(sessionToken)};");
if(TypeIsList(res))
each(var item : res)
{
Sqlite.Close(db);
this.Mutex.Unlock();
return ParseLong(item.flags) | 1;
}
}
Sqlite.Close(db);
this.Mutex.Unlock();
return 0;
}
public WhoAmI(ctx)
{
his.Mutex.Lock();
const db = this.OpenDB();
const res=Sqlite.Exec(db, "SELECT COUNT(*) FROM users;");
var noAccounts=true;
if(TypeOf(res) == "List" && res.Length != 0)
{
if(res[0].["COUNT(*)"] != "0")
noAccounts=false;
}
if(noAccounts) {
Sqlite.Close(db);
this.Mutex.Unlock();
return { flags = 0xFFFFFFFE, username = "N/A" };
}
const sessionToken = this.GetSessionToken(ctx);
if(TypeIsString(sessionToken))
{
const res = Sqlite.Exec(db, $"SELECT * FROM sessions s INNER JOIN users u ON s.accountId = u.id WHERE key = {Sqlite.Escape(sessionToken)};");
if(TypeIsList(res))
each(var item : res)
{
Sqlite.Close(db);
this.Mutex.Unlock();
item.flags = ParseLong(item.flags);
return item;
}
}
Sqlite.Close(db);
this.Mutex.Unlock();
return { flags = 0, username = "N/A" };
}
public Passwd(ctx, oldPassword, newPassword, logout)
{
const whoami = this.WhoAmI(ctx);
if(TypeIsDictionary(user) && TypeIsString(item.password_salt))
{
var salt = Crypto.Base64Decode(item.password_salt);
var hash = Crypto.PBKDF2(password, salt, UserFlags.ITTR,64,384);
var hashStr = Crypto.Base64Encode(hash);
if(item.password_hash == hashStr)
{
this.Mutex.Lock();
const db = this.OpenDB();
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(item.username)};");
if(TypeIsList(res))
{
if(logout)
{
Sqlite.Exec(db, $"DELETE FROM sessions WHERE accountId = {Sqlite.Escape(res.accountId)};");
}
Sqlite.Close(db);
this.Mutex.Unlock();
return {success=true};
}
else
{
Sqlite.Close(db);
this.Mutex.Unlock();
return {success=false, reason = res};
}
}
}
}
public Login(username, password)
{
this.Mutex.Lock();
const db = this.OpenDB();
const user = Sqlite.Exec(db, $"SELECT * FROM users WHERE username = {Sqlite.Escape(username)};");
if(TypeIsList(user))
{
each(var item : user)
{
this.Mutex.Unlock();
var salt = Crypto.Base64Decode(item.password_salt);
var hash = Crypto.PBKDF2(password, salt, UserFlags.ITTR,64,384);
var hashStr = Crypto.Base64Encode(hash);
if(item.password_hash == hashStr)
{
var rand = Net.Http.UrlEncode(Crypto.Base64Encode(Crypto.RandomBytes(32, "TYTD2025")));
this.Mutex.Lock();
const dbCon = this.OpenDB();
Sqlite.Exec(dbCon, $"INSERT INTO sessions (accountId,key) VALUES ({item.id},{Sqlite.Escape(rand)});");
Sqlite.Close(dbCon);
this.Mutex.Unlock();
return rand;
}
return null;
}
}
this.Mutex.Unlock();
return null;
}
/^
Send the database as http response
^/
public SendDatabase(ctx)
{
if(UserFlags.CanDownloadDB(this.IsLoggedIn(ctx)))
{
this.Mutex.Lock();
try {
var strm = FS.Local.OpenFile(this.DatabaseDirectory/"tytd.db","rb");
@@ -1732,5 +1987,11 @@ class TYTD.Downloader {
Console.WriteLine($"ERROR: {ex}");
}
this.Mutex.Unlock();
return true;
}
return false;
}
}