1
Fork 0

Implement access token

This commit is contained in:
viktorstrate 2020-01-31 18:51:24 +01:00
parent 0518c0e360
commit 9d734af5e2
7 changed files with 101 additions and 28 deletions

View File

@ -1 +1,2 @@
DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS users;
DROP TABLE IF NOT EXISTS access_tokens;

View File

@ -7,3 +7,13 @@ CREATE TABLE IF NOT EXISTS users (
PRIMARY KEY (user_id) 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)
);

View File

@ -50,7 +50,7 @@ type ComplexityRoot struct {
Mutation struct { Mutation struct {
AuthorizeUser func(childComplexity int, username string, password string) int 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 { Query struct {
@ -67,7 +67,7 @@ type ComplexityRoot struct {
type MutationResolver interface { type MutationResolver interface {
AuthorizeUser(ctx context.Context, username string, password string) (*AuthorizeResult, error) 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 { type QueryResolver interface {
Users(ctx context.Context) ([]*User, error) Users(ctx context.Context) ([]*User, error)
@ -131,7 +131,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return 0, false 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": case "Query.users":
if e.complexity.Query.Users == nil { if e.complexity.Query.Users == nil {
@ -230,15 +230,20 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
} }
var parsedSchema = gqlparser.MustLoadSchema( 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!]! users: [User!]!
} }
type Mutation { type Mutation {
authorizeUser(username: String!, password: String!): AuthorizeResult! authorizeUser(username: String!, password: String!): AuthorizeResult!
# Add rootPath later registerUser(
registerUser(username: String!, password: String!): AuthorizeResult! username: String!
password: String!
rootPath: String!
): AuthorizeResult!
} }
type AuthorizeResult { type AuthorizeResult {
@ -304,6 +309,14 @@ func (ec *executionContext) field_Mutation_registerUser_args(ctx context.Context
} }
} }
args["password"] = arg1 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 return args, nil
} }
@ -535,7 +548,7 @@ func (ec *executionContext) _Mutation_registerUser(ctx context.Context, field gr
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children 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 { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)

View File

@ -1,18 +1,26 @@
package models package models
import ( import (
"crypto/rand"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type User struct { type User struct {
User_id int UserID int
Username string Username string
Password string Password string
Root_path *string RootPath string
Admin bool Admin bool
}
type AccessToken struct {
Value string
Expire time.Time
} }
var UserInvalidCredentialsError = errors.New("invalid credentials") var UserInvalidCredentialsError = errors.New("invalid credentials")
@ -20,16 +28,19 @@ var UserInvalidCredentialsError = errors.New("invalid credentials")
func NewUserFromRow(row *sql.Row) (*User, error) { func NewUserFromRow(row *sql.Row) (*User, error) {
user := User{} 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 return &user, nil
} }
func AuthorizeUser(database *sql.DB, username string, password string) (*User, error) { func AuthorizeUser(database *sql.DB, username string, password string) (*User, error) {
row := database.QueryRow("SELECT * FROM users WHERE username = ?", username) row := database.QueryRow("SELECT * FROM users WHERE username = ?", username)
if row == nil {
return nil, UserInvalidCredentialsError
}
user, err := NewUserFromRow(row) user, err := NewUserFromRow(row)
if err != nil { if err != nil {
@ -47,14 +58,14 @@ func AuthorizeUser(database *sql.DB, username string, password string) (*User, e
return user, nil 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) hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
if err != nil { if err != nil {
return nil, err return nil, err
} }
hashedPass := string(hashedPassBytes) 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 return nil, err
} }
@ -70,3 +81,29 @@ func RegisterUser(database *sql.DB, username string, password string) (*User, er
return user, nil 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
}

View File

@ -3,7 +3,9 @@ package api
import ( import (
"context" "context"
"database/sql" "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 { type Resolver struct {
Database *sql.DB Database *sql.DB

View File

@ -2,7 +2,6 @@ package api
import ( import (
"context" "context"
"fmt"
"github.com/viktorstrate/photoview/api/graphql/models" "github.com/viktorstrate/photoview/api/graphql/models"
) )
@ -16,16 +15,19 @@ func (r *mutationResolver) AuthorizeUser(ctx context.Context, username string, p
}, nil }, nil
} }
token := fmt.Sprintf("token:%d", user.User_id) token, err := user.GenerateAccessToken(r.Database)
if err != nil {
return nil, err
}
return &AuthorizeResult{ return &AuthorizeResult{
Success: true, Success: true,
Status: "ok", Status: "ok",
Token: &token, Token: &token.Value,
}, nil }, nil
} }
func (r *mutationResolver) RegisterUser(ctx context.Context, username string, password string) (*AuthorizeResult, error) { func (r *mutationResolver) RegisterUser(ctx context.Context, username string, password string, rootPath string) (*AuthorizeResult, error) {
user, err := models.RegisterUser(r.Database, username, password) user, err := models.RegisterUser(r.Database, username, password, rootPath)
if err != nil { if err != nil {
return &AuthorizeResult{ return &AuthorizeResult{
Success: false, Success: false,
@ -33,11 +35,14 @@ func (r *mutationResolver) RegisterUser(ctx context.Context, username string, pa
}, nil }, nil
} }
token := fmt.Sprintf("token:%d", user.User_id) token, err := user.GenerateAccessToken(r.Database)
if err != nil {
return nil, err
}
return &AuthorizeResult{ return &AuthorizeResult{
Success: true, Success: true,
Status: "ok", Status: "ok",
Token: &token, Token: &token.Value,
}, nil }, nil
} }

View File

@ -1,3 +1,5 @@
scalar Time
type Query { type Query {
users: [User!]! users: [User!]!
} }
@ -5,8 +7,11 @@ type Query {
type Mutation { type Mutation {
authorizeUser(username: String!, password: String!): AuthorizeResult! authorizeUser(username: String!, password: String!): AuthorizeResult!
# Add rootPath later registerUser(
registerUser(username: String!, password: String!): AuthorizeResult! username: String!
password: String!
rootPath: String!
): AuthorizeResult!
} }
type AuthorizeResult { type AuthorizeResult {