ch16.2
This commit is contained in:
@@ -56,3 +56,8 @@ func (app *application) rateLimitExceededResponse(w http.ResponseWriter, r *http
|
|||||||
message := "rate limit exceeded"
|
message := "rate limit exceeded"
|
||||||
app.errorResponse(w, r, http.StatusTooManyRequests, message)
|
app.errorResponse(w, r, http.StatusTooManyRequests, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) invalidCredentialsResponse(w http.ResponseWriter, r *http.Request) {
|
||||||
|
message := "invalid authentication credentials"
|
||||||
|
app.errorResponse(w, r, http.StatusUnauthorized, message)
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,5 +23,7 @@ func (app *application) routes() http.Handler {
|
|||||||
router.HandlerFunc(http.MethodPost, "/v1/users", app.registerUserHandler)
|
router.HandlerFunc(http.MethodPost, "/v1/users", app.registerUserHandler)
|
||||||
router.HandlerFunc(http.MethodPut, "/v1/users/activated", app.activateUserHandler)
|
router.HandlerFunc(http.MethodPut, "/v1/users/activated", app.activateUserHandler)
|
||||||
|
|
||||||
|
router.HandlerFunc(http.MethodPost, "/v1/tokens/authentication", app.createAuthenticationTokenHandler)
|
||||||
|
|
||||||
return app.recoverPanic(app.rateLimit(router))
|
return app.recoverPanic(app.rateLimit(router))
|
||||||
}
|
}
|
||||||
|
|||||||
66
cmd/api/tokens.go
Normal file
66
cmd/api/tokens.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.local.lab/Lbenedar/greenlight/internal/data"
|
||||||
|
"gitea.local.lab/Lbenedar/greenlight/internal/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *application) createAuthenticationTokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var input struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.readJSON(w, r, &input)
|
||||||
|
if err != nil {
|
||||||
|
app.badRequestResponse(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
data.ValidateEmail(v, input.Email)
|
||||||
|
data.ValidatePasswordPlaintext(v, input.Password)
|
||||||
|
|
||||||
|
if !v.Valid() {
|
||||||
|
app.failedValidationResponse(w, r, v.Errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := app.models.Users.GetByEmail(input.Email)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, data.ErrRecordNotFound):
|
||||||
|
app.invalidCredentialsResponse(w, r)
|
||||||
|
default:
|
||||||
|
app.serverErrorResponse(w, r, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
match, err := user.Password.Matches(input.Password)
|
||||||
|
if err != nil {
|
||||||
|
app.serverErrorResponse(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
app.invalidCredentialsResponse(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := app.models.Tokens.New(user.ID, 24*time.Hour, data.ScopeAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
app.serverErrorResponse(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.writeJSON(w, http.StatusCreated, envelope{"authentication_token": token}, nil)
|
||||||
|
if err != nil {
|
||||||
|
app.serverErrorResponse(w, r, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,15 +12,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ScopeActivation = "activation"
|
ScopeActivation = "activation"
|
||||||
|
ScopeAuthentication = "authentication"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Plaintext string
|
Plaintext string `json:"token"`
|
||||||
Hash []byte
|
Hash []byte `json:"-"`
|
||||||
UserID int64
|
UserID int64 `json:"-"`
|
||||||
Expiry time.Time
|
Expiry time.Time `json:"expiry"`
|
||||||
Scope string
|
Scope string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateToken(userID int64, ttl time.Duration, scope string) (*Token, error) {
|
func generateToken(userID int64, ttl time.Duration, scope string) (*Token, error) {
|
||||||
@@ -66,7 +67,7 @@ func (m TokenModel) New(userID int64, ttl time.Duration, scope string) (*Token,
|
|||||||
|
|
||||||
func (m TokenModel) Insert(token *Token) error {
|
func (m TokenModel) Insert(token *Token) error {
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO tokens (hash, user_id, expirt, scope)
|
INSERT INTO tokens (hash, user_id, expiry, scope)
|
||||||
VALUES ($1, $2, $3, $4)`
|
VALUES ($1, $2, $3, $4)`
|
||||||
|
|
||||||
args := []any{token.Hash, token.UserID, token.Expiry, token.Scope}
|
args := []any{token.Hash, token.UserID, token.Expiry, token.Scope}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
CREATE TABLE IF NOT EXISTS tokens {
|
CREATE TABLE IF NOT EXISTS tokens (
|
||||||
hash bytea PRIMARY KEY,
|
hash bytea PRIMARY KEY,
|
||||||
user_id bigint NOT NULL REFERENCES users ON DELETE CASCADE,
|
user_id bigint NOT NULL REFERENCES users ON DELETE CASCADE,
|
||||||
expiry timestamp(0) with time zone NOT NULL,
|
expiry timestamp(0) with time zone NOT NULL,
|
||||||
scope text NOT NULL
|
scope text NOT NULL
|
||||||
};
|
);
|
||||||
Reference in New Issue
Block a user