users: decouple into delete_user

- manage.cmd.user: add deluser cmd
This commit is contained in:
Luna 2019-02-05 19:21:22 -03:00
parent 3a8eaec147
commit ec324f3107
2 changed files with 117 additions and 72 deletions

View File

@ -477,13 +477,14 @@ def rand_hex(length: int = 8) -> str:
return urandom(length).hex()[:length]
async def _del_from_table(table: str, user_id: int):
async def _del_from_table(db, table: str, user_id: int):
"""Delete a row from a table."""
column = {
'channel_overwrites': 'target_user',
'user_settings': 'id'
}.get(table, 'user_id')
res = await app.db.execute(f"""
res = await db.execute(f"""
DELETE FROM {table}
WHERE {column} = $1
""", user_id)
@ -492,6 +493,74 @@ async def _del_from_table(table: str, user_id: int):
user_id, table, res)
async def delete_user(user_id, *, db=None):
"""Delete a user. Does not disconnect the user."""
if db is None:
db = app.db
new_username = f'Deleted User {rand_hex()}'
# by using a random hex in password_hash
# we break attempts at using the default '123' password hash
# to issue valid tokens for deleted users.
await db.execute("""
UPDATE users
SET
username = $1,
email = NULL,
mfa_enabled = false,
verified = false,
avatar = NULL,
flags = 0,
premium_since = NULL,
phone = '',
password_hash = $2
WHERE
id = $3
""", new_username, rand_hex(32), user_id)
# remove the user from various tables
await _del_from_table(db, 'user_settings', user_id)
await _del_from_table(db, 'user_payment_sources', user_id)
await _del_from_table(db, 'user_subscriptions', user_id)
await _del_from_table(db, 'user_payments', user_id)
await _del_from_table(db, 'user_read_state', user_id)
await _del_from_table(db, 'guild_settings', user_id)
await _del_from_table(db, 'guild_settings_channel_overrides', user_id)
await db.execute("""
DELETE FROM relationships
WHERE user_id = $1 OR peer_id = $1
""", user_id)
# DMs are still maintained, but not the state.
await _del_from_table(db, 'dm_channel_state', user_id)
# TODO: group DMs
await _del_from_table(db, 'members', user_id)
await _del_from_table(db, 'member_roles', user_id)
await _del_from_table(db, 'channel_overwrites', user_id)
async def _force_disconnect(user_id):
# after removing the user from all tables, we need to force
# all known user states to reconnect, causing the user to not
# be online anymore.
user_states = app.state_manager.user_states(user_id)
for state in user_states:
# make it unable to resume
app.state_manager.remove(state)
if not state.ws:
continue
# force a close, 4000 should make the client reconnect.
await state.ws.close(4000)
@bp.route('/@me/delete', methods=['POST'])
async def delete_account():
"""Delete own account.
@ -526,64 +595,7 @@ async def delete_account():
if not await check_password(pwd_hash, password):
raise Unauthorized('password does not match')
new_username = f'Deleted User {rand_hex()}'
# by using a random hex in password_hash
# we break attempts at using the default '123' password hash
# to issue valid tokens for deleted users.
await app.db.execute("""
UPDATE users
SET
username = $1,
email = NULL,
mfa_enabled = false,
verified = false,
avatar = NULL,
flags = 0,
premium_since = NULL,
phone = '',
password_hash = $2
WHERE
id = $3
""", new_username, rand_hex(32), user_id)
# remove the user from various tables
await _del_from_table('user_settings', user_id)
await _del_from_table('user_payment_sources', user_id)
await _del_from_table('user_subscriptions', user_id)
await _del_from_table('user_payments', user_id)
await _del_from_table('user_read_state', user_id)
await _del_from_table('guild_settings', user_id)
await _del_from_table('guild_settings_channel_overrides', user_id)
await app.db.execute("""
DELETE FROM relationships
WHERE user_id = $1 OR peer_id = $1
""", user_id)
# DMs are still maintained, but not the state.
await _del_from_table('dm_channel_state', user_id)
# TODO: group DMs
await _del_from_table('members', user_id)
await _del_from_table('member_roles', user_id)
await _del_from_table('channel_overwrites', user_id)
# after removing the user from all tables, we need to force
# all known user states to reconnect, causing the user to not
# be online anymore.
user_states = app.state_manager.user_states(user_id)
for state in user_states:
# make it unable to resume
app.state_manager.remove(state)
if not state.ws:
continue
# force a close, 4000 should make the client reconnect.
await state.ws.close(4000)
await delete_user(user_id)
await _force_disconnect(user_id)
return '', 204

View File

@ -21,10 +21,12 @@ import base64
import itsdangerous
import bcrypt
from litecord.blueprints.auth import create_user, make_token
from litecord.blueprints.users import delete_user
from litecord.enums import UserFlags
async def find_user(username, discrim, ctx):
async def find_user(username, discrim, ctx) -> int:
"""Get a user ID via the username/discrim pair."""
return await ctx.db.fetchval("""
SELECT id
FROM users
@ -53,8 +55,12 @@ async def adduser(ctx, args):
uid, _ = await create_user(args.username, args.email,
args.password, ctx.db, ctx.loop)
user = await ctx.storage.get_user(uid)
print('created!')
print(f'\tuid: {uid}')
print(f'\tusername: {user["username"]}')
print(f'\tdiscrim: {user["discriminator"]}')
async def make_staff(ctx, args):
@ -88,6 +94,31 @@ async def generate_bot_token(ctx, args):
print(make_token(args.user_id, password_hash))
async def del_user(ctx, args):
"""Delete a user."""
uid = await find_user(args.username, args.discrim, ctx)
if uid is None:
print('user not found')
return
user = await ctx.storage.get_user(uid)
print(f'\tuid: {user["user_id"]}')
print(f'\tuname: {user["username"]}')
print(f'\tdiscrim: {user["discriminator"]}')
print('\n you sure you want to delete user? press Y (uppercase)')
confirm = input()
if confirm != 'Y':
print('not confirmed')
return
await delete_user(uid)
print('ok')
def setup(subparser):
setup_test_parser = subparser.add_parser(
'adduser',
@ -109,23 +140,25 @@ def setup(subparser):
description=make_staff.__doc__
)
staff_parser.add_argument('username')
staff_parser.add_argument(
'username'
)
staff_parser.add_argument(
'discrim', help='the discriminator of the user'
)
'discrim', help='the discriminator of the user')
staff_parser.set_defaults(func=make_staff)
del_user_parser = subparser.add_parser(
'deluser', help='delete a single user')
del_user_parser.add_argument('username')
del_user_parser.add_argument('discriminator')
del_user_parser.set_defaults(func=del_user)
token_parser = subparser.add_parser(
'generate_token',
help='generate a token for specified bot',
description=generate_bot_token.__doc__
)
description=generate_bot_token.__doc__)
token_parser.add_argument(
'user_id'
)
token_parser.add_argument('user_id')
token_parser.set_defaults(func=generate_bot_token)