Implement access token
This commit is contained in:
parent
0518c0e360
commit
9d734af5e2
|
@ -1 +1,2 @@
|
|||
DROP TABLE IF EXISTS users;
|
||||
DROP TABLE IF NOT EXISTS access_tokens;
|
||||
|
|
|
@ -7,3 +7,13 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
|
||||
PRIMARY KEY (user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS access_tokens (
|
||||
token_id int NOT NULL AUTO_INCREMENT,
|
||||
user_id int NOT NULL,
|
||||
value char(24) NOT NULL UNIQUE,
|
||||
expire timestamp NOT NULL,
|
||||
|
||||
PRIMARY KEY (token_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
||||
);
|
||||
|
|
|
@ -50,7 +50,7 @@ type ComplexityRoot struct {
|
|||
|
||||
Mutation struct {
|
||||
AuthorizeUser func(childComplexity int, username string, password string) int
|
||||
RegisterUser func(childComplexity int, username string, password string) int
|
||||
RegisterUser func(childComplexity int, username string, password string, rootPath string) int
|
||||
}
|
||||
|
||||
Query struct {
|
||||
|
@ -67,7 +67,7 @@ type ComplexityRoot struct {
|
|||
|
||||
type MutationResolver interface {
|
||||
AuthorizeUser(ctx context.Context, username string, password string) (*AuthorizeResult, error)
|
||||
RegisterUser(ctx context.Context, username string, password string) (*AuthorizeResult, error)
|
||||
RegisterUser(ctx context.Context, username string, password string, rootPath string) (*AuthorizeResult, error)
|
||||
}
|
||||
type QueryResolver interface {
|
||||
Users(ctx context.Context) ([]*User, error)
|
||||
|
@ -131,7 +131,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.RegisterUser(childComplexity, args["username"].(string), args["password"].(string)), true
|
||||
return e.complexity.Mutation.RegisterUser(childComplexity, args["username"].(string), args["password"].(string), args["rootPath"].(string)), true
|
||||
|
||||
case "Query.users":
|
||||
if e.complexity.Query.Users == nil {
|
||||
|
@ -230,15 +230,20 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
|
|||
}
|
||||
|
||||
var parsedSchema = gqlparser.MustLoadSchema(
|
||||
&ast.Source{Name: "graphql/schema.graphql", Input: `type Query {
|
||||
&ast.Source{Name: "graphql/schema.graphql", Input: `scalar Time
|
||||
|
||||
type Query {
|
||||
users: [User!]!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
authorizeUser(username: String!, password: String!): AuthorizeResult!
|
||||
|
||||
# Add rootPath later
|
||||
registerUser(username: String!, password: String!): AuthorizeResult!
|
||||
registerUser(
|
||||
username: String!
|
||||
password: String!
|
||||
rootPath: String!
|
||||
): AuthorizeResult!
|
||||
}
|
||||
|
||||
type AuthorizeResult {
|
||||
|
@ -304,6 +309,14 @@ func (ec *executionContext) field_Mutation_registerUser_args(ctx context.Context
|
|||
}
|
||||
}
|
||||
args["password"] = arg1
|
||||
var arg2 string
|
||||
if tmp, ok := rawArgs["rootPath"]; ok {
|
||||
arg2, err = ec.unmarshalNString2string(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["rootPath"] = arg2
|
||||
return args, nil
|
||||
}
|
||||
|
||||
|
@ -535,7 +548,7 @@ func (ec *executionContext) _Mutation_registerUser(ctx context.Context, field gr
|
|||
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Mutation().RegisterUser(rctx, args["username"].(string), args["password"].(string))
|
||||
return ec.resolvers.Mutation().RegisterUser(rctx, args["username"].(string), args["password"].(string), args["rootPath"].(string))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
User_id int
|
||||
Username string
|
||||
Password string
|
||||
Root_path *string
|
||||
Admin bool
|
||||
UserID int
|
||||
Username string
|
||||
Password string
|
||||
RootPath string
|
||||
Admin bool
|
||||
}
|
||||
|
||||
type AccessToken struct {
|
||||
Value string
|
||||
Expire time.Time
|
||||
}
|
||||
|
||||
var UserInvalidCredentialsError = errors.New("invalid credentials")
|
||||
|
@ -20,16 +28,19 @@ var UserInvalidCredentialsError = errors.New("invalid credentials")
|
|||
func NewUserFromRow(row *sql.Row) (*User, error) {
|
||||
user := User{}
|
||||
|
||||
row.Scan(&user.User_id, &user.Username, &user.Password, &user.Root_path, &user.Admin)
|
||||
if err := row.Scan(&user.UserID, &user.Username, &user.Password, &user.RootPath, &user.Admin); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, UserInvalidCredentialsError
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func AuthorizeUser(database *sql.DB, username string, password string) (*User, error) {
|
||||
row := database.QueryRow("SELECT * FROM users WHERE username = ?", username)
|
||||
if row == nil {
|
||||
return nil, UserInvalidCredentialsError
|
||||
}
|
||||
|
||||
user, err := NewUserFromRow(row)
|
||||
if err != nil {
|
||||
|
@ -47,14 +58,14 @@ func AuthorizeUser(database *sql.DB, username string, password string) (*User, e
|
|||
return user, nil
|
||||
}
|
||||
|
||||
func RegisterUser(database *sql.DB, username string, password string) (*User, error) {
|
||||
func RegisterUser(database *sql.DB, username string, password string, rootPath string) (*User, error) {
|
||||
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedPass := string(hashedPassBytes)
|
||||
|
||||
if _, err := database.Query("INSERT INTO users (username, password) VALUES (?, ?)", username, hashedPass); err != nil {
|
||||
if _, err := database.Exec("INSERT INTO users (username, password, root_path) VALUES (?, ?, ?)", username, hashedPass, rootPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -70,3 +81,29 @@ func RegisterUser(database *sql.DB, username string, password string) (*User, er
|
|||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (user *User) GenerateAccessToken(database *sql.DB) (*AccessToken, error) {
|
||||
bytes := make([]byte, 24)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Could not generate token: %s\n", err.Error()))
|
||||
}
|
||||
const CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
for i, b := range bytes {
|
||||
bytes[i] = CHARACTERS[b%byte(len(CHARACTERS))]
|
||||
}
|
||||
|
||||
token_value := string(bytes)
|
||||
expire := time.Now().Add(14 * 24 * time.Hour)
|
||||
expireString := expire.UTC().Format("2006-01-02 15:04:05")
|
||||
|
||||
if _, err := database.Exec("INSERT INTO access_tokens (value, expire, user_id) VALUES (?, ?, ?)", token_value, expireString, user.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := AccessToken{
|
||||
Value: token_value,
|
||||
Expire: expire,
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
) // THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
|
||||
)
|
||||
|
||||
//go:generate go run github.com/99designs/gqlgen
|
||||
|
||||
type Resolver struct {
|
||||
Database *sql.DB
|
||||
|
|
|
@ -2,7 +2,6 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
)
|
||||
|
@ -16,16 +15,19 @@ func (r *mutationResolver) AuthorizeUser(ctx context.Context, username string, p
|
|||
}, nil
|
||||
}
|
||||
|
||||
token := fmt.Sprintf("token:%d", user.User_id)
|
||||
token, err := user.GenerateAccessToken(r.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AuthorizeResult{
|
||||
Success: true,
|
||||
Status: "ok",
|
||||
Token: &token,
|
||||
Token: &token.Value,
|
||||
}, nil
|
||||
}
|
||||
func (r *mutationResolver) RegisterUser(ctx context.Context, username string, password string) (*AuthorizeResult, error) {
|
||||
user, err := models.RegisterUser(r.Database, username, password)
|
||||
func (r *mutationResolver) RegisterUser(ctx context.Context, username string, password string, rootPath string) (*AuthorizeResult, error) {
|
||||
user, err := models.RegisterUser(r.Database, username, password, rootPath)
|
||||
if err != nil {
|
||||
return &AuthorizeResult{
|
||||
Success: false,
|
||||
|
@ -33,11 +35,14 @@ func (r *mutationResolver) RegisterUser(ctx context.Context, username string, pa
|
|||
}, nil
|
||||
}
|
||||
|
||||
token := fmt.Sprintf("token:%d", user.User_id)
|
||||
token, err := user.GenerateAccessToken(r.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AuthorizeResult{
|
||||
Success: true,
|
||||
Status: "ok",
|
||||
Token: &token,
|
||||
Token: &token.Value,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
scalar Time
|
||||
|
||||
type Query {
|
||||
users: [User!]!
|
||||
}
|
||||
|
@ -5,8 +7,11 @@ type Query {
|
|||
type Mutation {
|
||||
authorizeUser(username: String!, password: String!): AuthorizeResult!
|
||||
|
||||
# Add rootPath later
|
||||
registerUser(username: String!, password: String!): AuthorizeResult!
|
||||
registerUser(
|
||||
username: String!
|
||||
password: String!
|
||||
rootPath: String!
|
||||
): AuthorizeResult!
|
||||
}
|
||||
|
||||
type AuthorizeResult {
|
||||
|
|
Loading…
Reference in New Issue