2023-01-03 07:40:38 +00:00
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Copyright © 2022 Roberto Hidalgo <nidito@un.rob.mx>
|
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
"git.rob.mx/nidito/puerta/internal/constants"
|
|
|
|
"git.rob.mx/nidito/puerta/internal/errors"
|
|
|
|
"git.rob.mx/nidito/puerta/internal/user"
|
2023-01-03 07:40:38 +00:00
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/upper/db/v4"
|
|
|
|
)
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
func withUser(handler httprouter.Handle) httprouter.Handle {
|
2023-01-03 07:40:38 +00:00
|
|
|
return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
2023-01-04 04:21:49 +00:00
|
|
|
u := user.FromContext(req)
|
|
|
|
if u != nil {
|
|
|
|
handler(w, req, ps)
|
|
|
|
return
|
|
|
|
}
|
2023-01-03 07:40:38 +00:00
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
req = func() *http.Request {
|
|
|
|
cookie, err := req.Cookie(string(constants.ContextCookieName))
|
2023-01-03 07:40:38 +00:00
|
|
|
if err != nil {
|
|
|
|
logrus.Debugf("no cookie for user found in jar <%s>", req.Cookies())
|
|
|
|
return req
|
|
|
|
}
|
|
|
|
|
|
|
|
session := &SessionUser{}
|
2023-01-04 04:21:49 +00:00
|
|
|
q := _db.SQL().
|
|
|
|
Select("s.token as token, s.expires as expires", "u.*").
|
2023-01-03 07:40:38 +00:00
|
|
|
From("session as s").
|
|
|
|
Join("user as u").On("s.user = u.id").
|
|
|
|
Where(db.Cond{"s.token": cookie.Value})
|
|
|
|
|
|
|
|
if err := q.One(&session); err != nil {
|
|
|
|
logrus.Debugf("no cookie found in DB for jar <%s>: %s", req.Cookies(), err)
|
2023-01-04 04:21:49 +00:00
|
|
|
w.Header().Add("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Secure; Path=/;", constants.ContextCookieName, "", -1))
|
2023-01-03 07:40:38 +00:00
|
|
|
return req
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
if session.Expired() || session.User.Expired() {
|
2023-01-03 07:40:38 +00:00
|
|
|
logrus.Debugf("expired cookie found in DB for jar <%s>", req.Cookies())
|
2023-01-04 04:21:49 +00:00
|
|
|
w.Header().Add("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Secure; Path=/;", constants.ContextCookieName, "", -1))
|
|
|
|
err := _db.Collection("session").Find(db.Cond{"token": cookie.Value}).Delete()
|
2023-01-03 07:40:38 +00:00
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("could not purge expired session from DB: %s", err)
|
|
|
|
}
|
|
|
|
return req
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
return req.WithContext(context.WithValue(req.Context(), constants.ContextUser, &session.User))
|
2023-01-03 07:40:38 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
handler(w, req, ps)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
func RequireAuth(handler httprouter.Handle) httprouter.Handle {
|
|
|
|
return withUser(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
|
|
if req.Context().Value(constants.ContextUser) == nil {
|
|
|
|
requestAuth(w, http.StatusUnauthorized)
|
2023-01-03 07:40:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
handler(w, req, ps)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
func RequireAuthOrRedirect(handler httprouter.Handle, target string) httprouter.Handle {
|
2023-04-16 22:00:21 +00:00
|
|
|
return withUser(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
|
|
if req.Context().Value(constants.ContextUser) == nil {
|
|
|
|
http.Redirect(w, req, target, http.StatusTemporaryRedirect)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
handler(w, req, ps)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func RequireAdminOrRedirect(handler httprouter.Handle, target string) httprouter.Handle {
|
2023-01-04 04:21:49 +00:00
|
|
|
return withUser(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
|
|
if req.Context().Value(constants.ContextUser) == nil {
|
2023-01-03 07:40:38 +00:00
|
|
|
http.Redirect(w, req, target, http.StatusTemporaryRedirect)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
handler(w, req, ps)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
func RegisterSecondFactor() httprouter.Handle {
|
|
|
|
return RequireAuth(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
|
|
u := user.FromContext(req)
|
|
|
|
if !u.Require2FA {
|
2023-01-03 07:40:38 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
err := webAuthnFinishRegistration(req)
|
2023-01-03 07:40:38 +00:00
|
|
|
if err != nil {
|
|
|
|
logrus.Errorf("Failed during webauthn flow: %s", err.Error())
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
func Enforce2FA(handler httprouter.Handle) httprouter.Handle {
|
|
|
|
return RequireAuth(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
|
|
u := user.FromContext(req)
|
|
|
|
if !u.Require2FA {
|
2023-01-03 07:40:38 +00:00
|
|
|
handler(w, req, ps)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Debug("Enforcing 2fa for request")
|
2023-01-04 04:21:49 +00:00
|
|
|
if err := u.FetchCredentials(_db); err != nil {
|
2023-01-03 07:40:38 +00:00
|
|
|
logrus.Errorf("Failed fetching credentials: %s", err.Error())
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
var flow func(*http.Request) error
|
|
|
|
if !u.HasCredentials() {
|
|
|
|
flow = webAuthnBeginRegistration
|
2023-01-03 07:40:38 +00:00
|
|
|
} else {
|
2023-01-04 04:21:49 +00:00
|
|
|
flow = webAuthnLogin
|
2023-01-03 07:40:38 +00:00
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
if err := flow(req); err != nil {
|
|
|
|
if wafc, ok := err.(errors.WebAuthFlowChallenge); ok {
|
2023-01-03 07:40:38 +00:00
|
|
|
w.WriteHeader(200)
|
|
|
|
w.Header().Add("content-type", "application/json")
|
|
|
|
w.Header().Add("webauthn", wafc.Header())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Errorf("Failed during webauthn flow: %s", err.Error())
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
defer func() {
|
|
|
|
if err := _sess.RenewToken(req.Context()); err != nil {
|
|
|
|
logrus.Errorf("could not renew token")
|
|
|
|
}
|
|
|
|
}()
|
2023-01-03 07:40:38 +00:00
|
|
|
handler(w, req, ps)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-04 04:21:49 +00:00
|
|
|
func RequireAdmin(handler httprouter.Handle) httprouter.Handle {
|
|
|
|
return RequireAuth(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
|
|
user := req.Context().Value(constants.ContextUser).(*user.User)
|
2023-01-03 07:40:38 +00:00
|
|
|
if !user.IsAdmin {
|
2023-01-04 04:21:49 +00:00
|
|
|
requestAuth(w, http.StatusUnauthorized)
|
2023-01-03 07:40:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
handler(w, req, ps)
|
|
|
|
})
|
|
|
|
}
|