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
import (
"context"
"encoding/json"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/skyrings/skyring-common/tools/uuid"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/storj/internal/post"
"storj.io/storj/pkg/auth"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/console/consoleweb/consoleql"
"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.
type Auth struct {
log *zap.Logger
service *console.Service
mailService *mailservice.Service
ExternalAddress string
log *zap.Logger
service *console.Service
mailService *mailservice.Service
ExternalAddress string
LetUsKnowURL string
TermsAndConditionsURL string
ContactInfoURL string
}
// 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{
log: log,
service: service,
mailService: mailService,
ExternalAddress: externalAddress,
log: log,
service: service,
mailService: mailService,
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) {
ctx := r.Context()
var err error
@ -66,12 +74,12 @@ func (a *Auth) Token(w http.ResponseWriter, r *http.Request) {
err = json.NewEncoder(w).Encode(tokenResponse)
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
}
}
// 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) {
ctx := r.Context()
var err error
@ -124,13 +132,13 @@ func (a *Auth) Register(w http.ResponseWriter, r *http.Request) {
err = json.NewEncoder(w).Encode(&user.ID)
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
}
}
// PasswordChange auth user, changes users password for a new one.
func (a *Auth) PasswordChange(w http.ResponseWriter, r *http.Request) {
// ChangePassword auth user, changes users password for a new one.
func (a *Auth) ChangePassword(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
@ -140,8 +148,6 @@ func (a *Auth) PasswordChange(w http.ResponseWriter, r *http.Request) {
NewPassword string `json:"newPassword"`
}
ctx = a.authorize(ctx, r)
err = json.NewDecoder(r.Body).Decode(&passwordChange)
if err != nil {
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.
func (a *Auth) serveJSONError(w http.ResponseWriter, status int, err error) {
w.WriteHeader(status)
@ -167,19 +277,6 @@ func (a *Auth) serveJSONError(w http.ResponseWriter, status int, err error) {
err = json.NewEncoder(w).Encode(response)
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"
"time"
"github.com/gorilla/mux"
"github.com/graphql-go/graphql"
"github.com/skyrings/skyring-common/tools/uuid"
"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() + "/"
}
router := http.NewServeMux()
router := mux.NewRouter()
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)
authController := consoleapi.NewAuth(logger, service, mailService, config.ExternalAddress)
router.Handle("/api/v0/payments/cards", http.HandlerFunc(paymentController.AddCreditCard))
router.Handle("/api/v0/payments/account/balance", http.HandlerFunc(paymentController.AccountBalance))
router.Handle("/api/v0/payments/account", http.HandlerFunc(paymentController.SetupAccount))
router.HandleFunc("/api/v0/graphql", server.grapqlHandler)
router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler)
router.HandleFunc("/robots.txt", server.seoHandler)
router.Handle("/api/v0/register", http.HandlerFunc(authController.Register))
router.Handle("/api/v0/token", http.HandlerFunc(authController.Token))
router.Handle("/api/v0/passwordChange", http.HandlerFunc(authController.PasswordChange))
authRouter := router.PathPrefix("/api/auth").Subrouter()
authRouter.Use()
router.Handle("/api/v0/graphql", http.HandlerFunc(server.grapqlHandler))
router.Handle("/registrationToken/", http.HandlerFunc(server.createRegistrationTokenHandler))
router.Handle("/robots.txt", http.HandlerFunc(server.seoHandler))
authRouter.HandleFunc("/token", authController.Token).Methods("POST")
authRouter.HandleFunc("/register", authController.Register).Methods("POST")
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 != "" {
router.Handle("/activation/", http.HandlerFunc(server.accountActivationHandler))
router.Handle("/password-recovery/", http.HandlerFunc(server.passwordRecoveryHandler))
router.Handle("/cancel-password-recovery/", http.HandlerFunc(server.cancelPasswordRecoveryHandler))
router.Handle("/usage-report/", http.HandlerFunc(server.bucketUsageReportHandler))
router.Handle("/static/", server.gzipHandler(http.StripPrefix("/static", fs)))
router.Handle("/", http.HandlerFunc(server.appHandler))
router.HandleFunc("/activation/", server.accountActivationHandler)
router.HandleFunc("/password-recovery/", server.passwordRecoveryHandler)
router.HandleFunc("/cancel-password-recovery/", server.cancelPasswordRecoveryHandler)
router.HandleFunc("/usage-report/", server.bucketUsageReportHandler)
router.PathPrefix("/static/").Handler(server.gzipHandler(http.StripPrefix("/static", fs)))
router.PathPrefix("/").Handler(http.HandlerFunc(server.appHandler))
}
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
func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()