î†\{{tWHQ=Õ�c§ù
�´ì6Nìb¼³ÚdÁÓOûð}aX]ô: ûûÎ»Ös.ËéE£Ï(Æ®sj•¾4~Á.‹Ÿ±ªšÚï=3+¥È­˜¦()ç(ÿZ˜o‰�Ê±×ax¤²ÐŒõŸÕæü‡ÇdÄäæâ¢½†Ér8¡§xF�?¡U’=åˆ;u¢æà³n.y�l¶úêj®–“,³VM†rDå}£ÔWaG2ÊLë)ªVÙO­«TZ2Ž•ƒ=Æ‰³1L[hù}KEïZ>
²E\ý|GÀ;Þ/v…¼åF-æÁSÇiHtðLe[¤é±¡Ì.Ößü9Dš�‚®ãÁ\§ï“(p	„)'ÉÈV£Í02Û³õ3:¼Î7y¡×q÷ÕmÙ<ŸæÅi²O±=bæB©m‹ƒ A©ë�â1Q’+| òåC©ˆîo!àŠ»‰ ø¨mÑdUB0÷AN\`ÇP‚n´(Æê.Z#äoˆò dùj8Mçp”µ¢Ž²ŸzÑêÊü7Än&’¾¡¥åŸÂR\©|ÂGïõ3‡”(h\ôíº¬|=E­òm‹`ôß0
ó°WõF‘JZ%+ÝÓg¾!St¡•™¿qxk¾ÀÄÀo‚ˆçG³¤C›¯‰inõL†ñÕÝoJTPú	<Ÿ¡Jr�Ìšþ¶ËØ¨LP©œìÖçÃþø’'E–‹Š”ƒ½:£AC	‚(É·
4[ÈJ«©O~GÒÄÆ£#.ÅBHñÿÏ„…¶	Æ(¹.Â­0iÌ_¸ðãókG„:È=0”ÒÎcØ:Jž=×k¦8Ùõ?6õZ»ÔwÊE,ÁhsöÁ‰q\g`wÞn¥8–k©u,L¥§d‘PÐÊÕktab,£­’.=óI)‡f‚ÅRÄ�:ƒ§„¸Ç¸ðgù _FÓÎ.Tqh^ŸIã[çÕÔHÉïãeeÅÁr^³­ejZè¡‰5Oäù·m¸W  ÉÁVPtZ@ˆ,~:KÆè‡È7�Øïyç¥&/T¹×ææµ¨^æ<È�ÄýÑ|ØA/…¬aò=�,ÿ‡Ó˜´†“FÄ÷V”0É¦QiëikðYb{¢ª´ôÉÆÓ§§†¾bòìeo?í 1‡Ÿ£XjNBµ‚xàñæ»^©Ü´þª÷9Ãã£Q�&=p³Mõ àÞøãK®•M~å4qWg®bò¸G¹ya9¦û,¾äß6Q˜¹uÂ€jªwOEÂˆŠI~º^÷o’OU:wÏm#§]¾nœ�“£/æ˜zÆf BÍ]Q­Ä\4ý;Ó<ëU7ù�2òã—«DÑåÐ—«Þwú²[R[í) ÐnÕs�,¾wªâ‚è^Sž'Éf�z„áÔÖ®—‹ä™lÁ0¤§IDOhsÎöE4¨Zï±?! ùZfÚÑ�N·­\½—ôRÖ‚'¡–0Ýüµ^¯´œBÞìÝÉåoûT
`1’±û�ð5310t.*Å×­üP×’7[œ;˜ºK¨û‚Sˆÿœ	Âé›´>À$Þå‰ê½Øù“·ÄW}'[6ðÿ’(9”S D¦s{®ðpjé“¨×Wifñ»láHðôoŽ“ ò�€»÷ÊßIîÉ‘ã€<Iè±ÉëÅhäÊ9S°âäí›ðyOgÆ¡]n–C;Óq‹)	+ÕvÒs[«­leJ)¥oç¡r[!�E6*ÃµÈ<jWo¤¯Ð«Büz÷èMñ¬47×ò¼wEésŸ¥WG¤-&“à‚O›¿±–ØŒˆ<î‹þxw`…&2ºÓ8Åý<‰"Q‘]61å[ìJ”äãG½,P„vA×d”í��°1gÁæ§mNÄ›ìaÆT}Zœ‘JBïÅÊ,#u•�Ä¹swÈµG=”S€-�õ¼jŽØ"z¼`ØßÆ�A–§„‰yÊà–Î ¸p¤z1ÈpM•òýBÝ¹HF½	µòM>UçY?†û>*þòžP~âo�·2`ôLz³Ê’ÂA)k"2U�Çè6.éi§¶‹¶)a7�
pAEnÙôw„ƒ€Œ”6^|P5¡\óì1,›qX
 >=íÔ¦ó-Ò S5Ø@»ÛU—×/©ü3¿¾QwÛ-áézÇ…ƒ¥¢šrí¨inT%ÆŽ°Ùœ–wªæqÁŒu&† ýõ:ÇaCº°›ûÈ¥±öÏ²yÛÒž€Pp•ºÞ±Cz_9Fûý*mi¦@ï«1úCìàÏ|,€«by.žWâ‡X§ŸóEÿ|I2Xåòä‰£OŽc¤ðÓIQ?µXÏ	R,�«!£�n<s6¹ò�I5ÖE¨0~Ç’cÄÂî·h†Z–„Pï!wµèkcÃ•€êtö@™ì;ìCÑÄéh…%ër£&¹#>\/DÙåÈ}¦… uÓi �ðÔä8[8ÛáùÞýKUˆx [ÓJO¨|pF±zRÒ4øœ]�'º´�l�øýA{bÆúcxw94¦¨VAûý*mi¦@ï«1úCìàÏ|ÜñŒ®àw\9å¶Âx¤œB„Èq|çÜà´d†A&#m›Œã�C_xµU—&&l«\ñ¤o}¦Ã�/a,fhÎ›kq³,>…LSë'_Á, >Å›6,˜æHÈˆ@�«Ê²–1Ïh°	f±^8d@¶Ç�a6CkÍ
Z¢C˜çÁå‡¡3P 11X_Ë�-‘üœ^;Flim#âkŒÑÈ~K
9‡G©Î>­oUmÌ“<?¾=Ñ	…vgÇ'‹Oj–H{63Æ�¨é˜cÈÒ£Š�+È‘VÐ(bœüX³K(Îi®{ñ>"~y"òkúdu„þðÜ<ÕB(¸“£z†ÔT<0ûØÀËüƒ©Ýe(Å‘¹…WZÉnÎQ½bÔ”KeÆ ‹Ç“Eb†w(u,“(·œ(–¾²ªJ!±ÉAÐX~TdWGžSÿÕ1lÙæR
•OŒ¯ˆ£úR“3Qá„	Ö¢^X½Éª²©W‚ü‰ªOÅäô! ÑšÓ$t�ÔŽo&ö`®	]�r^ôéT¡‹ê{
ñ':li¹§–M™òÊd‡ñM«'ÖqÍÝ Å°Úy‘³r…n´å}5liÍì‚)Üt¥Š3òfd�bgRújÛ§c&Ý=k¯uÁ2£ï`
Ž‹¤®²E×2ä£…MeweðèJCŠ
øÉ5Õiçol_A h5£Ù5PÉ¢]«ïåÇVi–*ûìQ--€n\SLN|ßÂÞ'híO�A‚¯£íåw¶dàÌ©¼…D;}ÜJÏÛAU0�ß�„ýT…} ÷ß‘¿,èú>ùÎØ0œùšÝ›£o¥„¿€Ð:Ï=T
ºù-£ ìoÀ¦xÃ#1†AO}L�0åÆß¤ðX•¾ÿÙî%eÇ1!?Ï˜á­Û­�ÜëéÛ�U‡—"ÚÍ<[ÄNÄ²"cbËœÈØÄ×Z Ý» ƒc9ÔZÀq›eê7�‰�YHfÈÏŸÏ]K{ë¶¬$÷¤|KêVñÿ+ŸgnhÙ‚«kö�È…yh‡!Å+ÏMI„ûí5÷„Ùëªf`ÒCbÎÐ¨âÓK’Ý¦òC·•Ê½Ù(ïâh­}5— E¿Lð¹‰We-¸ºÍ{ [´èc«amôýÕ¼IÈØ¡‡�ÖÜ y�&!æ±¯ß‡7‹l•Ú[`A�‹ÅVñÜz~DNhDn)pdÖ‡èËX+³4ëO=¥'d;â3?Â¶Ð5áV‹Dî¨÷‹”O¼{Si´”Rûý1ßCr-Ó7Öt'I�¡a7ö»´„�—JÃ`rCÚCöý§¢¥É¦Œ;º*w‚µ‡eEX<1WØ½’+Ó*ùçHê›‘rÒEC"ë:\D¦-‡Ÿà¬¶Ý×Æ8‘4†Hm9<gÄh6H™Àû2€±b�Œìì§M|¦èMèúmér7ÖÕ&þ€ùíªG
­=)Àúž(ü¯!Þ=š¦ Çš„/ÇÚÑÏÝ8Åó[´íÁiUPeÆ ‹Ç“Eb†w(u,“Q´˜£&©îWF‡˜rû¸Ò'í6 ªÇýï.7Âmw!Í´‰DÂ{Ù®™¤þR�Ù¸Ï%¾vq€!4gOwã¿|³¹à›1"Æ“(g4Å™¢º[`(·S]ß\øbö—ìŒ€Ì–j8š°Ì¥”*(Tq(ëdWfð¦¬Ù\Ðúë¶t	�åQ<yÛPŸ±Ÿ	77"Dd¹ÆÓÈN	ôÑ0VëÌ�J³�×Ý¡6ÈfþÕœ­X³§K¤AG‚-�!nõA–F Ü=PM6Ï˜rj9ì?ÔO½zYQ›yÑµå¢L’åúßì
ó
¬Ÿ.Oô_p˜Cñ‡g§’ì•%?Ë1k0»d™pS0ò¶
™K”ÁI†ðÏgŸ:BÈâ1‚žo‡³"t†Š�Ì^Ã–ÁBÊ'6Ž©·‹Jæº£ü},Rñ£¤â—8=9¹m³èÓY¯ÄØWmô“çÑ¡w€çHñÌ(F|)%Ùc•7ƒîÃ_îjø[¼Z´öÖ ý§ÜÌc++Ú»ÖaUn	ÃJ@a¿O+NÉiú`à’ò—ˆ(C](g7ß›&WvîñHB :@AÎ4E•ÁC‚;‰ÅvH4@cEóüÙÆÅãcàÛ4l6Ž‡F¬r~[t­F�­îtR�v„©=/tªð´>"˜ê$»7^”[A•w?²x¯À®�Ç¯‰œcý€µ±ÀÚ´4õ*“¨(G)…_¾únw{á9¡Œ˜x<’"œ*ÛCVÂÌòžŒª5IW{î¤<.2NÂ­ˆ_•<]–|——ÊÐ‹£§aûF2+Rú4†¢�/…¦O”��Uôuáâÿ{w_	ºáëT…BW»I;ì.ó°þvíàI­â•-?E†ˆb‚36IŒ	#ýùXC‡›ù™ïê6±¾ø–.úã¸Å“@Æ2ôéz˜æ}D†€ôB
p9ð>·èM’3ªB‚dŠ`' ´9-cŠ‘ÄÓó^‹�'uðíbåùÁ™ÃlóÃ-®G88‚1�óiø»Íãàó..ÉHúrÑr{|b44)�¢e½�Ou·kr¢v‚ã²—nvB(>JèlFPìcÿ°D‚üÛ¯G£ÀYŸcïA'©1|9þÈæ
 ’�•é:Æ”ŽñÌ¦rE·Ýñ°È›(3F7$OÛÀzåüXÁÝýóë#G××’W‘™Û“F«Ñ‚�nsÉ€:”ó°è~ã‚\úíQ†@Cù»3�ÕÝïŠÆÛåM_!QijÎ}úÊæô;~[Òb‰ðr¥WN¡ZRþH›�ý¢xU»RÈ_ÉAÛ[ãµdword = None
    if old:
        member = dict(old)
        if member['is_super']:
            raise HTTPException(400, 'Platform administrators already have explicit access to all businesses.')
        if not member['active']:
            raise HTTPException(400, 'This account is disabled. Ask the platform administrator to enable it.')
    else:
        password = secrets.token_urlsafe(18)
        member = create_user(conn, name, email, password, must_change=True)
    if conn.execute('SELECT 1 FROM memberships WHERE business_id=? AND user_id=?', (bid, member['id'])).fetchone():
        raise HTTPException(409, 'This person is already a member of this business.')
    insert(conn, 'memberships', dict(business_id=bid, user_id=member['id'], role=role))
    audit(conn, user, bid, 'Added team member', 'users', member['id'], role)
    return dict(user=safe_user(member), temporary_password=password)


def login(request: Request, response: Response, email: str, password: str):
    email = email.strip().lower()
    ip = request.client.host if request.client else 'unknown'
    ts = time.time()
    keys = [('ip:' + digest(ip), 30), ('email:' + digest(email), 10)]
    blocked = False
    with db(write=True) as conn:
        conn.execute('DELETE FROM login_limits WHERE resets_at<?', (ts,))
        conn.execute('DELETE FROM sessions WHERE expires_at<? OR last_seen<?', (ts, ts-IDLE))
        for key, maximum in keys:
            limit = conn.execute('SELECT * FROM login_limits WHERE key=?', (key,)).fetchone()
            if limit and limit['attempts'] >= maximum:
                blocked = True
        if not blocked:
            for key, _ in keys:
                conn.execute('''INSERT INTO login_limits(key,attempts,resets_at) VALUES(?,1,?)
                    ON CONFLICT(key) DO UPDATE SET attempts=attempts+1''', (key, ts + 900))
        row = conn.execute('SELECT * FROM users WHERE email=? COLLATE NOCASE', (email,)).fetchone()
    if blocked:
        raise HTTPException(429, 'Too many sign-in attempts. Wait 15 minutes and try again.')
    valid = verify(row['password_hash'] if row else DUMMY, password)
    if not row or not valid or not row['active']:
        raise HTTPException(401, 'Email or password is incorrect.')
    user = dict(row)
    token, csrf = secrets.token_urlsafe(32), secrets.token_urlsafe(32)
    with db(write=True) as conn:
        conn.execute('DELETE FROM login_limits WHERE key=?', ('email:' + digest(email),))
        if HASHER.check_needs_rehash(user['password_hash']):
            conn.execute('UPDATE users SET password_hash=? WHERE id=?', (hash_password(password), user['id']))
        # Rotate any previous session attached to this browser.
        previous = request.cookies.get(COOKIE)
        if previous:
            conn.execute('DELETE FROM sessions WHERE token_hash=?', (digest(previous),))
        insert(conn, 'sessions', dict(token_hash=digest(token), user_id=user['id'], csrf=csrf,
                                    expires_at=ts+ABSOLUTE, last_seen=ts))
        audit(conn, user, None, 'Signed in', 'users', user['id'])
    response.set_cookie(COOKIE, token, httponly=True, secure=SECURE, samesite='lax', max_age=ABSOLUTE, path='/')
    return dict(user=safe_user(user), csrf=csrf)
