fix CSRF, more user routes
This commit is contained in:
parent
b6fa88f201
commit
aa9adb075a
2 changed files with 61 additions and 21 deletions
|
|
@ -39,6 +39,7 @@ class AppConfig(ConfigOptions):
|
||||||
app_name = ConfigValue()
|
app_name = ConfigValue()
|
||||||
server_name = ConfigValue()
|
server_name = ConfigValue()
|
||||||
private_assets = ConfigValue(cast=ssv_list)
|
private_assets = ConfigValue(cast=ssv_list)
|
||||||
|
# deprecated
|
||||||
jquery_url = ConfigValue(default='https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js')
|
jquery_url = ConfigValue(default='https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js')
|
||||||
app_is_behind_proxy = ConfigValue(cast=int, default=0)
|
app_is_behind_proxy = ConfigValue(cast=int, default=0)
|
||||||
impressum = ConfigValue(cast=twocolon_list, default='')
|
impressum = ConfigValue(cast=twocolon_list, default='')
|
||||||
|
|
@ -142,6 +143,7 @@ async def _load_user():
|
||||||
logger.error(f'{e}')
|
logger.error(f'{e}')
|
||||||
g.no_user = True
|
g.no_user = True
|
||||||
|
|
||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
async def _unload_user(resp):
|
async def _unload_user(resp):
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,21 @@ from werkzeug.security import check_password_hash
|
||||||
from suou.quart import add_rest
|
from suou.quart import add_rest
|
||||||
|
|
||||||
from freak.accounts import LoginStatus, check_login
|
from freak.accounts import LoginStatus, check_login
|
||||||
from freak.algorithms import topic_timeline
|
from freak.algorithms import topic_timeline, user_timeline
|
||||||
|
|
||||||
from ..models import Guild, Post, User, db
|
from ..models import Guild, Post, User, db
|
||||||
from .. import UserLoader, app, app_config, __version__ as freak_version
|
from .. import UserLoader, app, app_config, __version__ as freak_version, csrf
|
||||||
|
|
||||||
bp = Blueprint('rest', __name__, url_prefix='/v1')
|
bp = Blueprint('rest', __name__, url_prefix='/v1')
|
||||||
rest = add_rest(app, '/v1', '/ajax')
|
rest = add_rest(app, '/v1', '/ajax')
|
||||||
|
|
||||||
|
## XXX potential security hole, but needed for REST to work
|
||||||
|
csrf.exempt(bp)
|
||||||
|
|
||||||
current_user: UserLoader
|
current_user: UserLoader
|
||||||
|
|
||||||
## TODO deprecate auth_required since it does not work
|
## TODO deprecate auth_required since it does not work
|
||||||
|
## will be removed in 0.6
|
||||||
from suou.flask_sqlalchemy import require_auth
|
from suou.flask_sqlalchemy import require_auth
|
||||||
auth_required = deprecated('use login_required() and current_user instead')(require_auth(User, db))
|
auth_required = deprecated('use login_required() and current_user instead')(require_auth(User, db))
|
||||||
|
|
||||||
|
|
@ -37,11 +41,22 @@ async def get_nurupo():
|
||||||
|
|
||||||
@bp.get('/health')
|
@bp.get('/health')
|
||||||
async def health():
|
async def health():
|
||||||
|
async with db as session:
|
||||||
|
hi = dict(
|
||||||
|
version=freak_version,
|
||||||
|
name = app_config.app_name,
|
||||||
|
post_count = await Post.count(),
|
||||||
|
user_count = await User.active_count(),
|
||||||
|
me = Snowflake(current_user.id).to_b32l() if current_user else None
|
||||||
|
)
|
||||||
|
|
||||||
|
return hi
|
||||||
|
|
||||||
|
@bp.get('/oath')
|
||||||
|
async def oath():
|
||||||
return dict(
|
return dict(
|
||||||
version=freak_version,
|
## XXX might break any time!
|
||||||
name = app_config.app_name,
|
csrf_token= await csrf._get_csrf_token()
|
||||||
post_count = await Post.count(),
|
|
||||||
user_count = await User.active_count()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
## TODO coverage of REST is still partial, but it's planned
|
## TODO coverage of REST is still partial, but it's planned
|
||||||
|
|
@ -56,16 +71,10 @@ async def health():
|
||||||
@bp.get('/user/@me')
|
@bp.get('/user/@me')
|
||||||
@login_required
|
@login_required
|
||||||
async def get_user_me():
|
async def get_user_me():
|
||||||
return redirect(url_for(f'rest.user_get', current_user.id)), 302
|
return redirect(url_for(f'rest.user_get', id=current_user.id)), 302
|
||||||
|
|
||||||
@bp.get('/user/<b32l:id>')
|
def _user_info(u: User):
|
||||||
async def user_get(id: int):
|
return dict(
|
||||||
## TODO sanizize REST to make blocked users inaccessible
|
|
||||||
async with db as session:
|
|
||||||
u: User | None = (await session.execute(select(User).where(User.id == id))).scalar()
|
|
||||||
if u is None:
|
|
||||||
return dict(error='User not found'), 404
|
|
||||||
uj = dict(
|
|
||||||
id = f'{Snowflake(u.id):l}',
|
id = f'{Snowflake(u.id):l}',
|
||||||
username = u.username,
|
username = u.username,
|
||||||
display_name = u.display_name,
|
display_name = u.display_name,
|
||||||
|
|
@ -75,8 +84,33 @@ async def user_get(id: int):
|
||||||
biography=u.biography,
|
biography=u.biography,
|
||||||
badges = u.badges()
|
badges = u.badges()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@bp.get('/user/<b32l:id>')
|
||||||
|
async def user_get(id: int):
|
||||||
|
## TODO sanizize REST to make blocked users inaccessible
|
||||||
|
async with db as session:
|
||||||
|
u: User | None = (await session.execute(select(User).where(User.id == id))).scalar()
|
||||||
|
if u is None:
|
||||||
|
return dict(error='User not found'), 404
|
||||||
|
uj = _user_info(u)
|
||||||
return dict(users={f'{Snowflake(id):l}': uj})
|
return dict(users={f'{Snowflake(id):l}': uj})
|
||||||
|
|
||||||
|
@bp.get('/user/<b32l:id>/feed')
|
||||||
|
async def user_feed_get(id: int):
|
||||||
|
async with db as session:
|
||||||
|
u: User | None = (await session.execute(select(User).where(User.id == id))).scalar()
|
||||||
|
if u is None:
|
||||||
|
return dict(error='User not found'), 404
|
||||||
|
uj = _user_info(u)
|
||||||
|
|
||||||
|
feed = []
|
||||||
|
algo = user_timeline(u)
|
||||||
|
posts = await db.paginate(algo)
|
||||||
|
async for p in posts:
|
||||||
|
feed.append(p.feed_info())
|
||||||
|
|
||||||
|
return dict(users={f'{Snowflake(id):l}': uj}, feed=feed)
|
||||||
|
|
||||||
@bp.get('/user/@<username>')
|
@bp.get('/user/@<username>')
|
||||||
async def resolve_user(username: str):
|
async def resolve_user(username: str):
|
||||||
async with db as session:
|
async with db as session:
|
||||||
|
|
@ -85,6 +119,14 @@ async def resolve_user(username: str):
|
||||||
abort(404, 'User not found')
|
abort(404, 'User not found')
|
||||||
return redirect(url_for('rest.user_get', id=uid)), 302
|
return redirect(url_for('rest.user_get', id=uid)), 302
|
||||||
|
|
||||||
|
@bp.get('/user/@<username>/feed')
|
||||||
|
async def resolve_user_feed(username: str):
|
||||||
|
async with db as session:
|
||||||
|
uid: User | None = (await session.execute(select(User.id).select_from(User).where(User.username == username))).scalar()
|
||||||
|
if uid is None:
|
||||||
|
abort(404, 'User not found')
|
||||||
|
return redirect(url_for('rest.user_feed_get', id=uid)), 302
|
||||||
|
|
||||||
## POSTS ##
|
## POSTS ##
|
||||||
|
|
||||||
@bp.get('/post/<b32l:id>')
|
@bp.get('/post/<b32l:id>')
|
||||||
|
|
@ -138,8 +180,6 @@ async def guild_feed(gname: str):
|
||||||
if gu is None:
|
if gu is None:
|
||||||
return dict(error='Not found'), 404
|
return dict(error='Not found'), 404
|
||||||
gj = await _guild_info(gu)
|
gj = await _guild_info(gu)
|
||||||
|
|
||||||
# TODO add feed
|
|
||||||
feed = []
|
feed = []
|
||||||
algo = topic_timeline(gname)
|
algo = topic_timeline(gname)
|
||||||
posts = await db.paginate(algo)
|
posts = await db.paginate(algo)
|
||||||
|
|
@ -158,8 +198,6 @@ class LoginIn(BaseModel):
|
||||||
@bp.post('/login')
|
@bp.post('/login')
|
||||||
@validate_request(LoginIn)
|
@validate_request(LoginIn)
|
||||||
async def login(data: LoginIn):
|
async def login(data: LoginIn):
|
||||||
|
|
||||||
print(data)
|
|
||||||
async with db as session:
|
async with db as session:
|
||||||
u = (await session.execute(select(User).where(User.username == data.username))).scalar()
|
u = (await session.execute(select(User).where(User.username == data.username))).scalar()
|
||||||
match check_login(u, data.password):
|
match check_login(u, data.password):
|
||||||
|
|
@ -169,7 +207,7 @@ async def login(data: LoginIn):
|
||||||
login_user(UserLoader(u.get_id()), remember=True)
|
login_user(UserLoader(u.get_id()), remember=True)
|
||||||
else:
|
else:
|
||||||
login_user(UserLoader(u.get_id()))
|
login_user(UserLoader(u.get_id()))
|
||||||
return {'id': f'{Snowflake(u.id):l}'}, 204
|
return {'id': f'{Snowflake(u.id):l}'}, 200
|
||||||
case LoginStatus.ERROR:
|
case LoginStatus.ERROR:
|
||||||
abort(404, 'Invalid username or password')
|
abort(404, 'Invalid username or password')
|
||||||
case LoginStatus.SUSPENDED:
|
case LoginStatus.SUSPENDED:
|
||||||
|
|
@ -182,5 +220,5 @@ async def login(data: LoginIn):
|
||||||
@login_required
|
@login_required
|
||||||
async def logout():
|
async def logout():
|
||||||
logout_user()
|
logout_user()
|
||||||
return {}, 204
|
return '', 204
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue