Progress.
This commit is contained in:
parent
12ca8e4296
commit
bb6bcf41d1
@ -21,6 +21,7 @@ from snek.view.docs import DocsHTMLView, DocsMDView
|
|||||||
from snek.view.index import IndexView
|
from snek.view.index import IndexView
|
||||||
from snek.view.login import LoginView
|
from snek.view.login import LoginView
|
||||||
from snek.view.login_form import LoginFormView
|
from snek.view.login_form import LoginFormView
|
||||||
|
from snek.view.logout import LogoutView
|
||||||
from snek.view.register import RegisterView
|
from snek.view.register import RegisterView
|
||||||
from snek.view.register_form import RegisterFormView
|
from snek.view.register_form import RegisterFormView
|
||||||
from snek.view.status import StatusView
|
from snek.view.status import StatusView
|
||||||
@ -68,6 +69,8 @@ class Application(BaseApplication):
|
|||||||
)
|
)
|
||||||
self.router.add_view("/about.html", AboutHTMLView)
|
self.router.add_view("/about.html", AboutHTMLView)
|
||||||
self.router.add_view("/about.md", AboutMDView)
|
self.router.add_view("/about.md", AboutMDView)
|
||||||
|
self.router.add_view("/logout.json", LogoutView)
|
||||||
|
self.router.add_view("/logout.html", LogoutView)
|
||||||
self.router.add_view("/docs.html", DocsHTMLView)
|
self.router.add_view("/docs.html", DocsHTMLView)
|
||||||
self.router.add_view("/docs.md", DocsMDView)
|
self.router.add_view("/docs.md", DocsMDView)
|
||||||
self.router.add_view("/status.json", StatusView)
|
self.router.add_view("/status.json", StatusView)
|
||||||
|
@ -1,11 +1,24 @@
|
|||||||
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
|
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
|
||||||
|
|
||||||
|
|
||||||
|
class AuthField(FormInputElement):
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def errors(self):
|
||||||
|
result = await super().errors
|
||||||
|
if self.model.password.value and self.model.username.value:
|
||||||
|
if not await self.app.services.user.validate_login(
|
||||||
|
self.model.username.value, self.model.password.value
|
||||||
|
):
|
||||||
|
return ["Invalid username or password"]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class LoginForm(Form):
|
class LoginForm(Form):
|
||||||
|
|
||||||
title = HTMLElement(tag="h1", text="Login")
|
title = HTMLElement(tag="h1", text="Login")
|
||||||
|
|
||||||
username = FormInputElement(
|
username = AuthField(
|
||||||
name="username",
|
name="username",
|
||||||
required=True,
|
required=True,
|
||||||
min_length=2,
|
min_length=2,
|
||||||
@ -14,7 +27,7 @@ class LoginForm(Form):
|
|||||||
place_holder="Username",
|
place_holder="Username",
|
||||||
type="text",
|
type="text",
|
||||||
)
|
)
|
||||||
password = FormInputElement(
|
password = AuthField(
|
||||||
name="password",
|
name="password",
|
||||||
required=True,
|
required=True,
|
||||||
regex=r"^[a-zA-Z0-9_.+-]{6,}",
|
regex=r"^[a-zA-Z0-9_.+-]{6,}",
|
||||||
@ -25,3 +38,14 @@ class LoginForm(Form):
|
|||||||
action = FormButtonElement(
|
action = FormButtonElement(
|
||||||
name="action", value="submit", text="Login", type="button"
|
name="action", value="submit", text="Login", type="button"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def is_valid(self):
|
||||||
|
return all(
|
||||||
|
[
|
||||||
|
self["username"],
|
||||||
|
self["password"],
|
||||||
|
not await self.username.errors,
|
||||||
|
not await self.password.errors,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@ -5,13 +5,23 @@ from snek.system.service import BaseService
|
|||||||
class UserService(BaseService):
|
class UserService(BaseService):
|
||||||
mapper_name = "user"
|
mapper_name = "user"
|
||||||
|
|
||||||
|
async def validate_login(self, username, password):
|
||||||
|
model = await self.get(username=username)
|
||||||
|
print("FOUND USER!", model, flush=True)
|
||||||
|
if not model:
|
||||||
|
return False
|
||||||
|
print("AU", password, model.password.value, flush=True)
|
||||||
|
if not await security.verify(password, model["password"]):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
async def register(self, email, username, password):
|
async def register(self, email, username, password):
|
||||||
if await self.exists(username=username):
|
if await self.exists(username=username):
|
||||||
raise Exception("User already exists.")
|
raise Exception("User already exists.")
|
||||||
model = await self.new()
|
model = await self.new()
|
||||||
model.email = email
|
model.email.value = email
|
||||||
model.username = username
|
model.username.value = username
|
||||||
model.password = await security.hash(password)
|
model.password.value = await security.hash(password)
|
||||||
if await self.save(model):
|
if await self.save(model):
|
||||||
return model
|
return model
|
||||||
raise Exception(f"Failed to create user: {model.errors}.")
|
raise Exception(f"Failed to create user: {model.errors}.")
|
||||||
|
@ -113,9 +113,98 @@ class RESTClient {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rest = new RESTClient()
|
const rest = new RESTClient()
|
||||||
|
|
||||||
|
class EventHandler {
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
this.subscribers = {}
|
||||||
|
}
|
||||||
|
addEventListener(type,handler){
|
||||||
|
if(!this.subscribers[type])
|
||||||
|
this.subscribers[type] = []
|
||||||
|
this.subscribers[type].push(handler)
|
||||||
|
}
|
||||||
|
emit(type,...data){
|
||||||
|
if(this.subscribers[type])
|
||||||
|
this.subscribers[type].forEach(handler=>handler(...data))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Chat extends EventHandler {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this._url = window.location.hostname == 'localhost' ? 'ws://localhost/chat.ws' : 'wss://' + window.location.hostname +'/chat.ws'
|
||||||
|
this._socket = null
|
||||||
|
this._wait_connect = null
|
||||||
|
this._promises = {}
|
||||||
|
}
|
||||||
|
connect(){
|
||||||
|
if(this._wait_connect)
|
||||||
|
return this._wait_connect
|
||||||
|
|
||||||
|
const me = this
|
||||||
|
return new Promise(async (resolve,reject)=>{
|
||||||
|
me._wait_connect = resolve
|
||||||
|
me._socket = new WebSocket(me._url)
|
||||||
|
console.debug("Connecting..")
|
||||||
|
|
||||||
|
me._socket.onconnect = ()=>{
|
||||||
|
me._connected()
|
||||||
|
me._wait_socket(me)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
generateUniqueId() {
|
||||||
|
return 'id-' + Math.random().toString(36).substr(2, 9); // Example: id-k5f9zq7
|
||||||
|
}
|
||||||
|
call(method,...args){
|
||||||
|
const me = this
|
||||||
|
return new Promise(async (resolve,reject)=>{
|
||||||
|
try{
|
||||||
|
const command = {method:method,args:args,message_id:me.generateUniqueId()}
|
||||||
|
me._promises[command.message_id] = resolve
|
||||||
|
await me._socket.send(JSON.stringify(command))
|
||||||
|
|
||||||
|
}catch(e){
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_connected() {
|
||||||
|
const me = this
|
||||||
|
this._socket.onmessage = (event) => {
|
||||||
|
const message = JSON.parse(event.data)
|
||||||
|
if(message.message_id && me._promises[message.message_id]){
|
||||||
|
me._promises[message.message_id](message)
|
||||||
|
delete me._promises[message.message_id]
|
||||||
|
}else{
|
||||||
|
me.emit("message",me, message)
|
||||||
|
}
|
||||||
|
//const room = this.rooms.find(room=>room.name == message.room)
|
||||||
|
//if(!room){
|
||||||
|
// this.rooms.push(new Room(message.room))
|
||||||
|
}
|
||||||
|
this._socket.onclose = (event) => {
|
||||||
|
me._wait_socket = null
|
||||||
|
me._socket = null
|
||||||
|
me.emit('close',me)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async privmsg(room, text) {
|
||||||
|
await rest.post("/api/privmsg",{
|
||||||
|
room:room,
|
||||||
|
text:text
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
rooms = []
|
rooms = []
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -29,8 +29,14 @@ class BaseMapper:
|
|||||||
async def get(self, uid: str = None, **kwargs) -> BaseModel:
|
async def get(self, uid: str = None, **kwargs) -> BaseModel:
|
||||||
if uid:
|
if uid:
|
||||||
kwargs["uid"] = uid
|
kwargs["uid"] = uid
|
||||||
self.new()
|
|
||||||
record = self.table.find_one(**kwargs)
|
record = self.table.find_one(**kwargs)
|
||||||
|
if not record:
|
||||||
|
return None
|
||||||
|
record = dict(record)
|
||||||
|
model = await self.new()
|
||||||
|
for key, value in record.items():
|
||||||
|
model[key] = value
|
||||||
|
return model
|
||||||
return await self.model_class.from_record(mapper=self, record=record)
|
return await self.model_class.from_record(mapper=self, record=record)
|
||||||
|
|
||||||
async def exists(self, **kwargs):
|
async def exists(self, **kwargs):
|
||||||
@ -40,10 +46,9 @@ class BaseMapper:
|
|||||||
return self.table.count(**kwargs)
|
return self.table.count(**kwargs)
|
||||||
|
|
||||||
async def save(self, model: BaseModel) -> bool:
|
async def save(self, model: BaseModel) -> bool:
|
||||||
record = await model.record
|
if not model.record.get("uid"):
|
||||||
if not record.get("uid"):
|
raise Exception(f"Attempt to save without uid: {model.record}.")
|
||||||
raise Exception(f"Attempt to save without uid: {record}.")
|
return self.table.upsert(model.record, ["uid"])
|
||||||
return self.table.upsert(record, ["uid"])
|
|
||||||
|
|
||||||
async def find(self, **kwargs) -> typing.AsyncGenerator:
|
async def find(self, **kwargs) -> typing.AsyncGenerator:
|
||||||
if not kwargs.get("_limit"):
|
if not kwargs.get("_limit"):
|
||||||
|
@ -243,7 +243,7 @@ class BaseModel:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_record(cls, record, mapper):
|
async def from_record(cls, record, mapper):
|
||||||
model = cls.__new__()
|
model = cls()
|
||||||
model.mapper = mapper
|
model.mapper = mapper
|
||||||
model.record = record
|
model.record = record
|
||||||
return model
|
return model
|
||||||
@ -258,15 +258,15 @@ class BaseModel:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def record(self):
|
def record(self):
|
||||||
return {field.name: field.value for field in self.fields}
|
return {key: field.value for key, field in self.fields.items()}
|
||||||
|
|
||||||
@record.setter
|
@record.setter
|
||||||
def record(self, value):
|
def record(self, val):
|
||||||
for key, value in self._record.items():
|
for key, value in val.items():
|
||||||
field = self.fields.get(key)
|
field = self.fields.get(key)
|
||||||
if not field:
|
if not field:
|
||||||
continue
|
continue
|
||||||
field.value = value
|
self[key] = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -321,7 +321,7 @@ class BaseModel:
|
|||||||
self.__dict__[key] = value
|
self.__dict__[key] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def record(self):
|
async def recordz(self):
|
||||||
obj = await self.to_json()
|
obj = await self.to_json()
|
||||||
record = {}
|
record = {}
|
||||||
for key, value in obj.items():
|
for key, value in obj.items():
|
||||||
|
@ -11,10 +11,10 @@
|
|||||||
<header>
|
<header>
|
||||||
<div class="logo">Snek</div>
|
<div class="logo">Snek</div>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="#">Home</a>
|
<a href="/web.html">Home</a>
|
||||||
<a href="#">Rooms</a>
|
<a href="#">Rooms</a>
|
||||||
<a href="#">Settings</a>
|
<a href="#">Settings</a>
|
||||||
<a href="#">Logout</a>
|
<a href="/logout.html">Logout</a>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
|
@ -4,3 +4,11 @@ from snek.system.view import BaseFormView
|
|||||||
|
|
||||||
class LoginFormView(BaseFormView):
|
class LoginFormView(BaseFormView):
|
||||||
form = LoginForm
|
form = LoginForm
|
||||||
|
|
||||||
|
async def submit(self, form):
|
||||||
|
if await form.is_valid:
|
||||||
|
self.session["logged_in"] = True
|
||||||
|
self.session["username"] = form.username.value
|
||||||
|
self.session["uid"] = form.uid.value
|
||||||
|
return {"redirect_url": "/web.html"}
|
||||||
|
return {"is_valid": False}
|
||||||
|
27
src/snek/view/logout.py
Normal file
27
src/snek/view/logout.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class LogoutView(BaseView):
|
||||||
|
|
||||||
|
redirect_url = "/"
|
||||||
|
login_required = True
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
try:
|
||||||
|
del self.session["logged_in"]
|
||||||
|
del self.session["uid"]
|
||||||
|
del self.session["username"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return web.HTTPFound(self.redirect_url)
|
||||||
|
|
||||||
|
async def post(self):
|
||||||
|
try:
|
||||||
|
del self.session["logged_in"]
|
||||||
|
del self.session["uid"]
|
||||||
|
del self.session["username"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return await self.json_response({"redirect_url": self.redirect_url})
|
Loading…
Reference in New Issue
Block a user