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 NOT EXISTS access_tokens;

View File

@ -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)
);

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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 {