diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..582eda4 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,3 @@ +# ComputerCraft Programs & APIs + +ComputerCraft Lua programs and APIs diff --git a/lib/auth.lua b/lib/auth.lua index 75a8ee6..08edeae 100644 --- a/lib/auth.lua +++ b/lib/auth.lua @@ -1,3 +1,6 @@ +-- Desc: Authentication library +-- Auth: Layla Manley +-- Link: https://gitea.layla.gg/layla/computercraft settings.define("auth.token", { description = "Authentication token", type = "number", @@ -11,6 +14,75 @@ settings.define("auth.server", { }) +function login(username, password) + local data = { + ["action"] = "login", + ["username"] = username, + ["password"] = password + } + + local auth_server_id = lns.lookup(settings.get("auth.server")) + rednet.send(auth_server_id, data, "auth") + + local token = nil + while true do + id, msg = rednet.receive("auth") + if id == auth_server_id then + if msg == "invalid request" then + io.write("Invalid request\n") + return false + elseif msg == "user not found" then + io.write("User not found\n") + return false + elseif msg == "invalid password" then + io.write("Invalid password\n") + return false + else + token = msg + break + end + end + end + + settings.set("auth.username", username) + settings.set("auth.token", token) + settings.save() + return true +end + +function register(username, password) + local data = { + ["action"] = "register", + ["username"] = username, + ["password"] = password + } + + local auth_server_id = lns.lookup(settings.get("auth.server")) + rednet.send(auth_server_id, data, "auth") + + while true do + id, msg = rednet.receive("auth") + if id == auth_server_id then + if msg == "invalid request" then + io.write("Invalid request\n") + return false + elseif msg == "user already exists" then + io.write("User already exists\n") + return false + else + io.write("Registered user " .. username .. "\n") + return true + end + end + end +end + +function logout() + settings.set("auth.token", -1) + settings.save() + io.write("Logged out\n") +end + function get_token() return settings.get("auth.token") end diff --git a/lib/ccttp.lua b/lib/ccttp.lua new file mode 100644 index 0000000..95ece40 --- /dev/null +++ b/lib/ccttp.lua @@ -0,0 +1,14 @@ +-- Desc: CCTTP Networking Library +-- Auth: Layla Manley +-- Link: https://gitea.layla.gg/layla/computercraft +local interface Response + status: number + body: string +end + +function get(url: string): Response: + + + + return true +end diff --git a/lib/json.lua b/lib/json.lua new file mode 100644 index 0000000..d0c7280 --- /dev/null +++ b/lib/json.lua @@ -0,0 +1,212 @@ +-- Desc: JSON encoder/decoder +-- Auth: ElvishJerricco +-- Link: http://www.computercraft.info/forums2/index.php?/topic/5854-json-api-v201-for-computercraft/ +------------------------------------------------------------------ utils +local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"} + +local function isArray(t) + local max = 0 + for k,v in pairs(t) do + if type(k) ~= "number" then + return false + elseif k > max then + max = k + end + end + return max == #t +end + +local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true} +function removeWhite(str) + while whites[str:sub(1, 1)] do + str = str:sub(2) + end + return str +end + +------------------------------------------------------------------ encoding + +local function encodeCommon(val, pretty, tabLevel, tTracking) + local str = "" + + -- Tabbing util + local function tab(s) + str = str .. ("\t"):rep(tabLevel) .. s + end + + local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc) + str = str .. bracket + if pretty then + str = str .. "\n" + tabLevel = tabLevel + 1 + end + for k,v in iterator(val) do + tab("") + loopFunc(k,v) + str = str .. "," + if pretty then str = str .. "\n" end + end + if pretty then + tabLevel = tabLevel - 1 + end + if str:sub(-2) == ",\n" then + str = str:sub(1, -3) .. "\n" + elseif str:sub(-1) == "," then + str = str:sub(1, -2) + end + tab(closeBracket) + end + + -- Table encoding + if type(val) == "table" then + assert(not tTracking[val], "Cannot encode a table holding itself recursively") + tTracking[val] = true + if isArray(val) then + arrEncoding(val, "[", "]", ipairs, function(k,v) + str = str .. encodeCommon(v, pretty, tabLevel, tTracking) + end) + else + arrEncoding(val, "{", "}", pairs, function(k,v) + assert(type(k) == "string", "JSON object keys must be strings", 2) + str = str .. encodeCommon(k, pretty, tabLevel, tTracking) + str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking) + end) + end + -- String encoding + elseif type(val) == "string" then + str = '"' .. val:gsub("[%c\"\\]", controls) .. '"' + -- Number encoding + elseif type(val) == "number" or type(val) == "boolean" then + str = tostring(val) + else + error("JSON only supports arrays, objects, numbers, booleans, and strings", 2) + end + return str +end + +function encode(val) + return encodeCommon(val, false, 0, {}) +end + +function encodePretty(val) + return encodeCommon(val, true, 0, {}) +end + +------------------------------------------------------------------ decoding + +local decodeControls = {} +for k,v in pairs(controls) do + decodeControls[v] = k +end + +function parseBoolean(str) + if str:sub(1, 4) == "true" then + return true, removeWhite(str:sub(5)) + else + return false, removeWhite(str:sub(6)) + end +end + +function parseNull(str) + return nil, removeWhite(str:sub(5)) +end + +local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true} +function parseNumber(str) + local i = 1 + while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do + i = i + 1 + end + local val = tonumber(str:sub(1, i - 1)) + str = removeWhite(str:sub(i)) + return val, str +end + +function parseString(str) + str = str:sub(2) + local s = "" + while str:sub(1,1) ~= "\"" do + local next = str:sub(1,1) + str = str:sub(2) + assert(next ~= "\n", "Unclosed string") + + if next == "\\" then + local escape = str:sub(1,1) + str = str:sub(2) + + next = assert(decodeControls[next..escape], "Invalid escape character") + end + + s = s .. next + end + return s, removeWhite(str:sub(2)) +end + +function parseArray(str) + str = removeWhite(str:sub(2)) + + local val = {} + local i = 1 + while str:sub(1, 1) ~= "]" do + local v = nil + v, str = parseValue(str) + val[i] = v + i = i + 1 + str = removeWhite(str) + end + str = removeWhite(str:sub(2)) + return val, str +end + +function parseObject(str) + str = removeWhite(str:sub(2)) + + local val = {} + while str:sub(1, 1) ~= "}" do + local k, v = nil, nil + k, v, str = parseMember(str) + val[k] = v + str = removeWhite(str) + end + str = removeWhite(str:sub(2)) + return val, str +end + +function parseMember(str) + local k = nil + k, str = parseValue(str) + local val = nil + val, str = parseValue(str) + return k, val, str +end + +function parseValue(str) + local fchar = str:sub(1, 1) + if fchar == "{" then + return parseObject(str) + elseif fchar == "[" then + return parseArray(str) + elseif tonumber(fchar) ~= nil or numChars[fchar] then + return parseNumber(str) + elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then + return parseBoolean(str) + elseif fchar == "\"" then + return parseString(str) + elseif str:sub(1, 4) == "null" then + return parseNull(str) + end + return nil +end + +function decode(str) + str = removeWhite(str) + t = parseValue(str) + return t +end + +function decodeFromFile(path) + local file = assert(fs.open(path, "r")) + local decoded = decode(file.readAll()) + file.close() + return decoded +end \ No newline at end of file diff --git a/lib/lns.lua b/lib/lns.lua index 0bee2bd..a53b172 100644 --- a/lib/lns.lua +++ b/lib/lns.lua @@ -1,3 +1,6 @@ +-- Desc: LNS Client +-- Auth: Layla Manley +-- Link: https://gitea.layla.gg/layla/computercraft local cache = {} local cache_file = "/var/cache/lns_cache.db" diff --git a/lib/lum.lua b/lib/lum.lua index b96bd20..546b174 100644 --- a/lib/lum.lua +++ b/lib/lum.lua @@ -1,3 +1,6 @@ +-- Desc: Lum Package Manager +-- Auth: Layla Manley +-- Link: https://gitea.layla.gg/layla/computercraft local target_branch = "main" local bin_path = "/bin/" local lib_path = "/lib/" diff --git a/lib/sha256.lua b/lib/sha256.lua new file mode 100644 index 0000000..74f4137 --- /dev/null +++ b/lib/sha256.lua @@ -0,0 +1,195 @@ +-- Desc: SHA-256 hashing algorithm +-- Auth: GravityScore +-- Link: http://www.computercraft.info/forums2/index.php?/topic/8169-sha-256-in-pure-lua/ +------------------------------------------------------------------- +-- +-- Adaptation of the Secure Hashing Algorithm (SHA-244/256) +-- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm +-- +-- Using an adapted version of the bit library +-- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua +-- + +local MOD = 2^32 +local MODM = MOD-1 + +local function memoize(f) + local mt = {} + local t = setmetatable({}, mt) + function mt:__index(k) + local v = f(k) + t[k] = v + return v + end + return t +end + +local function make_bitop_uncached(t, m) + local function bitop(a, b) + local res,p = 0,1 + while a ~= 0 and b ~= 0 do + local am, bm = a % m, b % m + res = res + t[am][bm] * p + a = (a - am) / m + b = (b - bm) / m + p = p*m + end + res = res + (a + b) * p + return res + end + return bitop +end + +local function make_bitop(t) + local op1 = make_bitop_uncached(t,2^1) + local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end) + return make_bitop_uncached(op2, 2 ^ (t.n or 1)) +end + +local bxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4}) + +local function bxor(a, b, c, ...) + local z = nil + if b then + a = a % MOD + b = b % MOD + z = bxor1(a, b) + if c then z = bxor(z, c, ...) end + return z + elseif a then return a % MOD + else return 0 end +end + +local function band(a, b, c, ...) + local z + if b then + a = a % MOD + b = b % MOD + z = ((a + b) - bxor1(a,b)) / 2 + if c then z = bit32_band(z, c, ...) end + return z + elseif a then return a % MOD + else return MODM end +end + +local function bnot(x) return (-1 - x) % MOD end + +local function rshift1(a, disp) + if disp < 0 then return lshift(a,-disp) end + return math.floor(a % 2 ^ 32 / 2 ^ disp) +end + +local function rshift(x, disp) + if disp > 31 or disp < -31 then return 0 end + return rshift1(x % MOD, disp) +end + +local function lshift(a, disp) + if disp < 0 then return rshift(a,-disp) end + return (a * 2 ^ disp) % 2 ^ 32 +end + +local function rrotate(x, disp) + x = x % MOD + disp = disp % 32 + local low = band(x, 2 ^ disp - 1) + return rshift(x, disp) + lshift(low, 32 - disp) +end + +local k = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +} + +local function str2hexa(s) + return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end)) +end + +local function num2s(l, n) + local s = "" + for i = 1, n do + local rem = l % 256 + s = string.char(rem) .. s + l = (l - rem) / 256 + end + return s +end + +local function s232num(s, i) + local n = 0 + for i = i, i + 3 do n = n*256 + string.byte(s, i) end + return n +end + +local function preproc(msg, len) + local extra = 64 - ((len + 9) % 64) + len = num2s(8 * len, 8) + msg = msg .. "\128" .. string.rep("\0", extra) .. len + assert(#msg % 64 == 0) + return msg +end + +local function initH256(H) + H[1] = 0x6a09e667 + H[2] = 0xbb67ae85 + H[3] = 0x3c6ef372 + H[4] = 0xa54ff53a + H[5] = 0x510e527f + H[6] = 0x9b05688c + H[7] = 0x1f83d9ab + H[8] = 0x5be0cd19 + return H +end + +local function digestblock(msg, i, H) + local w = {} + for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end + for j = 17, 64 do + local v = w[j - 15] + local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) + v = w[j - 2] + w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) + end + + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + for i = 1, 64 do + local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) + local maj = bxor(band(a, b), band(a, c), band(b, c)) + local t2 = s0 + maj + local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) + local ch = bxor (band(e, f), band(bnot(e), g)) + local t1 = h + s1 + ch + k[i] + w[i] + h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 + end + + H[1] = band(H[1] + a) + H[2] = band(H[2] + b) + H[3] = band(H[3] + c) + H[4] = band(H[4] + d) + H[5] = band(H[5] + e) + H[6] = band(H[6] + f) + H[7] = band(H[7] + g) + H[8] = band(H[8] + h) +end + +local function sha256(msg) + msg = preproc(msg, #msg) + local H = initH256({}) + for i = 1, #msg, 64 do digestblock(msg, i, H) end + return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) .. + num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4)) +end \ No newline at end of file diff --git a/packages/auth.lua b/packages/auth.lua index 7b4f434..0dcf183 100644 --- a/packages/auth.lua +++ b/packages/auth.lua @@ -6,10 +6,6 @@ if args[1] == nil then end local action = args[1] -local server = settings.get("auth.server") - - -auth_server_id = lns.lookup(server) if action == "login" then io.write("Username: ") @@ -17,43 +13,12 @@ if action == "login" then io.write("Password: ") local password = read("*") - local data = { - ["action"] = "login", - ["username"] = username, - ["password"] = password - } - - rednet.send(auth_server_id, data, "auth") - - while true do - id, msg = rednet.receive("auth") - if id == auth_server_id then - if msg == "invalid request" then - io.write("Invalid request\n") - return - elseif msg == "user not found" then - io.write("User not found\n") - return - elseif msg == "invalid password" then - io.write("Invalid password\n") - return - else - token = msg - break - end - end + if login(username, password) then + io.write("Logged in as " .. username .. "\n") end - settings.set("auth.username", username) - settings.set("auth.token", token) - settings.save() - io.write("Logged in as " .. username .. "\n") - return - elseif action == "logout" then - settings.set("auth.token", -1) - settings.save() - io.write("Logged out\n") + auth.logout() return elseif action == "register" then @@ -69,29 +34,7 @@ elseif action == "register" then return end - local data = { - ["action"] = "register", - ["username"] = username, - ["password"] = password - } - - rednet.send(auth_server_id, data, "auth") - - while true do - id, msg = rednet.receive("auth") - if id == auth_server_id then - if msg == "invalid request" then - io.write("Invalid request\n") - return - elseif msg == "user already exists" then - io.write("User already exists\n") - return - else - io.write("Registered user " .. username .. "\n") - return - end - end - end + auth.register(username, password) elseif action == "group" then local subaction = args[2] if subaction == nil then diff --git a/packages/auth_server.lua b/packages/auth_server.lua index 9fcf97d..566550a 100644 --- a/packages/auth_server.lua +++ b/packages/auth_server.lua @@ -1,4 +1,9 @@ +settings.define("auth_server.salt", { + description = "Salt for hashing passwords", + type = "string", + default = "3m&LmNm7" +}) local data = { users = {} @@ -7,6 +12,7 @@ local data = { -- data = { -- users = { -- ["username"] = { +-- token = "token", -- password = "hashed password", -- groups = { -- ["group"] = true @@ -31,28 +37,15 @@ function load_data() end function generate_token(user) - local token = hash(user .. os.time() .. math.random()) + local token = sha256.sha256(user .. os.time() .. math.random()) data.users[user].token = token return token end function convert_password(password) - return hash(password .. "3m&LmNm7") -end - --- Does a complex hash of a string to make it harder to guess -function hash(str) - local hash = 0 - local len = string.len(str) - local byte = 0 - - for i = 1, len do - byte = string.byte(str, i) - hash = bit32.band(hash * 31 + byte, 0xFFFFFFFF) - end - - return hash + local salt = settings.get("auth_server.salt") + return sha256.sha256(password .. salt) end function log(str)