satellite/console: forgot password, resend email endpoints added, default http route replaced with gorilla mux (#3327)

This commit is contained in:
Nikolai Siedov 2019-10-21 19:42:49 +03:00 committed by GitHub
parent 1014d5a7d1
commit 6dd478e43c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 176 additions and 53 deletions

View File

@ -4,40 +4,48 @@
package consoleapi package consoleapi
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"strings"
"github.com/gorilla/mux"
"github.com/skyrings/skyring-common/tools/uuid"
"github.com/zeebo/errs"
"go.uber.org/zap" "go.uber.org/zap"
"storj.io/storj/internal/post" "storj.io/storj/internal/post"
"storj.io/storj/pkg/auth"
"storj.io/storj/satellite/console" "storj.io/storj/satellite/console"
"storj.io/storj/satellite/console/consoleweb/consoleql" "storj.io/storj/satellite/console/consoleweb/consoleql"
"storj.io/storj/satellite/mailservice" "storj.io/storj/satellite/mailservice"
) )
// Error - console auth api error type
var Error = errs.Class("console auth api error")
// Auth is an api controller that exposes all auth functionality. // Auth is an api controller that exposes all auth functionality.
type Auth struct { type Auth struct {
log *zap.Logger log *zap.Logger
service *console.Service service *console.Service
mailService *mailservice.Service mailService *mailservice.Service
ExternalAddress string
ExternalAddress string LetUsKnowURL string
TermsAndConditionsURL string
ContactInfoURL string
} }
// NewAuth is a constructor for api auth controller. // NewAuth is a constructor for api auth controller.
func NewAuth(log *zap.Logger, service *console.Service, mailService *mailservice.Service, externalAddress string) *Auth { func NewAuth(log *zap.Logger, service *console.Service, mailService *mailservice.Service, externalAddress string, letUsKnowURL string, termsAndConditionsURL string, contactInfoURL string) *Auth {
return &Auth{ return &Auth{
log: log, log: log,
service: service, service: service,
mailService: mailService, mailService: mailService,
ExternalAddress: externalAddress, ExternalAddress: externalAddress,
LetUsKnowURL: letUsKnowURL,
TermsAndConditionsURL: termsAndConditionsURL,
ContactInfoURL: contactInfoURL,
} }
} }
// Token authenticates User by credentials and returns auth token. // Token authenticates user by credentials and returns auth token.
func (a *Auth) Token(w http.ResponseWriter, r *http.Request) { func (a *Auth) Token(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var err error var err error
@ -66,12 +74,12 @@ func (a *Auth) Token(w http.ResponseWriter, r *http.Request) {
err = json.NewEncoder(w).Encode(tokenResponse) err = json.NewEncoder(w).Encode(tokenResponse)
if err != nil { if err != nil {
a.log.Error("token handler could not encode token response", zap.Error(err)) a.log.Error("token handler could not encode token response", zap.Error(Error.Wrap(err)))
return return
} }
} }
// Register creates new User, sends activation e-mail. // Register creates new user, sends activation e-mail.
func (a *Auth) Register(w http.ResponseWriter, r *http.Request) { func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var err error var err error
@ -124,13 +132,13 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
err = json.NewEncoder(w).Encode(&user.ID) err = json.NewEncoder(w).Encode(&user.ID)
if err != nil { if err != nil {
a.log.Error("registration handler could not encode error", zap.Error(err)) a.log.Error("registration handler could not encode error", zap.Error(Error.Wrap(err)))
return return
} }
} }
// PasswordChange auth user, changes users password for a new one. // ChangePassword auth user, changes users password for a new one.
func (a *Auth) PasswordChange(w http.ResponseWriter, r *http.Request) { func (a *Auth) ChangePassword(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var err error var err error
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
@ -140,8 +148,6 @@ func (a *Auth) PasswordChange(w http.ResponseWriter, r *http.Request) {
NewPassword string `json:"newPassword"` NewPassword string `json:"newPassword"`
} }
ctx = a.authorize(ctx, r)
err = json.NewDecoder(r.Body).Decode(&passwordChange) err = json.NewDecoder(r.Body).Decode(&passwordChange)
if err != nil { if err != nil {
a.serveJSONError(w, http.StatusBadRequest, err) a.serveJSONError(w, http.StatusBadRequest, err)
@ -155,6 +161,110 @@ func (a *Auth) PasswordChange(w http.ResponseWriter, r *http.Request) {
} }
} }
// ForgotPassword creates password-reset token and sends email to user.
func (a *Auth) ForgotPassword(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
params := mux.Vars(r)
email, ok := params["email"]
if !ok {
err = errs.New("email expected")
a.serveJSONError(w, http.StatusBadRequest, err)
return
}
user, err := a.service.GetUserByEmail(ctx, email)
if err != nil {
a.serveJSONError(w, http.StatusInternalServerError, err)
return
}
recoveryToken, err := a.service.GeneratePasswordRecoveryToken(ctx, user.ID)
if err != nil {
a.serveJSONError(w, http.StatusInternalServerError, err)
return
}
passwordRecoveryLink := a.ExternalAddress + consoleql.CancelPasswordRecoveryPath + recoveryToken
cancelPasswordRecoveryLink := a.ExternalAddress + consoleql.CancelPasswordRecoveryPath + recoveryToken
userName := user.ShortName
if user.ShortName == "" {
userName = user.FullName
}
contactInfoURL := a.ContactInfoURL
letUsKnowURL := a.LetUsKnowURL
termsAndConditionsURL := a.TermsAndConditionsURL
a.mailService.SendRenderedAsync(
ctx,
[]post.Address{{Address: user.Email, Name: userName}},
&consoleql.ForgotPasswordEmail{
Origin: a.ExternalAddress,
UserName: userName,
ResetLink: passwordRecoveryLink,
CancelPasswordRecoveryLink: cancelPasswordRecoveryLink,
LetUsKnowURL: letUsKnowURL,
ContactInfoURL: contactInfoURL,
TermsAndConditionsURL: termsAndConditionsURL,
},
)
}
// ResendEmail generates activation token by userID and sends email account activation email to user.
func (a *Auth) ResendEmail(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
params := mux.Vars(r)
val, ok := params["id"]
if !ok {
a.serveJSONError(w, http.StatusBadRequest, errs.New("id expected"))
return
}
userID, err := uuid.Parse(val)
if err != nil {
a.serveJSONError(w, http.StatusBadRequest, err)
return
}
user, err := a.service.GetUser(ctx, *userID)
if err != nil {
a.serveJSONError(w, http.StatusNotFound, err)
return
}
token, err := a.service.GenerateActivationToken(ctx, user.ID, user.Email)
if err != nil {
a.serveJSONError(w, http.StatusInternalServerError, err)
return
}
link := a.ExternalAddress + consoleql.ActivationPath + token
userName := user.ShortName
if user.ShortName == "" {
userName = user.FullName
}
contactInfoURL := a.ContactInfoURL
termsAndConditionsURL := a.TermsAndConditionsURL
a.mailService.SendRenderedAsync(
ctx,
[]post.Address{{Address: user.Email, Name: userName}},
&consoleql.AccountActivationEmail{
Origin: a.ExternalAddress,
ActivationLink: link,
TermsAndConditionsURL: termsAndConditionsURL,
ContactInfoURL: contactInfoURL,
},
)
}
// serveJSONError writes JSON error to response output stream. // serveJSONError writes JSON error to response output stream.
func (a *Auth) serveJSONError(w http.ResponseWriter, status int, err error) { func (a *Auth) serveJSONError(w http.ResponseWriter, status int, err error) {
w.WriteHeader(status) w.WriteHeader(status)
@ -167,19 +277,6 @@ func (a *Auth) serveJSONError(w http.ResponseWriter, status int, err error) {
err = json.NewEncoder(w).Encode(response) err = json.NewEncoder(w).Encode(response)
if err != nil { if err != nil {
a.log.Error("failed to write json error response", zap.Error(err)) a.log.Error("failed to write json error response", zap.Error(Error.Wrap(err)))
} }
} }
// authorize checks request for authorization token, validates it and updates context with auth data.
func (a *Auth) authorize(ctx context.Context, r *http.Request) context.Context {
authHeaderValue := r.Header.Get("Authorization")
token := strings.TrimPrefix(authHeaderValue, "Bearer ")
auth, err := a.service.Authorize(auth.WithAPIKey(ctx, []byte(token)))
if err != nil {
return console.WithAuthFailure(ctx, err)
}
return console.WithAuth(ctx, auth)
}

View File

@ -17,6 +17,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/gorilla/mux"
"github.com/graphql-go/graphql" "github.com/graphql-go/graphql"
"github.com/skyrings/skyring-common/tools/uuid" "github.com/skyrings/skyring-common/tools/uuid"
"github.com/zeebo/errs" "github.com/zeebo/errs"
@ -113,31 +114,36 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
server.config.ExternalAddress = "http://" + server.listener.Addr().String() + "/" server.config.ExternalAddress = "http://" + server.listener.Addr().String() + "/"
} }
router := http.NewServeMux() router := mux.NewRouter()
fs := http.FileServer(http.Dir(server.config.StaticDir)) fs := http.FileServer(http.Dir(server.config.StaticDir))
authController := consoleapi.NewAuth(logger, service, mailService, config.ExternalAddress, config.LetUsKnowURL, config.TermsAndConditionsURL, config.ContactInfoURL)
paymentController := consoleapi.NewPayments(logger, service) paymentController := consoleapi.NewPayments(logger, service)
authController := consoleapi.NewAuth(logger, service, mailService, config.ExternalAddress)
router.Handle("/api/v0/payments/cards", http.HandlerFunc(paymentController.AddCreditCard)) router.HandleFunc("/api/v0/graphql", server.grapqlHandler)
router.Handle("/api/v0/payments/account/balance", http.HandlerFunc(paymentController.AccountBalance)) router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler)
router.Handle("/api/v0/payments/account", http.HandlerFunc(paymentController.SetupAccount)) router.HandleFunc("/robots.txt", server.seoHandler)
router.Handle("/api/v0/register", http.HandlerFunc(authController.Register)) authRouter := router.PathPrefix("/api/auth").Subrouter()
router.Handle("/api/v0/token", http.HandlerFunc(authController.Token)) authRouter.Use()
router.Handle("/api/v0/passwordChange", http.HandlerFunc(authController.PasswordChange))
router.Handle("/api/v0/graphql", http.HandlerFunc(server.grapqlHandler)) authRouter.HandleFunc("/token", authController.Token).Methods("POST")
router.Handle("/registrationToken/", http.HandlerFunc(server.createRegistrationTokenHandler)) authRouter.HandleFunc("/register", authController.Register).Methods("POST")
router.Handle("/robots.txt", http.HandlerFunc(server.seoHandler)) authRouter.Handle("/changePassword", server.withAuth(http.HandlerFunc(authController.ChangePassword))).Methods("POST")
authRouter.HandleFunc("/changePassword", authController.ChangePassword).Methods("POST")
authRouter.HandleFunc("/forgotPassword", authController.ForgotPassword).Methods("POST")
authRouter.HandleFunc("/resendEmail", authController.ResendEmail).Methods("POST")
router.HandleFunc("/api/v0/payments/cards", paymentController.AddCreditCard)
router.HandleFunc("/api/v0/payments/account/balance", paymentController.AccountBalance)
router.HandleFunc("/api/v0/payments/account", paymentController.SetupAccount)
if server.config.StaticDir != "" { if server.config.StaticDir != "" {
router.Handle("/activation/", http.HandlerFunc(server.accountActivationHandler)) router.HandleFunc("/activation/", server.accountActivationHandler)
router.Handle("/password-recovery/", http.HandlerFunc(server.passwordRecoveryHandler)) router.HandleFunc("/password-recovery/", server.passwordRecoveryHandler)
router.Handle("/cancel-password-recovery/", http.HandlerFunc(server.cancelPasswordRecoveryHandler)) router.HandleFunc("/cancel-password-recovery/", server.cancelPasswordRecoveryHandler)
router.Handle("/usage-report/", http.HandlerFunc(server.bucketUsageReportHandler)) router.HandleFunc("/usage-report/", server.bucketUsageReportHandler)
router.Handle("/static/", server.gzipHandler(http.StripPrefix("/static", fs))) router.PathPrefix("/static/").Handler(server.gzipHandler(http.StripPrefix("/static", fs)))
router.Handle("/", http.HandlerFunc(server.appHandler)) router.PathPrefix("/").Handler(http.HandlerFunc(server.appHandler))
} }
server.server = http.Server{ server.server = http.Server{
@ -204,6 +210,26 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
// authMiddlewareHandler performs initial authorization before every request.
func (server *Server) withAuth(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
token := getToken(r)
ctx = auth.WithAPIKey(ctx, []byte(token))
auth, err := server.service.Authorize(ctx)
if err != nil {
ctx = console.WithAuthFailure(ctx, err)
} else {
ctx = console.WithAuth(ctx, auth)
}
handler.ServeHTTP(w, r.Clone(ctx))
})
}
// bucketUsageReportHandler generate bucket usage report page for project // bucketUsageReportHandler generate bucket usage report page for project
func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Request) { func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()