Added login via API for app
This commit is contained in:
parent
7ce03fbf6f
commit
950db90547
|
@ -0,0 +1,119 @@
|
||||||
|
from sanic import response
|
||||||
|
from sanic import Blueprint, exceptions
|
||||||
|
from ext import *
|
||||||
|
from config import *
|
||||||
|
import sqlite3
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
bp = Blueprint("api", url_prefix="/manage/api")
|
||||||
|
|
||||||
|
@bp.route("/members.json")
|
||||||
|
async def api_members(request):
|
||||||
|
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
for o in sorted(request.app.ctx.om.cache.values(), key=lambda x: len(x.room_members), reverse=True):
|
||||||
|
if o.status in ['c', 'e']: continue
|
||||||
|
|
||||||
|
ret.append({
|
||||||
|
'code': o.code,
|
||||||
|
'sponsorship': o.sponsorship,
|
||||||
|
'is_fursuiter': o.is_fursuiter,
|
||||||
|
'name': o.name,
|
||||||
|
'has_early': o.has_early,
|
||||||
|
'has_late': o.has_late,
|
||||||
|
'propic': o.ans('propic'),
|
||||||
|
'propic_fursuiter': o.ans('propic_fursuiter'),
|
||||||
|
'staff_role': o.ans('staff_role'),
|
||||||
|
'country': o.country,
|
||||||
|
'is_checked_in': False
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.json(ret)
|
||||||
|
|
||||||
|
@bp.route("/events.json")
|
||||||
|
async def show_events(request):
|
||||||
|
|
||||||
|
with sqlite3.connect('data/event.db') as db:
|
||||||
|
db.row_factory = sqlite3.Row
|
||||||
|
events = db.execute('SELECT * FROM event ORDER BY start ASC')
|
||||||
|
return response.json([dict(x) for x in events])
|
||||||
|
|
||||||
|
@bp.route("/achievements.json")
|
||||||
|
async def show_events(request):
|
||||||
|
|
||||||
|
code = request.args.get("code")
|
||||||
|
|
||||||
|
with sqlite3.connect('data/achievement.db') as db:
|
||||||
|
db.row_factory = sqlite3.Row
|
||||||
|
events = db.execute('SELECT * FROM achievement ORDER BY ' + ('random() LIMIT 5' if code else 'points'))
|
||||||
|
return response.json([{'won_at': '2023-05-05T21:00Z' if code else None, **dict(x), 'about': 'This is instructions on how to win the field.'} for x in events])
|
||||||
|
|
||||||
|
@bp.get("/logout")
|
||||||
|
async def logout(request):
|
||||||
|
if not request.token:
|
||||||
|
return response.json({'ok': False, 'error': 'You need to provide a token.'}, status=401)
|
||||||
|
|
||||||
|
user = await request.app.ctx.om.get_order(code=request.token[:5])
|
||||||
|
if not user or user.api_token != request.token[5:]:
|
||||||
|
return response.json({'ok': False, 'error': 'The token you have provided is not valid.'}, status=401)
|
||||||
|
|
||||||
|
user.edit_answer('api_token', None)
|
||||||
|
await user.send_answers()
|
||||||
|
|
||||||
|
return response.json({'ok': True, 'message': 'You have been logged out and this token has been destroyed.'})
|
||||||
|
print(request.token)
|
||||||
|
|
||||||
|
@bp.get("/get_token/<code>/<login_code>")
|
||||||
|
async def get_token_from_code(request, code, login_code):
|
||||||
|
if not code in request.app.ctx.login_codes:
|
||||||
|
return response.json({'ok': False, 'error': 'You need to reauthenticate. The code has expired.'}, status=401)
|
||||||
|
|
||||||
|
if request.app.ctx.login_codes[code][1] == 0:
|
||||||
|
del request.app.ctx.login_codes[code]
|
||||||
|
return response.json({'ok': False, 'error': 'Too many tries. Please reauthenticate again.'}, status=401)
|
||||||
|
|
||||||
|
if request.app.ctx.login_codes[code][0] != login_code:
|
||||||
|
request.app.ctx.login_codes[code][1] -= 1
|
||||||
|
return response.json({'ok': False, 'error': 'The login code is incorrect. Try again.'}, status=401)
|
||||||
|
|
||||||
|
user = await request.app.ctx.om.get_order(code=code)
|
||||||
|
token = ''.join(random.choice(string.ascii_letters) for _ in range(48))
|
||||||
|
user.edit_answer('api_token', token)
|
||||||
|
await user.send_answers()
|
||||||
|
|
||||||
|
return response.json({'ok': True, 'token': code+token})
|
||||||
|
|
||||||
|
@bp.route("/get_token/<code>")
|
||||||
|
async def get_token(request, code):
|
||||||
|
user = await request.app.ctx.om.get_order(code=code)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return response.json({'ok': False, 'error': 'The user you have requested does not exist.'}, status=404)
|
||||||
|
|
||||||
|
if user.status != 'paid':
|
||||||
|
return response.json({'ok': False, 'error': 'This user is not allowed to login.'}, status=401)
|
||||||
|
|
||||||
|
if not user.email:
|
||||||
|
return response.json({'ok': False, 'error': 'This user has not provided their email.'}, status=401)
|
||||||
|
|
||||||
|
request.app.ctx.login_codes[code] = [''.join(random.choice(string.digits) for _ in range(6)), 3]
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = MIMEText(f"Hello {user.name}!\n\nWe have received a request to login in the app. If you didn't do this, please ignore this email. Somebody is probably playing with you.\n\nYour login code is: {request.app.ctx.login_codes[code][0]}\n\nPlease do not tell this to anybody!")
|
||||||
|
msg['Subject'] = '[Furizon] Your login code'
|
||||||
|
msg['From'] = 'Furizon <no-reply@furizon.net>'
|
||||||
|
msg['To'] = f"{user.name} <{user.email}>"
|
||||||
|
|
||||||
|
s = smtplib.SMTP_SSL(SMTP_HOST)
|
||||||
|
s.login(SMTP_USER, SMTP_PASSWORD)
|
||||||
|
s.sendmail(msg['From'], msg['to'], msg.as_string())
|
||||||
|
s.quit()
|
||||||
|
except:
|
||||||
|
return response.json({'ok': False, 'error': 'There has been an issue sending your e-mail. Please try again later or report to an admin.'}, status=500)
|
||||||
|
|
||||||
|
return response.json({'ok': True, 'message': 'A login code has been sent to your email.'})
|
||||||
|
|
17
app.py
17
app.py
|
@ -29,18 +29,19 @@ app.blueprint([room_bp, propic_bp, export_bp, stats_bp, api_bp, carpooling_bp])
|
||||||
@app.exception(exceptions.SanicException)
|
@app.exception(exceptions.SanicException)
|
||||||
async def clear_session(request, exception):
|
async def clear_session(request, exception):
|
||||||
tpl = app.ctx.tpl.get_template('error.html')
|
tpl = app.ctx.tpl.get_template('error.html')
|
||||||
response = html(tpl.render(exception=exception))
|
r = html(tpl.render(exception=exception))
|
||||||
|
|
||||||
if exception.status_code == 403:
|
if exception.status_code == 403:
|
||||||
del response.cookies["foxo_code"]
|
del r.cookies["foxo_code"]
|
||||||
del response.cookies["foxo_secret"]
|
del r.cookies["foxo_secret"]
|
||||||
return response
|
return r
|
||||||
|
|
||||||
@app.before_server_start
|
@app.before_server_start
|
||||||
async def main_start(*_):
|
async def main_start(*_):
|
||||||
print(">>>>>> main_start <<<<<<")
|
print(">>>>>> main_start <<<<<<")
|
||||||
|
|
||||||
app.ctx.om = OrderManager()
|
app.ctx.om = OrderManager()
|
||||||
|
app.ctx.login_codes = {}
|
||||||
app.ctx.tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=True)
|
app.ctx.tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=True)
|
||||||
app.ctx.tpl.globals.update(time=time)
|
app.ctx.tpl.globals.update(time=time)
|
||||||
app.ctx.tpl.globals.update(PROPIC_DEADLINE=PROPIC_DEADLINE)
|
app.ctx.tpl.globals.update(PROPIC_DEADLINE=PROPIC_DEADLINE)
|
||||||
|
@ -58,7 +59,7 @@ async def gen_barcode(request, code):
|
||||||
@app.route("/furizon/beyond/order/<code>/<secret>/open/<secret2>")
|
@app.route("/furizon/beyond/order/<code>/<secret>/open/<secret2>")
|
||||||
async def redirect_explore(request, code, secret, order: Order, secret2=None):
|
async def redirect_explore(request, code, secret, order: Order, secret2=None):
|
||||||
|
|
||||||
response = redirect(app.url_for("welcome"))
|
r = redirect(app.url_for("welcome"))
|
||||||
if order and order.code != code: order = None
|
if order and order.code != code: order = None
|
||||||
|
|
||||||
if not order:
|
if not order:
|
||||||
|
@ -70,9 +71,9 @@ async def redirect_explore(request, code, secret, order: Order, secret2=None):
|
||||||
res = res.json()
|
res = res.json()
|
||||||
if secret != res['secret']:
|
if secret != res['secret']:
|
||||||
raise exceptions.Forbidden("The secret part of the url is not correct. Check your E-Mail for the correct link, or contact support!")
|
raise exceptions.Forbidden("The secret part of the url is not correct. Check your E-Mail for the correct link, or contact support!")
|
||||||
response.cookies['foxo_code'] = code
|
r.cookies['foxo_code'] = code
|
||||||
response.cookies['foxo_secret'] = secret
|
r.cookies['foxo_secret'] = secret
|
||||||
return response
|
return r
|
||||||
|
|
||||||
@app.route("/manage/privacy")
|
@app.route("/manage/privacy")
|
||||||
async def privacy(request):
|
async def privacy(request):
|
||||||
|
|
5
ext.py
5
ext.py
|
@ -21,6 +21,8 @@ class Order:
|
||||||
self.code = data['code']
|
self.code = data['code']
|
||||||
self.pending_update = False
|
self.pending_update = False
|
||||||
|
|
||||||
|
self.email = data['email']
|
||||||
|
|
||||||
self.has_card = False
|
self.has_card = False
|
||||||
self.sponsorship = None
|
self.sponsorship = None
|
||||||
self.has_early = False
|
self.has_early = False
|
||||||
|
@ -83,6 +85,7 @@ class Order:
|
||||||
self.room_members = self.ans('room_members').split(',') if self.ans('room_members') else []
|
self.room_members = self.ans('room_members').split(',') if self.ans('room_members') else []
|
||||||
self.room_owner = (self.code == self.room_id)
|
self.room_owner = (self.code == self.room_id)
|
||||||
self.room_secret = self.ans('room_secret')
|
self.room_secret = self.ans('room_secret')
|
||||||
|
self.app_token = self.ans('app_token')
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, var):
|
def __getitem__(self, var):
|
||||||
|
@ -181,7 +184,7 @@ class OrderManager:
|
||||||
async def get_order(self, request=None, code=None, secret=None, cached=False):
|
async def get_order(self, request=None, code=None, secret=None, cached=False):
|
||||||
|
|
||||||
# Fill the cache on first load
|
# Fill the cache on first load
|
||||||
if not self.cache:
|
if not self.cache and FILL_CACHE:
|
||||||
p = 0
|
p = 0
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
|
|
Loading…
Reference in New Issue