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]
|
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 = {
|
column = {
|
||||||
'channel_overwrites': 'target_user',
|
'channel_overwrites': 'target_user',
|
||||||
'user_settings': 'id'
|
'user_settings': 'id'
|
||||||
}.get(table, 'user_id')
|
}.get(table, 'user_id')
|
||||||
|
|
||||||
res = await app.db.execute(f"""
|
res = await db.execute(f"""
|
||||||
DELETE FROM {table}
|
DELETE FROM {table}
|
||||||
WHERE {column} = $1
|
WHERE {column} = $1
|
||||||
""", user_id)
|
""", user_id)
|
||||||
|
|
@ -492,6 +493,74 @@ async def _del_from_table(table: str, user_id: int):
|
||||||
user_id, table, res)
|
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'])
|
@bp.route('/@me/delete', methods=['POST'])
|
||||||
async def delete_account():
|
async def delete_account():
|
||||||
"""Delete own account.
|
"""Delete own account.
|
||||||
|
|
@ -526,64 +595,7 @@ async def delete_account():
|
||||||
if not await check_password(pwd_hash, password):
|
if not await check_password(pwd_hash, password):
|
||||||
raise Unauthorized('password does not match')
|
raise Unauthorized('password does not match')
|
||||||
|
|
||||||
new_username = f'Deleted User {rand_hex()}'
|
await delete_user(user_id)
|
||||||
|
await _force_disconnect(user_id)
|
||||||
# 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)
|
|
||||||
|
|
||||||
return '', 204
|
return '', 204
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,12 @@ import base64
|
||||||
import itsdangerous
|
import itsdangerous
|
||||||
import bcrypt
|
import bcrypt
|
||||||
from litecord.blueprints.auth import create_user, make_token
|
from litecord.blueprints.auth import create_user, make_token
|
||||||
|
from litecord.blueprints.users import delete_user
|
||||||
from litecord.enums import UserFlags
|
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("""
|
return await ctx.db.fetchval("""
|
||||||
SELECT id
|
SELECT id
|
||||||
FROM users
|
FROM users
|
||||||
|
|
@ -53,8 +55,12 @@ async def adduser(ctx, args):
|
||||||
uid, _ = await create_user(args.username, args.email,
|
uid, _ = await create_user(args.username, args.email,
|
||||||
args.password, ctx.db, ctx.loop)
|
args.password, ctx.db, ctx.loop)
|
||||||
|
|
||||||
|
user = await ctx.storage.get_user(uid)
|
||||||
|
|
||||||
print('created!')
|
print('created!')
|
||||||
print(f'\tuid: {uid}')
|
print(f'\tuid: {uid}')
|
||||||
|
print(f'\tusername: {user["username"]}')
|
||||||
|
print(f'\tdiscrim: {user["discriminator"]}')
|
||||||
|
|
||||||
|
|
||||||
async def make_staff(ctx, args):
|
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))
|
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):
|
def setup(subparser):
|
||||||
setup_test_parser = subparser.add_parser(
|
setup_test_parser = subparser.add_parser(
|
||||||
'adduser',
|
'adduser',
|
||||||
|
|
@ -109,23 +140,25 @@ def setup(subparser):
|
||||||
description=make_staff.__doc__
|
description=make_staff.__doc__
|
||||||
)
|
)
|
||||||
|
|
||||||
|
staff_parser.add_argument('username')
|
||||||
staff_parser.add_argument(
|
staff_parser.add_argument(
|
||||||
'username'
|
'discrim', help='the discriminator of the user')
|
||||||
)
|
|
||||||
staff_parser.add_argument(
|
|
||||||
'discrim', help='the discriminator of the user'
|
|
||||||
)
|
|
||||||
|
|
||||||
staff_parser.set_defaults(func=make_staff)
|
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(
|
token_parser = subparser.add_parser(
|
||||||
'generate_token',
|
'generate_token',
|
||||||
help='generate a token for specified bot',
|
help='generate a token for specified bot',
|
||||||
description=generate_bot_token.__doc__
|
description=generate_bot_token.__doc__)
|
||||||
)
|
|
||||||
|
|
||||||
token_parser.add_argument(
|
token_parser.add_argument('user_id')
|
||||||
'user_id'
|
|
||||||
)
|
|
||||||
|
|
||||||
token_parser.set_defaults(func=generate_bot_token)
|
token_parser.set_defaults(func=generate_bot_token)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue