예제 #1
0
 async def create_user(
     self,
     *,
     company_id=None,
     password='******',
     first_name='Frank',
     last_name='Spencer',
     email='*****@*****.**',
     role='admin',
     status='active',
     **kwargs,
 ):
     user_id = await self.conn.fetchval_b(
         'INSERT INTO users (:values__names) VALUES :values RETURNING id',
         values=Values(
             company=company_id or self.company_id,
             password_hash=mk_password(password, self.settings),
             first_name=first_name,
             last_name=last_name,
             email=email,
             role=role,
             status=status,
             **kwargs,
         ),
     )
     self.user_id = self.user_id or user_id
     return user_id
예제 #2
0
파일: auth.py 프로젝트: samuelcolvin/nosht
async def set_password(request):
    conn = request['conn']
    m = await parse_request(request,
                            PasswordModel,
                            headers_=HEADER_CROSS_ORIGIN)
    user_id = decrypt_json(request.app,
                           m.token.encode(),
                           ttl=3600 * 24 * 7,
                           headers_=HEADER_CROSS_ORIGIN)
    nonce = m.token[:20]

    already_used = await conn.fetchval(
        """
        SELECT 1 FROM actions
        WHERE user_id=$1 AND type='password-reset' AND now() - ts < interval '7 days' AND extra->>'nonce'=$2
        """,
        user_id,
        nonce,
    )
    if already_used:
        raise JsonErrors.HTTP470(
            message='This password reset link has already been used.',
            headers_=HEADER_CROSS_ORIGIN)

    user = await conn.fetchrow(
        'SELECT id, first_name, last_name, email, role, status, company FROM users WHERE id=$1',
        user_id)
    user = dict(user)
    if user.pop('company') != request['company_id']:
        # should not happen
        raise JsonErrors.HTTPBadRequest(
            message='company and user do not match')
    if user['status'] == 'suspended':
        raise JsonErrors.HTTP470(
            message='user suspended, password update not allowed.',
            headers_=HEADER_CROSS_ORIGIN)

    pw_hash = mk_password(m.password1, request.app['settings'])
    del m

    await conn.execute(
        "UPDATE users SET password_hash=$1, status='active' WHERE id=$2",
        pw_hash, user_id)
    await record_action(request,
                        user_id,
                        ActionTypes.password_reset,
                        nonce=nonce)
    return successful_login(user, request.app, HEADER_CROSS_ORIGIN)
예제 #3
0
async def test_pagination(cli, url, login, factory: Factory, db_conn, settings):
    await factory.create_company()

    pw = mk_password('testing', settings)
    await db_conn.execute_b(
        'INSERT INTO users (:values__names) VALUES :values RETURNING id',
        values=MultipleValues(
            *(
                Values(
                    company=factory.company_id,
                    password_hash=pw,
                    email=f'user+{i + 1}@example.org',
                    role='admin',
                    status='active',
                )
                for i in range(120)
            )
        ),
    )
    await login('*****@*****.**')

    r = await cli.get(url('user-browse'))
    assert r.status == 200, await r.text()
    data = await r.json()
    assert data['count'] == 120
    assert data['pages'] == 3
    assert len(data['items']) == 50
    assert data['items'][0]['email'] == '*****@*****.**'

    r = await cli.get(url('user-browse', query={'page': '2'}))
    assert r.status == 200, await r.text()
    data = await r.json()
    assert data['count'] == 120
    assert data['pages'] == 3
    assert len(data['items']) == 50
    assert data['items'][0]['email'] == '*****@*****.**'

    r = await cli.get(url('user-browse', query={'page': '3'}))
    assert r.status == 200, await r.text()
    data = await r.json()
    assert data['count'] == 120
    assert data['pages'] == 3
    assert len(data['items']) == 20
    assert data['items'][0]['email'] == '*****@*****.**'
예제 #4
0
async def set_password(request):
    h = {'Access-Control-Allow-Origin': 'null'}
    conn = request['conn']
    m = await parse_request(request, PasswordModel, error_headers=h)
    data = decrypt_json(request.app, m.token, ttl=3600 * 24 * 7)
    user_id, nonce = data['user_id'], data['nonce']

    already_used = await conn.fetchval(
        """
        SELECT 1 FROM actions
        WHERE user_id=$1 AND type='password-reset' AND now() - ts < interval '7 days' AND extra->>'nonce'=$2
        """, user_id, nonce)
    if already_used:
        raise JsonErrors.HTTP470(
            message='This password reset link has already been used.')

    company_id, status = await conn.fetchrow(
        'SELECT company, status FROM users WHERE id=$1', user_id)
    if company_id != request['company_id']:
        # should not happen
        raise JsonErrors.HTTPBadRequest(
            message='company and user do not match')
    if status == 'suspended':
        raise JsonErrors.HTTP470(
            message='user suspended, password update not allowed.')

    pw_hash = mk_password(m.password1, request.app['settings'])
    del m

    await conn.execute(
        "UPDATE users SET password_hash=$1, status='active' WHERE id=$2",
        pw_hash, user_id)
    await record_action(request,
                        user_id,
                        ActionTypes.password_reset,
                        nonce=nonce)
    return json_response(status='success', headers_=h)
예제 #5
0
파일: main.py 프로젝트: samuelcolvin/nosht
def create_app(*, settings: Settings = None, logging_client=None):
    logging_client = logging_client or setup_logging()
    settings = settings or Settings()

    app = web.Application(logger=None,
                          middlewares=(pg_middleware, user_middleware,
                                       csrf_middleware))

    app.update(
        settings=settings,
        auth_fernet=fernet.Fernet(settings.auth_key),
        dummy_password_hash=mk_password(settings.dummy_password, settings),
        logging_client=logging_client,
    )
    app.on_startup.append(startup)
    app.on_cleanup.append(cleanup)

    app.add_routes([
        web.get(r'/', index, name='index'),
        web.get(r'/sitemap.xml', sitemap, name='sitemap'),
        web.post(r'/ses-webhook/', ses_webhook, name='ses-webhook'),
        web.get(r'/cat/{category}/', category_public, name='category'),
        # event admin
        web.get(r'/events/categories/',
                event_categories,
                name='event-categories'),
        *EventBread.routes(r'/events/'),
        web.get(r'/events/search/', event_search, name='event-search'),
        web.post(r'/events/{id:\d+}/set-status/',
                 SetEventStatus.view(),
                 name='event-set-status'),
        web.post(r'/events/{id:\d+}/set-image/new/',
                 set_event_image_new,
                 name='event-set-image-new'),
        web.post(r'/events/{id:\d+}/set-image/existing/',
                 set_event_image_existing,
                 name='event-set-image-existing'),
        web.post(r'/events/{id:\d+}/set-image/secondary/',
                 set_event_secondary_image,
                 name='event-set-image-secondary'),
        web.post(
            r'/events/{id:\d+}/remove-image/secondary/',
            remove_event_secondary_image,
            name='event-remove-image-secondary',
        ),
        web.post(
            r'/events/{id:\d+}/set-image/description/',
            set_event_description_image,
            name='event-set-image-description',
        ),
        web.post(
            r'/events/{id:\d+}/remove-image/description/',
            remove_event_description_image,
            name='event-remove-image-description',
        ),
        web.post(r'/events/{id:\d+}/clone/',
                 EventClone.view(),
                 name='event-clone'),
        web.get(r'/events/{id:\d+}/tickets/',
                event_tickets,
                name='event-tickets'),
        web.post(r'/events/{id:\d+}/tickets/{tid:\d+}/cancel/',
                 CancelTickets.view(),
                 name='event-tickets-cancel'),
        web.get(r'/events/{id:\d+}/tickets/export.csv',
                event_tickets_export,
                name='event-tickets-export'),
        web.get(r'/events/{id:\d+}/donations/export.csv',
                event_donations_export,
                name='event-donations-export'),
        web.get(r'/events/{id:\d+}/ticket-types/',
                event_ticket_types,
                name='event-ticket-types'),
        web.post(r'/events/{id:\d+}/ticket-types/update/',
                 SetTicketTypes.view(),
                 name='update-event-ticket-types'),
        web.post(r'/events/{id:\d+}/reserve/',
                 ReserveTickets.view(),
                 name='event-reserve-tickets'),
        web.post(r'/events/{id:\d+}/updates/send/',
                 EventUpdate.view(),
                 name='event-send-update'),
        web.get(r'/events/{id:\d+}/updates/list/',
                event_updates_sent,
                name='event-updates-sent'),
        web.post(r'/events/{id:\d+}/switch-highlight/',
                 switch_highlight,
                 name='event-switch-highlight'),
        web.post(r'/events/{id:\d+}/waiting-list/add/',
                 waiting_list_add,
                 name='event-waiting-list-add'),
        web.get(
            r'/events/{id:\d+}/waiting-list/remove/{user_id:\d+}/',
            waiting_list_remove,
            name='event-waiting-list-remove',
        ),
        # event public views
        web.post(r'/events/book-free/',
                 BookFreeTickets.view(),
                 name='event-book-tickets'),
        web.post(r'/events/cancel-reservation/',
                 CancelReservedTickets.view(),
                 name='event-cancel-reservation'),
        web.get(r'/events/{category}/{event}/',
                event_get,
                name='event-get-public'),
        web.get(r'/events/{category}/{event}/booking-info/',
                booking_info,
                name='event-booking-info-public'),
        web.get(r'/events/{category}/{event}/donating-info/',
                donating_info,
                name='event-donating-info-public'),
        web.get(r'/events/{category}/{event}/{sig}/',
                event_get,
                name='event-get-private'),
        web.get(r'/events/{category}/{event}/{sig}/booking-info/',
                booking_info,
                name='event-booking-info-private'),
        web.get(r'/events/{category}/{event}/{sig}/donating-info/',
                donating_info,
                name='event-donating-info-private'),
        # stripe views
        web.post(r'/stripe/webhook/', stripe_webhook, name='stripe-webhook'),
        web.get(
            r'/stripe/payment-method-details/{payment_method}/',
            get_payment_method_details,
            name='payment-method-details',
        ),
        web.post(r'/login/', login, name='login'),
        web.get(r'/login/captcha/',
                login_captcha_required,
                name='login-captcha-required'),
        web.post(r'/login/{site:(google|facebook)}/',
                 login_with,
                 name='login-google-facebook'),
        web.post(r'/auth-token/', authenticate_token, name='auth-token'),
        web.post(r'/reset-password/',
                 reset_password_request,
                 name='reset-password-request'),
        web.post(r'/set-password/', set_password, name='set-password'),
        web.post(r'/logout/', logout, name='logout'),
        web.post(r'/signup/guest/{site:(google|facebook|email)}/',
                 guest_signup,
                 name='signup-guest'),
        web.post(r'/signup/host/{site:(google|facebook|email)}/',
                 host_signup,
                 name='signup-host'),
        web.get(r'/unsubscribe/{id:\d+}/', unsubscribe, name='unsubscribe'),
        *CompanyBread.routes(r'/companies/'),
        web.post(r'/companies/upload/{field:(image|logo)}/',
                 company_upload,
                 name='company-upload'),
        web.post(r'/companies/footer-links/set/',
                 company_set_footer_link,
                 name='company-footer-links'),
        web.post(r'/categories/{cat_id:\d+}/add-image/',
                 category_add_image,
                 name='categories-add-image'),
        web.get(r'/categories/{cat_id:\d+}/images/',
                category_images,
                name='categories-images'),
        web.post(r'/categories/{cat_id:\d+}/images/set-default/',
                 category_set_image,
                 name='categories-set-image'),
        web.post(r'/categories/{cat_id:\d+}/images/delete/',
                 category_delete_image,
                 name='categories-delete-image'),
        *CategoryBread.routes(r'/categories/'),
        *UserBread.routes(r'/users/'),
        *UserSelfBread.routes(r'/account/', name='account'),
        web.get(r'/users/search/', user_search, name='user-search'),
        web.get(r'/users/{pk:\d+}/actions/', user_actions,
                name='user-actions'),
        web.get(r'/users/{pk:\d+}/tickets/', user_tickets,
                name='user-tickets'),
        web.post(r'/users/{pk:\d+}/switch-status/',
                 switch_user_status,
                 name='user-switch-status'),
        web.get(
            r'/export/{type:(events|categories|users|tickets|donations)}.csv',
            export,
            name='export'),
        web.get(r'/email-defs/', email_def_browse, name='email-defs-browse'),
        web.get(r'/email-defs/{trigger}/',
                email_def_retrieve,
                name='email-defs-retrieve'),
        web.post(r'/email-defs/{trigger}/edit/',
                 email_def_edit,
                 name='email-defs-edit'),
        web.post(r'/email-defs/{trigger}/clear/',
                 clear_email_def,
                 name='email-defs-clear'),
        # donations
        *DonationOptionBread.routes(r'/donation-options/',
                                    name='donation-options'),
        web.get(r'/categories/{cat_id:\d+}/donation-options/',
                donation_options,
                name='donation-options'),
        web.post(r'/donation-options/{pk:\d+}/upload-image/',
                 donation_image_upload,
                 name='donation-image-upload'),
        web.get(r'/donation-options/{pk:\d+}/donations/',
                opt_donations,
                name='donation-opt-donations'),
        web.post(
            r'/donation-options/{don_opt_id:\d+}/prepare/{event_id:\d+}/',
            donation_after_prepare,
            name='donation-after-prepare',
        ),
        web.post(r'/donation-prepare/{tt_id:\d+}/',
                 PrepareDirectDonation.view(),
                 name='donation-direct-prepare'),
        web.post(r'/donation/{action_id:\d+}/gift-aid/',
                 DonationGiftAid.view(),
                 name='donation-gift-aid'),
    ])

    wrapper_app = web.Application(
        client_max_size=settings.max_request_size,
        middlewares=(
            session_middleware(
                EncryptedCookieStorage(settings.auth_key,
                                       cookie_name='nosht')),
            error_middleware,
        ),
        logger=None,
    )
    wrapper_app.update(
        settings=settings,
        main_app=app,
    )
    static_dir = settings.custom_static_dir or (Path(__file__).parent /
                                                '../../js/build').resolve()
    assert static_dir.exists(
    ), f'js static directory "{static_dir}" does not exists'
    logger.debug('serving static files "%s"', static_dir)
    wrapper_app.update(
        static_dir=static_dir,
        csp_headers=get_csp_headers(settings),
    )
    wrapper_app.add_subapp(r'/api/', app)
    wrapper_app.add_routes(
        [web.get(r'/{path:.*}', static_handler, name='static')])
    return wrapper_app
예제 #6
0
def create_app(*, settings: Settings=None, logging_client=None):
    logging_client = logging_client or setup_logging()
    settings = settings or Settings()

    app = web.Application(middlewares=(
        session_middleware(EncryptedCookieStorage(settings.auth_key, cookie_name='nosht')),
        pg_middleware,
        host_middleware,
    ))

    app.update(
        settings=settings,
        auth_fernet=fernet.Fernet(settings.auth_key),
        dummy_password_hash=mk_password(settings.dummy_password, settings),
        logging_client=logging_client,
    )
    app.on_startup.append(startup)
    app.on_cleanup.append(cleanup)

    app.add_routes([
        web.get('/', index, name='index'),

        web.post('/categories/{cat_id:\d+}/add-image/', category_add_image, name='categories-add-image'),
        web.get('/categories/{cat_id:\d+}/images/', category_images, name='categories-images'),
        web.post('/categories/{cat_id:\d+}/set-default/', category_default_image, name='categories-set-default'),
        web.post('/categories/{cat_id:\d+}/delete/', category_delete_image, name='categories-delete'),
        *CategoryBread.routes('/categories/'),
        web.get('/cat/{category}/', category_public, name='category'),

        web.get('/events/categories/', event_categories, name='event-categories'),
        *EventBread.routes('/events/'),
        web.post('/events/{id:\d+}/set-status/', SetEventStatus.view(), name='event-set-status'),
        web.get('/events/{id:\d+}/booking-info/', booking_info, name='event-booking-info'),
        web.get('/events/{id:\d+}/tickets/', event_tickets, name='event-tickets'),
        web.post('/events/{id:\d+}/reserve/', ReserveTickets.view(), name='event-reserve-tickets'),
        web.post('/events/buy/', BuyTickets.view(), name='event-buy-tickets'),
        web.post('/events/cancel-reservation/', CancelReservedTickets.view(), name='event-cancel-reservation'),
        web.get('/events/{category}/{event}/', event_public, name='event-get'),

        web.post('/login/', login, name='login'),
        web.post('/login/{site:(google|facebook)}/', login_with, name='login-google-facebook'),
        web.post('/auth-token/', authenticate_token, name='auth-token'),
        web.post('/set-password/', set_password, name='set-password'),
        web.post('/logout/', logout, name='logout'),
        web.post('/login/guest/{site:(google|facebook|email)}/', guest_signin, name='login-guest'),
        web.post('/signup/{site:(google|facebook|email)}/', host_signup, name='signup-host'),

        web.get('/unsubscribe/{id:\d+}/', unsubscribe, name='unsubscribe'),

        *UserBread.routes('/users/'),
    ])

    wrapper_app = web.Application(
        client_max_size=settings.max_request_size,
        middlewares=(error_middleware,),
    )
    wrapper_app.update(
        settings=settings,
        main_app=app,
    )
    this_dir = Path(__file__).parent
    static_dir = (this_dir / '../../js/build').resolve()
    assert static_dir.exists(), f'js static directory "{static_dir}" does not exists'
    logger.debug('serving static files "%s"', static_dir)
    wrapper_app['static_dir'] = static_dir
    wrapper_app.add_subapp('/api/', app)
    wrapper_app.add_routes([
        web.get('/{path:.*}', static_handler, name='static'),
    ])
    return wrapper_app