Selaa lähdekoodia

Funcionando con passkeys. Falta el inicio de sesión

Tino 5 kuukautta sitten
vanhempi
sitoutus
54ee78fd55

+ 1 - 0
Dockerfile

@@ -8,6 +8,7 @@ RUN python3 -m pip install --upgrade pip
 RUN pip install --user --no-cache-dir -r requirements.txt
 
 COPY ./nginx/default.conf /etc/nginx/http.d/nginx.conf
+COPY ./src/config/FIDO_Passkey_mark_A_black.jpg /root/.local/lib/python3.14/site-packages/passkeys/static/passkeys/imgs/
 
 # link nginx logs to container stdout
 RUN ln -sf /dev/stdout /var/log/nginx/access.log && \

+ 1 - 1
K8S/.current_version

@@ -1 +1 @@
-alpine-7
+alpine-12

+ 1 - 1
K8S/.version

@@ -1 +1 @@
-alpine-8
+alpine-13

+ 1 - 3
K8S/Makefile

@@ -89,11 +89,9 @@ install: ## Instala todo (meno las claims)
 
 	-cat finanzas-deployment.yaml | sed -e "s/<NS>/${NAMESPACE}/" | sed -e "s/<TAG>/${BUILT_VERSION}/" | sed -e "s/<REGISTRO>/${REGISTRO}/" | kubectl --kubeconfig=$(KUBECONFIG) create -f -
 	-cat finanzas-service.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl --kubeconfig=$(KUBECONFIG) create -f -
-	-cat finanzas-ingress.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl --kubeconfig=$(KUBECONFIG) create -f -
 
 clean: ## Borra todo (meno las claims)
 
-	-cat finanzas-ingress.yaml | sed -e "s/<NS>/${NAMESPACE}/" | kubectl --kubeconfig=$(KUBECONFIG) delete -f -
 
 	-cat finanzas-deployment.yaml | sed -e "s/<NS>/${NAMESPACE}/" | sed -e "s/<TAG>/${BUILT_VERSION}/" | sed -e "s/<REGISTRO>/${REGISTRO}/" | kubectl --kubeconfig=$(KUBECONFIG) delete -f -
 
@@ -116,7 +114,7 @@ app: ## Borra y vuelve a crear el deployment
 	-cat finanzas-deployment.yaml | sed -e "s/<NS>/${NAMESPACE}/" | sed -e "s/<TAG>/${BUILT_VERSION}/" | sed -e "s/<REGISTRO>/${REGISTRO}/" | kubectl --kubeconfig=$(KUBECONFIG) create -f -
 
 status: ## Muestra el estado del deployment
-	kubectl --kubeconfig=$(KUBECONFIG) get deployments,pods,services,ingress -n $(NAMESPACE)
+	kubectl --kubeconfig=$(KUBECONFIG) get deployments,pods,services -n $(NAMESPACE)
 
 db: ## Crea la base de datos en el cluster
 	-kubectl --kubeconfig=$(KUBECONFIG) create -f finanzas-db.yaml

BIN
src/config/FIDO_Passkey_mark_A_black.jpg


+ 7 - 0
src/misfinanzas/settings.py

@@ -52,6 +52,8 @@ INSTALLED_APPS = [
     'reymotausers',
     'repostajes',
 
+    'passkeys',
+
     'rest_framework',
 ]
 
@@ -252,3 +254,8 @@ LOGGING = {
         }
     },
 }
+
+AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend']
+FIDO_SERVER_ID="finanzas.reymota.es"      # Server rp id for FIDO2, it the full domain of your project
+FIDO_SERVER_NAME="Finanzas"
+KEY_ATTACHMENT = None  # Set None to allow all authenticator attachment

+ 2 - 0
src/misfinanzas/urls.py

@@ -27,6 +27,8 @@ urlpatterns = [
     path("accounts/", include("accounts.urls")),  # new
     path("accounts/", include("django.contrib.auth.urls")),
 
+    path('passkeys/', include('passkeys.urls')),
+
     path('', include('finanzas.urls')),
 
     path('usuarios/', include("reymotausers.urls")),

+ 2 - 0
src/requirements.txt

@@ -24,3 +24,5 @@ weasyprint==68.0
 webencodings==0.5.1
 zopfli==0.4.0
 whitenoise
+django-passkeys>=2.1
+djangorestframework-simplejwt>=5.0

+ 2 - 1
src/templates/_cabecera.html

@@ -61,5 +61,6 @@
             margin-right: 8px;
         }
     </style>
-    
+    {% block head %}{% endblock %}    
  </head>
+

+ 5 - 4
src/templates/_head.html

@@ -3,21 +3,22 @@
 <!DOCTYPE html>
 <html lang="en"> 
 <head>
-    <title>Finanzas</title>
+    <title>Aplicaciones en Reymota.es</title>
     
     <!-- Meta -->
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     
-    <meta name="description" content="Finanzas - Portal de Gestión Financiera">
+    <meta name="description" content="Portal - Bootstrap 5 Admin Dashboard Template For Developers">
     <meta name="author" content="Celestino Rey" >    
     <link rel="shortcut icon" href="{% static 'images/favicon.ico' %}"> 
     
     <!-- FontAwesome JS-->
     <script defer src="{% static 'plugins/fontawesome/js/all.min.js' %}"></script>
+
+    <!-- jQuery (required for passkeys management) -->
+    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
     
     <!-- App CSS -->  
     <link id="theme-style" rel="stylesheet" href="{% static 'css/portal.css' %}">
-
-</head> 

+ 7 - 1
src/templates/base.html

@@ -1,6 +1,8 @@
 {% load static %}
 
-{% include "_cabecera.html" %}
+{% include "_head.html" %}
+{% block head %}{% endblock %}
+</head>
 
 <body {% if debug %}class="has-debug-banner"{% else %}class="app"{% endif %}>
     {% if debug %}
@@ -58,6 +60,10 @@
                                             type="submit">Cambiar contraseña</button>
                                         </form></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 %}

+ 159 - 0
src/templates/passkeys/manage.html

@@ -0,0 +1,159 @@
+{% 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 %}

+ 7 - 1
src/templates/registration/login.html

@@ -1,6 +1,7 @@
 {% load i18n static %}
 
 {% include "_head.html" %}
+</head>
 
 <body class="app app-login p-0">    	
     <div class="row g-0 app-auth-wrapper">
@@ -47,7 +48,6 @@
                             </div>
                             <div class="password mb-3">
                                 {{ form.password }}
-                                <input type="hidden" name="next" value="{{ next }}">
                             </div>
                             {% url 'admin_password_reset' as password_reset_url %}
                             {% if password_reset_url %}
@@ -59,6 +59,10 @@
                             <div class="text-center">
                                 <input type="submit" class="btn app-btn-primary w-100 theme-btn mx-auto" value="{% translate 'Log in' %}">
                             </div>
+				<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>
                         </form>
                     </div>
                 </div>
@@ -84,5 +88,7 @@
 
     </div><!--//row-->
 
+    {%include 'passkeys/passkeys.js' with allow_password=True %}
+
 </body>
 </html> 

+ 1 - 1
src/templates/registration/signup.html

@@ -1,5 +1,5 @@
-
 {% include "_head.html"  %}
+</head>
 
 <body class="app app-signup p-0">    	
     <div class="row g-0 app-auth-wrapper">

+ 2 - 1
src/templates/resumen_saldos_mensuales.html

@@ -4,7 +4,8 @@
 <!DOCTYPE html>
 <html lang="es">
 
-{% include "_cabecera.html" %}
+{% include "_head.html" %}
+</head>
 
 <body>
     <table>