diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000..92a0945 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,25 @@ +package auth + +import ( + "errors" + "net/http" + "strings" +) + +// GetAPIKey extracts an API key from the header of an HTTP request +// Example: +// Authorization: ApiKey {insert apikey here} +func GetAPIKey(header http.Header) (string, error) { + val := header.Get("Authorization") + if val == "" { + return "", errors.New("E#1OTELT - No authentication info found!") + } + vals := strings.Split(val, " ") + if len(vals) != 2 { + return "", errors.New("E#1OTEM0 - Malformed auth header") + } + if vals[0] != "ApiKey" { + return "", errors.New("E#1OTEM5 - Malformed auth header") + } + return vals[1], nil +} diff --git a/internal/database/models.go b/internal/database/models.go index 3ab4f65..209f333 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -15,4 +15,5 @@ type User struct { CreatedAt time.Time UpdatedAt time.Time Name string + ApiKey string } diff --git a/internal/database/users.sql.go b/internal/database/users.sql.go index f9c8bad..4bf6974 100644 --- a/internal/database/users.sql.go +++ b/internal/database/users.sql.go @@ -13,9 +13,9 @@ import ( ) const createUser = `-- name: CreateUser :one -INSERT INTO users (id, created_at, updated_at, name) -VALUES ($1, $2, $3, $4) -RETURNING id, created_at, updated_at, name +INSERT INTO users (id, created_at, updated_at, name, api_key) +VALUES ($1, $2, $3, $4, encode(sha256(random()::text::bytea), 'hex')) +RETURNING id, created_at, updated_at, name, api_key ` type CreateUserParams struct { @@ -38,6 +38,24 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e &i.CreatedAt, &i.UpdatedAt, &i.Name, + &i.ApiKey, + ) + return i, err +} + +const getUserByAPIKey = `-- name: GetUserByAPIKey :one +SELECT id, created_at, updated_at, name, api_key FROM users WHERE api_key = $1 +` + +func (q *Queries) GetUserByAPIKey(ctx context.Context, apiKey string) (User, error) { + row := q.db.QueryRowContext(ctx, getUserByAPIKey, apiKey) + var i User + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.Name, + &i.ApiKey, ) return i, err } diff --git a/main.go b/main.go index 4c7cbc9..7914b71 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,7 @@ func main() { v1Router.Get("/healthz", healthCheckHandler) v1Router.Get("/err", errorHandler) v1Router.Post("/users", apiCfg.createUserHandler) + v1Router.Get("/users", apiCfg.getUserHandler) router.Mount("/v1", v1Router) server := &http.Server{ diff --git a/models.go b/models.go new file mode 100644 index 0000000..111bb04 --- /dev/null +++ b/models.go @@ -0,0 +1,26 @@ +package main + +import ( + "time" + + "github.com/google/uuid" + "github.com/itishrishikesh/gofeed/internal/database" +) + +type User struct { + ID uuid.UUID `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Name string `json:"name"` + ApiKey string `json:"api_key"` +} + +func databaseUserToUser(dbUser database.User) User { + return User{ + ID: dbUser.ID, + CreatedAt: dbUser.CreatedAt, + UpdatedAt: dbUser.UpdatedAt, + Name: dbUser.Name, + ApiKey: dbUser.ApiKey, + } +} diff --git a/sql/queries/users.sql b/sql/queries/users.sql index 088b1ed..c5fc4b5 100644 --- a/sql/queries/users.sql +++ b/sql/queries/users.sql @@ -1,4 +1,7 @@ -- name: CreateUser :one -INSERT INTO users (id, created_at, updated_at, name) -VALUES ($1, $2, $3, $4) -RETURNING *; \ No newline at end of file +INSERT INTO users (id, created_at, updated_at, name, api_key) +VALUES ($1, $2, $3, $4, encode(sha256(random()::text::bytea), 'hex')) +RETURNING *; + +-- name: GetUserByAPIKey :one +SELECT * FROM users WHERE api_key = $1; \ No newline at end of file diff --git a/sql/schema/002_users_apikey.sql b/sql/schema/002_users_apikey.sql new file mode 100644 index 0000000..06234da --- /dev/null +++ b/sql/schema/002_users_apikey.sql @@ -0,0 +1,7 @@ +-- +goose Up +ALTER TABLE users ADD COLUMN api_key VARCHAR(64) UNIQUE NOT NULL DEFAULT ( + encode(sha256(random()::text::bytea), 'hex') +); + +-- +goose Down +ALTER TABLE users DROP COLUMN api_key; \ No newline at end of file diff --git a/user_handler.go b/user_handler.go index bc48759..bf26f4c 100644 --- a/user_handler.go +++ b/user_handler.go @@ -8,6 +8,7 @@ import ( "time" "github.com/google/uuid" + "github.com/itishrishikesh/gofeed/auth" "github.com/itishrishikesh/gofeed/constants" "github.com/itishrishikesh/gofeed/internal/database" ) @@ -38,5 +39,20 @@ func (apiCfg *apiConfig) createUserHandler(writer http.ResponseWriter, request * return } + respondWithJSON(writer, constants.HTTP_SUCCESS, databaseUserToUser(user)) +} + +func (apiCfg *apiConfig) getUserHandler(writer http.ResponseWriter, request *http.Request) { + apiKey, err := auth.GetAPIKey(request.Header) + if err != nil { + respondWithError(writer, 403, fmt.Sprintf("E#1OTELA - Auth Error: %v", err)) + return + } + user, err := apiCfg.DB.GetUserByAPIKey(request.Context(), apiKey) + if err != nil { + respondWithError(writer, constants.HTTP_ERROR, fmt.Sprintf("E#1OTELI - Couldn't get user: %v", err)) + return + } + respondWithJSON(writer, constants.HTTP_SUCCESS, user) }