mirror of https://gitlab.com/litecord/litecord.git
Merge branch 'master' into 'master'
new invite handling and unconfirmed account support See merge request litecord/litecord!8
This commit is contained in:
commit
00346555d1
|
|
@ -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 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 invite:
|
||||
try:
|
||||
await use_invite(new_id, invite)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass # do nothing
|
||||
|
||||
return jsonify({
|
||||
'token': make_token(new_id, pwd_hash)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import datetime
|
||||
import hashlib
|
||||
import base64
|
||||
import os
|
||||
|
||||
from quart import Blueprint, request, current_app as app, jsonify
|
||||
|
|
@ -19,6 +20,82 @@ 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')
|
||||
|
||||
if inv['max_age'] is not 0:
|
||||
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'] is not -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):
|
||||
|
|
@ -34,7 +111,7 @@ async def create_invite(channel_id):
|
|||
ChannelType.GUILD_VOICE.value):
|
||||
raise BadRequest('Invalid channel type')
|
||||
|
||||
invite_code = hashlib.md5(os.urandom(64)).hexdigest()[:6]
|
||||
invite_code = base64.b64encode(hashlib.md5(os.urandom(64)).digest()).decode("utf-8").replace("/", "").replace("+", "")[:7]
|
||||
|
||||
await app.db.execute(
|
||||
"""
|
||||
|
|
@ -148,82 +225,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)
|
||||
|
|
|
|||
|
|
@ -171,6 +171,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'
|
||||
|
|
@ -248,6 +252,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)
|
||||
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ class GatewayWebsocket:
|
|||
|
||||
if self.state.bot:
|
||||
return [{
|
||||
'id': row[0],
|
||||
'id': row,
|
||||
'unavailable': True,
|
||||
} for row in guild_ids]
|
||||
|
||||
|
|
@ -373,12 +373,13 @@ class GatewayWebsocket:
|
|||
log.info('subscribing to {} dms', len(dm_ids))
|
||||
await self.ext.dispatcher.sub_many('channel', user_id, dm_ids)
|
||||
|
||||
# subscribe to all friends
|
||||
# (their friends will also subscribe back
|
||||
# when they come online)
|
||||
friend_ids = await self.user_storage.get_friend_ids(user_id)
|
||||
log.info('subscribing to {} friends', len(friend_ids))
|
||||
await self.ext.dispatcher.sub_many('friend', user_id, friend_ids)
|
||||
if not self.state.bot:
|
||||
# subscribe to all friends
|
||||
# (their friends will also subscribe back
|
||||
# when they come online)
|
||||
friend_ids = await self.user_storage.get_friend_ids(user_id)
|
||||
log.info('subscribing to {} friends', len(friend_ids))
|
||||
await self.ext.dispatcher.sub_many('friend', user_id, friend_ids)
|
||||
|
||||
async def update_status(self, status: dict):
|
||||
"""Update the status of the current websocket connection."""
|
||||
|
|
@ -891,7 +892,6 @@ class GatewayWebsocket:
|
|||
log.warning('conn close, state={}, err={}', self.state, err)
|
||||
except WebsocketClose as err:
|
||||
log.warning('ws close, state={} err={}', self.state, err)
|
||||
|
||||
await self.ws.close(code=err.code, reason=err.reason)
|
||||
except Exception as err:
|
||||
log.exception('An exception has occoured. state={}', self.state)
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE users ALTER COLUMN email DROP NOT NULL;
|
||||
ALTER TABLE users ALTER COLUMN email SET DEFAULT NULL;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue