diff --git a/BSBshared/IO/SimpleIO.cs b/BSBshared/IO/SimpleIO.cs index 4c95c8a..50e65c0 100644 --- a/BSBshared/IO/SimpleIO.cs +++ b/BSBshared/IO/SimpleIO.cs @@ -57,6 +57,16 @@ namespace BetterSecondBotShared.IO { serializer.Serialize(writer, commandsfile); } + } + + public void WriteJsonTokens(JsonScopedTokens tokensfile, string targetfile) + { + JsonSerializer serializer = new JsonSerializer(); + using (StreamWriter sw = new StreamWriter(@"" + root_folder + "" + targetfile)) + using (JsonWriter writer = new JsonTextWriter(sw) { Formatting = Formatting.Indented }) + { + serializer.Serialize(writer, tokensfile); + } } public void makeOld(string targetfile) diff --git a/BSBshared/Json/JsonConfig.cs b/BSBshared/Json/JsonConfig.cs index 88c1183..5861e2a 100644 --- a/BSBshared/Json/JsonConfig.cs +++ b/BSBshared/Json/JsonConfig.cs @@ -3,11 +3,19 @@ using System.Collections.Generic; using System.Text; namespace BetterSecondBotShared.Json -{ +{ + + public class JsonScopedTokens + { + public string[] ScopedTokens { get; set; } + } + public class JsonCommandsfile { public string[] CustomCommands { get; set; } } + + public class JsonConfig { // Basics diff --git a/CLI/CliExitOnLogout.cs b/CLI/CliExitOnLogout.cs index d2e9dee..772de90 100644 --- a/CLI/CliExitOnLogout.cs +++ b/CLI/CliExitOnLogout.cs @@ -53,7 +53,7 @@ namespace BetterSecondBot */ new Thread(() => { - new HttpAsService(Bot, Config); + new HttpAsService(Bot, Config, as_docker); }).Start(); } if(use_self_keep_alive == true) diff --git a/CLI/HttpService/Chat.cs b/CLI/HttpService/Chat.cs index b2b94b2..48d4592 100644 --- a/CLI/HttpService/Chat.cs +++ b/CLI/HttpService/Chat.cs @@ -20,7 +20,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/localchathistory/{token}")] public object localchatHistory(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "chat", "localchathistory", getClientIP()) == true) { return BasicReply(JsonConvert.SerializeObject(bot.getLocalChatHistory())); } @@ -33,7 +33,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Post, "/localchatsay/{token}")] public object localchatSay(string token, [FormField] string message) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "chat", "localchatsay", getClientIP()) == true) { bot.GetCommandsInterface.Call("say", message); return BasicReply(JsonConvert.SerializeObject(bot.getLocalChatHistory())); diff --git a/CLI/HttpService/Core.cs b/CLI/HttpService/Core.cs index fb6a98d..5f85688 100644 --- a/CLI/HttpService/Core.cs +++ b/CLI/HttpService/Core.cs @@ -59,7 +59,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/friends/{token}")] public object Friends(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "friends", getClientIP()) == true) { return BasicReply(bot.getJsonFriendlist()); } @@ -75,7 +75,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/walkto/{x}/{y}/{z}/{token}")] public object WalkTo(string x, string y, string z, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "walkto", getClientIP()) == true) { bool status = bot.GetCommandsInterface.Call("AutoPilot", "<"+x+","+y+","+z+">"); if (status == true) @@ -94,7 +94,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/gesture/{gesture}/{token}")] public object Gesture(string gesture, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "gesture", getClientIP()) == true) { bool status = bot.GetCommandsInterface.Call("PlayGesture", gesture); if (status == true) @@ -116,7 +116,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/teleport/{region}/{x}/{y}/{z}/{token}")] public object Teleport(string region,string x, string y, string z, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "teleport", getClientIP()) == true) { bool status = bot.GetCommandsInterface.Call("Teleport", ""+region+"/" + x + "/" + y + "/" + z + ""); if (status == true) @@ -135,7 +135,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/regiontile/{regionname}/{token}")] public object Regiontile(string regionname, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "regiontile", getClientIP()) == true) { if (bot.GetClient.Grid.GetGridRegion(regionname, GridLayerType.Objects, out GridRegion region)) { @@ -155,7 +155,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/nearmewithdetails/{token}")] public object NearmeWithDetails(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "nearmewithdetails", getClientIP()) == true) { if (bot.GetClient.Network.CurrentSim != null) { @@ -188,7 +188,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/nearme/{token}")] public object Nearme(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "nearme", getClientIP()) == true) { if (bot.GetClient.Network.CurrentSim != null) { @@ -224,7 +224,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/logout/{token}")] public object Logout(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "logout", getClientIP()) == true) { return tokens.Expire(token); } @@ -238,7 +238,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/version/{token}")] public object Version(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "version", getClientIP()) == true) { return BasicReply(bot.MyVersion); } @@ -250,7 +250,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/name/{token}")] public object Name(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "name", getClientIP()) == true) { return BasicReply(bot.GetClient.Self.FirstName + " " + bot.GetClient.Self.LastName); } @@ -263,7 +263,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/regionname/{token}")] public object Regionname(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "regionname", getClientIP()) == true) { if(bot.GetClient.Network.CurrentSim != null) { @@ -280,7 +280,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/location/{token}")] public object Location(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "location", getClientIP()) == true) { if (bot.GetClient.Network.CurrentSim != null) { @@ -302,7 +302,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Post, "/command/{token}")] public async Task command(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "core", "command", getClientIP()) == true) { CommandLibCall data = await HttpContext.GetRequestDataAsync(); string compressedArgs = string.Join("~#~", data.Args); diff --git a/CLI/HttpService/Group.cs b/CLI/HttpService/Group.cs index fe860fa..0ae39a3 100644 --- a/CLI/HttpService/Group.cs +++ b/CLI/HttpService/Group.cs @@ -26,7 +26,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/ismember/{group}/{avatar}/{token}")] public object ismember(string group,string avatar,string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "groups", "ismember", getClientIP()) == true) { if (UUID.TryParse(group, out UUID groupuuid) == true) { @@ -62,7 +62,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/listgroups/{token}")] public object listgroups(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "groups", "listgroups", getClientIP()) == true) { Dictionary grouppackage = new Dictionary(); foreach(KeyValuePair entry in bot.MyGroups) @@ -83,7 +83,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/listgrouproles/{group}/{token}")] public object listgrouproles(string group, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "groups", "listgrouproles", getClientIP()) == true) { if (UUID.TryParse(group, out UUID groupuuid) == true) { @@ -132,7 +132,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/listgroupswithunread/{token}")] public object listgroupswithunread(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "groups", "listgroupswithunread", getClientIP()) == true) { return BasicReply(JsonConvert.SerializeObject(bot.UnreadGroupchatGroups())); } @@ -144,7 +144,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/haveunreadgroupchat/{token}")] public object haveunreadgroupchat(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "groups", "haveunreadgroupchat", getClientIP()) == true) { return BasicReply(bot.HasUnreadGroupchats().ToString()); } @@ -158,7 +158,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/getgroupchat/{group}/{token}")] public object getgroupchat(string group, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "groups", "getgroupchat", getClientIP()) == true) { if (UUID.TryParse(group, out UUID groupUUID) == true) { @@ -177,7 +177,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Post, "/sendgroupchat/{group}/{token}")] public object sendgroupchat(string group, string token, [FormField] string message) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "groups", "sendgroupchat", getClientIP()) == true) { if (UUID.TryParse(group, out UUID groupUUID) == true) { diff --git a/CLI/HttpService/HttpAsService.cs b/CLI/HttpService/HttpAsService.cs index 4788d00..870ccda 100644 --- a/CLI/HttpService/HttpAsService.cs +++ b/CLI/HttpService/HttpAsService.cs @@ -11,6 +11,9 @@ using EmbedIO.Security; using BetterSecondBotShared.Static; using System.Net; using EmbedIO.Routing; +using BetterSecondBotShared.logs; +using BetterSecondBotShared.IO; +using Newtonsoft.Json; namespace BetterSecondBot.HttpService { @@ -19,11 +22,122 @@ namespace BetterSecondBot.HttpService protected JsonConfig Config; protected SecondBot Bot; protected TokenStorage Tokens; - public HttpAsService(SecondBot MainBot, JsonConfig BotConfig) + + protected void loadScopedTokensFromENV() + { + int loop = 1; + bool found = true; + while (found == true) + { + if (helpers.notempty(Environment.GetEnvironmentVariable("scoped_token_" + loop.ToString())) == true) + { + processScopedTokenRaw(Environment.GetEnvironmentVariable("scoped_token_" + loop.ToString())); + } + else + { + found = false; + } + loop++; + } + } + + protected void loadScopedTokensFromFile() + { + JsonScopedTokens LoadedTokens = new JsonScopedTokens + { + ScopedTokens = new string[] { "t:[XXX],ws:core" } + }; + string targetfile = "scoped_tokens.json"; + SimpleIO io = new SimpleIO(); + if (SimpleIO.FileType(targetfile, "json") == false) + { + io.WriteJsonTokens(LoadedTokens, targetfile); + return; + } + if (io.Exists(targetfile) == false) + { + io.WriteJsonTokens(LoadedTokens, targetfile); + return; + } + string json = io.ReadFile(targetfile); + if (json.Length > 0) + { + try + { + LoadedTokens = JsonConvert.DeserializeObject(json); + foreach (string loaded in LoadedTokens.ScopedTokens) + { + processScopedTokenRaw(loaded); + } + } + catch + { + io.makeOld(targetfile); + io.WriteJsonTokens(LoadedTokens, targetfile); + } + return; + } + } + + protected void processScopedTokenRaw(string raw) + { + raw = raw.Replace(" ", ""); + string[] bits = raw.Split(','); + string code = null; + int accessids = 0; + scopedTokenInfo stinfo = new scopedTokenInfo(); + foreach(string bit in bits) + { + string[] subbits = bit.Split(':'); + if(subbits[0] == "t") + { + code = subbits[1]; + } + else if (subbits[0] == "cm") + { + stinfo.AllowCommands.Add(subbits[1]); + accessids++; + } + else if (subbits[0] == "cg") + { + stinfo.AllowAccessGroups.Add(subbits[1]); + accessids++; + } + else if (subbits[0] == "ws") + { + stinfo.AllowWorkgroups.Add(subbits[1]); + accessids++; + } + } + if((code != null) && (accessids > 0)) + { + Tokens.AddScopedToken(code, stinfo); + LogFormater.Info("Adding scopped command " + code + " with: " + accessids.ToString() + " access types"); + } + else + { + LogFormater.Warn("Scoped access code: "+raw+" did not pass checks"); + } + + } + public HttpAsService(SecondBot MainBot, JsonConfig BotConfig, bool running_in_docker) { Bot = MainBot; Config = BotConfig; + Tokens = new TokenStorage(); + // Load scoped tokens + if(running_in_docker == true) + { + // using ENV values + loadScopedTokensFromENV(); + } + else + { + // from file + loadScopedTokensFromFile(); + } + // Our web server is disposable Logger.UnregisterLogger(); using (var server = CreateWebServer(Config.Http_Host)) @@ -39,7 +153,7 @@ namespace BetterSecondBot.HttpService private WebServer CreateWebServer(string url) { - Tokens = new TokenStorage(); + var server = new WebServer(o => o .WithUrlPrefix(url) .WithMode(HttpListenerMode.EmbedIO)) @@ -123,8 +237,18 @@ namespace BetterSecondBot.HttpService public class TokenStorage { + // global access tokens protected Dictionary tokens = new Dictionary(); + // scoped access tokens + protected Dictionary scopedtokens = new Dictionary(); + + + public void AddScopedToken(string code, scopedTokenInfo info) + { + scopedtokens.Add(code, info); + } + public void PurgeExpired() { List oldTokens = new List(); @@ -154,7 +278,7 @@ namespace BetterSecondBot.HttpService return "Failed to remove token"; } - public bool Allow(string code, IPAddress IPaddress) + public bool Allow(string code, string workgroup, string command, IPAddress IPaddress) { if(tokens.ContainsKey(code) == true) { @@ -169,7 +293,35 @@ namespace BetterSecondBot.HttpService return true; } } - return false; + return ScopeAllow(code, workgroup, command); + } + + public bool ScopeAllow(string code, string workgroup, string command) + { + if (scopedtokens.ContainsKey(code) == false) + { + return false; + } + scopedTokenInfo info = scopedtokens[code]; + string build = workgroup + "/" + command; + if (info.AllowWorkgroups.Contains(workgroup) == true) + { + return true; + } + else if(info.AllowCommands.Contains(build) == true) + { + return true; + } + bool allowed = false; + foreach(string A in info.AllowAccessGroups) + { + allowed = accessgroupcontrol.isAllowed(A, workgroup, command); + if(allowed == true) + { + break; + } + } + return allowed; } public string CreateToken(IPAddress IP) @@ -237,4 +389,51 @@ namespace BetterSecondBot.HttpService public IPAddress IP = null; } + public class scopedTokenInfo + { + public List AllowWorkgroups = new List(); + public List AllowAccessGroups = new List(); + public List AllowCommands = new List(); + } + + public static class accessgroupcontrol + { + public static bool isAllowed(string permsgroupname, string workgroup, string command) + { + string build = workgroup + "/" + command; + List allowed = new List(); + if (permsgroupname == "chat") + { + allowed = new List(){ + "chat/localchathistory", + "chat/localchatsay", + "groups/getgroupchat", + "groups/sendgroupchat", + "groups/listgroups", + "im/chatwindows", + "im/listwithunread", + "im/getimchat", + "im/sendimchat" + }; + } + else if (permsgroupname == "giver") + { + allowed = new List(){ + "inventory/send", + "inventory/folders", + "inventory/contents" + }; + } + else if (permsgroupname == "movement") + { + allowed = new List(){ + "core/walkto", + "core/teleport", + "core/gesture" + }; + } + return allowed.Contains(build); + } + } + } diff --git a/CLI/HttpService/Im.cs b/CLI/HttpService/Im.cs index a2dec57..f0e803d 100644 --- a/CLI/HttpService/Im.cs +++ b/CLI/HttpService/Im.cs @@ -21,7 +21,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/chatwindows/{token}")] public object chatwindows(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "im", "chatwindows", getClientIP()) == true) { return BasicReply(JsonConvert.SerializeObject(bot.GetIMChatWindowKeyNames())); } @@ -33,7 +33,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/listwithunread/{token}")] public object listwithunread(string group, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "im", "listwithunread", getClientIP()) == true) { List unreadimwindows = new List(); foreach (UUID window in bot.IMChatWindows()) @@ -53,7 +53,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/haveunreadims/{token}")] public object haveunreadims(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "im", "haveunreadims", getClientIP()) == true) { bool reply = false; foreach(UUID window in bot.IMChatWindows()) @@ -76,7 +76,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/getimchat/{window}/{token}")] public object getimchat(string window, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "im", "getimchat", getClientIP()) == true) { if (UUID.TryParse(window, out UUID windowUUID) == true) { @@ -94,7 +94,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Post, "/sendimchat/{avatar}/{token}")] public object sendimchat(string avatar, string token, [FormField] string message) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "im", "sendimchat", getClientIP()) == true) { bot.GetCommandsInterface.Call("im", avatar + "~#~" + message, UUID.Zero, "~#~"); return BasicReply("ok"); diff --git a/CLI/HttpService/Inventory.cs b/CLI/HttpService/Inventory.cs index 51fea85..431aff2 100644 --- a/CLI/HttpService/Inventory.cs +++ b/CLI/HttpService/Inventory.cs @@ -20,7 +20,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/rezobject/{item}/{token}")] public object rezobject(string item, string token, [FormField] string newname) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "inventory", "rezobject", getClientIP()) == true) { bool status = bot.GetCommandsInterface.Call("RezObject", item); return BasicReply(status.ToString()); @@ -35,7 +35,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Post, "/rename/{item}/{token}")] public object rename(string item, string token, [FormField] string newname) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "inventory", "rename", getClientIP()) == true) { bool status = bot.GetCommandsInterface.Call("RenameInventory", "12~#~" + item + "~#~" + newname); return BasicReply(status.ToString()); @@ -50,7 +50,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/realuuid/{item}/{token}")] public object realuuid(string item, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "inventory", "realuuid", getClientIP()) == true) { if (UUID.TryParse(item, out UUID itemUUID) == true) { @@ -73,7 +73,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/send/{item}/{avatar}/{token}")] public object send(string item, string avatar, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "inventory", "send", getClientIP()) == true) { if (UUID.TryParse(item, out UUID itemUUID) == true) { @@ -94,7 +94,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/delete/{item}/{isfolder}/{token}")] public object delete(string item,string isfolder, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "inventory", "delete", getClientIP()) == true) { if (UUID.TryParse(item, out UUID itemUUID) == true) { @@ -118,7 +118,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/folders/{token}")] public object folders(string token) { - if(tokens.Allow(token, getClientIP()) == true) + if(tokens.Allow(token, "inventory", "folders", getClientIP()) == true) { string reply = HelperInventory.MapFolderJson(bot); if(reply != null) return BasicReply(reply); @@ -134,7 +134,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/contents/{folderUUID}/{token}")] public object Contents(string folderUUID, string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "inventory", "contents", getClientIP()) == true) { if (UUID.TryParse(folderUUID, out UUID folder) == true) { diff --git a/CLI/HttpService/ParcelEstate.cs b/CLI/HttpService/ParcelEstate.cs index ac442b8..c9ebe69 100644 --- a/CLI/HttpService/ParcelEstate.cs +++ b/CLI/HttpService/ParcelEstate.cs @@ -23,7 +23,7 @@ namespace BetterSecondBot.HttpService [Route(HttpVerbs.Get, "/parcelbanlist/{token}")] public object parcelbanlist(string token) { - if (tokens.Allow(token, getClientIP()) == true) + if (tokens.Allow(token, "parcelestate", "parcelbanlist", getClientIP()) == true) { if (bot.GetClient.Network.CurrentSim != null) { diff --git a/README.md b/README.md index f739dae..723cd50 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,7 @@ Secondbot is a CommandLine based bot for SecondLife based on libremetaverse reta ## Releases These are pre compiled versions for the most upto date please use docker or compile it yourself. -[Windows x64](https://github.com/Madpeterz/SecondBot/releases/tag/windows-x64) - -[Linux (Tested on Deb) x64](https://github.com/Madpeterz/SecondBot/releases/tag/linux-x64) +[View releases](https://github.com/Madpeterz/SecondBot/releases) ### Starting multiple bots from the same executable in windows @@ -66,5 +64,77 @@ and the command would look something like this sayexample!!!say|||Hello [C_ARG_1]{-}delay|||2500{-}say|||Bye [C_ARG_2] +### HTTP web interface + +by setting + +> Http_Enable to true +> Http_Host to http://*:8080 +> Security_WebUIKey to a vaild code (12 letters+numbers long or more) + +you will be able to connect to the bot via HTTP and use the webUI +to control the bot! + +[Dev server UI](http://webui.magicmadpeter.xyz/) +or +[Host it yourself](https://github.com/Madpeterz/secondbot_web_folders) + +### HTTP scoped tokens +--- +Scoped tokens allow you to hardcode access to the HTTP interface (if enabled) +and set more detailed control over what areas can be accessed +plus no need to give the full http + +> Security_WebUIKey + +You get set these tokens up by file or Environment Variables. +##### File + +> { +> "ScopedTokens": [ +> "t:[10charcode],ws:core,ws:group", +> "t:[10charcode],cm:chat/localchathistory", +> ] +> } +##### Environment Variables +|Name | Value| +|--|--| +| scoped_token_1 | "t:[10charcode],ws:core,ws:group" | +| scoped_token_2 | "t:[10charcode],cg:chat" | +| scoped_token_3 | "t:[10charcode],cm:chat/localchathistory" | + + +### HTTP scoped tokens (info) +--- + + command [cm] "example: `cm:chat/localchathistory` + + workspaces [ws] "example: ws: core, ws: groups" + + commandgroups [cg] "example: `cg:chat`" + chat + chat/localchathistory + chat/localchatsay + groups/getgroupchat + groups/sendgroupchat + groups/listgroups + im/chatwindows + im/listwithunread + im/getimchat + im/sendimchat + + giver + inventory/send + inventory/folders + inventory/contents + + + groupinvite + [WIP] + + movement + core/walkto + core/teleport + core/gesture