satellite/console: retrieve a web UI session using a API key

retrieve a web UI session using a API key and add new test

Issue https://github.com/storj/storj/issues/5688

Change-Id: I0ad9c783f0573b87b212b8fb065cdbf7074b782c
This commit is contained in:
Lizzy Thomson 2023-04-25 14:06:58 -06:00
parent 81b2b067e6
commit 7e69b22dd4
4 changed files with 105 additions and 0 deletions

View File

@ -125,6 +125,49 @@ func (a *Auth) Token(w http.ResponseWriter, r *http.Request) {
}
}
// TokenByAPIKey authenticates user by API key and returns auth token.
func (a *Auth) TokenByAPIKey(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
authToken := r.Header.Get("Authorization")
split := strings.Split(authToken, "Bearer ")
if len(split) != 2 {
a.log.Info("authorization key format is incorrect. Should be 'Bearer <key>'")
a.serveJSONError(w, err)
return
}
apiKey := split[1]
userAgent := r.UserAgent()
ip, err := web.GetRequestIP(r)
if err != nil {
a.serveJSONError(w, err)
return
}
tokenInfo, err := a.service.TokenByAPIKey(ctx, userAgent, ip, apiKey)
if err != nil {
a.log.Info("Error authenticating token request", zap.Error(ErrAuthAPI.Wrap(err)))
a.serveJSONError(w, err)
return
}
a.cookieAuth.SetTokenCookie(w, *tokenInfo)
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(struct {
console.TokenInfo
Token string `json:"token"`
}{*tokenInfo, tokenInfo.Token.String()})
if err != nil {
a.log.Error("token handler could not encode token response", zap.Error(ErrAuthAPI.Wrap(err)))
return
}
}
// getSessionID gets the session ID from the request.
func (a *Auth) getSessionID(r *http.Request) (id uuid.UUID, err error) {

View File

@ -329,6 +329,42 @@ returned response:
}
}
func TestTokenByAPIKeyEndpoint(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
satellite := planet.Satellites[0]
restKeys := satellite.API.REST.Keys
user, err := satellite.AddUser(ctx, console.CreateUser{
FullName: "Test User",
Email: "test@mail.test",
}, 1)
require.NoError(t, err)
expires := 5 * time.Hour
apiKey, _, err := restKeys.Create(ctx, user.ID, expires)
require.NoError(t, err)
require.NotEmpty(t, apiKey)
url := planet.Satellites[0].ConsoleURL() + "/api/v0/auth/token-by-api-key"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
require.NoError(t, err)
req.Header.Set("Authorization", "Bearer "+apiKey)
response, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NotEmpty(t, response)
require.NoError(t, response.Body.Close())
cookies := response.Cookies()
require.NoError(t, err)
require.Len(t, cookies, 1)
require.Equal(t, "_tokenKey", cookies[0].Name)
require.NotEmpty(t, cookies[0].Value)
})
}
func TestMFAEndpoints(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,

View File

@ -285,6 +285,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
authRouter.Handle("/mfa/generate-recovery-codes", server.withAuth(http.HandlerFunc(authController.GenerateMFARecoveryCodes))).Methods(http.MethodPost)
authRouter.Handle("/logout", server.withAuth(http.HandlerFunc(authController.Logout))).Methods(http.MethodPost)
authRouter.Handle("/token", server.ipRateLimiter.Limit(http.HandlerFunc(authController.Token))).Methods(http.MethodPost)
authRouter.Handle("/token-by-api-key", server.ipRateLimiter.Limit(http.HandlerFunc(authController.TokenByAPIKey))).Methods(http.MethodPost)
authRouter.Handle("/register", server.ipRateLimiter.Limit(http.HandlerFunc(authController.Register))).Methods(http.MethodPost, http.MethodOptions)
authRouter.Handle("/forgot-password", server.ipRateLimiter.Limit(http.HandlerFunc(authController.ForgotPassword))).Methods(http.MethodPost)
authRouter.Handle("/resend-email/{email}", server.ipRateLimiter.Limit(http.HandlerFunc(authController.ResendEmail))).Methods(http.MethodPost)

View File

@ -57,6 +57,9 @@ const (
emailNotFoundErrMsg = "There are no users with the specified email"
passwordRecoveryTokenIsExpiredErrMsg = "Your password recovery link has expired, please request another one"
credentialsErrMsg = "Your login credentials are incorrect, please try again"
generateSessionTokenErrMsg = "Failed to generate session token"
failedToRetrieveUserErrMsg = "Failed to retrieve user from database"
apiKeyCredentialsErrMsg = "Your API Key is incorrect"
changePasswordErrMsg = "Your old password is incorrect, please try again"
passwordTooShortErrMsg = "Your password needs to be at least %d characters long"
passwordTooLongErrMsg = "Your password must be no longer than %d characters"
@ -1206,6 +1209,28 @@ func (s *Service) Token(ctx context.Context, request AuthUser) (response *TokenI
return response, nil
}
// TokenByAPIKey authenticates User by API Key and returns session token.
func (s *Service) TokenByAPIKey(ctx context.Context, userAgent string, ip string, apiKey string) (response *TokenInfo, err error) {
defer mon.Task()(&ctx)(&err)
userID, _, err := s.restKeys.GetUserAndExpirationFromKey(ctx, apiKey)
if err != nil {
return nil, ErrUnauthorized.New(apiKeyCredentialsErrMsg)
}
user, err := s.store.Users().Get(ctx, userID)
if err != nil {
return nil, Error.New(failedToRetrieveUserErrMsg)
}
response, err = s.GenerateSessionToken(ctx, user.ID, user.Email, ip, userAgent)
if err != nil {
return nil, Error.New(generateSessionTokenErrMsg)
}
return response, nil
}
// UpdateUsersFailedLoginState updates User's failed login state.
func (s *Service) UpdateUsersFailedLoginState(ctx context.Context, user *User) (err error) {
defer mon.Task()(&ctx)(&err)