mirror of https://gitlab.com/litecord/litecord.git
new invite handling and unconfirmed account support
This commit is contained in:
parent
2542084c73
commit
cf5ad107ad
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue