예제 #1
0
파일: users.py 프로젝트: CraftJobs/backend
async def follow(username: str):
    request: Request = quart_rq

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con

        if 'Authorization' not in request.headers:
            return no('Missing authorization.')

        user_id = await con.fetchval(
            'SELECT user_id FROM sessions WHERE token = $1',
            request.headers.get('Authorization'))

        if not user_id:
            return no('Invalid authorization.')

        following_user_id = await con.fetchval(
            'SELECT id FROM users WHERE username_lower = $1', username.lower())

        if not following_user_id:
            return no('User not found.')

        await con.execute(
            'INSERT INTO following (id, follower_user_id, following_user_id) '
            + 'VALUES ($1, $2, $3) ON CONFLICT (follower_user_id, ' +
            'following_user_id) DO NOTHING', uuid4(), user_id,
            following_user_id)

        return {'success': True, 'message': ''}
예제 #2
0
파일: users.py 프로젝트: CraftJobs/backend
async def unfollow(username: str):
    request: Request = quart_rq

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con

        if 'Authorization' not in request.headers:
            return no('Missing authorization.')

        user_id = await con.fetchval(
            'SELECT user_id FROM sessions WHERE token = $1',
            request.headers.get('Authorization'))

        if not user_id:
            return no('Invalid authorization.')

        following_user_id = await con.fetchval(
            'SELECT id FROM users WHERE username_lower = $1', username.lower())

        if not following_user_id:
            return no('User not found.')

        await con.execute(
            'DELETE FROM following WHERE ' +
            'follower_user_id = $1 AND following_user_id = $2', user_id,
            following_user_id)

        return {'success': True, 'message': ''}
예제 #3
0
파일: users.py 프로젝트: CraftJobs/backend
async def rep(username: str):
    request: Request = quart_rq
    json = await request.get_json()

    if 'amount' not in json:
        return no('Missing amount.')
    if 'message' not in json:
        return no('Missing message')

    amount = json['amount']
    message = json['message']

    if not isinstance(amount, int):
        return no('Invalid amount.')

    if len(message) > 255:
        return no('Message cannot be more than 255 characters.')

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con

        if 'Authorization' not in request.headers:
            return no('Missing authorization.')

        user = await con.fetchrow(
            'SELECT user_id as id, u.admin as admin FROM sessions ' +
            'INNER JOIN users u on u.id = sessions.user_id WHERE token = $1',
            request.headers.get('Authorization'))

        if not user:
            return no('Invalid authorization.')

        if abs(amount) > 1 and not user['admin']:
            return no('Invalid amount.')

        target_user_id = await con.fetchval(
            'SELECT id FROM users WHERE username_lower = $1', username.lower())

        if not target_user_id:
            return no('User not found.')

        if user['id'] == target_user_id:
            return no('You cannot give yourself reputation.')

        await con.execute(
            'DELETE FROM reputation_log WHERE ' +
            'from_user_id = $1 AND to_user_id = $2', user['id'],
            target_user_id)
        if amount != 0:
            await con.execute(
                'INSERT INTO reputation_log (id, ' +
                'from_user_id, to_user_id, amount, message) ' +
                'VALUES ($1, $2, $3, $4, $5)', uuid4(), user['id'],
                target_user_id, amount, message)

        return {'success': True, 'message': ''}
예제 #4
0
파일: users.py 프로젝트: CraftJobs/backend
async def list_users():
    request: Request = quart_rq
    category = 'rep'

    if 'c' in request.args:
        category = request.args['c']

        if category not in QUERIES.keys():
            category = 'rep'

    query = LIST_USERS_BASE_QUERY + ' ' + QUERIES[category] + ' LIMIT 50'
    query_args = []

    users = []

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con

        user_id = await con.fetchval(
            'SELECT user_id FROM sessions WHERE token = $1',
            request.headers.get('Authorization'))

        if category == 'fol' or category == 'fpr':
            if 'Authorization' not in request.headers:
                return no('missing_authorization')
            elif not user_id:
                return no('invalid_authorization')
            else:
                query_args = [user_id]

        rows = await con.fetch(query, *query_args)

        for row in rows:
            description = row['description']

            if len(description) > 32:
                description = description[:29] + '...'

            users.append({
                'username': row['username'],
                'fullName': row['full_name'],
                'admin': row['admin'],
                'partialDescription': description,
                'reputation': row['reputation'],
                'createdAt': row['created_at'].isoformat()
            })

    return jsonify(users)
예제 #5
0
파일: login.py 프로젝트: CraftJobs/backend
async def logout():
    if 'Authorization' not in request.headers:
        return no('Missing authorization.')

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con
        await con.execute('DELETE FROM sessions WHERE token = $1',
                          request.headers.get('Authorization'))
        return {'success': True, 'message': ''}
예제 #6
0
파일: users.py 프로젝트: CraftJobs/backend
async def me_avatar():
    request: Request = quart_rq

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con

        if 'Authorization' not in request.headers:
            return no('Missing authorization.')

        user_id = await con.fetchval(
            'SELECT user_id FROM sessions WHERE token = $1',
            request.headers.get('Authorization'))

        if not user_id:
            return no('Invalid authorization.')

        files = await request.files

        if 'avatar' not in files:
            return no('Missing avatar')

        avatar = files['avatar']
        avatar.save('avatartemp/' + str(user_id))

        with open('avatartemp/' + str(user_id), 'rb') as file:
            file_bytes = file.read()
            md5sum = md5(file_bytes).hexdigest()

            path = g.config['avatar_dir'] + '/' + md5sum

            if not isfile(path):
                with open(path, 'wb') as avatar_file:
                    avatar_file.write(file_bytes)

        remove('avatartemp/' + str(user_id), )

        url = 'https://static.craftjobs.net/avatars/' + md5sum
        await con.execute('UPDATE users SET avatar_url = $1 WHERE id = $2',
                          url, user_id)

        return {'success': True, 'message': url}
예제 #7
0
파일: login.py 프로젝트: CraftJobs/backend
async def root():
    json = await request.get_json()

    if 'username' not in json:
        return no('Missing username.')
    if 'password' not in json:
        return no('Missing password.')
    if 'remember' not in json:
        return no('Missing remember.')

    username = json['username']
    password = json['password']
    remember = bool(json['remember'])
    is_email = '@' in username

    column = 'email' if is_email else 'username_lower'

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con
        user = await con.fetchrow(
            'SELECT id, password_hashed FROM users ' +
            f'WHERE {column} = $1', username.lower())

        if not user:
            return no('Invalid username/email or password.')

        if not bcrypt.checkpw(password.encode('utf-8'),
                              user['password_hashed'].encode('utf-8')):
            return no('Invalid username/email or password.')

        token = token_hex(32)

        await con.execute(
            'INSERT INTO sessions (id, token, user_id, expires_at) ' +
            'VALUES ($1, $2, $3, $4)', uuid4(), token, user['id'],
            datetime.now() + timedelta(days=30 if remember else 1))

        return {'success': True, 'token': token}
예제 #8
0
파일: users.py 프로젝트: CraftJobs/backend
async def get_user(username: str):
    request: Request = quart_rq
    self_only = 's' in request.args

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con

        db_user = await con.fetchrow(
            USER_SELECT_QUERY if not self_only else USER_BASIC_SELECT_QUERY,
            username.lower())

        authed_as = None
        self = {}

        if 'Authorization' in request.headers:
            db_self = await con.fetchrow(USER_BY_SESSION_QUERY,
                                         request.headers.get('Authorization'))

            if db_self:
                authed_as = db_self['id']
                self['username'] = db_self['username']
                self['admin'] = db_self['admin']
                self['isSelf'] = db_self['username'].lower() == username.lower(
                )
                self['isFollowing'] = False
                self['reputationGiven'] = 0
                self['plusReputationFollowing'] = []
                self['minusReputationFollowing'] = []

        if not db_user:
            return no('not_found')

        user_id = db_user['id']

        if not self_only:
            rate_lower = db_user['rate_lower']
            rate_higher = db_user['rate_higher']
            rate_range = []

            if rate_lower != -1:
                rate_range.append(rate_lower)

                if rate_higher != -1:
                    rate_range.append(rate_higher)

        rep_log = await con.fetch(REP_LOG_QUERY, user_id)

        if not self_only:
            total_rep = 0
            formatted_rep_log = []

        rep_amount_by_user = {}

        for rep_entry in rep_log:
            if not self_only:
                total_rep += rep_entry['amount']
                formatted_rep_log.append({
                    'user':
                    rep_entry['from_username'],
                    'amount':
                    rep_entry['amount'],
                    'time':
                    rep_entry['time'].isoformat(),
                    'message':
                    rep_entry['message'],
                    'userFullName':
                    rep_entry['from_full_name']
                })
            rep_amount_by_user[rep_entry['from_user_id']] = {
                'amount': rep_entry['amount'],
                'user': rep_entry['from_username']
            }

            if authed_as and rep_entry['from_user_id'] == authed_as:
                self['reputationGiven'] = rep_entry['amount']

        if authed_as:
            db_following = await con.fetch(
                'SELECT following_user_id FROM following ' +
                'WHERE follower_user_id = $1', authed_as)

            for follow_row in db_following:
                following_id = follow_row['following_user_id']

                if following_id == user_id:
                    self['isFollowing'] = True

                if following_id in rep_amount_by_user:
                    following_rep = rep_amount_by_user[following_id]

                    if following_rep['amount'] > 0:
                        self['plusReputationFollowing'].append(
                            following_rep['user'])
                    else:
                        self['minusReputationFollowing'].append(
                            following_rep['user'])

        if not self_only:
            connections = {}

            db_connections = await con.fetch(
                'SELECT connection_type, link FROM connections WHERE ' +
                'user_id = $1', user_id)

            for connection_row in db_connections:
                con_type = connection_row['connection_type']
                link = connection_row['link']

                if con_type == 'EMAIL' and not authed_as:
                    link = '_unauthed'

                connections[con_type] = link

            april_fools = g.config['april_fools']
            shrek = 'https://static.craftjobs.net/TheOneTrueAvatar.jpg'

            user = {
                'avatarUrl': shrek if april_fools else db_user['avatar_url'],
                'fullName': db_user['full_name'],
                'username': db_user['username'],
                'lookingFor': [],  # TODO
                'rateRange': rate_range,
                'reputation': total_rep,
                'role': db_user['role'],
                'experience': [],  # TODO
                'rateRangeType': db_user['rate_range_type'],
                'languages': [],  # TODO
                'admin': True if april_fools else db_user['admin'],
                'description': db_user['description'],
                'reputationLog': formatted_rep_log,
                'connections': connections,
            }

        ret = {'success': True}

        if authed_as:
            ret['self'] = self
        if not self_only:
            ret['user'] = user

        return ret
예제 #9
0
파일: users.py 프로젝트: CraftJobs/backend
async def edit_me():
    request: Request = quart_rq
    json: Dict = await request.get_json()

    if 'Authorization' not in request.headers:
        return no('Missing authorization.')

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con

        user_id = await con.fetchval(
            'SELECT user_id FROM sessions WHERE token = $1',
            request.headers.get('Authorization'))

        if not user_id:
            return no('Invalid authorization.')

        main_query = 'UPDATE users SET '
        set_idx = 1
        set_args = []

        for key, value in json.items():
            str_value: str = value

            if key == 'fullName':
                main_query += f'full_name = ${set_idx}, '

                if len(str_value) > 32:
                    return no('Full name must be less than 32 characters.')

                set_args.append(str_value)
                set_idx += 1
            elif key == 'rateRange':
                intl_value: List[int] = value
                last_value = -1

                if len(intl_value) > 2:
                    return no('Invalid rate range')

                for rate in intl_value:
                    if rate > 32767 or rate < 0:
                        return no('Rates must be between 0 and 32767.')
                    if rate < last_value:
                        return no(
                            'End of rate range must not be less than start.')
                    last_value = rate

                rate_lower = -1 if len(intl_value) < 1 else intl_value[0]
                rate_higher = -1 if len(intl_value) < 2 else intl_value[1]

                main_query += f'rate_lower = ${set_idx}, rate_higher = ${set_idx + 1}, '
                set_args.append(rate_lower)
                set_args.append(rate_higher)
                set_idx += 2
            elif key == 'role':
                if str_value not in ROLES:
                    return no('Invalid role.')

                main_query += f'role = ${set_idx}, '
                set_args.append(str_value)
                set_idx += 1
            elif key == 'rateRangeType':
                if str_value not in RATE_RANGE_TYPES:
                    return no('Invalid rate range type.')

                main_query += f'rate_range_type = ${set_idx}, '
                set_args.append(str_value)
                set_idx += 1
            elif key == 'description':
                if len(str_value) > 2000:
                    return no(
                        'Description cannot be greater than 2000 characters.')

                main_query += f'description = ${set_idx}, '
                set_args.append(str_value)
                set_idx += 1
            elif key == 'connections':
                strd_value: Dict[str, str] = value

                for con_key, con_val in strd_value.items():
                    if con_key not in CONNECTION_VALIDATORS:
                        return no('Invalid connection: ' + con_key + '.')

                    if con_val == '':
                        await con.execute(
                            'DELETE FROM connections WHERE ' +
                            'connection_type = $1 ' + 'AND user_id = $2',
                            con_key, user_id)
                    else:
                        if not CONNECTION_VALIDATORS[con_key](con_val):
                            return no('Invalid ' + con_key.lower())

                        await con.execute(
                            'INSERT INTO connections (id, ' +
                            'user_id, connection_type, link) ' +
                            'VALUES ($1, $2, $3, $4) ON ' +
                            'CONFLICT (user_id, ' +
                            'connection_type) DO UPDATE SET ' + 'link = $4',
                            uuid4(), user_id, con_key, con_val)

        # No additions
        if main_query[-4:] == 'SET ':
            return {
                'success': True,
                'message': 'Look ma, I\'m a reverse engineer!'
            }

        main_query = main_query[:-2]  # remove last comma and space
        main_query += ' WHERE id = $' + str(set_idx)
        set_args.append(user_id)

        await con.execute(main_query, *set_args)

    return {'success': True, 'message': 'Look ma, I\'m a reverse engineer!'}
예제 #10
0
파일: login.py 프로젝트: CraftJobs/backend
async def change_password():
    # Two potential forms of authentication here.
    # We use a prefix in the authorization header to determine it.
    #
    # Authorization: PasswordReset ... <- password reset, this serves as enough
    #                                     auth itself
    # Authorization: Bearer ...        <- generic password change, needs valid
    #                                     oldPassword
    if 'Authorization' not in request.headers:
        return no('Missing authorization.')

    auth_parts = request.headers.get('Authorization').split(' ')
    json = await request.get_json()

    if 'password' not in json:
        return no('Missing password.')

    if len(auth_parts) != 2:
        return no('Invalid authorization structure.')

    auth_type = auth_parts[0]
    auth_token = auth_parts[1]

    is_bearer = auth_type == 'Bearer'
    is_reset = auth_type == 'PasswordReset'

    if not is_bearer and not is_reset:
        return no('Invalid authorization type.')

    async with g.pool.acquire() as con:
        con: asyncpg.Connection = con

        if is_bearer:
            if 'oldPassword' not in json:
                return no('Missing oldPassword.')

            user = await con.fetchrow(GET_PASSWORD_AND_ID_BY_SESSION_QUERY,
                                      auth_token)

            if not user:
                return no('Invalid authorization.')

            if not bcrypt.checkpw(json['oldPassword'].encode('utf-8'),
                                  user['password_hashed'].encode('utf-8')):
                return no('Old password is incorrect.')

            user_id = user['id']
            field = 'id'
        else:
            email = await g.redis.get('login:password_reset:' + auth_token)
            if not email:
                return no('Invalid authorization.')
            user_id = email.decode('utf-8')
            field = 'email'

        hashed = bcrypt.hashpw(json['password'].encode('utf-8'),
                               bcrypt.gensalt()).decode('utf-8')

        user_id = await con.fetchval('UPDATE users SET password_hashed = $1 ' +
                                     f'WHERE {field} = $2 RETURNING id',
                                     hashed, user_id)

        await con.execute('DELETE FROM sessions WHERE user_id = $1', user_id)

        return {'success': True, 'message': ''}