stillbox/pkg/gordio/auth/jwt.go

134 lines
3.3 KiB
Go
Raw Normal View History

2024-07-29 00:21:07 -04:00
package auth
import (
"context"
"net/http"
"time"
2024-07-29 00:47:58 -04:00
"golang.org/x/crypto/bcrypt"
2024-07-29 00:21:07 -04:00
"dynatron.me/x/stillbox/pkg/gordio/database"
"github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5"
"github.com/go-chi/render"
"github.com/rs/zerolog/log"
)
2024-07-29 00:58:32 -04:00
type jwtAuth interface {
// Authenticated returns whether the request is authenticated. It also returns the claims.
Authenticated(r *http.Request) (claims, bool)
// Login attempts to return a JWT for the provided user and password.
Login(ctx context.Context, username, password string) (token string, err error)
// InstallVerifyMiddleware installs the JWT verifier middleware to the provided chi Router.
2024-08-03 00:05:02 -04:00
VerifyMiddleware() func(http.Handler) http.Handler
2024-07-29 00:58:32 -04:00
// InstallAuthMiddleware installs the JWT authenticator middleware to the provided chi Router.
2024-08-03 00:05:02 -04:00
AuthMiddleware() func(http.Handler) http.Handler
2024-07-29 00:58:32 -04:00
// InstallRoutes installs the auth route to the provided chi Router.
2024-08-03 00:16:23 -04:00
PublicRoutes(chi.Router)
2024-07-29 00:58:32 -04:00
}
2024-07-29 00:21:07 -04:00
type claims map[string]interface{}
2024-07-29 00:58:32 -04:00
func (a *authenticator) Authenticated(r *http.Request) (claims, bool) {
2024-07-29 00:21:07 -04:00
// TODO: check IP against ACL, or conf.Public, and against map of routes
tok, cl, err := jwtauth.FromContext(r.Context())
return cl, err != nil && tok != nil
}
2024-08-03 00:05:02 -04:00
func (a *authenticator) VerifyMiddleware() func(http.Handler) http.Handler {
return jwtauth.Verifier(a.jwt)
2024-07-29 00:21:07 -04:00
}
2024-08-03 00:05:02 -04:00
func (a *authenticator) AuthMiddleware() func(http.Handler) http.Handler {
return jwtauth.Authenticator(a.jwt)
2024-07-29 00:21:07 -04:00
}
2024-07-29 00:58:32 -04:00
func (a *authenticator) Login(ctx context.Context, username, password string) (token string, err error) {
2024-07-29 00:21:07 -04:00
q := database.New(database.FromCtx(ctx))
users, err := q.GetUsers(ctx)
if err != nil {
log.Error().Err(err).Msg("getUsers failed")
return "", ErrLoginFailed
}
var found *database.User
for _, u := range users {
if u.Username == username {
found = &u
}
}
if found == nil {
_ = bcrypt.CompareHashAndPassword([]byte("lol@timing"), []byte(password))
return "", ErrLoginFailed
} else {
err = bcrypt.CompareHashAndPassword([]byte(found.Password), []byte(password))
if err != nil {
return "", ErrLoginFailed
}
}
2024-07-29 00:47:58 -04:00
return a.newToken(found.ID), nil
2024-07-29 00:21:07 -04:00
}
2024-07-29 00:58:32 -04:00
func (a *authenticator) newToken(uid int32) string {
2024-07-29 00:21:07 -04:00
claims := claims{
"user_id": uid,
}
jwtauth.SetExpiryIn(claims, time.Hour*24*30) // one month
_, tokenString, err := a.jwt.Encode(claims)
if err != nil {
panic(err)
}
return tokenString
}
2024-08-03 00:16:23 -04:00
func (a *authenticator) PublicRoutes(r chi.Router) {
2024-08-04 00:55:28 -04:00
r.Post("/login", a.routeAuth)
2024-07-29 00:21:07 -04:00
}
2024-08-04 08:55:12 -04:00
func (a *authenticator) allowInsecureCookie(r *http.Request) bool {
v, has := a.cfg.AllowInsecure[r.Host]
2024-08-04 10:56:46 -04:00
return has && v
2024-08-04 08:55:12 -04:00
}
2024-07-29 00:58:32 -04:00
func (a *authenticator) routeAuth(w http.ResponseWriter, r *http.Request) {
2024-07-29 00:21:07 -04:00
err := r.ParseForm()
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
username, password := r.PostFormValue("username"), r.PostFormValue("password")
if username == "" || password == "" {
2024-08-04 07:39:52 -04:00
http.Error(w, "blank credentials", http.StatusBadRequest)
2024-07-29 00:21:07 -04:00
return
}
tok, err := a.Login(r.Context(), username, password)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
http.SetCookie(w, &http.Cookie{
Name: "jwt",
Value: tok,
HttpOnly: true,
2024-08-04 10:56:46 -04:00
Secure: !a.allowInsecureCookie(r),
2024-08-04 09:07:31 -04:00
Domain: a.cfg.Domain,
2024-07-29 00:21:07 -04:00
})
jr := struct {
JWT string `json:"jwt"`
}{
JWT: tok,
}
render.JSON(w, r, &jr)
}