Change login flow to better verify all relevant fields.

master
Tom Hacohen 5 years ago
parent 32a8b9c90d
commit 93a0e41f03

@ -244,18 +244,20 @@ class AuthenticationLoginChallengeSerializer(serializers.Serializer):
raise NotImplementedError() raise NotImplementedError()
class AuthenticationLoginSerializer(AuthenticationLoginChallengeSerializer): class AuthenticationLoginSerializer(serializers.Serializer):
challenge = BinaryBase64Field() response = BinaryBase64Field()
host = serializers.CharField()
signature = BinaryBase64Field() signature = BinaryBase64Field()
def validate(self, data): def create(self, validated_data):
host = self.context.get('host', None) raise NotImplementedError()
if data['host'] != host:
raise serializers.ValidationError(
'Found wrong host name. Got: "{}" expected: "{}"'.format(data['host'], host))
return super().validate(data) def update(self, instance, validated_data):
raise NotImplementedError()
class AuthenticationLoginInnerSerializer(AuthenticationLoginChallengeSerializer):
challenge = BinaryBase64Field()
host = serializers.CharField()
def create(self, validated_data): def create(self, validated_data):
raise NotImplementedError() raise NotImplementedError()

@ -40,6 +40,7 @@ from .serializers import (
AuthenticationSignupSerializer, AuthenticationSignupSerializer,
AuthenticationLoginChallengeSerializer, AuthenticationLoginChallengeSerializer,
AuthenticationLoginSerializer, AuthenticationLoginSerializer,
AuthenticationLoginInnerSerializer,
CollectionSerializer, CollectionSerializer,
CollectionItemSerializer, CollectionItemSerializer,
CollectionItemRevisionSerializer, CollectionItemRevisionSerializer,
@ -368,35 +369,42 @@ class AuthenticationViewSet(viewsets.ViewSet):
def login(self, request): def login(self, request):
from datetime import datetime from datetime import datetime
serializer = AuthenticationLoginSerializer( outer_serializer = AuthenticationLoginSerializer(data=request.data)
data=request.data, context={'host': request.get_host()}) if outer_serializer.is_valid():
if serializer.is_valid(): response_raw = outer_serializer.validated_data['response']
user = self.get_login_user(serializer) response = json.loads(response_raw.decode())
challenge = serializer.validated_data['challenge'] signature = outer_serializer.validated_data['signature']
signature = serializer.validated_data['signature']
serializer = AuthenticationLoginInnerSerializer(data=response, context={'host': request.get_host()})
salt = user.userinfo.salt if serializer.is_valid():
enc_key = self.get_encryption_key(salt) user = self.get_login_user(serializer)
box = nacl.secret.SecretBox(enc_key) host = serializer.validated_data['host']
challenge = serializer.validated_data['challenge']
challenge_data = json.loads(box.decrypt(challenge).decode())
now = int(datetime.now().timestamp()) salt = user.userinfo.salt
if now - challenge_data['timestamp'] > app_settings.CHALLENGE_VALID_SECONDS: enc_key = self.get_encryption_key(salt)
content = {'code': 'challenge_expired', 'detail': 'Login challange has expired'} box = nacl.secret.SecretBox(enc_key)
return Response(content, status=status.HTTP_400_BAD_REQUEST)
elif challenge_data['userId'] != user.id: challenge_data = json.loads(box.decrypt(challenge).decode())
content = {'code': 'wrong_user', 'detail': 'This challenge is for the wrong user'} now = int(datetime.now().timestamp())
return Response(content, status=status.HTTP_400_BAD_REQUEST) if now - challenge_data['timestamp'] > app_settings.CHALLENGE_VALID_SECONDS:
content = {'code': 'challenge_expired', 'detail': 'Login challange has expired'}
host_hash = nacl.hash.blake2b( return Response(content, status=status.HTTP_400_BAD_REQUEST)
serializer.validated_data['host'].encode(), encoder=nacl.encoding.RawEncoder) elif challenge_data['userId'] != user.id:
verify_key = nacl.signing.VerifyKey(user.userinfo.pubkey, encoder=nacl.encoding.RawEncoder) content = {'code': 'wrong_user', 'detail': 'This challenge is for the wrong user'}
verify_key.verify(challenge + host_hash, signature) return Response(content, status=status.HTTP_400_BAD_REQUEST)
elif host != request.get_host():
data = { detail = 'Found wrong host name. Got: "{}" expected: "{}"'.format(host, request.get_host())
'token': Token.objects.get_or_create(user=user)[0].key, content = {'code': 'wrong_host', 'detail': detail}
} return Response(content, status=status.HTTP_400_BAD_REQUEST)
return Response(data, status=status.HTTP_200_OK)
verify_key = nacl.signing.VerifyKey(user.userinfo.pubkey, encoder=nacl.encoding.RawEncoder)
verify_key.verify(response_raw, signature)
data = {
'token': Token.objects.get_or_create(user=user)[0].key,
}
return Response(data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Loading…
Cancel
Save