add bitcoin to address model

This commit is contained in:
Gonçalo Valério 2020-02-19 15:04:19 +00:00
parent 0071013d71
commit 1eac8809e8
7 changed files with 117 additions and 13 deletions

View File

@ -11,7 +11,10 @@ Django-Cryptolock
.. image:: https://coveralls.io/repos/github/dethos/django-cryptolock/badge.svg
:target: https://coveralls.io/github/dethos/django-cryptolock
Django authentication using cryptocurrency wallets
Django authentication using cryptocurrency wallets.
**DISCLAIMER:** This package is still in an early stage of development. It isn't meant to be
used on any production scenario yet (in other words, only test projects for now).
Documentation
-------------

View File

@ -40,3 +40,17 @@ class MoneroAddressBackend(ModelBackend):
return stored_address.user
return None
class BitcoinAddressBackend(ModelBackend):
"""Custom Bitcoin-BitId authentication backend."""
def authenticate(
self, request, address=None, challenge=None, signature=None, **kwargs
):
"""
Validates the provided signature for the given Bitcoin address and challenge.
This method does not rely on any external components, everything is done locally.
"""
pass

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.5 on 2020-02-18 19:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_cryptolock', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='address',
name='network',
field=models.PositiveSmallIntegerField(choices=[(1, 'Monero'), (2, 'Bitcoin')], default=1),
),
migrations.AlterField(
model_name='address',
name='address',
field=models.CharField(max_length=106, unique=True),
),
]

View File

@ -3,19 +3,24 @@
from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
from model_utils.models import TimeStampedModel
from .validators import validate_monero_address
from .validators import validate_monero_address, validate_bitcoin_address
class Address(TimeStampedModel):
"""Addresses that belong to a given user account."""
NETWORK_MONERO = 1
NETWORK_BITCOIN = 2
NETWORKS = ((NETWORK_MONERO, "Monero"), (NETWORK_BITCOIN, "Bitcoin"))
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
address = models.CharField(
max_length=106, validators=[validate_monero_address], unique=True
)
network = models.PositiveSmallIntegerField(choices=NETWORKS, default=NETWORK_MONERO)
address = models.CharField(max_length=106, unique=True)
class Meta:
"""Meta definition for Address."""
@ -26,3 +31,12 @@ class Address(TimeStampedModel):
def __str__(self):
"""Unicode representation of Address."""
return self.address
def clean(self):
try:
if self.network == self.NETWORK_MONERO:
validate_monero_address(self.address)
else:
validate_bitcoin_address(self.address)
except ValidationError:
raise ValidationError(_("Invalid address for the given network"))

View File

@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _
from django.conf import settings
from monero.address import Address
from pybitid.bitid import address_valid
def validate_monero_address(value):
@ -24,3 +25,14 @@ def validate_monero_address(value):
raise ValidationError(_("Invalid address for stagenet"))
elif network == "testnet" and not address.is_testnet():
raise ValidationError(_("Invalid address for testnet"))
def validate_bitcoin_address(value):
network = getattr(settings, "DJCL_BITCOIN_NETWORK", None)
if not network:
raise ValidationError(
_("Please configure the monero network in the settings file")
)
testnet = True if network == "testnet" else False
if not address_valid(value, is_testnet=testnet):
raise ValidationError(_(f"Invalid address for {network}"))

View File

@ -2,3 +2,5 @@ django>=2.2
django-model-utils>=2.0
monero>=0.6
python-monerorpc>=0.5.5
python-bitcoinaddress>=0.2.2
pybitid>=0.0.4

View File

@ -19,30 +19,51 @@ VALID_MONERO_MAINNET_ADDR = "45D8b4XiUdz86FwztAJHVeLnQqGHQUqiHSwZe6rXFHSoXw522dP
VALID_MONERO_STAGENET_ADDR = "55LTR8KniP4LQGJSPtbYDacR7dz8RBFnsfAKMaMuwUNYX6aQbBcovzDPyrQF9KXF9tVU6Xk3K8no1BywnJX6GvZX8yJsXvt"
VALID_MONERO_TESTNET_ADDR = "9vmn8Vyxh6JEVmPr4qTcj3ND3FywDpMXH2fVLLEARyKCJTc3jWjxeWcbRNcaa57Bj36cARBSfWnfS89oFVKBBvGTAegdRxG"
VALID_BITCOIN_TESTNET_ADDR = "n47QBape2PcisN2mkHR2YnhqoBr56iPhJh"
VALID_BITCOIN_MAINNET_ADDR = "1AUeWMGD9hDYtAhZGZLmDjEzKSrPow4zNt"
pytestmark = pytest.mark.django_db
def test_valid_mainnet_address(settings):
def test_valid_monero_mainnet_address(settings):
settings.DJCL_MONERO_NETWORK = "mainnet"
addr = mommy.make(Address, address=VALID_MONERO_MAINNET_ADDR)
addr.full_clean()
def test_valid_stagenet_addr(settings):
def test_valid_monero_stagenet_addr(settings):
settings.DJCL_MONERO_NETWORK = "stagenet"
addr = mommy.make(Address, address=VALID_MONERO_STAGENET_ADDR)
addr.full_clean()
def test_valid_testnet_addr(settings):
def test_valid_monero_testnet_addr(settings):
settings.DJCL_MONERO_NETWORK = "testnet"
addr = mommy.make(Address, address=VALID_MONERO_TESTNET_ADDR)
addr.full_clean()
def test_valid_bitcoin_mainnet_address(settings):
settings.DJCL_BITCOIN_NETWORK = "mainnet"
addr = mommy.make(
Address, address=VALID_BITCOIN_MAINNET_ADDR, network=Address.NETWORK_BITCOIN
)
addr.full_clean()
def test_valid_bitcoin_testnet_address(settings):
settings.DJCL_BITCOIN_NETWORK = "testnet"
addr = mommy.make(
Address, address=VALID_BITCOIN_TESTNET_ADDR, network=Address.NETWORK_BITCOIN
)
addr.full_clean()
def test_invalid_address():
bad_addr = "Verywrongaddress"
addr = mommy.make(Address, address=bad_addr)
@ -51,23 +72,38 @@ def test_invalid_address():
addr.full_clean()
assert (
"{} is not a valid address".format(bad_addr)
in error.value.message_dict["address"]
"Invalid address for the given network" in error.value.message_dict["__all__"]
)
def test_wrong_network_address(settings):
def test_wrong_monero_network_address(settings):
settings.DJCL_MONERO_NETWORK = "stagenet"
addr = mommy.make(Address, address=VALID_MONERO_MAINNET_ADDR)
with pytest.raises(ValidationError) as error:
addr.full_clean()
assert "Invalid address for stagenet" in error.value.message_dict["address"]
assert (
"Invalid address for the given network" in error.value.message_dict["__all__"]
)
def test_wrong_bitcoin_network_address(settings):
settings.DJCL_MONERO_NETWORK = "testnet"
addr = mommy.make(
Address, address=VALID_BITCOIN_MAINNET_ADDR, network=Address.NETWORK_BITCOIN
)
with pytest.raises(ValidationError) as error:
addr.full_clean()
assert (
"Invalid address for the given network" in error.value.message_dict["__all__"]
)
def test_address_is_unique():
addr = mommy.make(Address, address=VALID_MONERO_MAINNET_ADDR)
mommy.make(Address, address=VALID_MONERO_MAINNET_ADDR)
with pytest.raises(IntegrityError):
mommy.make(Address, address=VALID_MONERO_MAINNET_ADDR)