satellite/console: forgot password, resend email endpoints added, default http route replaced with gorilla mux (#3327)
This commit is contained in:
parent
1014d5a7d1
commit
6dd478e43c
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user