Sfoglia il codice sorgente

Lo tengo que llevar a otra rama

creylopez 3 mesi fa
parent
commit
fe7bb2b455
1 ha cambiato i file con 0 aggiunte e 397 eliminazioni
  1. 0 397
      passkeys-django.md

+ 0 - 397
passkeys-django.md

@@ -1,397 +0,0 @@
-# Añadir Passkeys a mis aplicaciones Django
-
-## Cambios en Dockerfile
-
-En el Dockerfile de creación de la imagen hay que hacer un pequeño cambio para copiar el logo de passkeys con fondo negro al directorio de instalación del módulo de passkeys dentro del contenedor.
-
-Esto es necesario porque el logo que viene por defecto es blanco y si el botón de entrar con passkey es también blanco, pues no se ve.
-
-    COPY ./src/config/FIDO_Passkey_mark_A_black.jpg /root/.local/lib/python3.14/site-packages/passkeys/static/passkeys/imgs/
-
-Para ello, en el directorio ```./src/config``` hay que dejar el logo correcto. Lo descargué de la página de la [FIDO Alliance](https://fidoalliance.org/get-the-passkey-icon/).
-
-
-## Cambios en ```settings.py````
-
-En ```settings.py```` hay que hacer los siguientes cambios:
-
-1. Añadir ```'passkeys',``` a ```INSTALLED_APPS```:
-
-        INSTALLED_APPS = [
-            ...
-            ...
-            'passkeys',
-            ...
-            ...
-        ]
-
-2. Añadir el backend de autenticación:
-
-        AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend']
-
-3. Añadir las siguientes variables:
-
-        FIDO_SERVER_ID="nc.reymota.es"      # Server rp id for FIDO2, it the full domain of your project
-        FIDO_SERVER_NAME="ReyMota.es"
-        KEY_ATTACHMENT = None  # Set None to allow all authenticator attachment
-
-> en ```FIDO_SERVER_ID``` hay que poner el nombre del dominio. Este de aquí es un ejemplo.
-
-## Cambios en ```urls.py``` del proyecto.
-
-En el fichero ```urls.py``` del proyecto, hay que añadir una entrada para passkeys.
-
-    urlpatterns = [
-        ...
-        ...
-        path('passkeys/', include('passkeys.urls')),
-        ...
-        ...
-    ]
-
-No olvidar importar ```include```:
-
-    from django.urls import path, include
-
-## Cambios en ```reymotausers```
-
-En ```reymotausers``` hay que añadir un nuevo fichero llamado ```auth.py``` con el siguiente contenido:
-
-    import json
-    from django.contrib.auth import authenticate, login, logout
-    from django.contrib.auth.forms import AuthenticationForm
-    from django.shortcuts import render, redirect
-    from rest_framework.exceptions import AuthenticationFailed
-    from rest_framework.permissions import AllowAny
-    from rest_framework.response import Response
-    from rest_framework.serializers import Serializer, CharField
-    from rest_framework.views import APIView
-
-    from passkeys.api.token_backends import get_token_response
-
-    def loginView(request):
-        if request.method == "POST":
-            passkeys_raw = request.POST.get("passkeys", "")
-            if passkeys_raw:
-                user = authenticate(request)
-                if user:
-                    login(request, user)
-                    return redirect(request.POST.get("next", "") or "principal")
-                form = AuthenticationForm(request)
-                form.add_error(None, "Passkey authentication failed. Try again.")
-            else:
-                form = AuthenticationForm(request, data=request.POST)
-                if form.is_valid():
-                    user = form.get_user()
-                    login(request, user)
-                    return redirect(request.POST.get("next", "") or "principal")
-        else:
-            form = AuthenticationForm(request)
-        return render(request, "registration/login.html", {"form": form})
-
-    # def create_session(request,username):
-    #     user=User.objects.get(username=username)
-    #     user.backend='django.contrib.auth.backends.ModelBackend'
-    #     login(request, user)
-    #     return HttpResponseRedirect(reverse('home'))
-
-
-    def logoutView(request):
-        logout(request) # pragma: no cover
-        return  render(request,"logout.html",{}) # pragma: no cover
-
-
-    class LoginSerializer(Serializer):
-        username = CharField()
-        password = CharField()
-
-
-    class LoginAPIView(APIView):
-        """
-        Username + password login API.
-
-        Returns the same token format as passkey authentication
-        (JWT, DRF Token, or session depending on project config).
-        Use this to get an initial token before registering passkeys.
-        """
-
-        permission_classes = [AllowAny]
-        serializer_class = LoginSerializer
-
-        def post(self, request):
-            serializer = LoginSerializer(data=request.data)
-            serializer.is_valid(raise_exception=True)
-            user = authenticate(
-                request,
-                username=serializer.validated_data['username'],
-                password=serializer.validated_data['password'],
-            )
-            if user is None:
-                raise AuthenticationFailed("Invalid username or password")
-            token_data = get_token_response(user, request)
-            return Response({
-                'user_id': user.pk,
-                'username': user.get_username(),
-                **token_data,
-            })
-
-A continuación, hay que modificar ```urls.py``` de ```accounts``` añadiendo
-
-    from reymotausers.auth import loginView
-y en ```urlpatterns``` cambiar el path de login por:
-
-    path('login/', loginView, name='login'),
-
-
-
-
-## Templates para passkeys.
-
-En el directorio ```templates``` hay que añadir una carpeta ```passkeys``` con un fichero ```manage.html``` cuyo contenido es el siguiente:
-
-    {% extends "passkeys/base.html" %}
-    {% load static %}
-    {% block head %}
-    {{block.super}}
-            <script type="application/javascript" src="{% static 'passkeys/js/base64url.js'%}"></script>
-        <script type="application/javascript" src="{% static 'passkeys/js/helpers.js'%}"></script>
-        <script type="application/javascript">
-        var MakeCredReq = (makeCredReq) => {
-                makeCredReq.publicKey.challenge = base64url.decode(makeCredReq.publicKey.challenge);
-                makeCredReq.publicKey.user.id   = base64url.decode(makeCredReq.publicKey.user.id);
-
-                for(let excludeCred of makeCredReq.publicKey.excludeCredentials) {
-                    excludeCred.id = base64url.decode(excludeCred.id);
-                }
-
-                return makeCredReq
-            }
-        function begin_reg(){
-        fetch('{% url 'passkeys:reg_begin' %}',{}).then(function(response) {
-        if(response.ok)
-        {
-            return response.json().then(function (req){
-                return MakeCredReq(req)
-            });
-        }
-        throw new Error('Error getting registration data!');
-        }).then(function(options) {
-            if (!navigator.credentials) {
-                throw new Error('WebAuthn is not available. Ensure the page is served over HTTPS (secure context).');
-            }
-            return navigator.credentials.create(options);
-        }).then(function(attestation) {
-            attestation["key_name"] = $("#key_name").val();
-        return fetch('{% url 'passkeys:reg_complete' %}', {
-            headers:{'X-CSRFToken': "{{csrf_token}}"},
-            method: 'POST',
-            body: JSON.stringify(publicKeyCredentialToJSON(attestation))
-        });
-        }).then(function(response) {
-
-            var stat = response.ok ? 'successful' : 'unsuccessful';
-            return response.json()
-        }).then(function (res)
-            {
-        if (res["status"] =='OK')
-            $("#res").html("<div class='alert alert-success'>Registered Successfully, <a href='{% url "passkeys:home"%}'> Refresh</a></div>")
-        else
-            $("#res").html("<div class='alert alert-danger'>Registration Failed as " + res["message"] + ", <a href='javascript:void(0)' onclick='begin_reg()'> try again </a> </div>")
-
-
-        }, function(reason) {
-        $("#res").html("<div class='alert alert-danger'>Registration Failed as " +reason +", <a href='javascript:void(0)' onclick='begin_reg()'> try again </a> </div>")
-        })
-        }
-
-
-
-
-        function confirmDel(id) {
-            $.ajax({
-                url:"{% url 'passkeys:delKey' %}",
-                method:"POST",
-                data:{"id":id,"csrfmiddlewaretoken":"{{ csrf_token }}"},
-                success:function (data) {
-                    alert(data)
-                    window.location= "{%url 'passkeys:home'%}";
-                }
-            })
-        }
-
-        function start(){
-            if (!navigator.credentials) {
-                alert("WebAuthn is not available. This feature requires a secure context (HTTPS).");
-                return;
-            }
-            $("#modal-title").html("Enter a token name")
-            $("#modal-body").html(`<p>Please enter a name for your new token</p>
-                                    <input type="text" placeholder="e.g Laptop, PC" id="key_name" class="form-control"/><br/>
-                                    <div id="res"></div>
-                                    `)
-            $("#actionBtn").remove();
-            $("#modal-footer").prepend(`<button id='actionBtn' class='btn btn-success' onclick="begin_reg()">Start</button>`)
-            $("#popUpModal").modal('show')
-        }
-        function deleteKey(id,name)
-        {
-            $("#modal-title").html("Confirm Delete")
-            $("#modal-body").html(`Are you sure you want to delete '${name}'? you may lose access to your system if this your only 2FA.`);
-            $("#actionBtn").remove()
-            $("#modal-footer").prepend("<button id='actionBtn' class='btn btn-danger' onclick='confirmDel("+id+")'>Confirm Deletion</button>")
-            $("#popUpModal").modal('show')
-        }
-
-        function toggleKey(id) {
-            $.ajax({
-                url:"{% url 'passkeys:toggle' %}",
-                data:{"id":id,"csrfmiddlewaretoken":"{{ csrf_token }}"},
-                method:"POST",
-                success:function (data) {
-                    if (data == "Error")
-                        $("#toggle_"+id).toggle()
-
-                },
-                error:function (data) {
-                    $("#toggle_"+id).toggle()
-                }
-            })
-        }
-        {%if enroll%}
-            $(document).ready(start)
-        {%endif%}
-
-        </script>
-        <link href="{% static  'passkeys/css/bootstrap-toggle.min.css' %}" rel="stylesheet">
-        <script src="{% static 'passkeys/js/bootstrap-toggle.min.js'%}"></script>
-    {% endblock %}
-    {% block content %}
-    {{block.super}}
-        <br/>
-        <br/>
-        <div class="container">
-            <div class="row">
-                <div class="offset-5 col-2" style="text-align: center">
-                    <div class="btn-group">
-                        <button class="btn btn-success" onclick='start()'> Add Key</button>
-                    </div>
-                </div>
-            </div>
-            <br/>
-        <table class="table table-striped">
-            <tr>
-                <th>Name</th>
-                <th>Date Added</th>
-                <th>Platform</th>
-                <th>Last Used</th>
-                <th>Status</th>
-                <th>Delete</th>
-            </tr>
-                {% if keys %}
-                    {% for key in keys %}
-                    <tr>
-
-                        <td>{{ key.name }}</td>
-                        <td>{{ key.added_on }}</td>
-                        <td>{{ key.platform }}</td>
-                        <td>{% if key.last_used %}{{ key.last_used }}{% else %}Never{% endif %}</td>
-                        <td><input type="checkbox" id="toggle_{{ key.id }}" {% if key.enabled %}checked{% endif %} data-onstyle="success" data-offstyle="danger"  onchange="toggleKey({{ key.id }})" data-toggle="toggle" class="status_chk"></td>
-                        <td><a href="javascript:void(0)" onclick="deleteKey({{ key.id }},'{{ key.name }}')"> <span class="fa fa-trash fa-solid fa-trash-can bi bi-trash-fill"></span></a></td>
-                    </tr>
-                    {% endfor %}
-                {% else %}
-                <tr>
-                    <td colspan="7" align="center">You didn't have any keys yet.</td>
-                </tr>
-            {% endif %}
-            </table>
-        </div>
-        {% include "passkeys/modal.html" %}
-    {% endblock %}
-
-## Template de login.
-
-en la template ```login.html``` hay que añadir los componentes necesarios para ofrecer la posibilidad de entrar con passkey.
-
-Para ello, antes del final del form (```</form>```) y después del botón de login propiamente dicho, hay que añadir otro botón para ofrecer la posibilidad de entrar con passkeys:
-
-
-    <div class="text-center">
-         <input type="hidden" name="passkeys" id="passkeys"/>
-         <button class="btn app-btn-secondary w-100 theme-btn mx-auto" style="margin-top: 10px" type="button" onclick="authn('login-form')"><img src="{% static 'passkeys/imgs/FIDO_Passkey_mark_A_black.jpg' %}" style="width: 24px"/> Entrar con clave de paso</button>
-    </div>
-
-La clave están en el tag ```button``` en cual tiene una opción ```onclick``` que hace que se invoque a la funcionalidad de passkeys.
-
-Aquí es donde he metido un nuevo logo de FIDO Passkey que es mejor (para mis web) que el que viene por defecto que está en blanco total.
-
-También he añadido un estilo específico para que se separe un poco del botón de login que tiene justo encima:
-
-    style="margin-top: 10px"
-
-Por último, antes del cierre de ```body``` hay que añadir:
-
-    {%include 'passkeys/passkeys.js' with allow_password=True %}
-
-## Cambios en ```base.html```
-
-En ```base.html``` hay que añadir una opción al menú de usuario para que este pueda gestionar las llaves de paso.
-
-
-En el dropdown, se añade la línea resaltada:
-
-    <ul class="dropdown-menu" aria-labelledby="user-dropdown-toggle">
-        {% if user.is_authenticated %}
-        <li>
-            <a class="dropdown-item">{{ user.nombre }}</a>
-        </li>
-        
-        <!-- Esto es para el menú de gestión de llaves de paso -->
-        <li>
-            <a class="dropdown-item" href="{% url 'passkeys:home' %}">Passkeys</a><
-        </li>
-
-        <li>
-            <a class="dropdown-item">                        
-                <form method="post" action="{% url 'logout' %}" >
-                    {% csrf_token %}
-                    <button
-                        style="background: none!important;
-                            border: none;
-                            padding: 0!important;
-                            /*optional*/
-                            font-family: arial, sans-serif;
-                            /*input has OS specific font-family*/
-                            /*color: #069;
-                            text-decoration: underline;*/
-                            cursor: pointer;"   
-                    
-                        type="submit">Salir</button>
-                </form>
-            </a>
-        </li>
-        {% else %}
-            <li>
-                <a class="dropdown-item" href="{% url 'login' %}">Entrar</a>
-            </li>
-        {% endif %} 
-    </ul>
-
-## Cambios en ```requirements.txt```
-
-En ```requirements.txt``` hay que añadir estos dos requisitos para que sean instalados en la imagen:
-
-    django-passkeys>=2.1
-    djangorestframework-simplejwt>=5.0
-
-## Migraciones.
-
-Cuando se lanza la aplicación, en ```entrypoing.sh``` se llama a ```migrate``` y a ```collectstatic```. 
-
-Esto hace que se cree la tabla de llaves de paso y también que se copien los componentes estáticos de passkeys en el directorio correspondiente.
-
-## Otras consideraciones.
-
-El primer proyecto que modifiqué fue el de ```ReyMotaAppsOC```. Luego seguí por ```misfinanzas```. En este último no estaban bien configuradas las templates en lo que se refiere principalmente al bloque head. 
-
-Lo dejé igual que en ```ReyMotaAppsOC```. Básicamente, ```_head.html``` no cierra el head con ```</head>``` y se cierra en las plantillas que la incluyen. Solo en ```base.html``` se include ```{% block head %}{% endblock %}```. Con esto la template ```passkeys/manage.html``` incluye el código necesario para la función de añadir llaves.