From 354e7fc5c7fa80697c6d819d52e338414de23841 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi Date: Sun, 2 Jan 2022 09:04:51 -0800 Subject: [PATCH] Create user generation API Endpoint request contains a token. Server checks whether it has enough entropy to be secure (at least 128 bits and >0.7 shannon heterogeneity). Server hashes the token (once as of now) and derives nickname and robohash avatar (goes into static/assets/avatars). Nickname is limited to only adjectives, nouns and 3 digits, restricted to 18 characters long max. The corpus of nicknames is 20 billion big (maybe not enough? nick collision chance can happen, but server will just reply with 400 unless the token matches perfectly too) --- .gitignore | 3 +++ api/urls.py | 3 ++- api/views.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index aefa74e2..2acb0130 100755 --- a/.gitignore +++ b/.gitignore @@ -638,3 +638,6 @@ FodyWeavers.xsd # Django *migrations* frontend/static/frontend/main* + +# robosats +frontend/static/assets/avatars* diff --git a/api/urls.py b/api/urls.py index d4fdeb38..27f39862 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,7 +1,8 @@ from django.urls import path -from .views import MakeOrder, OrderView +from .views import MakeOrder, OrderView, UserGenerator urlpatterns = [ path('make/', MakeOrder.as_view()), path('order/', OrderView.as_view()), + path('usergen/', UserGenerator.as_view()), ] \ No newline at end of file diff --git a/api/views.py b/api/views.py index 6bffaf7f..a900a42b 100644 --- a/api/views.py +++ b/api/views.py @@ -1,7 +1,7 @@ from rest_framework import status from rest_framework.views import APIView from rest_framework.response import Response - +from django.contrib.auth.backends import BaseBackend from django.contrib.auth.models import User from .serializers import OrderSerializer, MakeOrderSerializer @@ -9,6 +9,11 @@ from .models import Order from .nick_generator.nick_generator import NickGenerator from robohash import Robohash +from scipy.stats import entropy +from math import log2 +import numpy as np +import hashlib +from pathlib import Path # Create your views here. @@ -93,13 +98,55 @@ class UserGenerator(APIView): def get(self,request): ''' - Get a new user based on high entropy token + Get a new user derived from a high entropy token - Request has a high-entropy token, - Generates new nickname and avatar. - Creates login credentials (new User object) Response with Avatar and Nickname. ''' - pass + token = request.GET.get(self.lookup_url_kwarg) + + # Compute token entropy + value, counts = np.unique(list(token), return_counts=True) + shannon_entropy = entropy(counts, base=62) + bits_entropy = log2(len(value)**len(token)) + + # Start preparing payload + context = {'token_shannon_entropy': shannon_entropy, 'token_bits_entropy': bits_entropy} + + # Deny user gen if entropy below 128 bits or 0.7 shannon heterogeneity + if bits_entropy < 128 or shannon_entropy < 0.7: + context['Bad Request'] = 'The token does not have enough entropy' + return Response(context, status=status.HTTP_400_BAD_REQUEST) + + # Hashes the token, only 1 iteration. Maybe more is better. + hash = hashlib.sha256(str.encode(token)).hexdigest() + + # generate nickname + nickname = self.NickGen.short_from_SHA256(hash, max_length=18)[0] + context['nickname'] = nickname + + # generate avatar + rh = Robohash(hash) + rh.assemble(roboset='set1') # bgset='any' for backgrounds ON + + avatars_path = Path('frontend/static/assets/avatars') + avatars_path.mkdir(parents=True, exist_ok=True) + + with open(avatars_path.joinpath(nickname+".png"), "wb") as f: + rh.img.save(f, format="png") + + # Create new credentials if nickname is new + if len(User.objects.filter(username=nickname)) == 0: + User.objects.create_user(username=nickname, password=token, is_staff=False) + + else: + context['found'] = 'A matching nickname was found' + + # TODO Keep user authenticated. + # BaseBackend.authenticate(self, request=None,username=nickname, password=token) + + return Response(context, status=status.HTTP_201_CREATED)