summaryrefslogtreecommitdiff
path: root/indra/newview
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview')
-rw-r--r--indra/newview/llappviewer.cpp20
-rw-r--r--indra/newview/llpanellogin.cpp57
-rw-r--r--indra/newview/llpanellogin.h2
-rw-r--r--indra/newview/llpanelloginlistener.cpp196
-rw-r--r--indra/newview/llpanelloginlistener.h2
-rw-r--r--indra/newview/llsecapi.h2
-rw-r--r--indra/newview/scripts/lua/require/login.lua40
-rw-r--r--indra/newview/scripts/lua/test_login.lua13
8 files changed, 285 insertions, 47 deletions
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index ef12fe0bd3..ff29f64aeb 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -113,6 +113,7 @@
#include "llgltfmateriallist.h"
// Linden library includes
+#include "fsyspath.h"
#include "llavatarnamecache.h"
#include "lldiriterator.h"
#include "llexperiencecache.h"
@@ -1216,8 +1217,23 @@ bool LLAppViewer::init()
"--luafile", "LuaScript",
[](const LLSD& script)
{
- // no completion callback: we don't need to know
- LLLUAmanager::runScriptFile(script);
+ LLSD paths(gSavedSettings.getLLSD("LuaCommandPath"));
+ LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL;
+ for (const auto& path : llsd::inArray(paths))
+ {
+ // if script path is already absolute, operator/() preserves it
+ auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / path.asString());
+ auto absscript{ (abspath / script.asString()) };
+ std::error_code ec;
+ if (std::filesystem::exists(absscript, ec))
+ {
+ // no completion callback: we don't need to know
+ LLLUAmanager::runScriptFile(absscript.u8string());
+ return; // from lambda
+ }
+ }
+ LL_WARNS("Lua") << "--luafile " << std::quoted(script.asString())
+ << " not found on " << paths << LL_ENDL;
});
processComposeSwitch(
"LuaAutorunPath", "LuaAutorunPath",
diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp
index 86f56f0949..a6dbcedc4b 100644
--- a/indra/newview/llpanellogin.cpp
+++ b/indra/newview/llpanellogin.cpp
@@ -81,15 +81,16 @@ BOOL LLPanelLogin::sCredentialSet = FALSE;
LLPointer<LLCredential> load_user_credentials(std::string &user_key)
{
- if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid()))
+ std::string grid{ LLGridManager::instance().getGrid() };
+ if (gSecAPIHandler->hasCredentialMap("login_list", grid))
{
// user_key should be of "name Resident" format
- return gSecAPIHandler->loadFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), user_key);
+ return gSecAPIHandler->loadFromCredentialMap("login_list", grid, user_key);
}
else
{
// legacy (or legacy^2, since it also tries to load from settings)
- return gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid());
+ return gSecAPIHandler->loadCredential(grid);
}
}
@@ -223,7 +224,8 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect,
sendChildToBack(getChildView("forgot_password_text"));
sendChildToBack(getChildView("sign_up_text"));
- std::string current_grid = LLGridManager::getInstance()->getGrid();
+ LLGridManager& gridmgr{ LLGridManager::instance() };
+ std::string current_grid = gridmgr.getGrid();
if (!mFirstLoginThisInstall)
{
LLComboBox* favorites_combo = getChild<LLComboBox>("start_location_combo");
@@ -237,7 +239,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect,
// Load all of the grids, sorted, and then add a bar and the current grid at the top
server_choice_combo->removeall();
- std::map<std::string, std::string> known_grids = LLGridManager::getInstance()->getKnownGrids();
+ std::map<std::string, std::string> known_grids = gridmgr.getKnownGrids();
for (std::map<std::string, std::string>::iterator grid_choice = known_grids.begin();
grid_choice != known_grids.end();
grid_choice++)
@@ -251,7 +253,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect,
server_choice_combo->sortByName();
LL_DEBUGS("AppInit") << "adding current " << current_grid << LL_ENDL;
- server_choice_combo->add(LLGridManager::getInstance()->getGridLabel(),
+ server_choice_combo->add(gridmgr.getGridLabel(),
current_grid,
ADD_TOP);
server_choice_combo->selectFirstItem();
@@ -760,6 +762,7 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl)
LL_DEBUGS("AppInit")<<new_start_slurl.asString()<<LL_ENDL;
LLComboBox* location_combo = sInstance->getChild<LLComboBox>("start_location_combo");
+ LLGridManager& gridmgr{ LLGridManager::instance() };
/*
* Determine whether or not the new_start_slurl modifies the grid.
*
@@ -774,17 +777,17 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl)
{
case LLSLURL::LOCATION:
{
- std::string slurl_grid = LLGridManager::getInstance()->getGrid(new_start_slurl.getGrid());
+ std::string slurl_grid = gridmgr.getGrid(new_start_slurl.getGrid());
if ( ! slurl_grid.empty() ) // is that a valid grid?
{
- if ( slurl_grid != LLGridManager::getInstance()->getGrid() ) // new grid?
+ if ( slurl_grid != gridmgr.getGrid() ) // new grid?
{
// the slurl changes the grid, so update everything to match
- LLGridManager::getInstance()->setGridChoice(slurl_grid);
+ gridmgr.setGridChoice(slurl_grid);
// update the grid selector to match the slurl
LLComboBox* server_combo = sInstance->getChild<LLComboBox>("server_combo");
- std::string server_label(LLGridManager::getInstance()->getGridLabel(slurl_grid));
+ std::string server_label(gridmgr.getGridLabel(slurl_grid));
server_combo->setSimple(server_label);
updateServer(); // to change the links and splash screen
@@ -831,8 +834,7 @@ void LLPanelLogin::autologinToLocation(const LLSLURL& slurl)
if ( LLPanelLogin::sInstance != NULL )
{
- void* unused_parameter = 0;
- LLPanelLogin::sInstance->onClickConnect(unused_parameter);
+ LLPanelLogin::sInstance->onClickConnect(false);
}
}
@@ -872,7 +874,8 @@ void LLPanelLogin::loadLoginPage()
{
if (!sInstance) return;
- LLURI login_page = LLURI(LLGridManager::getInstance()->getLoginPage());
+ LLGridManager& gridmgr{ LLGridManager::instance() };
+ LLURI login_page = LLURI(gridmgr.getLoginPage());
LLSD params(login_page.queryMap());
LL_DEBUGS("AppInit") << "login_page: " << login_page << LL_ENDL;
@@ -899,7 +902,7 @@ void LLPanelLogin::loadLoginPage()
params["channel"] = LLVersionInfo::instance().getChannel();
// Grid
- params["grid"] = LLGridManager::getInstance()->getGridId();
+ params["grid"] = gridmgr.getGridId();
// add OS info
params["os"] = LLOSInfo::instance().getOSStringSimple();
@@ -916,7 +919,7 @@ void LLPanelLogin::loadLoginPage()
login_page.path(),
params));
- gViewerWindow->setMenuBackgroundColor(false, !LLGridManager::getInstance()->isInProductionGrid());
+ gViewerWindow->setMenuBackgroundColor(false, !gridmgr.isInProductionGrid());
LLMediaCtrl* web_browser = sInstance->getChild<LLMediaCtrl>("login_html");
if (web_browser->getCurrentNavUrl() != login_uri.asString())
@@ -949,9 +952,10 @@ void LLPanelLogin::onClickConnect(bool commit_fields)
// the grid definitions may come from a user-supplied grids.xml, so they may not be good
LL_DEBUGS("AppInit")<<"grid "<<combo_val.asString()<<LL_ENDL;
+ LLGridManager& gridmgr{ LLGridManager::instance() };
try
{
- LLGridManager::getInstance()->setGridChoice(combo_val.asString());
+ gridmgr.setGridChoice(combo_val.asString());
}
catch (LLInvalidGridName ex)
{
@@ -984,7 +988,7 @@ void LLPanelLogin::onClickConnect(bool commit_fields)
std::string identifier_type;
cred->identifierType(identifier_type);
LLSD allowed_credential_types;
- LLGridManager::getInstance()->getLoginIdentifierTypes(allowed_credential_types);
+ gridmgr.getLoginIdentifierTypes(allowed_credential_types);
// check the typed in credential type against the credential types expected by the server.
for(LLSD::array_iterator i = allowed_credential_types.beginArray();
@@ -1133,6 +1137,7 @@ void LLPanelLogin::updateServer()
{
if (sInstance)
{
+ LLGridManager& gridmgr{ LLGridManager::instance() };
try
{
// if they've selected another grid, we should load the credentials
@@ -1152,7 +1157,7 @@ void LLPanelLogin::updateServer()
// populate dropbox and setFields
// Note: following call is related to initializeLoginInfo()
- LLPointer<LLCredential> credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid());
+ LLPointer<LLCredential> credential = gSecAPIHandler->loadCredential(gridmgr.getGrid());
sInstance->populateUserList(credential);
// restore creds
@@ -1165,12 +1170,12 @@ void LLPanelLogin::updateServer()
{
// populate dropbox and setFields
// Note: following call is related to initializeLoginInfo()
- LLPointer<LLCredential> credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid());
+ LLPointer<LLCredential> credential = gSecAPIHandler->loadCredential(gridmgr.getGrid());
sInstance->populateUserList(credential);
}
// update the login panel links
- bool system_grid = LLGridManager::getInstance()->isSystemGrid();
+ bool system_grid = gridmgr.isSystemGrid();
// Want to vanish not only create_new_account_btn, but also the
// title text over it, so turn on/off the whole layout_panel element.
@@ -1219,11 +1224,12 @@ void LLPanelLogin::populateUserList(LLPointer<LLCredential> credential)
getChild<LLUICtrl>("password_edit")->setValue(std::string());
mUsernameLength = 0;
mPasswordLength = 0;
+ std::string grid{ LLGridManager::instance().getGrid() };
- if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid()))
+ if (gSecAPIHandler->hasCredentialMap("login_list", grid))
{
LLSecAPIHandler::credential_map_t credencials;
- gSecAPIHandler->loadCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), credencials);
+ gSecAPIHandler->loadCredentialMap("login_list", grid, credencials);
LLSecAPIHandler::credential_map_t::iterator cr_iter = credencials.begin();
LLSecAPIHandler::credential_map_t::iterator cr_end = credencials.end();
@@ -1278,7 +1284,8 @@ void LLPanelLogin::onSelectServer()
LLComboBox* server_combo = getChild<LLComboBox>("server_combo");
LLSD server_combo_val = server_combo->getSelectedValue();
LL_INFOS("AppInit") << "grid "<<server_combo_val.asString()<< LL_ENDL;
- LLGridManager::getInstance()->setGridChoice(server_combo_val.asString());
+ auto& gridmgr{ LLGridManager::instance() };
+ gridmgr.setGridChoice(server_combo_val.asString());
addFavoritesToStartLocation();
/*
@@ -1308,7 +1315,7 @@ void LLPanelLogin::onSelectServer()
LLSLURL slurl(location); // generata a slurl from the location combo contents
if (location.empty()
|| (slurl.getType() == LLSLURL::LOCATION
- && slurl.getGrid() != LLGridManager::getInstance()->getGrid())
+ && slurl.getGrid() != gridmgr.getGrid())
)
{
// the grid specified by the location is not this one, so clear the combo
@@ -1338,7 +1345,7 @@ bool LLPanelLogin::getShowFavorites()
}
// static
-std::string LLPanelLogin::getUserName(LLPointer<LLCredential> &cred)
+std::string LLPanelLogin::getUserName(const LLPointer<LLCredential> &cred)
{
if (cred.isNull())
{
diff --git a/indra/newview/llpanellogin.h b/indra/newview/llpanellogin.h
index 7f4c2487bf..a7019ea2a3 100644
--- a/indra/newview/llpanellogin.h
+++ b/indra/newview/llpanellogin.h
@@ -88,7 +88,7 @@ public:
static bool getShowFavorites();
// extract name from cred in a format apropriate for username field
- static std::string getUserName(LLPointer<LLCredential> &cred);
+ static std::string getUserName(const LLPointer<LLCredential> &cred);
private:
friend class LLPanelLoginListener;
diff --git a/indra/newview/llpanelloginlistener.cpp b/indra/newview/llpanelloginlistener.cpp
index fb6f034b7f..aeaf2d1d00 100644
--- a/indra/newview/llpanelloginlistener.cpp
+++ b/indra/newview/llpanelloginlistener.cpp
@@ -32,9 +32,19 @@
#include "llpanelloginlistener.h"
// STL headers
// std headers
+#include <iomanip>
+#include <memory>
// external library headers
// other Linden headers
+#include "llcombobox.h"
#include "llpanellogin.h"
+#include "llsdutil.h"
+#include "llsecapi.h"
+#include "llslurl.h"
+#include "llstartup.h"
+#include "lluictrl.h"
+#include "llviewernetwork.h"
+#include "stringize.h"
LLPanelLoginListener::LLPanelLoginListener(LLPanelLogin* instance):
LLEventAPI("LLPanelLogin", "Access to LLPanelLogin methods"),
@@ -43,9 +53,195 @@ LLPanelLoginListener::LLPanelLoginListener(LLPanelLogin* instance):
add("onClickConnect",
"Pretend user clicked the \"Log In\" button",
&LLPanelLoginListener::onClickConnect);
+ add("login",
+ "Login to Second Life with saved credentials.\n"
+ "Pass [\"username\"] to select credentials previously saved with that username.\n"
+ "Pass [\"slurl\"] to specify target location.\n"
+ "Pass [\"grid\"] to select one of the valid grids in grids.xml.",
+ &LLPanelLoginListener::login,
+ llsd::map("reply", LLSD::String()));
+ add("savedLogins",
+ "Return \"logins\" as a list of {} saved login names.\n"
+ "Pass [\"grid\"] to select one of the valid grids in grids.xml.",
+ &LLPanelLoginListener::savedLogins,
+ llsd::map("reply", LLSD::String()));
}
void LLPanelLoginListener::onClickConnect(const LLSD&) const
{
mPanel->onClickConnect(false);
}
+
+namespace
+{
+
+ std::string gridkey(const std::string& grid)
+ {
+ if (grid.find('.') != std::string::npos)
+ {
+ return grid;
+ }
+ else
+ {
+ return stringize("util.", grid, ".lindenlab.com");
+ }
+ }
+
+} // anonymous namespace
+
+void LLPanelLoginListener::login(const LLSD& args) const
+{
+ // Although we require a "reply" key, don't instantiate a Response object:
+ // if things go well, response to our invoker will come later, once the
+ // viewer gets the response from the login server.
+ std::string username(args["username"]), slurlstr(args["slurl"]), grid(args["grid"]);
+ LL_DEBUGS("AppInit") << "Login with saved credentials";
+ if (! username.empty())
+ {
+ LL_CONT << " for user " << std::quoted(username);
+ }
+ if (! slurlstr.empty())
+ {
+ LL_CONT << " to location " << std::quoted(slurlstr);
+ }
+ if (! grid.empty())
+ {
+ LL_CONT << " on grid " << std::quoted(grid);
+ }
+ LL_CONT << LL_ENDL;
+
+ // change grid first, allowing slurl to override
+ auto& gridmgr{ LLGridManager::instance() };
+ if (! grid.empty())
+ {
+ grid = gridkey(grid);
+ // setGridChoice() can throw LLInvalidGridName -- but if so, let it
+ // propagate, trusting that LLEventAPI will catch it and send an
+ // appropriate reply.
+ auto server_combo = mPanel->getChild<LLComboBox>("server_combo");
+ server_combo->setSimple(gridmgr.getGridLabel(grid));
+ LLPanelLogin::updateServer();
+ }
+ if (! slurlstr.empty())
+ {
+ LLSLURL slurl(slurlstr);
+ if (! slurl.isSpatial())
+ {
+ // don't bother with LLTHROW() because we expect LLEventAPI to
+ // catch and report back to invoker
+ throw DispatchError(stringize("Invalid start SLURL ", std::quoted(slurlstr)));
+ }
+ // Bypass LLStartUp::setStartSLURL() because, after validating as
+ // above, it bounces right back to LLPanelLogin::onUpdateStartSLURL().
+ // It also sets the "NextLoginLocation" user setting, but as with grid,
+ // we don't yet know whether that's desirable for scripted login.
+ LLPanelLogin::onUpdateStartSLURL(slurl);
+ }
+ if (! username.empty())
+ {
+ // Transform (e.g.) "Nat Linden" to the internal form expected by
+ // loadFromCredentialMap(), e.g. "nat_linden"
+ LLStringUtil::toLower(username);
+ LLStringUtil::replaceChar(username, ' ', '_');
+ LLStringUtil::replaceChar(username, '.', '_');
+
+ // call gridmgr.getGrid() because our caller didn't necessarily pass
+ // ["grid"] -- or it might have been overridden by the SLURL
+ auto cred = gSecAPIHandler->loadFromCredentialMap("login_list",
+ gridmgr.getGrid(),
+ username);
+ LLPanelLogin::setFields(cred);
+ }
+
+ if (mPanel->getChild<LLUICtrl>("username_combo")->getValue().asString().empty() ||
+ mPanel->getChild<LLUICtrl>("password_edit") ->getValue().asString().empty())
+ {
+ // as above, let LLEventAPI report back to invoker
+ throw DispatchError(stringize("Unrecognized username ", std::quoted(username)));
+ }
+
+ // Here we're about to trigger actual login, which is all we can do in
+ // this method. All subsequent responses must come via the "login"
+ // LLEventPump.
+ LLEventPumps::instance().obtain("login").listen(
+ "Lua login",
+ [args]
+ (const LLBoundListener& conn, const LLSD& update)
+ {
+ LLSD response{ llsd::map("ok", false) };
+ if (update["change"] == "connect")
+ {
+ // success!
+ response["ok"] = true;
+ }
+ else if (update["change"] == "disconnect")
+ {
+ // Does this ever actually happen?
+ // LLLoginInstance::handleDisconnect() says it's a placeholder.
+ response["error"] = "login disconnect";
+ }
+ else if (update["change"] == "fail.login")
+ {
+ // why?
+ // LLLoginInstance::handleLoginFailure() has special code to
+ // handle "tos", "update" and "mfa_challenge". Do not respond
+ // automatically because these things are SUPPOSED to engage
+ // the user.
+ // Copy specific response fields because there's an enormous
+ // chunk of stuff that comes back on success.
+ LLSD data{ update["data"] };
+ const char* fields[] = {
+ "reason",
+ "error_code"
+ };
+ for (auto field : fields)
+ {
+ response[field] = data[field];
+ }
+ response["error"] = update["message"];
+ }
+ else
+ {
+ // Ignore all other "change" values: LLLogin sends multiple update
+ // events. Only a few of them (above) indicate completion.
+ return;
+ }
+ // For all the completion cases, disconnect from the "login"
+ // LLEventPump.
+ conn.disconnect();
+ // and at last, send response to the original invoker
+ sendReply(response, args);
+ });
+
+ // Turn the Login crank.
+ mPanel->onClickConnect(false);
+}
+
+LLSD LLPanelLoginListener::savedLogins(const LLSD& args) const
+{
+ LL_INFOS() << "called with " << args << LL_ENDL;
+ std::string grid = (args.has("grid")? gridkey(args["grid"].asString())
+ : LLGridManager::instance().getGrid());
+ if (! gSecAPIHandler->hasCredentialMap("login_list", grid))
+ {
+ LL_INFOS() << "no creds for " << grid << LL_ENDL;
+ return llsd::map("error",
+ stringize("No saved logins for grid ", std::quoted(grid)));
+ }
+ LLSecAPIHandler::credential_map_t creds;
+ gSecAPIHandler->loadCredentialMap("login_list", grid, creds);
+ auto result = llsd::map(
+ "logins",
+ llsd::toArray(
+ creds,
+ [](const auto& pair)
+ {
+ return llsd::map(
+ "name",
+ LLPanelLogin::getUserName(pair.second),
+ "key",
+ pair.first);
+ }));
+ LL_INFOS() << "returning creds " << result << LL_ENDL;
+ return result;
+}
diff --git a/indra/newview/llpanelloginlistener.h b/indra/newview/llpanelloginlistener.h
index b552ccd5b0..f2f4d70ff9 100644
--- a/indra/newview/llpanelloginlistener.h
+++ b/indra/newview/llpanelloginlistener.h
@@ -40,6 +40,8 @@ public:
private:
void onClickConnect(const LLSD&) const;
+ void login(const LLSD&) const;
+ LLSD savedLogins(const LLSD&) const;
LLPanelLogin* mPanel;
};
diff --git a/indra/newview/llsecapi.h b/indra/newview/llsecapi.h
index 5cc78d09dc..367bfe1469 100644
--- a/indra/newview/llsecapi.h
+++ b/indra/newview/llsecapi.h
@@ -311,7 +311,7 @@ public:
mIdentifier = identifier;
mAuthenticator = authenticator;
}
- virtual LLSD getIdentifier() { return mIdentifier; }
+ virtual LLSD getIdentifier() const { return mIdentifier; }
virtual void identifierType(std::string& idType);
virtual LLSD getAuthenticator() { return mAuthenticator; }
virtual void authenticatorType(std::string& authType);
diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua
index 0d8591cace..50fc9e3793 100644
--- a/indra/newview/scripts/lua/require/login.lua
+++ b/indra/newview/scripts/lua/require/login.lua
@@ -1,19 +1,33 @@
-local UI = require 'UI'
local leap = require 'leap'
+local startup = require 'startup'
+local mapargs = require 'mapargs'
-local function login(username, password)
- if username and password then
- local userpath = '//username_combo/Combo Text Entry'
- local passpath = '//password_edit'
- -- first clear anything presently in those text fields
- for _, path in pairs({userpath, passpath}) do
- UI.click(path)
- UI.keypress{keysym='Backsp', path=path}
- end
- UI.type{path=userpath, text=username}
- UI.type{path=passpath, text=password}
+local login = {}
+
+local function ensure_login_state(op)
+ -- no point trying to login until the viewer is ready
+ startup.wait('STATE_LOGIN_WAIT')
+ -- Once we've actually started login, LLPanelLogin is destroyed, and so is
+ -- its "LLPanelLogin" listener. At that point,
+ -- leap.request("LLPanelLogin", ...) will hang indefinitely because no one
+ -- is listening on that LLEventPump any more. Intercept that case and
+ -- produce a sensible error.
+ local state = startup.state()
+ if startup.before('STATE_LOGIN_WAIT', state) then
+ error(`Can't engage login operation {op} once we've reached state {state}`, 2)
end
- leap.send('LLPanelLogin', {op='onClickConnect'})
+end
+
+function login.login(...)
+ ensure_login_state('login')
+ local args = mapargs('username,grid,slurl', ...)
+ args.op = 'login'
+ return leap.request('LLPanelLogin', args)
+end
+
+function login.savedLogins(grid)
+ ensure_login_state('savedLogins')
+ return leap.request('LLPanelLogin', {op='savedLogins'})['logins']
end
return login
diff --git a/indra/newview/scripts/lua/test_login.lua b/indra/newview/scripts/lua/test_login.lua
index a8c31807bc..54d3635a41 100644
--- a/indra/newview/scripts/lua/test_login.lua
+++ b/indra/newview/scripts/lua/test_login.lua
@@ -1,7 +1,10 @@
-startup = require 'startup'
+inspect = require 'inspect'
login = require 'login'
-startup.wait('STATE_LOGIN_WAIT')
-login()
--- Fill in valid credentials as they would be entered on the login screen
--- login('My Username', 'password')
+local grid = 'agni'
+print(inspect(login.savedLogins(grid)))
+
+print(inspect(login.login{
+ username='Your Username',
+ grid=grid
+ }))