-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clarify custom credential model usage #26
Conversation
@@ -64,14 +65,6 @@ class Format(models.TextChoices): | |||
FIDO_U2F = "fido-u2f", "fido-u2f" | |||
NONE = "none", "none" | |||
|
|||
credential = models.OneToOneField( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SInce this got moved and __str__()
references self.credential, we may also want to specify just the type annotation for that field on this model if we want to run mypy on it regularly. I'm not going to fight with it right now, though, unless you'd rather solve for it immediately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't fancy picking a fight with the type checking system either. I'll leave that up to someone else as a future improvement.
2ed63c4
to
8698218
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great docs @jmichalicek! Some minor points 👍
README.md
Outdated
""" | ||
A WebAuthn credential associated with a specific site. | ||
""" | ||
|
||
site = models.ForeignKey(Site, on_delete=models.CASCADE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: I don't think a illustrative example of a customization is necessary, we can just pass
here and keep it short.
README.md
Outdated
The `AbstractWebAuthnCredential` model creates an index with a name which includes the concrete model's name with `_sha256_idx` appended to the end. If this combination is longer than 30 characters then you will also need to override the index on your credential model to ensure an appropriate length for the index name. | ||
|
||
|
||
```python | ||
class MyCredentiialModelWithALongName(AbstractWebAuthnCredential): | ||
|
||
class Meta: | ||
indexes = [ | ||
models.Index(fields=["credential_id_sha256"], name="mycredential_id_sha256_idx"), | ||
] | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Praise: good callout! I ran into this already when I experimented with the suggestions I made in #21
src/django_otp_webauthn/models.py
Outdated
"missing 'credential' field", | ||
hint=f"Add a field named 'credential' as a OneToOneField relating to {get_credential_model_string()}.", | ||
obj=cls, | ||
id="otp_webauthn.E001", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: can we make this refer to a constant in checks.py
? I like to keep an overview of what error codes we have defined in one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, can't believe I didn't think to do that in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stormheg Somehow I had totally missed that you already had a checks.py
with constants in it for current errors. Is there a scheme you are using for choosing the error codes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't thought about it much. Looking at it now, it looks like I grouped similar checks together
ERR_NO_RP_ID = "otp_webauthn.E010"
ERR_NO_RP_NAME = "otp_webauthn.E011"
ERR_UNSUPPORTED_COSE_ALGORITHM = "otp_webauthn.E020"
ERR_NO_COSE_ALGORITHMS_CONFIGURED = "otp_webauthn.E021"
...
I'd just add to the list of constants. The next available code following this scheme would be otp_webauthn.E050
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I'll have this ready to go at some point this weekend. Not much work to do, just gotta find time around family, etc.
@@ -64,14 +65,6 @@ class Format(models.TextChoices): | |||
FIDO_U2F = "fido-u2f", "fido-u2f" | |||
NONE = "none", "none" | |||
|
|||
credential = models.OneToOneField( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't fancy picking a fight with the type checking system either. I'll leave that up to someone else as a future improvement.
README.md
Outdated
|
||
|
||
```python | ||
class MyCredentiialModelWithALongName(AbstractWebAuthnCredential): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class MyCredentiialModelWithALongName(AbstractWebAuthnCredential): | |
class MyCredentialModelWithALongName(AbstractWebAuthnCredential): |
@@ -198,6 +199,54 @@ If you are exclusively using Passkeys as a secondary verification step, you don' | |||
|
|||
9. That's it! You should now see a "Register Passkey" button on your logged-in user template. Clicking this button will start the registration process. After registration, you should see a "Login using a Passkey" button on your login page. Clicking this button will prompt you to use your Passkey to authenticate. Or if your browser supports it, you will be prompted to use your Passkey when you focus the username field. | |||
|
|||
## Using custom credential and attestation models. | |||
|
|||
Django OTP WebAuthn provides its own models for credentials and attestations for those credentials. If these do not suit your needs you can also provide your own models. When using a custom credential model them you *must* use a custom attestation model as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought: we should explain better what attestations do to the uniniated, but that is a topic for the docs which have yet to be written...
@jmichalicek I have already started work on a testsuite. I'll make sure to commit and push the few test cases I have along with some CI soon. Would love help with improving test coverage if you're up for that. |
I'd be happy to help with writing tests. I spend a lot of time writing tests and advocating for improving test coverage and testing techniques (or having them at all, in some cases) as part of my job. The same goes for any other related stuff - formatter and linter configuration and enforcement, fighting with mypy as has already come up, etc. |
* Moved `AbstractWebAuthnAttestation.credential` field the concrete `WebAUthnAttestation` model * Added a check on `AbstractWebAuthnAttestation` to ensure that there is a `credential` field * Updated README.md with instructions on how to implement custom models.
8698218
to
ec0deb2
Compare
@Stormheg just a small bump for this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dropped of my radar – LGTM ✅ ✅ 👍
I'm going to spend some more on the test suite and get something going before making a release. Expect new things and a release at the end of the week. Thanks again for the help 👍 |
Glad to help. This is very useful to me. I also love helping open source stuff and while helping the big guys is fun, helping smaller projects is more important. Everyone wants to help the big ones. Managing a small one is hard (not that managing a big one isn't, just a different hard). I have a couple of my own with a few users and, honestly, I fear them taking off in any way. |
This PR is to resolve #21
AbstractWebAuthnAttestation.credential
field the concreteWebAuthnAttestation
modelAbstractWebAuthnAttestation
to ensure that there is acredential
fieldThe
credential
field was moved because it always needs to be specified by the user. When implementing their own credential model, a custom attestation model with a relation back to their custom credential has to be specified. If implementing only an attestation model then thecredential
field still needs to be overridden or therelated_name
would cause a conflict due to having two models with the samerelated_name
pointing atWebAuthnAttestation
.A check was added to the
AbstractWebAuthnAttestation
model to ensure that it has acredential
. We could take that a step further and, if it exists, ensure that it is aOneToOneField
relating back to anAbstractWebAuthnCredential
, but a user doesn't technically need subclass that as long as they put the correct fields and methods on their model (although mypy will complain if they use that don't subclass properly).Locally I added a
swapped_models
app with a handful of models. I'll keep it around for reference. That could be good to make use of when writing a test suite (something I just started looking into doing with hatch the other day).