From 0480c4a9e61964b096dc01bd82d61667cf072306 Mon Sep 17 00:00:00 2001
From: meskio <meskio@sindominio.net>
Date: Tue, 29 Sep 2020 12:30:17 +0200
Subject: [PATCH] Create tokens that don't expire

---
 api/api.go    |  2 +-
 api/auth.go   | 23 ++++++++++++++++-------
 src/App.js    | 17 ++++++++++-------
 src/SignIn.js | 16 +++++++++++++++-
 4 files changed, 42 insertions(+), 16 deletions(-)

diff --git a/api/api.go b/api/api.go
index 91d8705..79f2ffe 100644
--- a/api/api.go
+++ b/api/api.go
@@ -31,7 +31,7 @@ func Init(dbPath string, signKey string, r *mux.Router) error {
 
 	a := api{db, []byte(signKey)}
 
-	token, err := a.newToken(0, "admin")
+	token, err := a.newToken(0, "admin", false)
 	log.Print(token)
 
 	r.HandleFunc("/signin", a.SignIn).Methods("POST")
diff --git a/api/auth.go b/api/auth.go
index 94eba5a..66930ee 100644
--- a/api/auth.go
+++ b/api/auth.go
@@ -15,6 +15,7 @@ import (
 type creds struct {
 	Name     string `json:"name"`
 	Password string `json:"password"`
+	NoExpire bool   `json:"noExpire"`
 }
 
 func (a *api) SignIn(w http.ResponseWriter, req *http.Request) {
@@ -44,7 +45,7 @@ func (a *api) SignIn(w http.ResponseWriter, req *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.WriteHeader(http.StatusOK)
 
-	token, err := a.newToken(member.Num, member.Role)
+	token, err := a.newToken(member.Num, member.Role, !c.NoExpire)
 	if err != nil {
 		log.Printf("Can't create a token: %v", err)
 		w.WriteHeader(http.StatusInternalServerError)
@@ -80,7 +81,7 @@ func (a *api) GetToken(w http.ResponseWriter, req *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.WriteHeader(http.StatusOK)
 
-	token, err := a.newToken(int(num), role)
+	token, err := a.newToken(int(num), role, true)
 	if err != nil {
 		log.Printf("Can't create a token: %v", err)
 		w.WriteHeader(http.StatusInternalServerError)
@@ -162,12 +163,16 @@ func (a *api) authNumRole(fn func(int, string, http.ResponseWriter, *http.Reques
 	}
 }
 
-func (a *api) newToken(num int, role string) (string, error) {
-	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
+func (a *api) newToken(num int, role string, expire bool) (string, error) {
+	claims := jwt.MapClaims{
 		"num":  num,
 		"role": role,
-		"exp":  time.Now().Add(time.Hour * 24).Unix(),
-	})
+	}
+	if expire {
+		claims["exp"] = time.Now().Add(time.Hour * 24).Unix()
+	}
+
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
 	return token.SignedString(a.signKey)
 }
 
@@ -185,7 +190,11 @@ func (a *api) validateToken(token string) (bool, jwt.MapClaims) {
 	if !ok {
 		return false, nil
 	}
-	exp, ok := claims["exp"].(float64)
+	expClaim, ok := claims["exp"]
+	if !ok {
+		return true, claims
+	}
+	exp, ok := expClaim.(float64)
 	if !ok {
 		return false, claims
 	}
diff --git a/src/App.js b/src/App.js
index 1b9fdea..6a867f8 100644
--- a/src/App.js
+++ b/src/App.js
@@ -73,10 +73,6 @@ class App extends React.Component {
         this.storeState(token, member.num, member.role);
     }
 
-    isLoged() {
-        return this.state.token !== null && !this.tokenExpired();
-    }
-
     logout() {
         this.setState({
             num: null,
@@ -86,16 +82,23 @@ class App extends React.Component {
         this.storeState("", "", "");
     }
 
-	tokenExpired() {
+	isLoged() {
+        if (!this.state.token) {
+            return false;
+        }
+
         const token = this.state.token;
 		const base64Url = token.split('.')[1];
 		const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
 		const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
 			return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
 		}).join(''));
-		const claims = JSON.parse(jsonPayload);
 
-        return claims["exp"] < Date.now();
+		const claims = JSON.parse(jsonPayload);
+        if (claims["exp"] === undefined) {
+            return true;
+        }
+        return claims["exp"] < Date.now()/1000;
 	};
 
     render() {
diff --git a/src/SignIn.js b/src/SignIn.js
index 3efae07..fe4d28b 100644
--- a/src/SignIn.js
+++ b/src/SignIn.js
@@ -8,6 +8,7 @@ class SignIn extends React.Component {
         this.state = {
             name: "",
             password: "",
+            noExpire: false,
             isLoading: false,
             badAuth: false,
             error: null
@@ -21,7 +22,10 @@ class SignIn extends React.Component {
 
         this.setState({isLoading: true, error: null});
         const body = JSON.stringify({
-            name: this.state.name, password: this.state.password});
+            name: this.state.name,
+            password: this.state.password,
+            noExpire: this.state.noExpire
+        });
 
         fetch("/api/signin", {method: "POST", body})
             .then(response => {
@@ -73,6 +77,16 @@ class SignIn extends React.Component {
                     Nombre o contraseña invalidos.
                   </Form.Control.Feedback>
                 </Form.Group>
+
+                <Form.Group>
+                    <Form.Check
+                        type="switch"
+                        id="noExpire"
+                        label="Permanecer conectada con este navegador"
+                        onChange={e => this.setState({noExpire: e.target.checked}) }
+                    />
+                </Form.Group>
+
                 <Button
                   variant="primary"
                   type="submit"
-- 
GitLab