mirror of https://gitlab.com/litecord/litecord.git
users: decouple into delete_user
- manage.cmd.user: add deluser cmd
This commit is contained in:
parent
3a8eaec147
commit
ec324f3107
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue