Skip to content

Commit

Permalink
Add a POST request handler to the direct provider
Browse files Browse the repository at this point in the history
  • Loading branch information
o1egl committed Jun 22, 2020
1 parent 5d421f1 commit 6087a84
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 75 deletions.
22 changes: 21 additions & 1 deletion README.md
Expand Up @@ -148,7 +148,27 @@ In addition to oauth2 providers `auth.Service` allows to use direct user-defined

Such provider acts like any other, i.e. will be registered as `/auth/local/login`.

The API for this provider - `GET /auth/<name>/login?user=<user>&passwd=<password>&aud=<site_id>&session=[1|0]`
The API for this provider supports both GET and POST requests:

* GET request with user credentials provided as query params:
```
GET /auth/<name>/login?user=<user>&passwd=<password>&aud=<site_id>&session=[1|0]
```
* POST request could be encoded as application/x-www-form-urlencoded or application/json:
```
POST /auth/<name>/login?session=[1|0]
body: application/x-www-form-urlencoded
user=<user>&passwd=<password>&aud=<site_id>
```
```
POST /auth/<name>/login?session=[1|0]
body: application/json
{
"user": "name",
"passwd": "xyz",
"aud": "bar",
}
```

_note: password parameter doesn't have to be naked/real password and can be any kind of password hash prepared by caller._

Expand Down
82 changes: 73 additions & 9 deletions provider/direct.go
Expand Up @@ -2,17 +2,24 @@ package provider

import (
"crypto/sha1"
"errors"
"encoding/json"
"mime"
"net/http"
"time"

"github.com/dgrijalva/jwt-go"
"github.com/go-pkgz/rest"
"github.com/pkg/errors"

"github.com/go-pkgz/auth/logger"
"github.com/go-pkgz/auth/token"
)

const (
// MaxHTTPBodySize defines max http body size
MaxHTTPBodySize = 1024 * 1024
)

// DirectHandler implements non-oauth2 provider authorizing user in traditional way with storage
// with users and hashes
type DirectHandler struct {
Expand All @@ -37,21 +44,45 @@ func (f CredCheckerFunc) Check(user, password string) (ok bool, err error) {
return f(user, password)
}

// credentials holds user credentials
type credentials struct {
User string `json:"user"`
Password string `json:"passwd"`
Audience string `json:"aud"`
}

// Name of the handler
func (p DirectHandler) Name() string { return p.ProviderName }

// LoginHandler checks "user" and "passwd" against data store and makes jwt if all passed
// GET /something?user=name&password=xyz&sess=[0|1]
// LoginHandler checks "user" and "passwd" against data store and makes jwt if all passed.
//
// GET /something?user=name&passwd=xyz&aud=bar&sess=[0|1]
//
// POST /something?sess[0|1]
// Accepts application/x-www-form-urlencoded or application/json encoded requests.
//
// application/x-www-form-urlencoded body example:
// user=name&passwd=xyz&aud=bar
//
// application/json body example:
// {
// "user": "name",
// "passwd": "xyz",
// "aud": "bar",
// }
func (p DirectHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
user, password := r.URL.Query().Get("user"), r.URL.Query().Get("passwd")
aud := r.URL.Query().Get("aud")
creds, err := p.getCredentials(w, r)
if err != nil {
rest.SendErrorJSON(w, r, p.L, http.StatusBadRequest, err, "failed to parse credentials")
return
}
sessOnly := r.URL.Query().Get("sess") == "1"
if p.CredChecker == nil {
rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError,
errors.New("no credential checker"), "no credential checker")
return
}
ok, err := p.CredChecker.Check(user, password)
ok, err := p.CredChecker.Check(creds.User, creds.Password)
if err != nil {
rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to check user credentials")
return
Expand All @@ -61,8 +92,8 @@ func (p DirectHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
return
}
u := token.User{
Name: user,
ID: p.ProviderName + "_" + token.HashID(sha1.New(), user),
Name: creds.User,
ID: p.ProviderName + "_" + token.HashID(sha1.New(), creds.User),
}
u, err = setAvatar(p.AvatarSaver, u, &http.Client{Timeout: 5 * time.Second})
if err != nil {
Expand All @@ -81,7 +112,7 @@ func (p DirectHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
StandardClaims: jwt.StandardClaims{
Id: cid,
Issuer: p.Issuer,
Audience: aud,
Audience: creds.Audience,
},
SessionOnly: sessOnly,
}
Expand All @@ -93,6 +124,39 @@ func (p DirectHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
rest.RenderJSON(w, r, claims.User)
}

// getCredentials extracts user and password from request
func (p DirectHandler) getCredentials(w http.ResponseWriter, r *http.Request) (credentials, error) {
if r.Body != nil {
r.Body = http.MaxBytesReader(w, r.Body, MaxHTTPBodySize)
}
contentType := r.Header.Get("Content-Type")
if contentType != "" {
mt, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return credentials{}, err
}
contentType = mt
}

if contentType == "application/json" {
var creds credentials
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
return credentials{}, errors.Wrap(err, "failed to parse request body")
}
return creds, nil
}

if err := r.ParseForm(); err != nil {
return credentials{}, errors.Wrap(err, "failed to parse request")
}

return credentials{
User: r.Form.Get("user"),
Password: r.Form.Get("passwd"),
Audience: r.Form.Get("aud"),
}, nil
}

// AuthHandler doesn't do anything for direct login as it has no callbacks
func (p DirectHandler) AuthHandler(w http.ResponseWriter, r *http.Request) {}

Expand Down

0 comments on commit 6087a84

Please sign in to comment.