forked from Hay1tsme/artemis
frontend: fix login, remove frontend_session in favor of twisted sessions
This commit is contained in:
parent
dc5e5c1440
commit
279f48dc0c
@ -31,7 +31,7 @@ class Data:
|
|||||||
self.arcade = ArcadeData(self.config, self.session)
|
self.arcade = ArcadeData(self.config, self.session)
|
||||||
self.card = CardData(self.config, self.session)
|
self.card = CardData(self.config, self.session)
|
||||||
self.base = BaseData(self.config, self.session)
|
self.base = BaseData(self.config, self.session)
|
||||||
self.schema_ver_latest = 2
|
self.schema_ver_latest = 4
|
||||||
|
|
||||||
log_fmt_str = "[%(asctime)s] %(levelname)s | Database | %(message)s"
|
log_fmt_str = "[%(asctime)s] %(levelname)s | Database | %(message)s"
|
||||||
log_fmt = logging.Formatter(log_fmt_str)
|
log_fmt = logging.Formatter(log_fmt_str)
|
||||||
|
@ -9,6 +9,7 @@ from sqlalchemy.sql import func, select, Delete
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from sqlalchemy.engine import Row
|
from sqlalchemy.engine import Row
|
||||||
|
import bcrypt
|
||||||
|
|
||||||
from core.data.schema.base import BaseData, metadata
|
from core.data.schema.base import BaseData, metadata
|
||||||
|
|
||||||
@ -26,17 +27,6 @@ aime_user = Table(
|
|||||||
mysql_charset='utf8mb4'
|
mysql_charset='utf8mb4'
|
||||||
)
|
)
|
||||||
|
|
||||||
frontend_session = Table(
|
|
||||||
"frontend_session",
|
|
||||||
metadata,
|
|
||||||
Column("id", Integer, primary_key=True, unique=True),
|
|
||||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
|
||||||
Column("ip", String(15)),
|
|
||||||
Column('session_cookie', String(32), nullable=False, unique=True),
|
|
||||||
Column("expires", TIMESTAMP, nullable=False),
|
|
||||||
mysql_charset='utf8mb4'
|
|
||||||
)
|
|
||||||
|
|
||||||
class PermissionBits(Enum):
|
class PermissionBits(Enum):
|
||||||
PermUser = 1
|
PermUser = 1
|
||||||
PermMod = 2
|
PermMod = 2
|
||||||
@ -74,50 +64,20 @@ class UserData(BaseData):
|
|||||||
if result is None: return None
|
if result is None: return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def login(self, user_id: int, passwd: bytes = None, ip: str = "0.0.0.0") -> Optional[str]:
|
def get_user(self, user_id: int) -> Optional[Row]:
|
||||||
sql = select(aime_user).where(and_(aime_user.c.id == user_id, aime_user.c.password == passwd))
|
sql = select(aime_user).where(aime_user.c.id == user_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
|
||||||
if result is None: return None
|
|
||||||
|
|
||||||
usr = result.fetchone()
|
|
||||||
if usr is None: return None
|
|
||||||
|
|
||||||
return self.create_session(user_id, ip)
|
|
||||||
|
|
||||||
def check_session(self, cookie: str, ip: str = "0.0.0.0") -> Optional[Row]:
|
|
||||||
sql = select(frontend_session).where(
|
|
||||||
and_(
|
|
||||||
frontend_session.c.session_cookie == cookie,
|
|
||||||
frontend_session.c.ip == ip
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = self.execute(sql)
|
|
||||||
if result is None: return None
|
|
||||||
return result.fetchone()
|
|
||||||
|
|
||||||
def delete_session(self, session_id: int) -> bool:
|
|
||||||
sql = Delete(frontend_session).where(frontend_session.c.id == session_id)
|
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None: return False
|
if result is None: return False
|
||||||
return True
|
return result.fetchone()
|
||||||
|
|
||||||
def create_session(self, user_id: int, ip: str = "0.0.0.0", expires: datetime = datetime.now() + timedelta(days=1)) -> Optional[str]:
|
def check_password(self, user_id: int, passwd: bytes = None) -> bool:
|
||||||
cookie = uuid4().hex
|
usr = self.get_user(user_id)
|
||||||
|
if usr is None: return False
|
||||||
|
|
||||||
sql = insert(frontend_session).values(
|
if usr['password'] is None:
|
||||||
user = user_id,
|
return False
|
||||||
ip = ip,
|
|
||||||
session_cookie = cookie,
|
|
||||||
expires = expires
|
|
||||||
)
|
|
||||||
|
|
||||||
result = self.execute(sql)
|
return bcrypt.checkpw(passwd, usr['password'].encode())
|
||||||
if result is None:
|
|
||||||
return None
|
|
||||||
return cookie
|
|
||||||
|
|
||||||
def reset_autoincrement(self, ai_value: int) -> None:
|
def reset_autoincrement(self, ai_value: int) -> None:
|
||||||
# ALTER TABLE isn't in sqlalchemy so we do this the ugly way
|
# ALTER TABLE isn't in sqlalchemy so we do this the ugly way
|
||||||
|
12
core/data/schema/versions/CORE_3_rollback.sql
Normal file
12
core/data/schema/versions/CORE_3_rollback.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE `frontend_session` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user` int(11) NOT NULL,
|
||||||
|
`ip` varchar(15) DEFAULT NULL,
|
||||||
|
`session_cookie` varchar(32) NOT NULL,
|
||||||
|
`expires` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `id` (`id`),
|
||||||
|
UNIQUE KEY `session_cookie` (`session_cookie`),
|
||||||
|
KEY `user` (`user`),
|
||||||
|
CONSTRAINT `frontend_session_ibfk_1` FOREIGN KEY (`user`) REFERENCES `aime_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4;
|
1
core/data/schema/versions/CORE_4_upgrade.sql
Normal file
1
core/data/schema/versions/CORE_4_upgrade.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE `frontend_session`;
|
@ -4,6 +4,9 @@ from twisted.web import resource
|
|||||||
from twisted.web.util import redirectTo
|
from twisted.web.util import redirectTo
|
||||||
from twisted.web.http import Request
|
from twisted.web.http import Request
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
|
from twisted.web.server import Session
|
||||||
|
from zope.interface import Interface, Attribute, implementer
|
||||||
|
from twisted.python.components import registerAdapter
|
||||||
import jinja2
|
import jinja2
|
||||||
import bcrypt
|
import bcrypt
|
||||||
|
|
||||||
@ -11,6 +14,18 @@ from core.config import CoreConfig
|
|||||||
from core.data import Data
|
from core.data import Data
|
||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
|
|
||||||
|
class IUserSession(Interface):
|
||||||
|
userId = Attribute("User's ID")
|
||||||
|
current_ip = Attribute("User's current ip address")
|
||||||
|
permissions = Attribute("User's permission level")
|
||||||
|
|
||||||
|
@implementer(IUserSession)
|
||||||
|
class UserSession(object):
|
||||||
|
def __init__(self, session):
|
||||||
|
self.userId = 0
|
||||||
|
self.current_ip = "0.0.0.0"
|
||||||
|
self.permissions = 0
|
||||||
|
|
||||||
class FrontendServlet(resource.Resource):
|
class FrontendServlet(resource.Resource):
|
||||||
def getChild(self, name: bytes, request: Request):
|
def getChild(self, name: bytes, request: Request):
|
||||||
self.logger.debug(f"{request.getClientIP()} -> {name.decode()}")
|
self.logger.debug(f"{request.getClientIP()} -> {name.decode()}")
|
||||||
@ -38,6 +53,7 @@ class FrontendServlet(resource.Resource):
|
|||||||
|
|
||||||
self.logger.setLevel(cfg.frontend.loglevel)
|
self.logger.setLevel(cfg.frontend.loglevel)
|
||||||
coloredlogs.install(level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str)
|
coloredlogs.install(level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str)
|
||||||
|
registerAdapter(UserSession, Session, IUserSession)
|
||||||
|
|
||||||
fe_game = FE_Game(cfg, self.environment)
|
fe_game = FE_Game(cfg, self.environment)
|
||||||
games = Utils.get_all_titles()
|
games = Utils.get_all_titles()
|
||||||
@ -60,7 +76,7 @@ class FrontendServlet(resource.Resource):
|
|||||||
def render_GET(self, request):
|
def render_GET(self, request):
|
||||||
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
|
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
|
||||||
template = self.environment.get_template("core/frontend/index.jinja")
|
template = self.environment.get_template("core/frontend/index.jinja")
|
||||||
return template.render(server_name=self.config.server.name, title=self.config.server.name, game_list=self.game_list).encode("utf-16")
|
return template.render(server_name=self.config.server.name, title=self.config.server.name, game_list=self.game_list, sesh=vars(IUserSession(request.getSession()))).encode("utf-16")
|
||||||
|
|
||||||
class FE_Base(resource.Resource):
|
class FE_Base(resource.Resource):
|
||||||
"""
|
"""
|
||||||
@ -80,6 +96,12 @@ class FE_Gate(FE_Base):
|
|||||||
def render_GET(self, request: Request):
|
def render_GET(self, request: Request):
|
||||||
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
|
self.logger.debug(f"{request.getClientIP()} -> {request.uri.decode()}")
|
||||||
uri: str = request.uri.decode()
|
uri: str = request.uri.decode()
|
||||||
|
|
||||||
|
sesh = request.getSession()
|
||||||
|
usr_sesh = IUserSession(sesh)
|
||||||
|
if usr_sesh.userId > 0:
|
||||||
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
if uri.startswith("/gate/create"):
|
if uri.startswith("/gate/create"):
|
||||||
return self.create_user(request)
|
return self.create_user(request)
|
||||||
|
|
||||||
@ -92,7 +114,7 @@ class FE_Gate(FE_Base):
|
|||||||
else: err = 0
|
else: err = 0
|
||||||
|
|
||||||
template = self.environment.get_template("core/frontend/gate/gate.jinja")
|
template = self.environment.get_template("core/frontend/gate/gate.jinja")
|
||||||
return template.render(title=f"{self.core_config.server.name} | Login Gate", error=err).encode("utf-16")
|
return template.render(title=f"{self.core_config.server.name} | Login Gate", error=err, sesh=vars(usr_sesh)).encode("utf-16")
|
||||||
|
|
||||||
def render_POST(self, request: Request):
|
def render_POST(self, request: Request):
|
||||||
uri = request.uri.decode()
|
uri = request.uri.decode()
|
||||||
@ -100,7 +122,7 @@ class FE_Gate(FE_Base):
|
|||||||
|
|
||||||
if uri == "/gate/gate.login":
|
if uri == "/gate/gate.login":
|
||||||
access_code: str = request.args[b"access_code"][0].decode()
|
access_code: str = request.args[b"access_code"][0].decode()
|
||||||
passwd: str = request.args[b"passwd"][0]
|
passwd: bytes = request.args[b"passwd"][0]
|
||||||
if passwd == b"":
|
if passwd == b"":
|
||||||
passwd = None
|
passwd = None
|
||||||
|
|
||||||
@ -109,20 +131,22 @@ class FE_Gate(FE_Base):
|
|||||||
return redirectTo(b"/gate?e=1", request)
|
return redirectTo(b"/gate?e=1", request)
|
||||||
|
|
||||||
if passwd is None:
|
if passwd is None:
|
||||||
sesh = self.data.user.login(uid, ip=ip)
|
sesh = self.data.user.check_password(uid)
|
||||||
|
|
||||||
if sesh is not None:
|
if sesh is not None:
|
||||||
return redirectTo(f"/gate/create?ac={access_code}".encode(), request)
|
return redirectTo(f"/gate/create?ac={access_code}".encode(), request)
|
||||||
return redirectTo(b"/gate?e=1", request)
|
return redirectTo(b"/gate?e=1", request)
|
||||||
|
|
||||||
salt = bcrypt.gensalt()
|
if not self.data.user.check_password(uid, passwd):
|
||||||
hashed = bcrypt.hashpw(passwd, salt)
|
|
||||||
sesh = self.data.user.login(uid, hashed, ip)
|
|
||||||
|
|
||||||
if sesh is None:
|
|
||||||
return redirectTo(b"/gate?e=1", request)
|
return redirectTo(b"/gate?e=1", request)
|
||||||
|
|
||||||
request.addCookie('session', sesh)
|
self.logger.info(f"Successful login of user {uid} at {ip}")
|
||||||
|
|
||||||
|
sesh = request.getSession()
|
||||||
|
usr_sesh = IUserSession(sesh)
|
||||||
|
usr_sesh.userId = uid
|
||||||
|
usr_sesh.current_ip = ip
|
||||||
|
|
||||||
return redirectTo(b"/user", request)
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
elif uri == "/gate/gate.create":
|
elif uri == "/gate/gate.create":
|
||||||
@ -142,10 +166,8 @@ class FE_Gate(FE_Base):
|
|||||||
if result is None:
|
if result is None:
|
||||||
return redirectTo(b"/gate?e=3", request)
|
return redirectTo(b"/gate?e=3", request)
|
||||||
|
|
||||||
sesh = self.data.user.login(uid, hashed, ip)
|
if not self.data.user.check_password(uid, passwd.encode()):
|
||||||
if sesh is None:
|
|
||||||
return redirectTo(b"/gate", request)
|
return redirectTo(b"/gate", request)
|
||||||
request.addCookie('session', sesh)
|
|
||||||
|
|
||||||
return redirectTo(b"/user", request)
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
@ -159,15 +181,19 @@ class FE_Gate(FE_Base):
|
|||||||
ac = request.args[b'ac'][0].decode()
|
ac = request.args[b'ac'][0].decode()
|
||||||
|
|
||||||
template = self.environment.get_template("core/frontend/gate/create.jinja")
|
template = self.environment.get_template("core/frontend/gate/create.jinja")
|
||||||
return template.render(title=f"{self.core_config.server.name} | Create User", code=ac).encode("utf-16")
|
return template.render(title=f"{self.core_config.server.name} | Create User", code=ac, sesh={"userId": 0}).encode("utf-16")
|
||||||
|
|
||||||
class FE_User(FE_Base):
|
class FE_User(FE_Base):
|
||||||
def render_GET(self, request: Request):
|
def render_GET(self, request: Request):
|
||||||
template = self.environment.get_template("core/frontend/user/index.jinja")
|
template = self.environment.get_template("core/frontend/user/index.jinja")
|
||||||
return template.render().encode("utf-16")
|
|
||||||
if b'session' not in request.cookies:
|
sesh: Session = request.getSession()
|
||||||
|
usr_sesh = IUserSession(sesh)
|
||||||
|
if usr_sesh.userId == 0:
|
||||||
return redirectTo(b"/gate", request)
|
return redirectTo(b"/gate", request)
|
||||||
|
|
||||||
|
return template.render(title=f"{self.core_config.server.name} | Account", sesh=vars(usr_sesh)).encode("utf-16")
|
||||||
|
|
||||||
class FE_Game(FE_Base):
|
class FE_Game(FE_Base):
|
||||||
isLeaf = False
|
isLeaf = False
|
||||||
children: Dict[str, Any] = {}
|
children: Dict[str, Any] = {}
|
||||||
|
@ -2,10 +2,23 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Gate</h1>
|
<h1>Gate</h1>
|
||||||
{% include "core/frontend/widgets/err_banner.jinja" %}
|
{% include "core/frontend/widgets/err_banner.jinja" %}
|
||||||
|
<style>
|
||||||
|
/* Chrome, Safari, Edge, Opera */
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<form id="login" style="max-width: 240px; min-width: 10%;" action="/gate/gate.login" method="post">
|
<form id="login" style="max-width: 240px; min-width: 10%;" action="/gate/gate.login" method="post">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="access_code">Card Access Code</label><br>
|
<label for="access_code">Card Access Code</label><br>
|
||||||
<input form="login" class="form-control" name="access_code" id="access_code" type="text" placeholder="00000000000000000000" maxlength="20" required>
|
<input form="login" class="form-control" name="access_code" id="access_code" type="number" placeholder="00000000000000000000" maxlength="20" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="passwd">Password</label><br>
|
<label for="passwd">Password</label><br>
|
||||||
|
@ -9,5 +9,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="background: #333; color: #f9f9f9; width: 10%; height: 50px; line-height: 50px; text-align: center; float: left;">
|
<div style="background: #333; color: #f9f9f9; width: 10%; height: 50px; line-height: 50px; text-align: center; float: left;">
|
||||||
|
{% if sesh is defined and sesh["userId"] > 0 %}
|
||||||
|
<a href="/user"><button class="btn btn-primary">Account</button></a>
|
||||||
|
{% else %}
|
||||||
<a href="/gate"><button class="btn btn-primary">Gate</button></a>
|
<a href="/gate"><button class="btn btn-primary">Gate</button></a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
Loading…
Reference in New Issue
Block a user