Add user token validation
This commit is contained in:
parent
3375481b21
commit
5e81b17e28
@ -4,8 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"gitlab.quimbo.fr/odwrtw/canape/backend/tokens"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
@ -17,6 +20,8 @@ var (
|
|||||||
ErrInvalidSecret = fmt.Errorf("Invalid secret")
|
ErrInvalidSecret = fmt.Errorf("Invalid secret")
|
||||||
// ErrInvalidToken returned when the jwt token is invalid
|
// ErrInvalidToken returned when the jwt token is invalid
|
||||||
ErrInvalidToken = fmt.Errorf("Invalid token")
|
ErrInvalidToken = fmt.Errorf("Invalid token")
|
||||||
|
// ErrUnauthenticatedUser returned when a user is not authenticated
|
||||||
|
ErrUnauthenticatedUser = fmt.Errorf("Unauthenticated user")
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserBackend interface for user backend
|
// UserBackend interface for user backend
|
||||||
@ -35,6 +40,7 @@ type User interface {
|
|||||||
|
|
||||||
// Authorizer handle sesssion
|
// Authorizer handle sesssion
|
||||||
type Authorizer struct {
|
type Authorizer struct {
|
||||||
|
db *sqlx.DB
|
||||||
Params
|
Params
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,8 +54,9 @@ type Params struct {
|
|||||||
|
|
||||||
// New Authorizer pepper is like a salt but not stored in database,
|
// New Authorizer pepper is like a salt but not stored in database,
|
||||||
// cost is the bcrypt cost for hashing the password
|
// cost is the bcrypt cost for hashing the password
|
||||||
func New(params Params) *Authorizer {
|
func New(db *sqlx.DB, params Params) *Authorizer {
|
||||||
return &Authorizer{
|
return &Authorizer{
|
||||||
|
db: db,
|
||||||
Params: params,
|
Params: params,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,7 +71,7 @@ func (a *Authorizer) GenHash(password string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login cheks password and creates a jwt token
|
// Login cheks password and creates a jwt token
|
||||||
func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, password string) (User, error) {
|
func (a *Authorizer) Login(username, password string) (*tokens.Token, error) {
|
||||||
u, err := a.Backend.GetUser(username)
|
u, err := a.Backend.GetUser(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -76,7 +83,34 @@ func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username,
|
|||||||
return nil, ErrInvalidPassword
|
return nil, ErrInvalidPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
// Create a jwt token
|
||||||
|
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
|
// Not before
|
||||||
|
"nbf": time.Now().Unix(),
|
||||||
|
// Issued at
|
||||||
|
"iat": time.Now().Unix(),
|
||||||
|
// Private claims
|
||||||
|
"username": u.GetName(),
|
||||||
|
"isAdmin": u.IsAdmin(),
|
||||||
|
"isActivated": u.IsActivated(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sign the token
|
||||||
|
ss, err := jwtToken.SignedString([]byte(a.Secret))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &tokens.Token{
|
||||||
|
Token: ss,
|
||||||
|
Username: u.GetName(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.Add(a.db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentUser returns the logged in username from session and verifies the token
|
// CurrentUser returns the logged in username from session and verifies the token
|
||||||
@ -92,14 +126,14 @@ func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (Use
|
|||||||
if tokenStr == "" {
|
if tokenStr == "" {
|
||||||
tokenCookie, err := req.Cookie("token")
|
tokenCookie, err := req.Cookie("token")
|
||||||
if err != nil || tokenCookie == nil {
|
if err != nil || tokenCookie == nil {
|
||||||
return nil, nil
|
return nil, ErrUnauthenticatedUser
|
||||||
}
|
}
|
||||||
tokenStr = tokenCookie.Value
|
tokenStr = tokenCookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// No user logged
|
// No user logged
|
||||||
if tokenStr == "" {
|
if tokenStr == "" {
|
||||||
return nil, nil
|
return nil, ErrUnauthenticatedUser
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyfunc to decode the token
|
// Keyfunc to decode the token
|
||||||
@ -111,13 +145,13 @@ func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (Use
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
jwt.StandardClaims
|
jwt.StandardClaims
|
||||||
}
|
}
|
||||||
token, err := jwt.ParseWithClaims(tokenStr, &tokenClaims, keyfunc)
|
jwtToken, err := jwt.ParseWithClaims(tokenStr, &tokenClaims, keyfunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the token validity
|
// Check the token validity
|
||||||
if !token.Valid {
|
if !jwtToken.Valid {
|
||||||
return nil, ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,5 +161,16 @@ func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (Use
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the token in database
|
||||||
|
token, err := tokens.GetUserToken(a.db, u.GetName(), tokenStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidToken
|
||||||
|
}
|
||||||
|
|
||||||
|
token.UserAgent = req.UserAgent()
|
||||||
|
if err := token.Update(a.db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
@ -25,21 +25,22 @@ func NewMiddleware(authorizer *Authorizer, log *logrus.Entry) *Middleware {
|
|||||||
|
|
||||||
func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
user, err := m.authorizer.CurrentUser(w, r)
|
user, err := m.authorizer.CurrentUser(w, r)
|
||||||
if err != nil {
|
switch err {
|
||||||
|
case nil:
|
||||||
|
m.log.Debugf("setting user %s in the context", user.GetName())
|
||||||
|
ctxKey := authContextKey("auth.user")
|
||||||
|
ctx := context.WithValue(r.Context(), ctxKey, user)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
case ErrUnauthenticatedUser:
|
||||||
|
m.log.Debugf("unauthenticated user")
|
||||||
|
case ErrInvalidToken:
|
||||||
|
m.log.Debugf("user has an invalid token")
|
||||||
|
default:
|
||||||
|
m.log.Error(err)
|
||||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil {
|
|
||||||
m.log.Debugf("setting user %s in the context", user.GetName())
|
|
||||||
} else {
|
|
||||||
m.log.Debugf("got a nil user in the context")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxKey := authContextKey("auth.user")
|
|
||||||
ctx := context.WithValue(r.Context(), ctxKey, user)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
next(w, r)
|
next(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ func main() {
|
|||||||
Cost: cf.Authorizer.Cost,
|
Cost: cf.Authorizer.Cost,
|
||||||
Secret: cf.Authorizer.Secret,
|
Secret: cf.Authorizer.Secret,
|
||||||
}
|
}
|
||||||
authorizer := auth.New(authParams)
|
authorizer := auth.New(db, authParams)
|
||||||
|
|
||||||
// Create web environment needed by the app
|
// Create web environment needed by the app
|
||||||
env := web.NewEnv(web.EnvParams{
|
env := web.NewEnv(web.EnvParams{
|
||||||
|
@ -16,6 +16,9 @@ func setupRoutes(env *web.Env) {
|
|||||||
env.Handle("/users/signup", users.SignupPOSTHandler).Methods("POST")
|
env.Handle("/users/signup", users.SignupPOSTHandler).Methods("POST")
|
||||||
env.Handle("/users/details", users.DetailsHandler).WithRole(users.UserRole).Methods("GET")
|
env.Handle("/users/details", users.DetailsHandler).WithRole(users.UserRole).Methods("GET")
|
||||||
env.Handle("/users/edit", users.EditHandler).WithRole(users.UserRole).Methods("POST")
|
env.Handle("/users/edit", users.EditHandler).WithRole(users.UserRole).Methods("POST")
|
||||||
|
env.Handle("/users/tokens", users.GetTokensHandler).WithRole(users.UserRole).Methods("GET")
|
||||||
|
env.Handle("/users/tokens/{token}", users.EditTokenHandler).WithRole(users.UserRole).Methods("POST")
|
||||||
|
env.Handle("/users/tokens/{token}", users.DeleteTokenHandler).WithRole(users.UserRole).Methods("DELETE")
|
||||||
|
|
||||||
// Movies routes
|
// Movies routes
|
||||||
env.Handle("/movies/polochon", movies.PolochonMoviesHandler).WithRole(users.UserRole).Methods("GET")
|
env.Handle("/movies/polochon", movies.PolochonMoviesHandler).WithRole(users.UserRole).Methods("GET")
|
||||||
|
96
backend/tokens/tokens.go
Normal file
96
backend/tokens/tokens.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"gitlab.quimbo.fr/odwrtw/canape/backend/sqly"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
addTokenQuery = `INSERT INTO tokens (token, username) VALUES ($1, $2);`
|
||||||
|
getTokenQuery = `SELECT * FROM tokens WHERE token=$1;`
|
||||||
|
getUserTokenQuery = `SELECT * FROM tokens WHERE username=$1 and token=$2;`
|
||||||
|
getUserTokensQuery = `SELECT * FROM tokens WHERE username=$1;`
|
||||||
|
deleteTokenQuery = `DELETE FROM tokens WHERE username=$1 AND token=$2;`
|
||||||
|
updateTokenQuery = `UPDATE tokens SET description=:description, user_agent=:user_agent WHERE token=:token RETURNING *;`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Custom errors
|
||||||
|
var (
|
||||||
|
ErrTokenNotFound = errors.New("tokens: token not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token represents a token
|
||||||
|
type Token struct {
|
||||||
|
sqly.BaseModel
|
||||||
|
Username string `db:"username" json:"username"`
|
||||||
|
Token string `db:"token" json:"token"`
|
||||||
|
Description string `db:"description" json:"description"`
|
||||||
|
UserAgent string `db:"user_agent" json:"user_agent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a token to the database
|
||||||
|
func (t *Token) Add(db *sqlx.DB) error {
|
||||||
|
_, err := db.Queryx(addTokenQuery, t.Token, t.Username)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserToken returns the token linked to a user
|
||||||
|
func GetUserToken(db *sqlx.DB, username, token string) (*Token, error) {
|
||||||
|
t := &Token{}
|
||||||
|
err := db.QueryRowx(getUserTokenQuery, username, token).StructScan(t)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, ErrTokenNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteToken deletes a token
|
||||||
|
func DeleteToken(db *sqlx.DB, username, token string) error {
|
||||||
|
r, err := db.Exec(deleteTokenQuery, username, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := r.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if count != 1 {
|
||||||
|
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserTokens returns all tokens owned by the user
|
||||||
|
func GetUserTokens(db *sqlx.DB, username string) ([]*Token, error) {
|
||||||
|
tokens := []*Token{}
|
||||||
|
err := db.Select(&tokens, getUserTokensQuery, username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tokens, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates a token
|
||||||
|
func (t *Token) Update(db *sqlx.DB) error {
|
||||||
|
rows, err := db.NamedQuery(updateTokenQuery, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
rows.StructScan(t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,12 +4,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape/backend/auth"
|
"gitlab.quimbo.fr/odwrtw/canape/backend/auth"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape/backend/config"
|
"gitlab.quimbo.fr/odwrtw/canape/backend/config"
|
||||||
|
"gitlab.quimbo.fr/odwrtw/canape/backend/tokens"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape/backend/web"
|
"gitlab.quimbo.fr/odwrtw/canape/backend/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,37 +64,21 @@ func LoginPOSTHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := e.Auth.Login(w, r, data.Username, data.Password)
|
token, err := e.Auth.Login(data.Username, data.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == auth.ErrInvalidPassword || err == ErrUnknownUser {
|
if err == auth.ErrInvalidPassword || err == ErrUnknownUser {
|
||||||
return e.RenderError(w, fmt.Errorf("Error invalid user or password"))
|
return e.RenderError(w, fmt.Errorf("Error invalid user or password"))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
e.Log.Debugf("logged %s", user.GetName())
|
e.Log.Debugf("logged %s", token.Username)
|
||||||
|
|
||||||
// Create a jwt token
|
// TODO: add token expiration / keep me login stuff
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
||||||
// Not before
|
|
||||||
"nbf": time.Now().Unix(),
|
|
||||||
// Issued at
|
|
||||||
"iat": time.Now().Unix(),
|
|
||||||
// Private claims
|
|
||||||
"username": user.GetName(),
|
|
||||||
"isAdmin": user.IsAdmin(),
|
|
||||||
"isActivated": user.IsActivated(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Sign the token
|
|
||||||
ss, err := token.SignedString([]byte(e.Auth.Secret))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out = struct {
|
var out = struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}{
|
}{
|
||||||
Token: ss,
|
Token: token.Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.RenderJSON(w, out)
|
return e.RenderJSON(w, out)
|
||||||
@ -167,3 +150,68 @@ func EditHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
|||||||
|
|
||||||
return e.RenderOK(w, "user updated")
|
return e.RenderOK(w, "user updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTokensHandler lists the tokens of a user
|
||||||
|
func GetTokensHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
v := auth.GetCurrentUser(r, e.Log)
|
||||||
|
user, ok := v.(*User)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid user type")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, err := tokens.GetUserTokens(e.Database, user.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.RenderJSON(w, tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTokenHandler helps delete a token
|
||||||
|
func DeleteTokenHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
token := vars["token"]
|
||||||
|
|
||||||
|
v := auth.GetCurrentUser(r, e.Log)
|
||||||
|
user, ok := v.(*User)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid user type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tokens.DeleteToken(e.Database, user.Name, token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.RenderOK(w, "token deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTokenHandler helps delete a token
|
||||||
|
func EditTokenHandler(e *web.Env, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
token := vars["token"]
|
||||||
|
|
||||||
|
v := auth.GetCurrentUser(r, e.Log)
|
||||||
|
user, ok := v.(*User)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid user type")
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := tokens.GetUserToken(e.Database, user.Name, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
}{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Description = data.Description
|
||||||
|
if err := t.Update(e.Database); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.RenderJSON(w, t)
|
||||||
|
}
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/odwrtw/papi"
|
"github.com/odwrtw/papi"
|
||||||
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape/backend/config"
|
"gitlab.quimbo.fr/odwrtw/canape/backend/config"
|
||||||
"gitlab.quimbo.fr/odwrtw/canape/backend/random"
|
|
||||||
"gitlab.quimbo.fr/odwrtw/canape/backend/sqly"
|
"gitlab.quimbo.fr/odwrtw/canape/backend/sqly"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,11 +21,6 @@ const (
|
|||||||
updateUserQuery = `UPDATE users SET name=:name, hash=:hash, admin=:admin, activated=:activated, rawconfig=:rawconfig WHERE id=:id RETURNING *;`
|
updateUserQuery = `UPDATE users SET name=:name, hash=:hash, admin=:admin, activated=:activated, rawconfig=:rawconfig WHERE id=:id RETURNING *;`
|
||||||
deleteUseQuery = `DELETE FROM users WHERE id=:id;`
|
deleteUseQuery = `DELETE FROM users WHERE id=:id;`
|
||||||
|
|
||||||
addTokenQuery = `INSERT INTO tokens (value, user_id) VALUES ($1, $2) RETURNING id;`
|
|
||||||
getTokensQuery = `SELECT id, value FROM tokens WHERE user_id=$1;`
|
|
||||||
checkTokenQuery = `SELECT count(*) FROM tokens WHERE user_id=$1 AND value=$2;`
|
|
||||||
deleteTokenQuery = `DELETE FROM tokens WHERE user_id=$1 AND value=$2;`
|
|
||||||
|
|
||||||
getAllUsersQuery = `SELECT * FROM users order by created_at;`
|
getAllUsersQuery = `SELECT * FROM users order by created_at;`
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -119,12 +113,6 @@ func (u *User) NewPapiClient() (*papi.Client, error) {
|
|||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token represents a token
|
|
||||||
type Token struct {
|
|
||||||
sqly.BaseModel
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns user with specified name
|
// Get returns user with specified name
|
||||||
func Get(q sqlx.Queryer, name string) (*User, error) {
|
func Get(q sqlx.Queryer, name string) (*User, error) {
|
||||||
u := &User{}
|
u := &User{}
|
||||||
@ -193,57 +181,6 @@ func (u *User) Delete(ex *sqlx.DB) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTokens returns all tokens owned by the user
|
|
||||||
func (u *User) GetTokens(ex *sqlx.DB) ([]*Token, error) {
|
|
||||||
tokens := []*Token{}
|
|
||||||
err := ex.Select(&tokens, getTokensQuery, u.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tokens, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewToken generates a new token for the user
|
|
||||||
func (u *User) NewToken(ex *sqlx.DB) (*Token, error) {
|
|
||||||
t := &Token{
|
|
||||||
Value: random.String(50),
|
|
||||||
}
|
|
||||||
|
|
||||||
var id string
|
|
||||||
err := ex.QueryRowx(addTokenQuery, t.Value, u.ID).Scan(&id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.ID = id
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckToken checks if specified value exists in token's values for the user
|
|
||||||
func (u *User) CheckToken(ex *sqlx.DB, value string) (bool, error) {
|
|
||||||
var count int
|
|
||||||
err := ex.QueryRowx(checkTokenQuery, u.ID, value).Scan(&count)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if count != 1 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteToken delete token by value
|
|
||||||
func (u *User) DeleteToken(ex *sqlx.DB, value string) error {
|
|
||||||
r, err := ex.Exec(deleteTokenQuery, u.ID, value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
count, _ := r.RowsAffected()
|
|
||||||
if count != 1 {
|
|
||||||
return fmt.Errorf("Unexpected number of row deleted: %d", count)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHash implements auth.User interface
|
// GetHash implements auth.User interface
|
||||||
func (u *User) GetHash() string {
|
func (u *User) GetHash() string {
|
||||||
return u.Hash
|
return u.Hash
|
||||||
|
1
migrations/0006_user_token.down.sql
Normal file
1
migrations/0006_user_token.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE tokens;
|
12
migrations/0006_user_token.up.sql
Normal file
12
migrations/0006_user_token.up.sql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE tokens (
|
||||||
|
token text PRIMARY KEY NOT NULL UNIQUE,
|
||||||
|
username text NOT NULL REFERENCES users(name),
|
||||||
|
description text NOT NULL DEFAULT '-',
|
||||||
|
user_agent text NOT NULL DEFAULT '-',
|
||||||
|
ip inet NOT NULL,
|
||||||
|
last_used timestamp with time zone NOT NULL DEFAULT current_timestamp,
|
||||||
|
LIKE base INCLUDING DEFAULTS
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ON tokens (username);
|
||||||
|
CREATE TRIGGER update_tokens_updated_at BEFORE UPDATE ON tokens FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
Loading…
x
Reference in New Issue
Block a user