Implement access token
This commit is contained in:
parent
0518c0e360
commit
9d734af5e2
|
@ -1 +1,2 @@
|
||||||
DROP TABLE IF EXISTS users;
|
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)
|
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 {
|
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)
|
||||||
|
|
|
@ -1,35 +1,46 @@
|
||||||
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")
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue