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
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)
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'] == '*****@*****.**'
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)
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
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