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
|
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)
|
|
||||||
}
|
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user