async def execute(self, m: Model): res = Reservation(**decrypt_json(self.app, m.booking_token, ttl=self.settings.ticket_ttl)) assert self.session['user_id'] == res.user_id, "user ids don't match" async with self.conn.transaction(): await self.conn.execute('DELETE FROM tickets WHERE reserve_action=$1', res.action_id) await self.conn.execute('SELECT check_tickets_remaining($1, $2)', res.event_id, self.settings.ticket_ttl) await record_action(self.request, self.session['user_id'], ActionTypes.cancel_reserved_tickets)
async def test_reserve_tickets_free(cli, url, factory: Factory, login): await factory.create_company() await factory.create_cat() await factory.create_user() await factory.create_event(status='published') await login() data = { 'tickets': [{'t': True, 'first_name': 'Ticket', 'last_name': 'Buyer', 'email': '*****@*****.**'}], 'ticket_type': factory.ticket_type_id, } r = await cli.json_post(url('event-reserve-tickets', id=factory.event_id), data=data) assert r.status == 200, await r.text() data = await r.json() assert data == { 'booking_token': RegexStr(r'.+'), 'ticket_count': 1, 'extra_donated': None, 'item_price': None, 'total_price': None, 'timeout': AnyInt(), 'client_secret': None, 'action_id': AnyInt(), } assert decrypt_json(cli.app['main_app'], data['booking_token'].encode()) == { 'user_id': factory.user_id, 'action_id': AnyInt(), 'event_id': factory.event_id, 'price_cent': None, 'ticket_count': 1, 'event_name': 'The Event Name', }
async def authenticate_token(request): m = await parse_request(request, AuthTokenModel) auth_session = decrypt_json(request.app, m.token, ttl=10) session = await new_session(request) session.update(auth_session) await record_action(request, session['user_id'], ActionTypes.login) return json_response(status='success')
async def test_reserve_tickets(cli, url, db_conn, factory: Factory, login): await factory.create_company() await factory.create_cat() await factory.create_user(first_name=None, last_name=None, email='*****@*****.**') await factory.create_event(status='published', price=10) await login(email='*****@*****.**') data = { 'tickets': [ { 't': True, 'first_name': 'Ticket', 'last_name': 'Buyer', 'email': '*****@*****.**', 'allow_marketing': True, }, { 't': True, 'first_name': 'Other', 'last_name': 'Person', 'email': '*****@*****.**', 'extra_info': 'I love to party', 'cover_costs': None, 'allow_marketing': None, }, ], 'ticket_type': factory.ticket_type_id, } r = await cli.json_post(url('event-reserve-tickets', id=factory.event_id), data=data) assert r.status == 200, await r.text() data = await r.json() assert data == { 'booking_token': RegexStr(r'.+'), 'ticket_count': 2, 'extra_donated': None, 'item_price': 10.0, 'total_price': 20.0, 'timeout': AnyInt(), 'client_secret': RegexStr(r'payment_intent_secret_\d+'), 'action_id': AnyInt(), } booking_token = decrypt_json(cli.app['main_app'], data['booking_token'].encode()) reserve_action_id = await db_conn.fetchval("SELECT id FROM actions WHERE type='reserve-tickets'") assert booking_token == { 'user_id': factory.user_id, 'action_id': reserve_action_id, 'event_id': factory.event_id, 'price_cent': 20_00, 'ticket_count': 2, 'event_name': 'The Event Name', }
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 execute(self, m: Model): # no ttl since a user may try to cancel a reservation after it has expired res = Reservation(**decrypt_json(self.app, m.booking_token)) async with self.conn.transaction(): user_id = await self.conn.fetchval( 'SELECT user_id FROM actions WHERE id=$1', res.action_id) v = await self.conn.execute( "DELETE FROM tickets WHERE reserve_action=$1 AND status='reserved'", res.action_id) if v == 'DELETE 0': # no tickets were deleted raise JsonErrors.HTTPBadRequest(message='no tickets deleted') await self.conn.execute('SELECT check_tickets_remaining($1, $2)', res.event_id, self.settings.ticket_ttl) await record_action(self.request, user_id, ActionTypes.cancel_reserved_tickets, event_id=res.event_id)
async def test_reserve_tickets_cover_costs(cli, url, factory: Factory, login): await factory.create_company() await factory.create_cat(cover_costs_message='Help!', cover_costs_percentage=12.5) await factory.create_user(first_name=None, last_name=None, email='*****@*****.**') await factory.create_event(status='published', price=10) await login(email='*****@*****.**') data = { 'tickets': [ { 't': True, 'first_name': 'Ticket', 'last_name': 'Buyer', 'email': '*****@*****.**', 'cover_costs': True, }, { 't': True, }, ], 'ticket_type': factory.ticket_type_id, } r = await cli.json_post(url('event-reserve-tickets', id=factory.event_id), data=data) assert r.status == 200, await r.text() data = await r.json() assert data == { 'booking_token': RegexStr(r'.+'), 'ticket_count': 2, 'extra_donated': 2.5, 'item_price': 10.0, 'total_price': 22.50, 'timeout': AnyInt(), 'client_secret': RegexStr(r'payment_intent_secret_\d+'), 'action_id': AnyInt(), } assert decrypt_json(cli.app['main_app'], data['booking_token'].encode()) == { 'user_id': factory.user_id, 'action_id': AnyInt(), 'event_id': factory.event_id, 'price_cent': 22_50, 'ticket_count': 2, 'event_name': 'The Event Name', }
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)
assert r.status == 200, await r.text() data = await r.json() assert data == { 'booking_token': RegexStr('.+'), 'ticket_count': 2, 'item_price_cent': 10_00, 'total_price_cent': 20_00, 'user': { 'id': factory.user_id, 'name': 'Ticket Buyer', 'email': '*****@*****.**', 'role': 'admin', }, 'timeout': AnyInt(), } booking_token = decrypt_json(cli.app['main_app'], data['booking_token'].encode()) reserve_action_id = await db_conn.fetchval( "SELECT id FROM actions WHERE type='reserve-tickets'") assert booking_token == { 'user_id': factory.user_id, 'action_id': reserve_action_id, 'event_id': factory.event_id, 'price_cent': 20_00, 'ticket_count': 2, 'event_name': 'The Event Name', } users = [ dict(r) for r in await db_conn.fetch( 'SELECT first_name, last_name, email, role FROM users ORDER BY id') ]