new invite handling and unconfirmed account support

This commit is contained in:
gabixdev 2018-12-03 21:06:47 +01:00
parent 2542084c73
commit cf5ad107ad
6 changed files with 114 additions and 80 deletions

View File

@ -7,6 +7,7 @@ from quart import Blueprint, jsonify, request, current_app as app
from litecord.auth import token_check, create_user from litecord.auth import token_check, create_user
from litecord.schemas import validate, REGISTER, REGISTER_WITH_INVITE from litecord.schemas import validate, REGISTER, REGISTER_WITH_INVITE
from litecord.errors import BadRequest from litecord.errors import BadRequest
from .invites import delete_invite, use_invite
bp = Blueprint('auth', __name__) bp = Blueprint('auth', __name__)
@ -35,15 +36,29 @@ async def register():
"""Register a single user.""" """Register a single user."""
enabled = app.config.get('REGISTRATIONS') enabled = app.config.get('REGISTRATIONS')
if not enabled: if not enabled:
return 'Registrations disabled', 405 raise BadRequest('Registrations disabled', {
'email': 'Registrations are disabled.'
})
j = validate(await request.get_json(), REGISTER) j = await request.get_json()
email, password, username = j['email'], j['password'], j['username']
if not 'password' in j:
j['password'] = 'default_password' # we need some password to make a token
j = validate(j, REGISTER)
email, password, username, invite = j['email'] if 'email' in j else None, j['password'], j['username'], j['invite']
new_id, pwd_hash = await create_user( new_id, pwd_hash = await create_user(
username, email, password, app.db username, email, password, app.db
) )
if j['invite']:
try:
await use_invite(new_id, j['invite'])
except Exception as e:
print(e)
pass # do nothing
return jsonify({ return jsonify({
'token': make_token(new_id, pwd_hash) 'token': make_token(new_id, pwd_hash)
}) })

View File

@ -19,6 +19,81 @@ from litecord.blueprints.checks import (
log = Logger(__name__) log = Logger(__name__)
bp = Blueprint('invites', __name__) bp = Blueprint('invites', __name__)
# TODO: Ban handling
async def use_invite(user_id, invite_code):
"""Try using an invite"""
inv = await app.db.fetchrow("""
SELECT guild_id, created_at, max_age, uses, max_uses
FROM invites
WHERE code = $1
""", invite_code)
if inv is None:
raise BadRequest('Unknown invite')
now = datetime.datetime.utcnow()
delta_sec = (now - inv['created_at']).total_seconds()
if delta_sec > inv['max_age']:
await delete_invite(invite_code)
raise BadRequest('Unknown invite (expiried)')
if inv['max_uses'] != -1 and inv['uses'] > inv['max_uses']:
await delete_invite(invite_code)
raise BadRequest('Unknown invite (too many uses)')
guild_id = inv['guild_id']
joined = await app.db.fetchval("""
SELECT joined_at
FROM members
WHERE user_id = $1 AND guild_id = $2
""", user_id, guild_id)
if joined is not None:
raise BadRequest('You are already in the guild')
await app.db.execute("""
INSERT INTO members (user_id, guild_id)
VALUES ($1, $2)
""", user_id, guild_id)
await create_guild_settings(guild_id, user_id)
# add the @everyone role to the invited member
await app.db.execute("""
INSERT INTO member_roles (user_id, guild_id, role_id)
VALUES ($1, $2, $3)
""", user_id, guild_id, guild_id)
await app.db.execute("""
UPDATE invites
SET uses = uses + 1
WHERE code = $1
""", invite_code)
# tell current members a new member came up
member = await app.storage.get_member_data_one(guild_id, user_id)
await app.dispatcher.dispatch_guild(guild_id, 'GUILD_MEMBER_ADD', {
**member,
**{
'guild_id': str(guild_id),
},
})
# update member lists for the new member
await app.dispatcher.dispatch(
'lazy_guild', guild_id, 'new_member', user_id)
# subscribe new member to guild, so they get events n stuff
await app.dispatcher.sub('guild', guild_id, user_id)
# tell the new member that theres the guild it just joined.
# we use dispatch_user_guild so that we send the GUILD_CREATE
# just to the shards that are actually tied to it.
guild = await app.storage.get_guild_full(guild_id, user_id, 250)
await app.dispatcher.dispatch_user_guild(
user_id, guild_id, 'GUILD_CREATE', guild)
@bp.route('/channels/<int:channel_id>/invites', methods=['POST']) @bp.route('/channels/<int:channel_id>/invites', methods=['POST'])
async def create_invite(channel_id): async def create_invite(channel_id):
@ -148,82 +223,11 @@ async def get_channel_invites(channel_id: int):
@bp.route('/invite/<invite_code>', methods=['POST']) @bp.route('/invite/<invite_code>', methods=['POST'])
async def use_invite(invite_code): async def _use_invite(invite_code):
"""Use an invite.""" """Use an invite."""
user_id = await token_check() user_id = await token_check()
inv = await app.db.fetchrow(""" await use_invite(user_id, invite_code)
SELECT guild_id, created_at, max_age, uses, max_uses
FROM invites
WHERE code = $1
""", invite_code)
if inv is None:
raise BadRequest('Invite not found')
now = datetime.datetime.utcnow()
delta_sec = (now - inv['created_at']).total_seconds()
if delta_sec > inv['max_age']:
await delete_invite(invite_code)
raise BadRequest('Invite has expired (age).')
if inv['uses'] > inv['max_uses']:
await delete_invite(invite_code)
raise BadRequest('Invite has expired (uses).')
guild_id = inv['guild_id']
joined = await app.db.fetchval("""
SELECT joined_at
FROM members
WHERE user_id = $1 AND guild_id = $2
""", user_id, guild_id)
if joined is not None:
raise BadRequest('You are already in the guild')
await app.db.execute("""
INSERT INTO members (user_id, guild_id)
VALUES ($1, $2)
""", user_id, guild_id)
await create_guild_settings(guild_id, user_id)
# add the @everyone role to the invited member
await app.db.execute("""
INSERT INTO member_roles (user_id, guild_id, role_id)
VALUES ($1, $2, $3)
""", user_id, guild_id, guild_id)
await app.db.execute("""
UPDATE invites
SET uses = uses + 1
WHERE code = $1
""", invite_code)
# tell current members a new member came up
member = await app.storage.get_member_data_one(guild_id, user_id)
await app.dispatcher.dispatch_guild(guild_id, 'GUILD_MEMBER_ADD', {
**member,
**{
'guild_id': str(guild_id),
},
})
# update member lists for the new member
await app.dispatcher.dispatch(
'lazy_guild', guild_id, 'new_member', user_id)
# subscribe new member to guild, so they get events n stuff
await app.dispatcher.sub('guild', guild_id, user_id)
# tell the new member that theres the guild it just joined.
# we use dispatch_user_guild so that we send the GUILD_CREATE
# just to the shards that are actually tied to it.
guild = await app.storage.get_guild_full(guild_id, user_id, 250)
await app.dispatcher.dispatch_user_guild(
user_id, guild_id, 'GUILD_CREATE', guild)
# the reply is an invite object for some reason. # the reply is an invite object for some reason.
inv = await app.storage.get_invite(invite_code) inv = await app.storage.get_invite(invite_code)

View File

@ -168,6 +168,10 @@ def to_update(j: dict, user: dict, field: str):
async def _check_pass(j, user): async def _check_pass(j, user):
# Do not do password checks on unclaimed accounts
if user['email'] is None:
return
if not j['password']: if not j['password']:
raise BadRequest('password required', { raise BadRequest('password required', {
'password': 'password required' 'password': 'password required'
@ -245,6 +249,11 @@ async def patch_me():
WHERE id = $2 WHERE id = $2
""", new_icon.icon_hash, user_id) """, new_icon.icon_hash, user_id)
if user['email'] is None and not 'new_password' in j:
raise BadRequest('missing password', {
'password': 'Please set a password.'
})
if 'new_password' in j and j['new_password']: if 'new_password' in j and j['new_password']:
await _check_pass(j, user) await _check_pass(j, user)

View File

@ -157,9 +157,13 @@ def validate(reqjson: Union[Dict, List], schema: Dict,
REGISTER = { REGISTER = {
'email': {'type': 'email', 'required': True},
'username': {'type': 'username', 'required': True}, 'username': {'type': 'username', 'required': True},
'password': {'type': 'string', 'minlength': 5, 'required': True} 'email': {'type': 'email', 'required': False},
'password': {'type': 'string', 'minlength': 5, 'required': False},
'invite': {'type': 'string', 'required': False, 'nullable': True}, # optional
'fingerprint': {'type': 'string', 'required': False, 'nullable': True}, # these are sent by official client
'captcha_key': {'type': 'string', 'required': False, 'nullable': True},
'consent': {'type': 'boolean'}
} }
# only used by us, not discord, hence 'invcode' (to separate from discord) # only used by us, not discord, hence 'invcode' (to separate from discord)
@ -470,7 +474,7 @@ INVITE = {
'temporary': {'type': 'boolean', 'required': False, 'default': False}, 'temporary': {'type': 'boolean', 'required': False, 'default': False},
'unique': {'type': 'boolean', 'required': False, 'default': True}, 'unique': {'type': 'boolean', 'required': False, 'default': True},
'validate': {'type': 'boolean', 'required': False, 'nullable': True} 'validate': {'type': 'string', 'required': False, 'nullable': True} # discord client sends invite code there
} }
USER_SETTINGS = { USER_SETTINGS = {

View File

@ -0,0 +1,2 @@
ALTER TABLE users ALTER COLUMN email DROP NOT NULL;
ALTER TABLE users ALTER COLUMN email SET DEFAULT NULL;

View File

@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS users (
id bigint UNIQUE NOT NULL, id bigint UNIQUE NOT NULL,
username text NOT NULL, username text NOT NULL,
discriminator varchar(4) NOT NULL, discriminator varchar(4) NOT NULL,
email varchar(255) NOT NULL UNIQUE, email varchar(255) DEFAULT NULL,
-- user properties -- user properties
bot boolean DEFAULT FALSE, bot boolean DEFAULT FALSE,