Skip to content

Commit d480a5a

Browse files
committed
feat: add password reset flow
1 parent 83bdd0a commit d480a5a

File tree

18 files changed

+1263
-23
lines changed

18 files changed

+1263
-23
lines changed

src/riskmatrix/__init__.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from pyramid.config import Configurator
33
from pyramid_beaker import session_factory_from_settings
44
from typing import Any
5+
from email.headerregistry import Address
6+
from pyramid.settings import asbool
7+
from .mail import PostmarkMailer
58

69
from riskmatrix.flash import MessageQueue
710
from riskmatrix.i18n import LocaleNegotiator
@@ -24,6 +27,19 @@
2427
def includeme(config: Configurator) -> None:
2528
settings = config.registry.settings
2629

30+
default_sender = settings.get(
31+
'email.default_sender',
32+
33+
)
34+
token = settings.get('mail.postmark_token', '***REMOVED***')
35+
stream = settings.get('mail.postmark_stream', 'development')
36+
blackhole = asbool(settings.get('mail.postmark_blackhole', False))
37+
config.registry.registerUtility(PostmarkMailer(
38+
Address(addr_spec=default_sender),
39+
token,
40+
stream,
41+
blackhole=blackhole
42+
))
2743
config.include('pyramid_beaker')
2844
config.include('pyramid_chameleon')
2945
config.include('pyramid_layout')
@@ -67,8 +83,12 @@ def main(
6783
environment=sentry_environment,
6884
integrations=[PyramidIntegration(), SqlalchemyIntegration()],
6985
traces_sample_rate=1.0,
70-
profiles_sample_rate=0.25,
86+
profiles_sample_rate=1.0,
87+
enable_tracing=True,
88+
send_default_pii=True
7189
)
90+
print("configured sentry")
91+
print(sentry_dsn)
7292

7393
with Configurator(settings=settings, root_factory=root_factory) as config:
7494
includeme(config)

src/riskmatrix/mail/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from .exceptions import InactiveRecipient
2+
from .exceptions import MailConnectionError
3+
from .exceptions import MailError
4+
from .interfaces import IMailer
5+
from .mailer import PostmarkMailer
6+
from .types import MailState
7+
8+
__all__ = (
9+
'IMailer',
10+
'InactiveRecipient',
11+
'MailConnectionError',
12+
'MailError',
13+
'MailState',
14+
'PostmarkMailer',
15+
)

src/riskmatrix/mail/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class MailError(Exception):
2+
pass
3+
4+
5+
class MailConnectionError(MailError, ConnectionError):
6+
pass
7+
8+
9+
class InactiveRecipient(MailError):
10+
pass

src/riskmatrix/mail/interfaces.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from typing import Any, TYPE_CHECKING
2+
from zope.interface import Interface
3+
4+
if TYPE_CHECKING:
5+
from collections.abc import Sequence
6+
from email.headerregistry import Address
7+
from .types import MailState
8+
from .types import MailParams
9+
from .types import TemplateMailParams
10+
from ..certificate.interfaces import ITemplate
11+
from ..models import Organization
12+
from ..types import JSONObject
13+
MailID = Any
14+
15+
16+
class IMailer(Interface): # pragma: no cover
17+
18+
# NOTE: We would like to say that kwargs is OptionalMailParams
19+
# however there is no way in mypy to express that yet.
20+
def send(sender: 'Address | None',
21+
receivers: 'Address | Sequence[Address]',
22+
subject: str,
23+
content: str,
24+
**kwargs: Any) -> 'MailID':
25+
"""
26+
Send a single email.
27+
28+
Returns a message uuid.
29+
"""
30+
pass
31+
32+
def bulk_send(mails: list['MailParams']
33+
) -> list['MailID | MailState']:
34+
"""
35+
Send multiple emails. "mails" is a list of dicts containing
36+
the arguments to an individual send call.
37+
38+
Returns a list of message uuids and their success/failure states
39+
in the same order as the sending list.
40+
"""
41+
pass
42+
43+
# NOTE: We would like to say that kwargs is OptionalTemplateMailParams
44+
# however there is no way in mypy to express that yet.
45+
def send_template(sender: 'Address | None',
46+
receivers: 'Address | Sequence[Address]',
47+
template: str,
48+
data: 'JSONObject',
49+
**kwargs: Any) -> 'MailID':
50+
"""
51+
Send a single email using a template using its id/name.
52+
"data" contains the template specific data.
53+
54+
Returns a message uuid.
55+
"""
56+
pass
57+
58+
def bulk_send_template(mails: list['TemplateMailParams'],
59+
default_template: str | None = None,
60+
) -> list['MailID | MailState']:
61+
"""
62+
Send multiple template emails using the same template.
63+
64+
Returns a list of message uuids. If a message failed to be sent
65+
the uuid will be replaced by a MailState value.
66+
"""
67+
pass
68+
69+
def template_exists(alias: str) -> bool:
70+
"""
71+
Returns whether a template by the given alias exists.
72+
"""
73+
pass
74+
75+
def create_or_update_template(
76+
template: 'ITemplate',
77+
organization: 'Organization | None' = None,
78+
) -> list[str]:
79+
"""
80+
Creates or updates a mailer template based on a certificate template.
81+
82+
Returns a list of errors. If the list is empty, it was successful.
83+
"""
84+
pass
85+
86+
def delete_template(template: 'ITemplate') -> list[str]:
87+
"""
88+
Deletes a mailer template based on a certificate template.
89+
90+
Returns a list of errors. If the list is empty, it was successful.
91+
"""

0 commit comments

Comments
 (0)