Letting someone act without an account
A customer must sign a quote. A supplier must confirm a date. These people have no account with you, never will, and creating one for them is out of the question.
The kernel provides the brick: a link pointing at a resource, and a single-use code sent by email to prove it is the intended recipient opening it.
from kewos.secure_link import issue_link_on, request_otp_on, verify_otp_onfrom kewos.secure_link.types import OtpPurpose
with engine.begin() as conn: token, ctx = issue_link_on( conn, resource_kind="library_loan", resource_ref="19", recipient_email="reader@example.test", scope="loan:read", secret_key=key, )token is the only plaintext copy: you put it in the email’s URL, and it is stored nowhere.
Run:
plaintext token : kws_sl_2_f... (50 characters)in the database : 67cac6636cba5ff24d3b... (is the plaintext token there? False)A stolen database yields no usable link: it holds hashes only.
The single-use code
with engine.begin() as conn: request_otp_on( conn, link_id=ctx.link_id, purpose=OtpPurpose.OPEN, recipient_email="reader@example.test", from_address="no-reply@example.test", render_email=my_template, secret_key=key, )The email leaves through the transactional outbox (rule 17): if your transaction fails, the code is never sent. Run:
code : 6 digitsis the plaintext code stored? : Falsewrong code : OtpInvalidErrorright code : acceptedthe same one, replayed : OtpNotFoundErrorThat last line is the property that matters: a code works once. It is consumed on verification, so replaying an intercepted email gets nothing.
What the kernel holds for you
| Guarantee | Default |
|---|---|
| Code lifetime | 10 minutes |
| Sends per window | 3 per 10 minutes |
| Attempts before lockout | 5 |
| Storage | HMAC hash only, never the code nor the token |
| Traceability | every outcome audited, never the code (rule 13) |
| Channel | email only, never SMS |
The five-attempt lockout is what makes six digits acceptable: without it, a short code is guessable.
Two consents rather than one
OtpPurpose separates opening (OPEN) from acting (APPLY). Two distinct codes, and
deliberately so: reading a document and signing it are not the same act. A third party who has
opened has not yet consented.