Implement bearer tokens
This commit is contained in:
parent
155750b746
commit
1136738111
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
@ -23,9 +24,13 @@ func initialCaretaker(c *fasthttp.RequestCtx, f func(md common.MethodData) commo
|
||||||
|
|
||||||
qa := c.Request.URI().QueryArgs()
|
qa := c.Request.URI().QueryArgs()
|
||||||
var token string
|
var token string
|
||||||
|
var bearerToken bool
|
||||||
switch {
|
switch {
|
||||||
case len(c.Request.Header.Peek("X-Ripple-Token")) > 0:
|
case len(c.Request.Header.Peek("X-Ripple-Token")) > 0:
|
||||||
token = string(c.Request.Header.Peek("X-Ripple-Token"))
|
token = string(c.Request.Header.Peek("X-Ripple-Token"))
|
||||||
|
case strings.HasPrefix(string(c.Request.Header.Peek("Authorization")), "Bearer "):
|
||||||
|
token = strings.TrimPrefix(string(c.Request.Header.Peek("Authorization")), "Bearer ")
|
||||||
|
bearerToken = true
|
||||||
case len(qa.Peek("token")) > 0:
|
case len(qa.Peek("token")) > 0:
|
||||||
token = string(qa.Peek("token"))
|
token = string(qa.Peek("token"))
|
||||||
case len(qa.Peek("k")) > 0:
|
case len(qa.Peek("k")) > 0:
|
||||||
|
@ -41,7 +46,15 @@ func initialCaretaker(c *fasthttp.RequestCtx, f func(md common.MethodData) commo
|
||||||
R: red,
|
R: red,
|
||||||
}
|
}
|
||||||
if token != "" {
|
if token != "" {
|
||||||
tokenReal, exists := GetTokenFull(token, db)
|
var (
|
||||||
|
tokenReal common.Token
|
||||||
|
exists bool
|
||||||
|
)
|
||||||
|
if bearerToken {
|
||||||
|
tokenReal, exists = BearerToken(token, db)
|
||||||
|
} else {
|
||||||
|
tokenReal, exists = GetTokenFull(token, db)
|
||||||
|
}
|
||||||
if exists {
|
if exists {
|
||||||
md.User = tokenReal
|
md.User = tokenReal
|
||||||
doggoTags = append(doggoTags, "authorised")
|
doggoTags = append(doggoTags, "authorised")
|
||||||
|
|
|
@ -79,8 +79,10 @@ func Start(conf common.Conf, dbO *sqlx.DB) *fhr.Router {
|
||||||
|
|
||||||
// v1 API
|
// v1 API
|
||||||
{
|
{
|
||||||
r.POSTMethod("/api/v1/tokens", v1.TokenNewPOST)
|
// These require an user to pass the password in cleartext and are
|
||||||
r.POSTMethod("/api/v1/tokens/new", v1.TokenNewPOST)
|
// as such really insecure.
|
||||||
|
//r.POSTMethod("/api/v1/tokens", v1.TokenNewPOST)
|
||||||
|
//r.POSTMethod("/api/v1/tokens/new", v1.TokenNewPOST)
|
||||||
r.POSTMethod("/api/v1/tokens/self/delete", v1.TokenSelfDeletePOST)
|
r.POSTMethod("/api/v1/tokens/self/delete", v1.TokenSelfDeletePOST)
|
||||||
|
|
||||||
// Auth-free API endpoints (public data)
|
// Auth-free API endpoints (public data)
|
||||||
|
|
|
@ -2,12 +2,14 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"zxq.co/ripple/rippleapi/common"
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"zxq.co/ripple/rippleapi/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetTokenFull retrieves an user ID and their token privileges knowing their API token.
|
// GetTokenFull retrieves an user ID and their token privileges knowing their API token.
|
||||||
|
@ -18,7 +20,7 @@ func GetTokenFull(token string, db *sqlx.DB) (common.Token, bool) {
|
||||||
userPrivsRaw uint64
|
userPrivsRaw uint64
|
||||||
priv8 bool
|
priv8 bool
|
||||||
)
|
)
|
||||||
err := db.QueryRow(`SELECT
|
err := db.QueryRow(`SELECT
|
||||||
t.id, t.user, t.privileges, t.private, u.privileges
|
t.id, t.user, t.privileges, t.private, u.privileges
|
||||||
FROM tokens t
|
FROM tokens t
|
||||||
LEFT JOIN users u ON u.id = t.user
|
LEFT JOIN users u ON u.id = t.user
|
||||||
|
@ -84,3 +86,41 @@ func tokenUpdater(db *sqlx.DB) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BearerToken parses a Token guiven in the Authorization header, with the
|
||||||
|
// Bearer prefix.
|
||||||
|
func BearerToken(token string, db *sqlx.DB) (common.Token, bool) {
|
||||||
|
var x struct {
|
||||||
|
Scope string
|
||||||
|
Extra int
|
||||||
|
}
|
||||||
|
db.Get(&x, "SELECT scope, extra FROM osin_access WHERE access_token = ? LIMIT 1", fmt.Sprintf("%x", sha256.Sum256([]byte(token))))
|
||||||
|
if x.Extra == 0 {
|
||||||
|
return common.Token{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var privs uint64
|
||||||
|
db.Get(&privs, "SELECT privileges FROM users WHERE id = ? LIMIT 1", x.Extra)
|
||||||
|
|
||||||
|
var t common.Token
|
||||||
|
t.ID = -1
|
||||||
|
t.UserID = x.Extra
|
||||||
|
t.Value = token
|
||||||
|
t.UserPrivileges = common.UserPrivileges(privs)
|
||||||
|
t.TokenPrivileges = oauthPrivileges(x.Scope).CanOnly(t.UserPrivileges)
|
||||||
|
|
||||||
|
return t, true
|
||||||
|
}
|
||||||
|
|
||||||
|
var privilegeMap = map[string]common.Privileges{
|
||||||
|
"read_confidential": common.PrivilegeReadConfidential,
|
||||||
|
"write": common.PrivilegeWrite,
|
||||||
|
}
|
||||||
|
|
||||||
|
func oauthPrivileges(scopes string) common.Privileges {
|
||||||
|
var p common.Privileges
|
||||||
|
for _, x := range strings.Split(scopes, " ") {
|
||||||
|
p |= privilegeMap[x]
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@ package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
@ -138,8 +142,14 @@ func TokenSelfDeletePOST(md common.MethodData) common.CodeMessager {
|
||||||
if md.ID() == 0 {
|
if md.ID() == 0 {
|
||||||
return common.SimpleResponse(400, "How should we delete your token if you haven't even given us one?!")
|
return common.SimpleResponse(400, "How should we delete your token if you haven't even given us one?!")
|
||||||
}
|
}
|
||||||
_, err := md.DB.Exec("DELETE FROM tokens WHERE token = ? LIMIT 1",
|
var err error
|
||||||
fmt.Sprintf("%x", md5.Sum([]byte(md.User.Value))))
|
if md.IsBearer() {
|
||||||
|
_, err = md.DB.Exec("DELETE FROM osin_access WHERE access_token = ? LIMIT 1",
|
||||||
|
fmt.Sprintf("%x", sha256.Sum256([]byte(md.User.Value))))
|
||||||
|
} else {
|
||||||
|
_, err = md.DB.Exec("DELETE FROM tokens WHERE token = ? LIMIT 1",
|
||||||
|
fmt.Sprintf("%x", md5.Sum([]byte(md.User.Value))))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
md.Err(err)
|
md.Err(err)
|
||||||
return Err500
|
return Err500
|
||||||
|
@ -160,7 +170,7 @@ type tokenResponse struct {
|
||||||
|
|
||||||
// TokenGET retrieves a list listing all the user's public tokens.
|
// TokenGET retrieves a list listing all the user's public tokens.
|
||||||
func TokenGET(md common.MethodData) common.CodeMessager {
|
func TokenGET(md common.MethodData) common.CodeMessager {
|
||||||
rows, err := md.DB.Query("SELECT id, privileges, description, last_updated FROM tokens WHERE user = ? AND private = '0'", md.ID())
|
rows, err := md.DB.Query("SELECT id, privileges, description, last_updated FROM tokens WHERE user = ? AND private = '0' "+common.Paginate(md.Query("p"), md.Query("l"), 50), md.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Err500
|
return Err500
|
||||||
}
|
}
|
||||||
|
@ -178,20 +188,66 @@ func TokenGET(md common.MethodData) common.CodeMessager {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type oauthClient struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
OwnerID int `json:"owner_id"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan scans the extra in the mysql table into Name, OwnerID and Avatar.
|
||||||
|
func (o *oauthClient) Scan(src interface{}) error {
|
||||||
|
var s []byte
|
||||||
|
switch x := src.(type) {
|
||||||
|
case string:
|
||||||
|
s = []byte(x)
|
||||||
|
case []byte:
|
||||||
|
s = x
|
||||||
|
default:
|
||||||
|
return errors.New("Can't scan non-string")
|
||||||
|
}
|
||||||
|
|
||||||
|
var vals [3]string
|
||||||
|
err := json.Unmarshal(s, &vals)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Name = vals[0]
|
||||||
|
o.OwnerID, _ = strconv.Atoi(vals[1])
|
||||||
|
o.Avatar = vals[2]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bearerToken struct {
|
||||||
|
Client oauthClient `json:"client"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Privileges common.Privileges `json:"privileges"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
type tokenSingleResponse struct {
|
type tokenSingleResponse struct {
|
||||||
common.ResponseBase
|
common.ResponseBase
|
||||||
token
|
token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type bearerTokenSingleResponse struct {
|
||||||
|
common.ResponseBase
|
||||||
|
bearerToken
|
||||||
|
}
|
||||||
|
|
||||||
// TokenSelfGET retrieves information about the token the user is connecting with.
|
// TokenSelfGET retrieves information about the token the user is connecting with.
|
||||||
func TokenSelfGET(md common.MethodData) common.CodeMessager {
|
func TokenSelfGET(md common.MethodData) common.CodeMessager {
|
||||||
if md.ID() == 0 {
|
if md.ID() == 0 {
|
||||||
return common.SimpleResponse(404, "How are we supposed to find the token you're using if you ain't even using one?!")
|
return common.SimpleResponse(404, "How are we supposed to find the token you're using if you ain't even using one?!")
|
||||||
}
|
}
|
||||||
|
if md.IsBearer() {
|
||||||
|
return getBearerToken(md)
|
||||||
|
}
|
||||||
var r tokenSingleResponse
|
var r tokenSingleResponse
|
||||||
// md.User.ID = token id, userid would have been md.User.UserID. what a clusterfuck
|
// md.User.ID = token id, userid would have been md.User.UserID. what a clusterfuck
|
||||||
err := md.DB.QueryRow("SELECT id, privileges, description, last_updated FROM tokens WHERE id = ? "+
|
err := md.DB.QueryRow("SELECT id, privileges, description, last_updated FROM tokens WHERE id = ?", md.User.ID).Scan(
|
||||||
common.Paginate(md.Query("p"), md.Query("l"), 50), md.User.ID).Scan(
|
|
||||||
&r.ID, &r.Privileges, &r.Description, &r.LastUpdated,
|
&r.ID, &r.Privileges, &r.Description, &r.LastUpdated,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -202,6 +258,25 @@ func TokenSelfGET(md common.MethodData) common.CodeMessager {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBearerToken(md common.MethodData) common.CodeMessager {
|
||||||
|
var b bearerTokenSingleResponse
|
||||||
|
err := md.DB.
|
||||||
|
QueryRow(`
|
||||||
|
SELECT t.scope, t.created_at, c.id, c.extra
|
||||||
|
FROM osin_access t INNER JOIN osin_client c
|
||||||
|
WHERE t.access_token = ?
|
||||||
|
`, fmt.Sprintf("%x", sha256.Sum256([]byte(md.User.Value)))).Scan(
|
||||||
|
&b.Scope, &b.Created, &b.Client.ID, &b.Client,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
md.Err(err)
|
||||||
|
return Err500
|
||||||
|
}
|
||||||
|
b.Code = 200
|
||||||
|
b.Privileges = md.User.TokenPrivileges
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// TokenFixPrivilegesPOST fixes the privileges on the token of the given user,
|
// TokenFixPrivilegesPOST fixes the privileges on the token of the given user,
|
||||||
// or of all the users if no user is given.
|
// or of all the users if no user is given.
|
||||||
func TokenFixPrivilegesPOST(md common.MethodData) common.CodeMessager {
|
func TokenFixPrivilegesPOST(md common.MethodData) common.CodeMessager {
|
||||||
|
@ -223,7 +298,7 @@ func fixPrivileges(user int, db *sqlx.DB) {
|
||||||
}
|
}
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
SELECT
|
SELECT
|
||||||
tokens.id, tokens.privileges, users.privileges
|
tokens.id, tokens.privileges, users.privileges
|
||||||
FROM tokens
|
FROM tokens
|
||||||
LEFT JOIN users ON users.id = tokens.user
|
LEFT JOIN users ON users.id = tokens.user
|
||||||
`+wc, params...)
|
`+wc, params...)
|
||||||
|
|
|
@ -143,3 +143,8 @@ func (md MethodData) HasQuery(q string) bool {
|
||||||
func (md MethodData) Unmarshal(into interface{}) error {
|
func (md MethodData) Unmarshal(into interface{}) error {
|
||||||
return json.Unmarshal(md.Ctx.PostBody(), into)
|
return json.Unmarshal(md.Ctx.PostBody(), into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsBearer tells whether the current token is a Bearer (oauth) token.
|
||||||
|
func (md MethodData) IsBearer() bool {
|
||||||
|
return md.User.ID == -1
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user