summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/app/handler/authentication')
-rw-r--r--pkg/app/handler/authentication/accessDenied.go10
-rw-r--r--pkg/app/handler/authentication/auth_session/authsession.go177
-rw-r--r--pkg/app/handler/authentication/login.go100
-rw-r--r--pkg/app/handler/authentication/logout.go29
-rw-r--r--pkg/app/handler/authentication/templates/admin.go36
-rw-r--r--pkg/app/handler/authentication/templates/login.go32
-rw-r--r--pkg/app/handler/authentication/templates/totp.go23
-rw-r--r--pkg/app/handler/authentication/templates/webauthn.go23
-rw-r--r--pkg/app/handler/authentication/totp/totp.go60
-rw-r--r--pkg/app/handler/authentication/utils/utils.go81
-rw-r--r--pkg/app/handler/authentication/webauthn/login.go118
-rw-r--r--pkg/app/handler/authentication/webauthn/register.go111
12 files changed, 800 insertions, 0 deletions
diff --git a/pkg/app/handler/authentication/accessDenied.go b/pkg/app/handler/authentication/accessDenied.go
new file mode 100644
index 0000000..de06ab2
--- /dev/null
+++ b/pkg/app/handler/authentication/accessDenied.go
@@ -0,0 +1,10 @@
+package authentication
+
+import (
+ "glsamaker/pkg/app/handler/authentication/templates"
+ "net/http"
+)
+
+func AccessDenied(w http.ResponseWriter, r *http.Request) {
+ templates.RenderAccessDeniedTemplate(w, r)
+}
diff --git a/pkg/app/handler/authentication/auth_session/authsession.go b/pkg/app/handler/authentication/auth_session/authsession.go
new file mode 100644
index 0000000..c86ca99
--- /dev/null
+++ b/pkg/app/handler/authentication/auth_session/authsession.go
@@ -0,0 +1,177 @@
+package auth_session
+
+import (
+ "glsamaker/pkg/database/connection"
+ "glsamaker/pkg/logger"
+ "glsamaker/pkg/models"
+ "glsamaker/pkg/models/users"
+ "github.com/google/uuid"
+ "net/http"
+ "strings"
+ "time"
+)
+
+func Create(w http.ResponseWriter, r *http.Request, user *users.User, bindSessionToIP bool, secondFactorMissing bool) {
+ sessionID := createSessionID()
+ sessionIP := "*"
+ expires := time.Now().AddDate(0, 1, 0)
+
+ if bindSessionToIP {
+ sessionIP = getIP(r)
+ }
+
+ if secondFactorMissing {
+ expires = time.Now().Add(10 * time.Minute)
+ }
+
+ session := &models.Session{
+ Id: sessionID,
+ UserId: user.Id,
+ IP: sessionIP,
+ SecondFactorMissing: secondFactorMissing,
+ Expires: expires,
+ }
+
+ _, err := connection.DB.Model(session).OnConflict("(id) DO UPDATE").Insert()
+ if err != nil {
+ logger.Error.Println("Err during creating session")
+ logger.Error.Println(err)
+ }
+
+ createSessionCookie(w, sessionID)
+}
+
+func createSessionID() string {
+ id, _ := uuid.NewUUID()
+ return id.String()
+}
+
+func createSessionCookie(w http.ResponseWriter, sessionID string) {
+
+ expires := time.Now().AddDate(0, 1, 0)
+
+ ck := http.Cookie{
+ Name: "session",
+ Domain: "localhost",
+ Path: "/",
+ Expires: expires,
+ }
+
+ ck.Value = sessionID
+
+ http.SetCookie(w, &ck)
+
+}
+
+func GetUserId(sessionId, userIP string) int64 {
+ session := &models.Session{Id: sessionId}
+ err := connection.DB.Model(session).Relation("User").WherePK().Select()
+
+ if err != nil || session.User.Disabled {
+ return -1
+ }
+
+ if session != nil &&
+ session.Expires.After(time.Now()) &&
+ isValidIP(session.IP, userIP) {
+ return session.UserId
+ } else {
+ return -1
+ }
+}
+
+func Only2FAMissing(sessionId, userIP string) bool {
+ session := &models.Session{Id: sessionId}
+ err := connection.DB.Model(session).Relation("User").WherePK().Select()
+
+ if err != nil {
+ return false
+ }
+
+ invalidateExpiredSession(session)
+
+ return session != nil &&
+ session.Expires.After(time.Now()) &&
+ !session.User.Disabled &&
+ session.SecondFactorMissing &&
+ isValidIP(session.IP, userIP)
+}
+
+func IsLoggedIn(sessionId, userIP string) bool {
+
+ session := &models.Session{Id: sessionId}
+ err := connection.DB.Model(session).Relation("User").WherePK().Select()
+
+ if err != nil {
+ return false
+ }
+
+ invalidateExpiredSession(session)
+
+ return session != nil &&
+ !session.SecondFactorMissing &&
+ !session.User.Disabled &&
+ session.Expires.After(time.Now()) &&
+ isValidIP(session.IP, userIP)
+}
+
+func IsLoggedInAndNeedsNewPassword(sessionId, userIP string) bool {
+
+ session := &models.Session{Id: sessionId}
+ err := connection.DB.Model(session).Relation("User").WherePK().Select()
+
+ if err != nil {
+ return false
+ }
+
+ invalidateExpiredSession(session)
+
+ return session != nil &&
+ !session.SecondFactorMissing &&
+ !session.User.Disabled &&
+ session.User.ForcePasswordRotation &&
+ session.Expires.After(time.Now()) &&
+ isValidIP(session.IP, userIP)
+}
+
+func IsLoggedInAndNeeds2FA(sessionId, userIP string) bool {
+
+ session := &models.Session{Id: sessionId}
+ err := connection.DB.Model(session).Relation("User").WherePK().Select()
+
+ if err != nil {
+ return false
+ }
+
+ invalidateExpiredSession(session)
+
+ return session != nil &&
+ !session.SecondFactorMissing &&
+ !session.User.Disabled &&
+ session.User.Force2FA &&
+ !session.User.IsUsing2FA() &&
+ session.Expires.After(time.Now()) &&
+ isValidIP(session.IP, userIP)
+}
+
+func invalidateExpiredSession(session *models.Session) {
+ if session.Expires.Before(time.Now()) {
+ _, err := connection.DB.Model(session).WherePK().Delete()
+ if err != nil {
+ logger.Error.Println("Error deleting expired session.")
+ logger.Error.Println(err)
+ }
+ }
+}
+
+func isValidIP(sessionIP, userIP string) bool {
+ return sessionIP == "*" || userIP == sessionIP
+}
+
+func getIP(r *http.Request) string {
+ forwarded := r.Header.Get("X-FORWARDED-FOR")
+ if forwarded != "" {
+ return strings.Split(forwarded, ":")[0]
+ }
+ return strings.Split(r.RemoteAddr, ":")[0]
+}
diff --git a/pkg/app/handler/authentication/login.go b/pkg/app/handler/authentication/login.go
new file mode 100644
index 0000000..7cd5c87
--- /dev/null
+++ b/pkg/app/handler/authentication/login.go
@@ -0,0 +1,100 @@
+package authentication
+
+import (
+ "glsamaker/pkg/app/handler/authentication/auth_session"
+ "glsamaker/pkg/app/handler/authentication/templates"
+ "glsamaker/pkg/app/handler/authentication/utils"
+ "glsamaker/pkg/database/connection"
+ "glsamaker/pkg/models/users"
+ "golang.org/x/crypto/argon2"
+ "net/http"
+)
+
+func Login(w http.ResponseWriter, r *http.Request) {
+
+ // in case '/login' is request but the user is
+ // already authenticated we will redirect to '/'
+ if utils.IsAuthenticated(w, r) {
+ http.Redirect(w, r, "/", 301)
+ }
+
+ username, pass, cameFrom, bindLoginToIP, _ := getParams(r)
+
+ if IsValidPassword(username, pass) {
+ user, _ := getLoginUser(username)
+ auth_session.Create(w, r, user, bindLoginToIP, user.IsUsing2FA())
+ if user.IsUsing2FA() {
+ http.Redirect(w, r, "/login/2fa", 301)
+ } else {
+ http.Redirect(w, r, cameFrom, 301)
+ }
+ } else {
+ templates.RenderLoginTemplate(w, r)
+ }
+
+}
+
+func SecondFactorLogin(w http.ResponseWriter, r *http.Request) {
+ user := utils.GetAuthenticatedUser(r)
+
+ if user == nil || !user.IsUsing2FA() {
+ // this should not occur
+ http.NotFound(w, r)
+ return
+ }
+
+ if user.IsUsingTOTP {
+ templates.RenderTOTPTemplate(w, r)
+ } else if user.IsUsingWebAuthn {
+ templates.RenderWebAuthnTemplate(w, r)
+ } else {
+ // this should not occur
+ http.NotFound(w, r)
+ }
+}
+
+// utility functions
+
+func getLoginUser(username string) (*users.User, bool) {
+ var potenialUsers []*users.User
+ err := connection.DB.Model(&potenialUsers).Where("nick = ?", username).Select()
+ isValidUser := err == nil
+
+ if len(potenialUsers) < 1 {
+ return &users.User{}, false
+ }
+
+ return potenialUsers[0], isValidUser
+}
+
+func getParams(r *http.Request) (string, string, string, bool, error) {
+ err := r.ParseForm()
+ if err != nil {
+ return "", "", "", false, err
+ }
+ username := r.Form.Get("username")
+ password := r.Form.Get("password")
+ cameFrom := r.Form.Get("cameFrom")
+ restrictLogin := r.Form.Get("restrictlogin")
+ return username, password, cameFrom, restrictLogin == "on", err
+}
+
+func IsValidPassword(username string, password string) bool {
+ user, isValidUser := getLoginUser(username)
+ if !isValidUser {
+ return false
+ }
+
+ hashedPassword := argon2.IDKey(
+ []byte(password),
+ user.Password.Salt,
+ user.Password.Time,
+ user.Password.Memory,
+ user.Password.Threads,
+ user.Password.KeyLen)
+
+ if user != nil && !user.Disabled && string(user.Password.Hash) == string(hashedPassword) {
+ return true
+ }
+ return false
+}
diff --git a/pkg/app/handler/authentication/logout.go b/pkg/app/handler/authentication/logout.go
new file mode 100644
index 0000000..87d17f4
--- /dev/null
+++ b/pkg/app/handler/authentication/logout.go
@@ -0,0 +1,29 @@
+package authentication
+
+import (
+ "glsamaker/pkg/database/connection"
+ "glsamaker/pkg/logger"
+ "glsamaker/pkg/models"
+ "net/http"
+)
+
+func Logout(w http.ResponseWriter, r *http.Request) {
+
+ sessionID, err := r.Cookie("session")
+
+ if err != nil || sessionID == nil {
+ // TODO Error
+ }
+
+ session := &models.Session{Id: sessionID.Value}
+ _, err = connection.DB.Model(session).WherePK().Delete()
+
+ if err != nil {
+ logger.Info.Println("Error deleting session")
+ logger.Error.Println("Error deleting session")
+ logger.Error.Println(err)
+ }
+
+ http.Redirect(w, r, "/", 301)
+
+}
diff --git a/pkg/app/handler/authentication/templates/admin.go b/pkg/app/handler/authentication/templates/admin.go
new file mode 100644
index 0000000..12d039e
--- /dev/null
+++ b/pkg/app/handler/authentication/templates/admin.go
@@ -0,0 +1,36 @@
+package templates
+
+import (
+ "glsamaker/pkg/app/handler/authentication/utils"
+ "glsamaker/pkg/models"
+ "glsamaker/pkg/models/users"
+ "html/template"
+ "net/http"
+)
+
+// renderIndexTemplate renders all templates used for the login page
+func RenderAccessDeniedTemplate(w http.ResponseWriter, r *http.Request) {
+
+ user := utils.GetAuthenticatedUser(r)
+
+ templates := template.Must(
+ template.Must(
+ template.New("Show").
+ ParseGlob("web/templates/layout/*.tmpl")).
+ ParseGlob("web/templates/authentication/accessDenied.tmpl"))
+
+ templates.ExecuteTemplate(w, "accessDenied.tmpl", createAccessDeniedData(user))
+}
+
+// createPageData creates the data used in the template of the landing page
+func createAccessDeniedData(user *users.User) interface{} {
+ return struct {
+ Page string
+ Application *models.GlobalSettings
+ User *users.User
+ }{
+ Page: "",
+ Application: models.GetDefaultGlobalSettings(),
+ User: user,
+ }
+}
diff --git a/pkg/app/handler/authentication/templates/login.go b/pkg/app/handler/authentication/templates/login.go
new file mode 100644
index 0000000..2e3b241
--- /dev/null
+++ b/pkg/app/handler/authentication/templates/login.go
@@ -0,0 +1,32 @@
+package templates
+
+import (
+ "html/template"
+ "net/http"
+)
+
+// renderIndexTemplate renders all templates used for the login page
+func RenderLoginTemplate(w http.ResponseWriter, r *http.Request) {
+
+ data := struct {
+ CameFrom string
+ }{
+ CameFrom: getPath(r),
+ }
+
+ templates := template.Must(
+ template.Must(
+ template.New("Show").
+ ParseGlob("web/templates/layout/*.tmpl")).
+ ParseGlob("web/templates/authentication/login.tmpl"))
+
+ templates.ExecuteTemplate(w, "login.tmpl", data)
+}
+
+func getPath(r *http.Request) string {
+ if r.URL.RawQuery == "" {
+ return r.URL.Path
+ } else {
+ return r.URL.Path + "?" + r.URL.RawQuery
+ }
+}
diff --git a/pkg/app/handler/authentication/templates/totp.go b/pkg/app/handler/authentication/templates/totp.go
new file mode 100644
index 0000000..acb34e5
--- /dev/null
+++ b/pkg/app/handler/authentication/templates/totp.go
@@ -0,0 +1,23 @@
+package templates
+
+import (
+ "html/template"
+ "net/http"
+)
+
+func RenderTOTPTemplate(w http.ResponseWriter, r *http.Request) {
+
+ data := struct {
+ CameFrom string
+ }{
+ CameFrom: getPath(r),
+ }
+
+ templates := template.Must(
+ template.Must(
+ template.New("Show").
+ ParseGlob("web/templates/layout/*.tmpl")).
+ ParseGlob("web/templates/authentication/totp.tmpl"))
+
+ templates.ExecuteTemplate(w, "totp.tmpl", data)
+}
diff --git a/pkg/app/handler/authentication/templates/webauthn.go b/pkg/app/handler/authentication/templates/webauthn.go
new file mode 100644
index 0000000..148f475
--- /dev/null
+++ b/pkg/app/handler/authentication/templates/webauthn.go
@@ -0,0 +1,23 @@
+package templates
+
+import (
+ "html/template"
+ "net/http"
+)
+
+func RenderWebAuthnTemplate(w http.ResponseWriter, r *http.Request) {
+
+ data := struct {
+ CameFrom string
+ }{
+ CameFrom: getPath(r),
+ }
+
+ templates := template.Must(
+ template.Must(
+ template.New("Show").
+ ParseGlob("web/templates/layout/*.tmpl")).
+ ParseGlob("web/templates/authentication/webauthn.tmpl"))
+
+ templates.ExecuteTemplate(w, "webauthn.tmpl", data)
+}
diff --git a/pkg/app/handler/authentication/totp/totp.go b/pkg/app/handler/authentication/totp/totp.go
new file mode 100644
index 0000000..00e6b83
--- /dev/null
+++ b/pkg/app/handler/authentication/totp/totp.go
@@ -0,0 +1,60 @@
+package totp
+
+import (
+ "glsamaker/pkg/app/handler/authentication/auth_session"
+ "glsamaker/pkg/app/handler/authentication/utils"
+ "glsamaker/pkg/models/users"
+ "bytes"
+ "encoding/base64"
+ "github.com/pquerna/otp/totp"
+ "image/png"
+ "net/http"
+ "time"
+)
+
+func Login(w http.ResponseWriter, r *http.Request) {
+
+ user := utils.GetAuthenticatedUser(r)
+ token, err := getParam(r)
+
+ if user == nil || err != nil || !IsValidTOTPToken(user, token) {
+ http.Redirect(w, r, "/login/2fa", 301)
+ } else {
+ auth_session.Create(w, r, user, true, false)
+ http.Redirect(w, r, "/", 301)
+ }
+
+}
+
+func IsValidTOTPToken(user *users.User, token string) bool {
+ return totp.Validate(token, user.TOTPSecret)
+}
+
+func GetToken(user *users.User) string {
+ token, _ := totp.GenerateCode(user.TOTPSecret, time.Now())
+ return token
+}
+
+func Generate(email string) (string, string) {
+
+ key, _ := totp.Generate(totp.GenerateOpts{
+ Issuer: "glsamakertest.gentoo.org",
+ AccountName: email,
+ })
+
+ var buf bytes.Buffer
+ img, _ := key.Image(250, 250)
+
+ png.Encode(&buf, img)
+
+ return key.Secret(), base64.StdEncoding.EncodeToString(buf.Bytes())
+}
+
+func getParam(r *http.Request) (string, error) {
+ err := r.ParseForm()
+ if err != nil {
+ return "", err
+ }
+ token := r.Form.Get("token")
+ return token, err
+}
diff --git a/pkg/app/handler/authentication/utils/utils.go b/pkg/app/handler/authentication/utils/utils.go
new file mode 100644
index 0000000..d06a2d7
--- /dev/null
+++ b/pkg/app/handler/authentication/utils/utils.go
@@ -0,0 +1,81 @@
+package utils
+
+import (
+ "glsamaker/pkg/app/handler/authentication/auth_session"
+ "glsamaker/pkg/database/connection"
+ "glsamaker/pkg/models/users"
+ "net/http"
+ "strings"
+)
+
+// utility methods to check whether a user is authenticated
+
+func Only2FAMissing(w http.ResponseWriter, r *http.Request) bool {
+ sessionID, err := r.Cookie("session")
+ userIP := getIP(r)
+
+ return err == nil && sessionID != nil && auth_session.Only2FAMissing(sessionID.Value, userIP)
+}
+
+func IsAuthenticated(w http.ResponseWriter, r *http.Request) bool {
+ sessionID, err := r.Cookie("session")
+ userIP := getIP(r)
+
+ return err == nil && sessionID != nil && auth_session.IsLoggedIn(sessionID.Value, userIP)
+}
+
+func IsAuthenticatedAndNeedsNewPassword(w http.ResponseWriter, r *http.Request) bool {
+ sessionID, err := r.Cookie("session")
+ userIP := getIP(r)
+
+ return err == nil && sessionID != nil && auth_session.IsLoggedInAndNeedsNewPassword(sessionID.Value, userIP)
+}
+
+func IsAuthenticatedAndNeeds2FA(w http.ResponseWriter, r *http.Request) bool {
+ sessionID, err := r.Cookie("session")
+ userIP := getIP(r)
+
+ return err == nil && sessionID != nil && auth_session.IsLoggedInAndNeeds2FA(sessionID.Value, userIP)
+}
+
+func IsAuthenticatedAsAdmin(w http.ResponseWriter, r *http.Request) bool {
+ sessionID, err := r.Cookie("session")
+ userIP := getIP(r)
+
+ if err != nil || sessionID == nil || !auth_session.IsLoggedIn(sessionID.Value, userIP) {
+ return false
+ }
+
+ user := GetAuthenticatedUser(r)
+
+ return user != nil && user.Permissions.Admin.View
+
+}
+
+func GetAuthenticatedUser(r *http.Request) *users.User {
+ sessionID, err := r.Cookie("session")
+ userIP := getIP(r)
+
+ if err != nil || sessionID == nil || !(auth_session.IsLoggedIn(sessionID.Value, userIP) || auth_session.Only2FAMissing(sessionID.Value, userIP)) {
+ return nil
+ }
+
+ userId := auth_session.GetUserId(sessionID.Value, userIP)
+
+ user := &users.User{Id: userId}
+ err = connection.DB.Select(user)
+
+ if err != nil {
+ return nil
+ }
+
+ return user
+}
+
+func getIP(r *http.Request) string {
+ forwarded := r.Header.Get("X-FORWARDED-FOR")
+ if forwarded != "" {
+ return strings.Split(forwarded, ":")[0]
+ }
+ return strings.Split(r.RemoteAddr, ":")[0]
+}
diff --git a/pkg/app/handler/authentication/webauthn/login.go b/pkg/app/handler/authentication/webauthn/login.go
new file mode 100644
index 0000000..7bf9c1d
--- /dev/null
+++ b/pkg/app/handler/authentication/webauthn/login.go
@@ -0,0 +1,118 @@
+package webauthn
+
+import (
+ "glsamaker/pkg/app/handler/authentication/auth_session"
+ "glsamaker/pkg/app/handler/authentication/utils"
+ "encoding/json"
+ "fmt"
+ "github.com/duo-labs/webauthn.io/session"
+ webauthn_lib "github.com/duo-labs/webauthn/webauthn"
+ "log"
+ "net/http"
+)
+
+var (
+ WebAuthn *webauthn_lib.WebAuthn
+ SessionStore *session.Store
+)
+
+
+func BeginLogin(w http.ResponseWriter, r *http.Request) {
+
+ user := utils.GetAuthenticatedUser(r)
+
+ CreateWebAuthn()
+ CreateSessionStore()
+
+ // user doesn't exist
+ if user == nil {
+ log.Println("Error fetching the user.")
+ JsonResponse(w, "Error fetching the user.", http.StatusBadRequest)
+ return
+ }
+
+ // generate PublicKeyCredentialRequestOptions, session data
+ options, sessionData, err := WebAuthn.BeginLogin(user)
+ if err != nil {
+ log.Println(err)
+ JsonResponse(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // store session data as marshaled JSON
+ err = SessionStore.SaveWebauthnSession("authentication", sessionData, r, w)
+ if err != nil {
+ log.Println(err)
+ JsonResponse(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ JsonResponse(w, options, http.StatusOK)
+}
+
+func FinishLogin(w http.ResponseWriter, r *http.Request) {
+
+ user := utils.GetAuthenticatedUser(r)
+
+ // user doesn't exist
+ if user == nil {
+ log.Println("Error fetching the user.")
+ JsonResponse(w, "Error fetching the user.", http.StatusBadRequest)
+ return
+ }
+
+ // load the session data
+ sessionData, err := SessionStore.GetWebauthnSession("authentication", r)
+ if err != nil {
+ log.Println(err)
+ JsonResponse(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ // in an actual implementation, we should perform additional checks on
+ // the returned 'credential', i.e. check 'credential.Authenticator.CloneWarning'
+ // and then increment the credentials counter
+ _, err = WebAuthn.FinishLogin(user, sessionData, r)
+ if err != nil {
+ log.Println(err)
+ JsonResponse(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ // handle successful login
+ // TODO handle bindLoginToIP correctly
+ auth_session.Create(w, r, user, true, false)
+ JsonResponse(w, "Login Success", http.StatusOK)
+}
+
+// from: https://github.com/duo-labs/webauthn.io/blob/3f03b482d21476f6b9fb82b2bf1458ff61a61d41/server/response.go#L15
+func JsonResponse(w http.ResponseWriter, d interface{}, c int) {
+ dj, err := json.Marshal(d)
+ if err != nil {
+ http.Error(w, "Error creating JSON response", http.StatusInternalServerError)
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(c)
+ fmt.Fprintf(w, "%s", dj)
+}
+
+func CreateWebAuthn() {
+
+ if WebAuthn == nil {
+ authn, _ := webauthn_lib.New(&webauthn_lib.Config{
+ RPDisplayName: "Gentoo GLSAMaker", // Display Name for your site
+ RPID: "glsamakertest.gentoo.org", // Generally the domain name for your site
+ RPOrigin: "https://glsamakertest.gentoo.org", // The origin URL for WebAuthn requests
+ RPIcon: "https://assets.gentoo.org/tyrian/site-logo.png", // Optional icon URL for your site
+ })
+
+ WebAuthn = authn
+ }
+
+}
+
+func CreateSessionStore() {
+ if SessionStore == nil {
+ SessionStore, _ = session.NewStore()
+ }
+}
diff --git a/pkg/app/handler/authentication/webauthn/register.go b/pkg/app/handler/authentication/webauthn/register.go
new file mode 100644
index 0000000..4e299b3
--- /dev/null
+++ b/pkg/app/handler/authentication/webauthn/register.go
@@ -0,0 +1,111 @@
+package webauthn
+
+import (
+ "glsamaker/pkg/app/handler/authentication/utils"
+ "glsamaker/pkg/database/connection"
+ "glsamaker/pkg/logger"
+ "fmt"
+ "github.com/duo-labs/webauthn/protocol"
+ "log"
+ "net/http"
+)
+
+func BeginRegistration(w http.ResponseWriter, r *http.Request) {
+ user := utils.GetAuthenticatedUser(r)
+
+ CreateWebAuthn()
+ CreateSessionStore()
+
+ if user == nil {
+ JsonResponse(w, fmt.Errorf("must supply a valid username i.e. foo@bar.com"), http.StatusBadRequest)
+ return
+ }
+
+ registerOptions := func(credCreationOpts *protocol.PublicKeyCredentialCreationOptions) {
+ credCreationOpts.CredentialExcludeList = user.CredentialExcludeList()
+ }
+
+ // generate PublicKeyCredentialCreationOptions, session data
+ //var options *protocol.CredentialCreation
+ //var err error
+ options, sessionData, err := WebAuthn.BeginRegistration(
+ user,
+ registerOptions,
+ )
+
+ if err != nil {
+ log.Println("Error begin register")
+ log.Println(err)
+ JsonResponse(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // store session data as marshaled JSON
+ err = SessionStore.SaveWebauthnSession("registration", sessionData, r, w)
+ if err != nil {
+ log.Println("Error store session")
+ log.Println(err)
+ JsonResponse(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ JsonResponse(w, options, http.StatusOK)
+}
+
+func FinishRegistration(w http.ResponseWriter, r *http.Request) {
+
+ authname := getParams(r)
+ user := utils.GetAuthenticatedUser(r)
+
+ if user == nil {
+ JsonResponse(w, "Cannot find User", http.StatusBadRequest)
+ return
+ }
+
+ // load the session data
+ sessionData, err := SessionStore.GetWebauthnSession("registration", r)
+ if err != nil {
+ log.Println("Error loading session")
+ log.Println(err)
+ JsonResponse(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ credential, err := WebAuthn.FinishRegistration(user, sessionData, r)
+ if err != nil {
+ log.Println("Error finish session")
+ log.Println(err)
+ JsonResponse(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ user.AddCredential(*credential, authname)
+
+ _, err = connection.DB.Model(user).Column("webauthn_credentials").WherePK().Update()
+ _, err = connection.DB.Model(user).Column("webauthn_credential_names").WherePK().Update()
+ if err != nil {
+ logger.Error.Println("Error adding WebAuthn credentials.")
+ logger.Error.Println(err)
+ }
+
+ JsonResponse(w, "Registration Success", http.StatusOK)
+}
+
+func getParams(r *http.Request) string {
+
+ keys, ok := r.URL.Query()["name"]
+
+ if !ok || len(keys[0]) < 1 {
+ logger.Info.Println("Url Param 'name' is missing")
+ return "Unnamed Authenticator"
+ }
+
+ // we only want the single item.
+ key := keys[0]
+
+ if len(key) > 20 {
+ key = key[0:20]
+ }
+
+ return key
+}