Skip to content
This repository has been archived by the owner on Dec 21, 2022. It is now read-only.

Commit

Permalink
aochi commits to asahi-web (no way)
Browse files Browse the repository at this point in the history
  • Loading branch information
7ez committed Aug 26, 2021
1 parent f629883 commit 162728c
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 280 deletions.
56 changes: 29 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@

[![Discord](https://discordapp.com/api/guilds/833325274934411274/widget.png?style=shield)](https://discord.gg/d62tzSYv3z)

Table of Contents
==================
- [Table of Contents](#table-of-contents)
- [What is guweb?](#what-is-guweb)
- [Requirements](#requirements)
- [What is asahi-web?](#what-is-asahi-web)
- [Setup](#setup)
- [Directory Structure](#directory-structure)
- [The team](#the-team)
- [The End](#the-end)

What is guweb?
What is asahi-web?
------

guweb is the front-facing appearance of the osu! server protocol, [gulag](https://github.com/cmyui/gulag)!
asahi-web is the front-facing appearance of the osu! server protocol, [Asahi](https://github.com/tsunyoku/Asahi)!

It's a project by me, that uses [guweb](https://github.com/Varkaria/guweb)'s source code, but is made to work with Asahi.

Using native async/await syntax written on top of [Quart](https://github.com/pgjones/quart) and
[cmyui's multipurpose library](https://github.com/cmyui/cmyui_pkg), guweb achieves flexability, cleanliness,
[cmyui's multipurpose library](https://github.com/cmyui/cmyui_pkg), asahi-web achieves flexability, cleanliness,
and efficiency not seen in other frontend implementations - all while maintaining the simplicity of Python.

A primary goal of asahi-web is to keep our codebase a developer-friendly API, so that
programming remains about the logic and ideas, rather than the code itself.

gulag-web is written by Yo-ru and Varkaria, but me and other contributors are mainly writing asahi-web.


Requirements
------

Expand All @@ -31,7 +41,7 @@ Setup is relatively simple - these commands should set you right up.
Notes:

- Ubuntu 20.04 is known to have issues with NGINX and osu! for unknown reasons?
- If you have any difficulties setting up guweb, feel free to join the Discord server at the top of the README, we now have a bit of a community!
- If you have any difficulties setting up asahi-web, feel free to join the Discord server at the top of the README, we now have a bit of a community!

```sh
# Install Python >=3.9 and latest version of PIP.
Expand All @@ -43,51 +53,43 @@ python3.9 get-pip.py && rm get-pip.py
# Install MySQL and NGINX.
sudo apt install mysql-server nginx

# Clone guweb from GitHub.
git clone https://github.com/varkaria/guweb.git
cd guweb
# Clone asahi-web from GitHub.
git clone https://github.com/7ez/asahi-web.git
cd asahi-web

# Initialize and update the submodules.
git submodule init && git submodule update

# Install requirements from pip.
python3.9 -m pip install -r ext/requirements.txt

# Add and configure guweb's NGINX config to your nginx/sites-enabled.
sudo ln -r -s ext/nginx.conf /etc/nginx/sites-enabled/guweb.conf
# Add and configure asahi-web's NGINX config to your nginx/sites-enabled.
sudo ln -r -s ext/nginx.conf /etc/nginx/sites-enabled/asahi-web.conf
sudo nano ext/nginx.conf
sudo nginx -s reload

# Configure guweb.
# Configure asahi-web.
cp ext/config.sample.py config.py
nano config.py

# Run guweb.
# Run asahi-web.
python3.9 main.py # Run directly to access debug features for development! (Port 5000)
hypercorn main.py # Please run guweb with hypercorn when in production! It will improve performance drastically by disabling all of the debug features a developer would need! (Port 8000)
hypercorn main.py # Please run asahi-web with hypercorn when in production! It will improve performance drastically by disabling all of the debug features a developer would need! (Port 8000)
```

Directory Structure
------

.
├── blueprints # Modular routes such as the API, Frontend, or Admin Panel.
├── docs # Markdown files used in guweb's documentation system.
├── ext # External files from guweb's primary operation.
├── docs # Markdown files used in asahi-web's documentation system.
├── ext # External files from asahi-web's primary operation.
├── objects # Code for representing privileges, global objects, and more.
├── static # Code or content that is not modified or processed by guweb itself.
├── static # Code or content that is not modified or processed by asahi-web itself.
├── templates # HTML that contains content that is rendered after the page has loaded.
├── admin # Templated content for the admin panel (/admin).
├── settings # Templated content for settings (/settings).
└ ... # Templated content for all of guweb (/).


The team
------
- [Yoru](https://github.com/Yo-ru) | Backend, Grammar Checking [Deprecated]
- [Varkaria](https://github.com/Varkaria) | Frontend, Backend?
└ ... # Templated content for all of asahi-web (/).

The End
------

Well know that you know everything, why not check out the original code guweb was based off of in [this](https://github.com/yo-ru/gulag-web) i think i should continue this work to finish work?
128 changes: 69 additions & 59 deletions blueprints/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

__all__ = ()

import bcrypt
import hashlib
import os
import time
Expand All @@ -12,6 +11,9 @@
from functools import wraps
from PIL import Image
from pathlib import Path
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand
from cryptography.hazmat.backends import default_backend as backend
from quart import Blueprint
from quart import redirect
from quart import render_template
Expand Down Expand Up @@ -76,7 +78,7 @@ async def settings_profile_post():
return await flash('error', 'No changes have been made.', 'settings/profile')

if new_name != old_name:
if not session['user_data']['is_donator']:
if not session['user_data']['is_donator'] or not session['user_data']['is_staff']:
return await flash('error', 'Username changes are currently a supporter perk.', 'settings/profile')

# Usernames must:
Expand Down Expand Up @@ -138,7 +140,7 @@ async def settings_avatar():
@login_required
async def settings_avatar_post():
# constants
AVATARS_PATH = f'{glob.config.path_to_gulag}.data/avatars'
AVATARS_PATH = f'{glob.config.path_to_asahi}.data/avatars'
ALLOWED_EXTENSIONS = ['.jpeg', '.jpg', '.png']

avatar = (await request.files).get('avatar')
Expand Down Expand Up @@ -251,44 +253,48 @@ async def settings_password_post():
return await flash('error', 'Your new password was deemed too simple.', 'settings/password')

# cache and other password related information
bcrypt_cache = glob.cache['bcrypt']
pw_bcrypt = (await glob.db.fetch(
'SELECT pw_bcrypt '
pw_cache = glob.cache['pw']
pw_hash = (await glob.db.fetchval(
'SELECT pw '
'FROM users '
'WHERE id = %s',
[session['user_data']['id']])
)['pw_bcrypt'].encode()
)['pw_bcrypt'].encode('ISO-8859-1').decode('unicode-escape').encode('ISO-8859-1')

pw_md5 = hashlib.md5(old_password.encode()).hexdigest().encode()

# check old password against db
# intentionally slow, will cache to speed up
if pw_bcrypt in bcrypt_cache:
if pw_md5 != bcrypt_cache[pw_bcrypt]: # ~0.1ms
if pw_hash in pw_cache:
if pw_md5 != pw_cache[pw_hash]: # ~0.1ms
if glob.config.debug:
log(f"{session['user_data']['name']}'s change pw failed - pw incorrect.", Ansi.LYELLOW)
return await flash('error', 'Your old password is incorrect.', 'settings/password')
else: # ~200ms
if not bcrypt.checkpw(pw_md5, pw_bcrypt):
k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend())
try:
k.verify(pw_hash, pw_md5)
except:
if glob.config.debug:
log(f"{session['user_data']['name']}'s change pw failed - pw incorrect.", Ansi.LYELLOW)
return await flash('error', 'Your old password is incorrect.', 'settings/password')

# remove old password from cache
if pw_bcrypt in bcrypt_cache:
del bcrypt_cache[pw_bcrypt]
if pw_hash in pw_cache:
del pw_cache[pw_hash]

# calculate new md5 & bcrypt pw
pw_md5 = hashlib.md5(new_password.encode()).hexdigest().encode()
pw_bcrypt = bcrypt.hashpw(pw_md5, bcrypt.gensalt())
k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend())
pw_hash_new = k.derive(pw_md5).decode('unicode-escape')

# update password in cache and db
bcrypt_cache[pw_bcrypt] = pw_md5
pw_cache[pw_hash_new] = pw_md5
await glob.db.execute(
'UPDATE users '
'SET pw_bcrypt = %s '
'SET pw = %s '
'WHERE safe_name = %s',
[pw_bcrypt, utils.get_safe_name(session['user_data']['name'])]
[pw_hash_new, utils.get_safe_name(session['user_data']['name'])]
)

# logout
Expand All @@ -314,7 +320,7 @@ async def profile(id):
else:
mods = 'vn'

user_data = await glob.db.fetch(
user_data = await glob.db.fetchrow(
'SELECT name, id, priv, country '
'FROM users '
'WHERE id = %s',
Expand All @@ -323,7 +329,7 @@ async def profile(id):

# user is banned and we're not staff; render 404
is_staff = 'authenticated' in session and session['user_data']['is_staff']
if not user_data or not (user_data['priv'] & Privileges.Normal or is_staff):
if not user_data or not (user_data['priv'] & Privileges.Verified or is_staff):
return (await render_template('404.html'), 404)

user_data['customisation'] = utils.has_profile_customizations(id)
Expand All @@ -332,10 +338,10 @@ async def profile(id):

@frontend.route('/leaderboard')
@frontend.route('/lb')
@frontend.route('/leaderboard/<mode>/<sort>/<mods>')
@frontend.route('/lb/<mode>/<sort>/<mods>')
async def leaderboard(mode='std', sort='pp', mods='vn'):
return await render_template('leaderboard.html', mode=mode, sort=sort, mods=mods)
@frontend.route('/leaderboard/<mode>/<mods>')
@frontend.route('/lb/<mode>/<mods>')
async def leaderboard(mode='std', mods='vn'):
return await render_template('leaderboard.html', mode=mode, mods=mods)

@frontend.route('/login')
async def login():
Expand All @@ -360,9 +366,9 @@ async def login_post():
return await flash('error', 'Invalid parameters.', 'home')

# check if account exists
user_info = await glob.db.fetch(
user_info = await glob.db.fetchrow(
'SELECT id, name, email, priv, '
'pw_bcrypt, silence_end '
'pw, silence_end '
'FROM users '
'WHERE safe_name = %s',
[utils.get_safe_name(username)]
Expand All @@ -376,25 +382,28 @@ async def login_post():
return await flash('error', 'Account does not exist.', 'login')

# cache and other related password information
bcrypt_cache = glob.cache['bcrypt']
pw_bcrypt = user_info['pw_bcrypt'].encode()
pw_cache = glob.cache['pw']
pw_hash = user_info['pw'].encode('ISO-8859-1').decode('unicode-escape').encode('ISO-8859-1')
pw_md5 = hashlib.md5(passwd_txt.encode()).hexdigest().encode()

# check credentials (password) against db
# intentionally slow, will cache to speed up
if pw_bcrypt in bcrypt_cache:
if pw_md5 != bcrypt_cache[pw_bcrypt]: # ~0.1ms
if pw_hash in pw_cache:
if pw_md5 != pw_cache[pw_hash]: # ~0.1ms
if glob.config.debug:
log(f"{username}'s login failed - pw incorrect.", Ansi.LYELLOW)
return await flash('error', 'Password is incorrect.', 'login')
else: # ~200ms
if not bcrypt.checkpw(pw_md5, pw_bcrypt):
k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend())
try:
k.verify(pw_md5, pw_hash)
except:
if glob.config.debug:
log(f"{username}'s login failed - pw incorrect.", Ansi.LYELLOW)
return await flash('error', 'Password is incorrect.', 'login')

# login successful; cache password for next login
bcrypt_cache[pw_bcrypt] = pw_md5
pw_cache[pw_hash] = pw_md5

# user not verified; render verify
if not user_info['priv'] & Privileges.Verified:
Expand All @@ -419,8 +428,8 @@ async def login_post():
'email': user_info['email'],
'priv': user_info['priv'],
'silence_end': user_info['silence_end'],
'is_staff': user_info['priv'] & Privileges.Staff != 0,
'is_donator': user_info['priv'] & Privileges.Donator != 0
'is_staff': user_info['priv'] & Privileges.Staff,
'is_donator': user_info['priv'] & Privileges.Supporter
}

if glob.config.debug:
Expand Down Expand Up @@ -506,37 +515,38 @@ async def register_post():
# TODO: add correct locking
# (start of lock)
pw_md5 = hashlib.md5(passwd_txt.encode()).hexdigest().encode()
pw_bcrypt = bcrypt.hashpw(pw_md5, bcrypt.gensalt())
glob.cache['bcrypt'][pw_bcrypt] = pw_md5 # cache pw
k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend())
pw_hash = k.derive(pw_md5).decode('unicode-escape')
glob.cache['pw'][pw_hash] = pw_md5 # cache pw

safe_name = utils.get_safe_name(username)

# fetch the users' country
if (
request.headers and
(ip := request.headers.get('X-Real-IP', type=str)) is not None
):
country = await utils.fetch_geoloc(ip)
else:
country = 'xx'

async with glob.db.pool.acquire() as conn:
async with conn.cursor() as db_cursor:
# add to `users` table.
await db_cursor.execute(
'INSERT INTO users '
'(name, safe_name, email, pw_bcrypt, country, creation_time, latest_activity) '
'VALUES (%s, %s, %s, %s, %s, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())',
[username, safe_name, email, pw_bcrypt, country]
)
user_id = db_cursor.lastrowid

# add to `stats` table.
await db_cursor.executemany(
'INSERT INTO stats '
'(id, mode) VALUES (%s, %s)',
[(user_id, mode) for mode in range(8)]
)
#if 'CF-Connecting-IP' in request.headers:
# ip = request.headers['CF-Connecting-IP']
#else:
# ip = request.headers['X-Forwarded-For'].split(',')[0]

#try:
# country = await utils.fetch_geoloc(ip)
#except:
country = 'PL'

user = await glob.db.execute(
'INSERT INTO users '
'(name, safe_name, email, pw, country, registered_at) '
'VALUES (%s, %s, %s, %s, %s, UNIX_TIMESTAMP())',
[username, safe_name, email, pw_hash, country]
)

user_id = user

# add to `stats` table.
await glob.db.execute(
'INSERT INTO stats '
'(id) VALUES (%s)',
user_id
)

# (end of lock)

Expand Down
2 changes: 1 addition & 1 deletion constants/regexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
import re

username = re.compile(r'^[\w \[\]-]{2,15}$')
email = re.compile(r'^[^@\s]{1,200}@[^@\s\.]{1,30}\.[^@\.\s]{1,24}$')
email = re.compile(r'^[^@\s]{1,200}@[^@\s\.]{1,30}(?:\.[^@\.\s]{2,24})+$')
8 changes: 4 additions & 4 deletions ext/config.sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
'password': 'changeme',
}

# path to gulag root (must have leading and following slash)
path_to_gulag = '/path/to/gulag/'
# path to asahi root (must have leading and following slash)
path_to_asahi = '/path/to/asahi/'

# enable debug (disable when in production to improve performance)
debug = False
Expand All @@ -41,8 +41,8 @@
# enable registration
registration = True

# social links (used throughout guweb)
github = 'https://github.com/varkaria/guweb'
# social links (used throughout asahi-web)
github = 'https://github.com/7ez/asahi-web'
discord_server = 'https://discord.com/invite/Y5uPvcNpD9'
youtube = 'https://youtube.com/'
twitter = 'https://twitter.com/'
Expand Down
Loading

0 comments on commit 162728c

Please sign in to comment.