diff --git a/bank-client.lua b/bank-client.lua new file mode 100644 index 0000000..a819933 --- /dev/null +++ b/bank-client.lua @@ -0,0 +1,106 @@ +--[[ +The bank-client is a library that applications can include to interact with +a central bank server. Note that it functions over the Rednet protocol, so you +should call `rednet.open("modem-name")` first. +]]-- + +local client = {} + +client.state = { + auth = nil, + hostId = nil, + timeout = 3 +} + +local function requestRaw(msg) + if not client.state.hostId or not rednet.isOpen() then + return {success = false, error = "Client not initialized"} + end + rednet.send(client.state.hostId, msg, "BANK") + local remoteId, response = rednet.receive("BANK", client.state.timeout) + if not remoteId then + return {success = false, error = "Request timed out"} + end + return response +end + +local function request(command, data) + return requestRaw({command = command, data = data}) +end + +local function requestAuth(command, data) + if not client.loggedIn() then + return {success = false, error = "Client not logged in"} + end + return requestRaw({command = command, auth = client.state.auth, data = data}) +end + +-- Base functions + +function client.init(modemName, host) + rednet.open(modemName) + client.state.hostId = rednet.lookup("BANK", host) + return client.state.hostId ~= nil +end + +function client.logIn(username, password) + client.state.auth = {username = username, password = password} +end + +function client.logOut() + client.state.auth = nil +end + +function client.loggedIn() + return client.state.auth ~= nil +end + +-- BANK functions + +function client.getStatus() + local response = request("STATUS") + return response.success, response.error +end + +function client.createUser(username, password) + local response = request("CREATE_USER", {username = username, password = password}) + return response.success, response.error +end + +function client.deleteUser() + local response = requestAuth("DELETE_USER") + return response.success +end + +function client.renameUser(newUsername) + local response = requestAuth("RENAME_USER", {newUsername = newUsername}) + return response.success, response.error +end + +function client.getAccounts() + local response = requestAuth("GET_ACCOUNTS") + if not response.success then + return nil, response.error + end + return response.data +end + +function client.createAccount(accountName) + local response = requestAuth("CREATE_ACCOUNT", {name = accountName}) + if not response.success then + return nil, response.error + end + return response.data +end + +function client.deleteAccount(accountId) + local response = requestAuth("DELETE_ACCOUNT", {accountId = accountId}) + return response.success +end + +function client.renameAccount(accountId, newName) + local response = requestAuth("RENAME_ACCOUNT", {accountId = accountId, newName = newName}) + return response.success, response.error +end + +return client diff --git a/bank.lua b/bank.lua index 80eacbd..82ea39a 100644 --- a/bank.lua +++ b/bank.lua @@ -28,6 +28,22 @@ local function log(msg) g.appendAndDrawConsole(term, console, textutils.formatTime(os.time()) .. ": " .. msg, 1, 3) end +-- Initialize security key +local SECURITY_KEY_FILE = "key.txt" +local SECURITY_KEY = nil +if not fs.exists(SECURITY_KEY_FILE) then + local f = io.open(SECURITY_KEY_FILE, "w") + SECURITY_KEY = randomAccountId() .. "-" .. randomAccountId() .. "-" .. randomAccountId() + f:write(SECURITY_KEY) + f:close() + log("Generated new security key.") +else + local f = io.open(SECURITY_KEY_FILE, "r") + SECURITY_KEY = f:read("*a") + f:close() + log("Loaded stored security key.") +end + -- Helper functions local function readJSON(filename) @@ -119,7 +135,7 @@ local function createAccount(username, accountName) table.insert(accounts, newAccount) saveAccounts(username, accounts) log("Created account " .. newAccount.id .. " for user " .. username) - return true, newAccount.id + return true, newAccount end local function deleteAccount(username, accountId) @@ -193,7 +209,7 @@ end ----------------- -- Helper function to wrap another function in an authentication check. -local function authProtect(func) +local function authProtect(func, secure) return function (msg) if ( not msg.auth or @@ -204,10 +220,17 @@ local function authProtect(func) ) then return {success = false, error = "Invalid credentials"} end + if secure and (not msg.auth.key or msg.auth.key ~= SECURITY_KEY) then + return {success = false, error = "Missing security key"} + end return func(msg) end end +local function handleGetStatus(msg) + return {success = true} +end + local function handleCreateUser(msg) if not msg.data or not msg.data.username or not msg.data.password then return {success = false, error = "Invalid request. Requires data.username and data.password."} @@ -239,6 +262,17 @@ local function handleGetUserAccounts(msg) return {success = true, data = getAccounts(msg.auth.username)} end +local function handleCreateUserAccount(msg) + if not msg.data or not msg.data.name then + return {success = false, error = "Invalid request. Requires data.name."} + end + local success, errorOrAccount = createAccount(msg.auth.username, msg.data.name) + if not success then + return {success = false, error = errorOrAccount} + end + return {success = true, data = errorOrAccount} +end + local function handleDeleteUserAccount(msg) if not msg.data or not msg.data.accountId then return {success = false, error = "Invalid request. Requires data.accountId."} @@ -255,12 +289,15 @@ local function handleRenameUserAccount(msg) return {success = success, error = errorMsg} end -local function BANK_REQUESTS = { +-- A registry of all possible BANK requests, and their handler functions. +local BANK_REQUESTS = { + ["STATUS"] = handleGetStatus, ["CREATE_USER"] = handleCreateUser, - ["DELETE_USER"] = authProtect(handleDeleteUser) - ["RENAME_USER"] = authProtect(handleRenameUser) - ["GET_ACCOUNTS"] = authProtect(handleGetUserAccounts) - ["DELETE_ACCOUNT"] = authProtect(handleDeleteUserAccount) + ["DELETE_USER"] = authProtect(handleDeleteUser), + ["RENAME_USER"] = authProtect(handleRenameUser), + ["GET_ACCOUNTS"] = authProtect(handleGetUserAccounts), + ["CREATE_ACCOUNT"] = authProtect(handleCreateUserAccount), + ["DELETE_ACCOUNT"] = authProtect(handleDeleteUserAccount), ["RENAME_ACCOUNT"] = authProtect(handleRenameUserAccount) }