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