feat: Added chapter 6 first auth attempt
This commit is contained in:
parent
9603509fcd
commit
e72fd2ee86
1
go.mod
1
go.mod
|
@ -6,4 +6,5 @@ require (
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -4,3 +4,5 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
|
|
12
internal/auth/auth.go
Normal file
12
internal/auth/auth.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import "golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
func HashPassword(password string) (string, error) {
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||||
|
return string(hashedPassword), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPasswordHash(password, hash string) error {
|
||||||
|
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
|
}
|
|
@ -19,8 +19,9 @@ type Chirp struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
Email string
|
Email string
|
||||||
|
HashedPassword string
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,31 +10,33 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const createUser = `-- name: CreateUser :one
|
const createUser = `-- name: CreateUser :one
|
||||||
INSERT INTO users (id, created_at, updated_at, email)
|
INSERT INTO users (id, created_at, updated_at, email, hashed_password)
|
||||||
VALUES (
|
VALUES (
|
||||||
gen_random_uuid(),
|
gen_random_uuid(),
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW(),
|
NOW(),
|
||||||
$1
|
$1,
|
||||||
|
$2
|
||||||
)
|
)
|
||||||
RETURNING id, created_at, updated_at, email
|
RETURNING id, created_at, updated_at, email, hashed_password
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) CreateUser(ctx context.Context, email string) (User, error) {
|
func (q *Queries) CreateUser(ctx context.Context, email string, hashed_password string) (User, error) {
|
||||||
row := q.db.QueryRowContext(ctx, createUser, email)
|
row := q.db.QueryRowContext(ctx, createUser, email, hashed_password)
|
||||||
var i User
|
var i User
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
|
&i.HashedPassword,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteUser = `-- name: DeleteUser :one
|
const deleteUser = `-- name: DeleteUser :one
|
||||||
DELETE FROM users
|
DELETE FROM users
|
||||||
RETURNING id, created_at, updated_at, email
|
RETURNING id, created_at, updated_at, email, hashed_password
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) DeleteUser(ctx context.Context) (User, error) {
|
func (q *Queries) DeleteUser(ctx context.Context) (User, error) {
|
||||||
|
@ -45,6 +47,25 @@ func (q *Queries) DeleteUser(ctx context.Context) (User, error) {
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
|
&i.HashedPassword,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserByEmail = `-- name: GetUserByEmail :one
|
||||||
|
SELECT id, created_at, updated_at, email, hashed_password FROM users
|
||||||
|
WHERE users.email = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getUserByEmail, email)
|
||||||
|
var i User
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Email,
|
||||||
|
&i.HashedPassword,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
51
login.go
Normal file
51
login.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/finchrelia/chirpy-server/internal/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cfg *apiConfig) Login(w http.ResponseWriter, r *http.Request) {
|
||||||
|
type params struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
p := params{}
|
||||||
|
err := decoder.Decode(&p)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Incorrect email or password")
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loggedUser, err := cfg.DB.GetUserByEmail(r.Context(), p.Email)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error retrieving user: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = auth.CheckPasswordHash(p.Password, loggedUser.HashedPassword)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Incorrect email or password")
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(User{
|
||||||
|
ID: loggedUser.ID,
|
||||||
|
CreatedAt: loggedUser.CreatedAt,
|
||||||
|
UpdatedAt: loggedUser.UpdatedAt,
|
||||||
|
Email: loggedUser.Email,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error marshalling JSON: %s", err)
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
1
main.go
1
main.go
|
@ -51,6 +51,7 @@ func main() {
|
||||||
mux.HandleFunc("POST /api/chirps", apiCfg.chirpsCreate)
|
mux.HandleFunc("POST /api/chirps", apiCfg.chirpsCreate)
|
||||||
mux.HandleFunc("POST /api/users", apiCfg.createUsers)
|
mux.HandleFunc("POST /api/users", apiCfg.createUsers)
|
||||||
mux.HandleFunc("GET /api/chirps/{chirpID}", apiCfg.getChirp)
|
mux.HandleFunc("GET /api/chirps/{chirpID}", apiCfg.getChirp)
|
||||||
|
mux.HandleFunc("POST /api/login", apiCfg.Login)
|
||||||
|
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: ":8080",
|
Addr: ":8080",
|
||||||
|
|
|
@ -10,4 +10,8 @@ RETURNING *;
|
||||||
|
|
||||||
-- name: DeleteUser :one
|
-- name: DeleteUser :one
|
||||||
DELETE FROM users
|
DELETE FROM users
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetUserByEmail :one
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE users.email = $1;
|
7
sql/schema/003_users_auth.sql
Normal file
7
sql/schema/003_users_auth.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
-- +goose Up
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN hashed_password TEXT DEFAULT 'unset' NOT NULL;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
ALTER TABLE users
|
||||||
|
DROP COLUMN hashed_password;
|
20
users.go
20
users.go
|
@ -6,19 +6,22 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/finchrelia/chirpy-server/internal/auth"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
HashedPassword string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *apiConfig) createUsers(w http.ResponseWriter, r *http.Request) {
|
func (cfg *apiConfig) createUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
type parameters struct {
|
type parameters struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
@ -30,7 +33,12 @@ func (cfg *apiConfig) createUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
newDBUser, err := cfg.DB.CreateUser(r.Context(), params.Email)
|
|
||||||
|
hashedPassword, err := auth.HashPassword(params.Password)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error hashing password: %s", err)
|
||||||
|
}
|
||||||
|
newDBUser, err := cfg.DB.CreateUser(r.Context(), params.Email, hashedPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error creating user %s: %s", params.Email, err)
|
log.Printf("Error creating user %s: %s", params.Email, err)
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user