First pass login support.
This commit is contained in:
parent
f51b1cf4c0
commit
52d2aa52e2
|
|
@ -11,17 +11,20 @@
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import asyncio
|
import asyncio
|
||||||
from ament_index_python import get_package_share_directory
|
from ament_index_python import get_package_share_directory
|
||||||
|
from am_i_up.Authenticator import Authenticator
|
||||||
import rclpy
|
import rclpy
|
||||||
|
|
||||||
class Api:
|
class Api:
|
||||||
def __init__(self, facts):
|
def __init__(self, facts):
|
||||||
self._facts = facts
|
self._facts = facts
|
||||||
|
|
||||||
|
self._authenticator = Authenticator(facts.get_password(), facts.get_open_endpoints())
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
ui_share_directory = get_package_share_directory(self._facts.get_ui_pkg())
|
ui_share_directory = get_package_share_directory(self._facts.get_ui_pkg())
|
||||||
ui_static_directory = ui_share_directory + "/dist/assets"
|
ui_static_directory = ui_share_directory + "/dist/assets"
|
||||||
|
|
||||||
app = web.Application()
|
app = web.Application(middlewares=[self._authenticator.build_middleware()])
|
||||||
app.add_routes([
|
app.add_routes([
|
||||||
web.get('/api/ping', self.ping),
|
web.get('/api/ping', self.ping),
|
||||||
web.get('/api/uptime', self.uptime),
|
web.get('/api/uptime', self.uptime),
|
||||||
|
|
@ -30,6 +33,7 @@ class Api:
|
||||||
web.get('/api/status', self.status),
|
web.get('/api/status', self.status),
|
||||||
web.get("/api/position", self.position),
|
web.get("/api/position", self.position),
|
||||||
web.get("/api/diagnostics", self.diagnostics),
|
web.get("/api/diagnostics", self.diagnostics),
|
||||||
|
web.post("/api/login", self.login),
|
||||||
web.static("/assets", ui_static_directory),
|
web.static("/assets", ui_static_directory),
|
||||||
# we're not actually using key anywhere, but doing this allows react router
|
# we're not actually using key anywhere, but doing this allows react router
|
||||||
# to work correctly.
|
# to work correctly.
|
||||||
|
|
@ -49,6 +53,23 @@ class Api:
|
||||||
await asyncio.sleep(3600)
|
await asyncio.sleep(3600)
|
||||||
await runner.cleanup()
|
await runner.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
async def login(self, request):
|
||||||
|
request_dict = await request.json()
|
||||||
|
login_successful = self._authenticator.confirm_creds(request_dict["password"])
|
||||||
|
if not login_successful:
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
redirect_location = f"{request.url.scheme}://{request.url.host_port_subcomponent}/"
|
||||||
|
redirect_header = {"Location": redirect_location}
|
||||||
|
response = web.Response(status=302, headers=redirect_header)
|
||||||
|
response.set_cookie(self._authenticator.cookie_name(),
|
||||||
|
self._authenticator.make_cookie_for_user(),
|
||||||
|
secure=False,
|
||||||
|
httponly=False,
|
||||||
|
samesite="Strict")
|
||||||
|
return response
|
||||||
|
|
||||||
async def index(self, request):
|
async def index(self, request):
|
||||||
ui_share_directory = get_package_share_directory(self._facts.get_ui_pkg())
|
ui_share_directory = get_package_share_directory(self._facts.get_ui_pkg())
|
||||||
ui_index_path = ui_share_directory + "/dist/index.html"
|
ui_index_path = ui_share_directory + "/dist/index.html"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
from aiohttp import web
|
||||||
|
import secrets
|
||||||
|
import re
|
||||||
|
|
||||||
|
# TODO: Note here about how this is not supposed to be put on the
|
||||||
|
# internet and if you wanted to do that it needs to be done
|
||||||
|
# differently.
|
||||||
|
|
||||||
|
class Authenticator():
|
||||||
|
def __init__(self, password, open_endpoints):
|
||||||
|
self._password = password
|
||||||
|
self._open_endpoints = open_endpoints
|
||||||
|
self._valid_cookies = []
|
||||||
|
|
||||||
|
def confirm_creds(self, password):
|
||||||
|
return secrets.compare_digest(password, self._password)
|
||||||
|
|
||||||
|
def make_cookie_for_user(self):
|
||||||
|
cookie = secrets.token_urlsafe(32)
|
||||||
|
# TODO: Normally these would expire, but we're assuming
|
||||||
|
# this app gets closed like once a day so.
|
||||||
|
self._valid_cookies.append(cookie)
|
||||||
|
return cookie
|
||||||
|
|
||||||
|
def confirm_user_from_cookie(self, cookie):
|
||||||
|
return cookie in self._valid_cookies
|
||||||
|
|
||||||
|
def cookie_name(self):
|
||||||
|
return "auth_cookie"
|
||||||
|
|
||||||
|
def build_middleware(self):
|
||||||
|
@web.middleware
|
||||||
|
async def auth_middleware(request, handler):
|
||||||
|
for open_endpoint in self._open_endpoints:
|
||||||
|
if re.fullmatch(open_endpoint, request.path):
|
||||||
|
return await handler(request)
|
||||||
|
if self.cookie_name() in request.cookies:
|
||||||
|
cookie = request.cookies[self.cookie_name()]
|
||||||
|
if self.confirm_user_from_cookie(cookie):
|
||||||
|
return await handler(request)
|
||||||
|
redirect_location = f"{request.url.scheme}://{request.url.host_port_subcomponent}/login"
|
||||||
|
redirect_header = {"Location": redirect_location}
|
||||||
|
return web.Response(status=302, headers=redirect_header)
|
||||||
|
|
||||||
|
return auth_middleware
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,6 +37,8 @@ class Facts:
|
||||||
self._listen_port = self._node.declare_parameter("listen_port", "8888").value
|
self._listen_port = self._node.declare_parameter("listen_port", "8888").value
|
||||||
self._ui_pkg = self._node.declare_parameter("ui_pkg", "am_i_up_ui").value
|
self._ui_pkg = self._node.declare_parameter("ui_pkg", "am_i_up_ui").value
|
||||||
|
|
||||||
|
self._password = self._node.declare_parameter("password", "password").value
|
||||||
|
|
||||||
self._status_string = None
|
self._status_string = None
|
||||||
self._lat_long = None
|
self._lat_long = None
|
||||||
self._diag_msg = None
|
self._diag_msg = None
|
||||||
|
|
@ -114,6 +116,13 @@ class Facts:
|
||||||
print("Can't find build info.\n{}".format(e))
|
print("Can't find build info.\n{}".format(e))
|
||||||
return project_state_content
|
return project_state_content
|
||||||
|
|
||||||
|
def get_password(self):
|
||||||
|
return self._password
|
||||||
|
|
||||||
|
def get_open_endpoints(self):
|
||||||
|
# Hardcoding for now, can make a parameter later.
|
||||||
|
return ["/login", "/api/login", "/assets/(.*)"]
|
||||||
|
|
||||||
def _status_callback(self, msg):
|
def _status_callback(self, msg):
|
||||||
self._status_string = msg.data
|
self._status_string = msg.data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ def main(args=None):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--action")
|
parser.add_argument("--action")
|
||||||
parser.add_argument("--options", nargs="*")
|
parser.add_argument("--options", nargs="*")
|
||||||
parser.add_argument("--host", default="http://localhost:8888")
|
parser.add_argument("--host", default="http://localhost:8000")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -40,6 +40,12 @@ class Client():
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
self._host = host
|
self._host = host
|
||||||
|
|
||||||
|
async def login(self, session):
|
||||||
|
request = { "password": "password" }
|
||||||
|
async with session.post(f'{self._host}/api/login', json=request) as resp:
|
||||||
|
if not resp.ok:
|
||||||
|
print("Login failed.")
|
||||||
|
|
||||||
async def call_ping(self, options):
|
async def call_ping(self, options):
|
||||||
if len(options) != 1 or options[0] == "":
|
if len(options) != 1 or options[0] == "":
|
||||||
raise RunTimeError("Ping option is an address as a string.")
|
raise RunTimeError("Ping option is an address as a string.")
|
||||||
|
|
@ -49,6 +55,7 @@ class Client():
|
||||||
print("Calling ping with request: {}", json.dumps(request))
|
print("Calling ping with request: {}", json.dumps(request))
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
|
await self.login(session)
|
||||||
async with session.get(f'{self._host}/api/ping', json=request) as resp:
|
async with session.get(f'{self._host}/api/ping', json=request) as resp:
|
||||||
print(await resp.text())
|
print(await resp.text())
|
||||||
|
|
||||||
|
|
@ -56,6 +63,7 @@ class Client():
|
||||||
print("Calling uptime.")
|
print("Calling uptime.")
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
|
await self.login(session)
|
||||||
async with session.get(f'{self._host}/api/uptime') as resp:
|
async with session.get(f'{self._host}/api/uptime') as resp:
|
||||||
print(await resp.text())
|
print(await resp.text())
|
||||||
|
|
||||||
|
|
@ -63,6 +71,7 @@ class Client():
|
||||||
print("Calling build_info.")
|
print("Calling build_info.")
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
|
await self.login(session)
|
||||||
async with session.get(f'{self._host}/api/build_info') as resp:
|
async with session.get(f'{self._host}/api/build_info') as resp:
|
||||||
print(await resp.text())
|
print(await resp.text())
|
||||||
|
|
||||||
|
|
@ -70,6 +79,7 @@ class Client():
|
||||||
print("Calling env.")
|
print("Calling env.")
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
|
await self.login(session)
|
||||||
async with session.get(f'{self._host}/api/env') as resp:
|
async with session.get(f'{self._host}/api/env') as resp:
|
||||||
print(await resp.text())
|
print(await resp.text())
|
||||||
|
|
||||||
|
|
@ -77,5 +87,6 @@ class Client():
|
||||||
print("Calling diagnostics.")
|
print("Calling diagnostics.")
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
|
await self.login(session)
|
||||||
async with session.get(f'{self._host}/api/diagnostics') as resp:
|
async with session.get(f'{self._host}/api/diagnostics') as resp:
|
||||||
print(await resp.text())
|
print(await resp.text())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue