Add token unwrapping, admin concept and user creation endpoint
This commit is contained in:
parent
7ebb14e431
commit
3c7c32423e
12 changed files with 141 additions and 16 deletions
|
@ -1,9 +1,21 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.ctrlz.es/mgdelacroix/craban/model"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const userContextKey = "user"
|
||||
|
||||
func (a *API) Login(w http.ResponseWriter, r *http.Request) {
|
||||
body := ParseBody(r)
|
||||
username := body.String("username")
|
||||
|
@ -22,3 +34,61 @@ func (a *API) Login(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Write([]byte(token))
|
||||
}
|
||||
|
||||
func (a *API) getUserFromToken(tokenStr string) (*model.User, error) {
|
||||
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
|
||||
// Validate the alg is what you expect:
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
||||
return []byte(*a.App.Config.Secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
userIDClaim, ok := claims["userID"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("userID claim is not set")
|
||||
}
|
||||
userID, err := strconv.Atoi(userIDClaim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.App.GetUserByID(userID)
|
||||
} else {
|
||||
return nil, errors.New("Malformed claims")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *API) Secured(fn func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tokenStr := r.Header.Get("Authorization")
|
||||
if tokenStr == "" {
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(tokenStr, "Bearer ") {
|
||||
tokenStr = tokenStr[7:]
|
||||
}
|
||||
|
||||
user, err := a.getUserFromToken(tokenStr)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Str("token", tokenStr).Msg("cannot get user from token")
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// get user
|
||||
ctx := context.WithValue(r.Context(), userContextKey, user)
|
||||
fn(w, r.Clone(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
func UserFromRequest(r *http.Request) (*model.User, bool) {
|
||||
user, ok := r.Context().Value(userContextKey).(*model.User)
|
||||
return user, ok
|
||||
}
|
||||
|
|
|
@ -1,9 +1,46 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.ctrlz.es/mgdelacroix/craban/model"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (a *API) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
user, _ := UserFromRequest(r)
|
||||
if !user.Admin {
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
body := ParseBody(r)
|
||||
username := body.String("username")
|
||||
password := body.String("password")
|
||||
name := body.String("name")
|
||||
mail := body.String("mail")
|
||||
admin := body.Bool("admin")
|
||||
|
||||
newUser := &model.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Name: name,
|
||||
Mail: mail,
|
||||
}
|
||||
|
||||
if err := newUser.IsValid(); err != nil {
|
||||
http.Error(w, fmt.Sprintf("%s: %s", http.StatusText(http.StatusBadRequest), err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
createdUser, err := a.App.CreateUser(username, password, name, mail, admin)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("user couldn't be created")
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
JSON(w, createdUser, 201)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,13 @@ func (b *Body) Int(name string) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (b *Body) Bool(name string) bool {
|
||||
if res, ok := (*b)[name].(bool); ok {
|
||||
return res
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func JSON(w http.ResponseWriter, data interface{}, statusCode int) {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue