Basic Access & Refresh Token

This commit is contained in:
2021-01-18 16:16:52 -05:00
parent 377903f7a1
commit b05b1eb9b6
9 changed files with 181 additions and 147 deletions

View File

@@ -1,13 +1,16 @@
package api
import (
"fmt"
"time"
"encoding/json"
"strings"
"net/http"
"encoding/json"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/lestrrat-go/jwx/jwt"
"reichard.io/imagini/internal/models"
// "github.com/lestrrat-go/jwx/jwt"
// "github.com/lestrrat-go/jwx/jwa"
// log "github.com/sirupsen/logrus"
)
// https://www.calhoun.io/pitfalls-of-context-values-and-how-to-avoid-or-mitigate-them/
@@ -35,20 +38,51 @@ func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Verify Device Name Exists
deviceHeader := r.Header.Get("X-Imagini-DeviceName")
if deviceHeader == "" {
errorJSON(w, "Missing 'X-Imagini-DeviceName' header.", http.StatusBadRequest)
return
}
// Derive Device Type
var deviceType string
userAgent := strings.ToLower(r.Header.Get("User-Agent"))
if strings.HasPrefix(userAgent, "ios-imagini"){
deviceType = "iOS"
} else if strings.HasPrefix(userAgent, "android-imagini"){
deviceType = "Android"
} else if strings.HasPrefix(userAgent, "chrome"){
deviceType = "Chrome"
} else if strings.HasPrefix(userAgent, "firefox"){
deviceType = "Firefox"
} else if strings.HasPrefix(userAgent, "msie"){
deviceType = "Internet Explorer"
} else if strings.HasPrefix(userAgent, "edge"){
deviceType = "Edge"
} else if strings.HasPrefix(userAgent, "safari"){
deviceType = "Safari"
}else {
deviceType = "Unknown"
}
// Do login
resp := api.Auth.AuthenticateUser(creds)
resp, user := api.Auth.AuthenticateUser(creds)
if !resp {
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
return
}
// Create tokens
accessToken := api.Auth.CreateJWTAccessToken()
refreshToken := api.Auth.CreateRefreshToken()
// Create New Device
device, err := api.DB.CreateDevice(models.Device{Name: deviceHeader, Type: deviceType})
// Create Tokens
accessToken, err := api.Auth.CreateJWTAccessToken(user, device)
refreshToken, err := api.Auth.CreateJWTRefreshToken(user, device)
// Set appropriate cookies
accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken}
refreshCookie := http.Cookie{Name: "RefreshToken", Value: refreshToken}
accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken, HttpOnly: true}
refreshCookie := http.Cookie{Name: "RefreshToken", Value: refreshToken, HttpOnly: true}
http.SetCookie(w, &accessCookie)
http.SetCookie(w, &refreshCookie)
@@ -78,15 +112,50 @@ func (api *API) logoutHandler(w http.ResponseWriter, r *http.Request) {
}
func (api *API) refreshLoginHandler(w http.ResponseWriter, r *http.Request) {
ok := api.Auth.ValidateRefreshToken()
refreshCookie, err := r.Cookie("RefreshToken")
if err != nil {
log.Warn("[middleware] Cookie not found")
w.WriteHeader(http.StatusUnauthorized)
return
}
// Validate Refresh Token
refreshToken, ok := api.Auth.ValidateJWTRefreshToken(refreshCookie.Value)
if !ok {
// TODO: Clear Access & Refresh Cookies
http.SetCookie(w, &http.Cookie{Name: "AccessToken", Expires: time.Unix(0, 0)})
http.SetCookie(w, &http.Cookie{Name: "RefreshToken", Expires: time.Unix(0, 0)})
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
return
}
// Acquire User & Device (Trusted)
did, ok := refreshToken.Get("did")
if !ok {
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
return
}
uid, ok := refreshToken.Get(jwt.SubjectKey)
if !ok {
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
return
}
deviceID, err := uuid.Parse(fmt.Sprintf("%v", did))
if err != nil {
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
return
}
userID, err := uuid.Parse(fmt.Sprintf("%v", uid))
if err != nil {
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
return
}
// Device Skeleton
user := models.User{Base: models.Base{UUID: userID}}
device := models.Device{Base: models.Base{UUID: deviceID}}
// Update token
accessToken := api.Auth.CreateJWTAccessToken()
accessToken, err := api.Auth.CreateJWTAccessToken(user, device)
accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken}
http.SetCookie(w, &accessCookie)

View File

@@ -22,27 +22,22 @@ func multipleMiddleware(h http.HandlerFunc, m ...Middleware) http.HandlerFunc {
func (api *API) authMiddleware(next http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("Token")
// Acquire Token
accessCookie, err := r.Cookie("AccessToken")
if err != nil {
log.Warn("[middleware] Cookie not found")
log.Warn("[middleware] AccessToken not found")
w.WriteHeader(http.StatusUnauthorized)
return
}
// Validate cookie.Value JWT with
api.Auth.ValidateJWTToken(cookie.Value)
// Validate JWT Tokens
_, accessOK := api.Auth.ValidateJWTAccessToken(accessCookie.Value)
log.Info("[middleware] Cookie Name: ", cookie.Name)
log.Info("[middleware] Cookie Value: ", cookie.Value)
next.ServeHTTP(w, r)
// if true {
// next.ServeHTTP(w, r)
// } else {
// w.WriteHeader(http.StatusUnauthorized)
// }
if accessOK {
next.ServeHTTP(w, r)
} else {
w.WriteHeader(http.StatusUnauthorized)
}
})
}

View File

@@ -27,13 +27,4 @@ func (api *API) meHandler(w http.ResponseWriter, r *http.Request) {
errorJSON(w, "Method is not supported.", http.StatusMethodNotAllowed)
return
}
// Get Authenticated User & Return Object
authCookie, err := r.Cookie("Token")
if err != nil {
log.Error("[api] ", err)
return
}
log.Info("[api] Auth Cookie: ", authCookie)
}