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.schemas import validate, REGISTER, REGISTER_WITH_INVITE
from litecord.errors import BadRequest
from .invites import delete_invite, use_invite
bp = Blueprint('auth', __name__)
@ -35,15 +36,29 @@ async def register():
"""Register a single user."""
enabled = app.config.get('REGISTRATIONS')
if not enabled:
return 'Registrations disabled', 405
raise BadRequest('Registrations disabled', {
'email': 'Registrations are disabled.'
})
j = validate(await request.get_json(), REGISTER)
email, password, username = j['email'], j['password'], j['username']
j = await request.get_json()
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(
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({
'token': make_token(new_id, pwd_hash)
})

View File

@ -19,6 +19,81 @@ from litecord.blueprints.checks import (
log = Logger(__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'])
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'])
async def use_invite(invite_code):
async def _use_invite(invite_code):
"""Use an invite."""
user_id = await token_check()
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('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)
await use_invite(user_id, invite_code)
# the reply is an invite object for some reason.
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):
# Do not do password checks on unclaimed accounts
if user['email'] is None:
return
if not j['password']:
raise BadRequest('password required', {
'password': 'password required'
@ -245,6 +249,11 @@ async def patch_me():
WHERE id = $2
""", 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']:
await _check_pass(j, user)

View File

@ -157,9 +157,13 @@ def validate(reqjson: Union[Dict, List], schema: Dict,
REGISTER = {
'email': {'type': 'email', '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)
@ -470,7 +474,7 @@ INVITE = {
'temporary': {'type': 'boolean', 'required': False, 'default': False},
'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 = {

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,
username text NOT NULL,
discriminator varchar(4) NOT NULL,
email varchar(255) NOT NULL UNIQUE,
email varchar(255) DEFAULT NULL,
-- user properties
bot boolean DEFAULT FALSE,