From c195fcd9f34c75677861971e24b32b4ce6d35872 Mon Sep 17 00:00:00 2001 From: jay-dee7 Date: Fri, 1 Apr 2022 12:17:14 +0530 Subject: [PATCH 1/3] Fix: Final changes before release Signed-off-by: jay-dee7 --- auth/auth.go | 3 +- auth/invites.go | 46 +++++++++++++++- auth/jwt.go | 4 +- auth/reset_password.go | 85 ++++++++++++++++++++++++------ auth/signin.go | 38 +++++++++---- auth/signup.go | 31 +++++++++-- auth/verify_email.go | 35 ++++++++++++ config/config.go | 4 +- router/helpers.go | 2 +- services/email/createEmail.go | 15 ++++-- services/email/email.go | 12 ++--- services/email/welcome_email.go | 5 +- store/postgres/container_image.go | 2 +- store/postgres/postgres.go | 5 ++ store/postgres/queries/registry.go | 2 +- store/postgres/queries/users.go | 10 ++-- store/postgres/users.go | 18 ++++++- 17 files changed, 258 insertions(+), 59 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 6df52067..1451d181 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -30,6 +30,7 @@ type Authentication interface { RenewAccessToken(ctx echo.Context) error VerifyEmail(ctx echo.Context) error ResetPassword(ctx echo.Context) error + ForgotPassword(ctx echo.Context) error Invites(ctx echo.Context) error } @@ -48,7 +49,7 @@ func New( } ghClient := gh.NewClient(nil) - emailClient := email.New(c.Email, c.Endpoint()) + emailClient := email.New(c.Email, c.WebAppEndpoint) a := &auth{ c: c, diff --git a/auth/invites.go b/auth/invites.go index f7fb01bc..bb04f67c 100644 --- a/auth/invites.go +++ b/auth/invites.go @@ -2,7 +2,9 @@ package auth import ( "encoding/json" + "fmt" "net/http" + "regexp" "strings" "github.com/labstack/echo/v4" @@ -22,7 +24,24 @@ func (a *auth) Invites(ctx echo.Context) error { "msg": "error decode body, expecting and array of emails", }) } - err = a.emailClient.WelcomeEmail(strings.Split(list.Emails, ",")) + + if list.Emails == "" { + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": "cannot send empty list", + }) + } + + emails := strings.Split(list.Emails, ",") + + if err = a.validateEmailList(emails); err != nil { + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + }) + } + + err = a.emailClient.WelcomeEmail(emails) if err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ @@ -36,3 +55,28 @@ func (a *auth) Invites(ctx echo.Context) error { "msg": "success", }) } + +func (a *auth) validateEmailList(emails []string) error { + for _, email := range emails { + if err := a.verifyEmail(email); err != nil { + return fmt.Errorf("ERR_INVALID_EMAIL: %s", email) + } + } + + return nil +} + +func (a *auth) verifyEmail(email string) error { + if email == "" { + return fmt.Errorf("email can not be empty") + } + + emailReg := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}" + + "[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") + + if !emailReg.Match([]byte(email)) { + return fmt.Errorf("email format invalid") + } + + return nil +} diff --git a/auth/jwt.go b/auth/jwt.go index 3cf4e23c..9448a7fa 100644 --- a/auth/jwt.go +++ b/auth/jwt.go @@ -234,7 +234,7 @@ claims format func (a *auth) createClaims(id, tokenType string, acl AccessList) Claims { - var tokenLife int64 + tokenLife := time.Now().Add(time.Minute * 10).Unix() switch tokenType { case "access": tokenLife = time.Now().Add(time.Hour).Unix() @@ -242,6 +242,8 @@ func (a *auth) createClaims(id, tokenType string, acl AccessList) Claims { tokenLife = time.Now().Add(time.Hour * 750).Unix() case "service": tokenLife = time.Now().Add(time.Hour * 750).Unix() + case "short-lived": + tokenLife = time.Now().Add(time.Minute * 30).Unix() } claims := Claims{ diff --git a/auth/reset_password.go b/auth/reset_password.go index 8c6f2efd..dc21029f 100644 --- a/auth/reset_password.go +++ b/auth/reset_password.go @@ -2,12 +2,14 @@ package auth import ( "encoding/json" + "errors" "fmt" "net/http" "github.com/containerish/OpenRegistry/services/email" "github.com/containerish/OpenRegistry/types" "github.com/golang-jwt/jwt" + "github.com/jackc/pgx/v4" "github.com/labstack/echo/v4" ) @@ -40,24 +42,8 @@ func (a *auth) ResetPassword(ctx echo.Context) error { } var pwd *types.Password - kind := ctx.QueryParam("kind") - if kind == "forgot" { - if err = a.emailClient.SendEmail(user, token.Raw, email.ResetPasswordEmailKind); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), - "msg": "error sending reset password link", - }) - } - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusAccepted, echo.Map{ - "msg": "success", - }) - - } - err = json.NewDecoder(ctx.Request().Body).Decode(&pwd) if err != nil { a.logger.Log(ctx, err) @@ -66,6 +52,7 @@ func (a *auth) ResetPassword(ctx echo.Context) error { "msg": "request body could not be decoded", }) } + _ = ctx.Request().Body.Close() if kind == "forgot_password_callback" { hashPassword, err := a.hashPassword(pwd.NewPassword) @@ -76,6 +63,15 @@ func (a *auth) ResetPassword(ctx echo.Context) error { }) } + if user.Password == hashPassword { + err = fmt.Errorf("new password can not be same as old password") + a.logger.Log(ctx, err) + // error is already user friendly + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "message": err.Error(), + }) + } + if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ @@ -83,7 +79,9 @@ func (a *auth) ResetPassword(ctx echo.Context) error { }) } - return ctx.NoContent(http.StatusOK) + return ctx.JSON(http.StatusAccepted, echo.Map{ + "message": "password changed successfully", + }) } if !a.verifyPassword(user.Password, pwd.OldPassword) { @@ -123,3 +121,56 @@ func (a *auth) ResetPassword(ctx echo.Context) error { "msg": "success", }) } + +func (a *auth) ForgotPassword(ctx echo.Context) error { + userEmail := ctx.QueryParam("email") + if err := a.verifyEmail(userEmail); err != nil { + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "email is invalid", + }) + } + + user, err := a.pgStore.GetUser(ctx.Request().Context(), userEmail, false) + if err != nil { + if errors.Unwrap(err) == pgx.ErrNoRows { + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "user does not exist with this email", + }) + } + + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "message": err.Error(), + }) + } + + if !user.IsActive { + return ctx.JSON(http.StatusUnauthorized, echo.Map{ + "message": "account is inactive, please check your email and verify your account", + }) + } + + token, err := a.newWebLoginToken(user.Id, user.Username, "service") + if err != nil { + return ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "ERR_GENERATE_RESET_PASSWORD_TOKEN", + }) + } + + if err = a.emailClient.SendEmail(user, token, email.ResetPasswordEmailKind); err != nil { + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "error sending password reset link", + }) + } + + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusAccepted, echo.Map{ + "message": "a password reset link has been sent to your email", + }) + +} diff --git a/auth/signin.go b/auth/signin.go index 70abd07b..b2eb9edd 100644 --- a/auth/signin.go +++ b/auth/signin.go @@ -2,12 +2,14 @@ package auth import ( "encoding/json" + "errors" "fmt" "net/http" "time" "github.com/containerish/OpenRegistry/types" "github.com/google/uuid" + "github.com/jackc/pgx/v4" "github.com/labstack/echo/v4" ) @@ -18,15 +20,17 @@ func (a *auth) SignIn(ctx echo.Context) error { if err := json.NewDecoder(ctx.Request().Body).Decode(&user); err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "invalid JSON object", }) } if err := user.Validate(); err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - "code": "INVALID_CREDENTIALS", + "error": err.Error(), + "message": "invalid data provided for user login", + "code": "INVALID_CREDENTIALS", }) } @@ -38,36 +42,48 @@ func (a *auth) SignIn(ctx echo.Context) error { userFromDb, err := a.pgStore.GetUser(ctx.Request().Context(), key, true) if err != nil { a.logger.Log(ctx, err) + + if errors.Unwrap(err) == pgx.ErrNoRows { + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "user not found", + }) + } + return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - "msg": "error while get user", + "message": err.Error(), }) } if !userFromDb.IsActive { return ctx.JSON(http.StatusUnauthorized, echo.Map{ - "error": "account is inactive, please check your email and verify your account", + "message": "account is inactive, please check your email and verify your account", }) } if !a.verifyPassword(userFromDb.Password, user.Password) { - errMsg := fmt.Errorf("invalid password") - a.logger.Log(ctx, errMsg) - return ctx.JSON(http.StatusUnauthorized, errMsg) + err = fmt.Errorf("password is wrong") + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusUnauthorized, echo.Map{ + "message": err.Error(), + }) } access, err := a.newWebLoginToken(userFromDb.Id, userFromDb.Username, "access") if err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "ERR_WEB_LOGIN_TOKEN", }) } + refresh, err := a.newWebLoginToken(userFromDb.Id, userFromDb.Username, "refresh") if err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "ERR_WEB_LOGIN_REFRESH_TOKEN", }) } diff --git a/auth/signup.go b/auth/signup.go index 97bf4fd1..8a7e1907 100644 --- a/auth/signup.go +++ b/auth/signup.go @@ -11,6 +11,7 @@ import ( "github.com/containerish/OpenRegistry/config" "github.com/containerish/OpenRegistry/services/email" + "github.com/containerish/OpenRegistry/store/postgres" "github.com/containerish/OpenRegistry/types" "github.com/google/uuid" "github.com/labstack/echo/v4" @@ -39,8 +40,9 @@ func (a *auth) SignUp(ctx echo.Context) error { if err := verifyPassword(u.Password); err != nil { a.logger.Log(ctx, err) + // err.Error() is already user friendly return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), + "message": err.Error(), }) } @@ -48,7 +50,8 @@ func (a *auth) SignUp(ctx echo.Context) error { if err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "internal server error: could not hash the password", }) } u.Password = passwordHash @@ -60,6 +63,9 @@ func (a *auth) SignUp(ctx echo.Context) error { Id: uuid.NewString(), } + newUser.Hireable = false + newUser.HTMLURL = "" + if a.c.Environment == config.CI { newUser.IsActive = true } @@ -67,8 +73,23 @@ func (a *auth) SignUp(ctx echo.Context) error { err = a.pgStore.AddUser(ctx.Request().Context(), newUser) if err != nil { a.logger.Log(ctx, err) + if strings.Contains(err.Error(), postgres.ErrDuplicateConstraintUsername) { + return ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "username already exists", + }) + } + + if strings.Contains(err.Error(), postgres.ErrDuplicateConstraintEmail) { + return ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "this email already taken, try sign in?", + }) + } + return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "could not persist the user", }) } @@ -93,7 +114,8 @@ func (a *auth) SignUp(ctx echo.Context) error { if err = a.emailClient.SendEmail(newUser, token, email.VerifyEmailKind); err != nil { ctx.Set(types.HttpEndpointErrorKey, err.Error()) return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "could not send verify link, please reach out to OpenRegistry Team", }) } @@ -173,5 +195,6 @@ func verifyPassword(password string) error { if len(errorString) != 0 { return fmt.Errorf(errorString) } + return nil } diff --git a/auth/verify_email.go b/auth/verify_email.go index fe9a6936..25693b1d 100644 --- a/auth/verify_email.go +++ b/auth/verify_email.go @@ -1,6 +1,7 @@ package auth import ( + "fmt" "net/http" "time" @@ -62,6 +63,40 @@ func (a *auth) VerifyEmail(ctx echo.Context) error { }) } + access, err := a.newWebLoginToken(userId, user.Username, "access") + if err != nil { + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + }) + } + refresh, err := a.newWebLoginToken(userId, user.Username, "refresh") + if err != nil { + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + }) + } + + id := uuid.NewString() + if err = a.pgStore.AddSession(ctx.Request().Context(), id, refresh, user.Username); err != nil { + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "ERR_CREATING_SESSION", + }) + } + + sessionId := fmt.Sprintf("%s:%s", id, userId) + sessionCookie := a.createCookie("session_id", sessionId, false, time.Now().Add(time.Hour*750)) + accessCookie := a.createCookie("access", access, true, time.Now().Add(time.Hour)) + refreshCookie := a.createCookie("refresh", refresh, true, time.Now().Add(time.Hour*750)) + + a.logger.Log(ctx, err) + ctx.SetCookie(accessCookie) + ctx.SetCookie(refreshCookie) + ctx.SetCookie(sessionCookie) + return ctx.JSON(http.StatusOK, echo.Map{ "message": "user profile activated successfully", }) diff --git a/config/config.go b/config/config.go index 458ae642..88755bf8 100644 --- a/config/config.go +++ b/config/config.go @@ -16,11 +16,11 @@ type ( LogConfig *Log `mapstructure:"log_service"` SkynetConfig *Skynet `mapstructure:"skynet"` OAuth *OAuth `mapstructure:"oauth"` + Email *Email `mapstructure:"email"` Environment string `mapstructure:"environment"` WebAppEndpoint string `mapstructure:"web_app_url"` WebAppRedirectURL string `mapstructure:"web_app_redirect_url"` Debug bool `mapstructure:"debug"` - Email *Email `mapstructure:"email"` } Registry struct { @@ -71,12 +71,12 @@ type ( } Email struct { - Enabled bool `mapstructure:"enabled"` ApiKey string `mapstructure:"api_key"` SendAs string `mapstructure:"send_as"` VerifyEmailTemplateId string `mapstructure:"verify_template_id"` ForgotPasswordTemplateId string `mapstructure:"forgot_password_template_id"` WelcomeEmailTemplateId string `mapstructure:"welcome_template_id"` + Enabled bool `mapstructure:"enabled"` } ) diff --git a/router/helpers.go b/router/helpers.go index 3b830f5a..e981fee5 100644 --- a/router/helpers.go +++ b/router/helpers.go @@ -23,5 +23,5 @@ func RegisterAuthRoutes(authRouter *echo.Group, authSvc auth.Authentication) { authRouter.Add(http.MethodDelete, "/sessions", authSvc.ExpireSessions) authRouter.Add(http.MethodGet, "/renew", authSvc.RenewAccessToken) authRouter.Add(http.MethodPost, "/reset-password", authSvc.ResetPassword, authSvc.JWT()) - + authRouter.Add(http.MethodGet, "/forgot-password", authSvc.ForgotPassword) } diff --git a/services/email/createEmail.go b/services/email/createEmail.go index 9d09dd7f..ae3b05a8 100644 --- a/services/email/createEmail.go +++ b/services/email/createEmail.go @@ -14,18 +14,23 @@ func (e *email) CreateEmail(u *types.User, kind EmailKind, token string) (*mail. mailReq.To = append(mailReq.To, u.Email) mailReq.Data.Username = u.Username + name := u.Username + if u.Name != "" { + name = u.Name + } + switch kind { case VerifyEmailKind: m.SetTemplateID(e.config.VerifyEmailTemplateId) - mailReq.Name = "OpenRegistry" + mailReq.Name = name mailReq.Subject = "Verify Email" - mailReq.Data.Link = fmt.Sprintf("%s/auth/signup/verify?token=%s", e.backendEndpoint, token) + mailReq.Data.Link = fmt.Sprintf("%s/auth/verify?token=%s", e.baseURL, token) case ResetPasswordEmailKind: m.SetTemplateID(e.config.ForgotPasswordTemplateId) - mailReq.Name = "OpenRegistry" + mailReq.Name = name mailReq.Subject = "Forgot Password" - mailReq.Data.Link = fmt.Sprintf("%s/auth/reset-password?token=%s", e.backendEndpoint, token) + mailReq.Data.Link = fmt.Sprintf("%s/auth/forgot-password?token=%s", e.baseURL, token) default: return nil, fmt.Errorf("incorrect email kind") @@ -36,7 +41,7 @@ func (e *email) CreateEmail(u *types.User, kind EmailKind, token string) (*mail. p := mail.NewPersonalization() tos := []*mail.Email{ - mail.NewEmail(mailReq.To[0], mailReq.To[0]), + mail.NewEmail(mailReq.Name, mailReq.To[0]), } p.AddTos(tos...) diff --git a/services/email/email.go b/services/email/email.go index 8b71fca9..2848a3d1 100644 --- a/services/email/email.go +++ b/services/email/email.go @@ -8,9 +8,9 @@ import ( ) type email struct { - client *sendgrid.Client - config *config.Email - backendEndpoint string + client *sendgrid.Client + config *config.Email + baseURL string } type MailType int @@ -23,9 +23,9 @@ type MailData struct { type Mail struct { Data MailData Name string - To []string Subject string Body string + To []string Mtype MailType } @@ -35,7 +35,7 @@ type MailService interface { WelcomeEmail(list []string) error } -func New(config *config.Email, backendEndpoint string) MailService { +func New(config *config.Email, baseURL string) MailService { client := sendgrid.NewSendClient(config.ApiKey) - return &email{client: client, config: config, backendEndpoint: backendEndpoint} + return &email{client: client, config: config, baseURL: baseURL} } diff --git a/services/email/welcome_email.go b/services/email/welcome_email.go index b6c68a4f..9dc346c7 100644 --- a/services/email/welcome_email.go +++ b/services/email/welcome_email.go @@ -13,9 +13,8 @@ func (e *email) WelcomeEmail(list []string) error { m := mail.NewV3Mail() m.SetTemplateID(e.config.WelcomeEmailTemplateId) - mailReq.Name = "OpenRegistry" mailReq.Subject = "Welcome to OpenRegistry" - mailReq.Data.Link = fmt.Sprintf("%s/send-email/welcome", e.backendEndpoint) + mailReq.Data.Link = fmt.Sprintf("%s/send-email/welcome", e.baseURL) email := mail.NewEmail(mailReq.Name, e.config.SendAs) m.SetFrom(email) @@ -25,8 +24,10 @@ func (e *email) WelcomeEmail(list []string) error { for _, v := range list { tos = append(tos, mail.NewEmail(v, v)) } + p.AddTos(tos...) m.AddPersonalizations(p) + p.Subject = mailReq.Subject resp, err := e.client.Send(m) if err != nil && resp.StatusCode != http.StatusAccepted { diff --git a/store/postgres/container_image.go b/store/postgres/container_image.go index 15a60ec2..d8a066c7 100644 --- a/store/postgres/container_image.go +++ b/store/postgres/container_image.go @@ -333,7 +333,7 @@ func (p *pg) GetCatalogDetail( } if ns != "" { - rows, err = p.conn.Query(childCtx, q, ns+"/%", sortBy, ps, offset) + rows, err = p.conn.Query(childCtx, q, ns+"/%", ps, offset) if err != nil { err = fmt.Errorf("ERR_USER_CATALOG: %w", err) } diff --git a/store/postgres/postgres.go b/store/postgres/postgres.go index 531c3d04..d9b90b3c 100644 --- a/store/postgres/postgres.go +++ b/store/postgres/postgres.go @@ -96,3 +96,8 @@ func New(cfg *config.Store) (PersistentStore, error) { color.Green("connection to database successful") return &pg{conn: conn}, nil } + +const ( + ErrDuplicateConstraintUsername = "username_key" + ErrDuplicateConstraintEmail = "email_key" +) diff --git a/store/postgres/queries/registry.go b/store/postgres/queries/registry.go index 474c512e..7aace810 100644 --- a/store/postgres/queries/registry.go +++ b/store/postgres/queries/registry.go @@ -39,7 +39,7 @@ var ( // be very careful using this one GetCatalogDetailWithPagination = `select namespace,created_at::timestamptz,updated_at::timestamptz from image_manifest order by %s limit $1 offset $2;` GetUserCatalogDetailWithPagination = `select namespace,created_at::timestamptz,updated_at::timestamptz from - image_manifest where namespace like $1 order by %s limit $3 offset $4;` + image_manifest where namespace like $1 order by %s limit $2 offset $3;` GetRepoDetailWithPagination = `select reference, digest, sky_link, (select sum(size) from layer where digest = ANY(layers)) as size, created_at::timestamptz, updated_at::timestamptz from config where namespace=$1 limit $2 offset $3;` diff --git a/store/postgres/queries/users.go b/store/postgres/queries/users.go index 8b3135ec..4d5587cb 100644 --- a/store/postgres/queries/users.go +++ b/store/postgres/queries/users.go @@ -2,21 +2,21 @@ package queries var ( - AddUser = `insert into users (id, is_active, username, name, email, password, created_at, updated_at) -values ($1, $2, $3, $4, $5, $6, $7, $8);` + AddUser = `insert into users (id, is_active, username, name, email, password, hireable, html_url, created_at, updated_at) +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);` GetUser = `select id, is_active, username, email, created_at, updated_at from users where email=$1 or username=$1;` GetUserWithPassword = `select id, is_active, username, email, password, created_at, updated_at from users where email=$1 or username=$1;` GetUserById = `select id, is_active, username, email, created_at, updated_at from users where id=$1;` GetUserByIdWithPassword = `select id, is_active, username, email, password, created_at, updated_at from users where id=$1;` - GetUserWithSession = `select id, is_active, name, username, email, created_at, updated_at from users where id=(select owner from session where id=$1);` + GetUserWithSession = `select id, is_active, name, username, email, hireable, html_url, created_at, updated_at from users where id=(select owner from session where id=$1);` UpdateUser = `update users set is_active = $1, updated_at = $2 where id = $3;` SetUserActive = `update users set is_active=true where id=$1` DeleteUser = `delete from users where username = $1;` UpdateUserPwd = `update users set password=$1 where id=$2;` GetAllEmails = `select email from users;` - AddOAuthUser = `insert into users (id, username, email, created_at, updated_at, + AddOAuthUser = `insert into users (id, username, email, html_url, created_at, updated_at, bio, type, gravatar_id, login, name, node_id, avatar_url, oauth_id, is_active, hireable) -values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) on conflict (email) do update set username=$2, email=$3` +values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) on conflict (email) do update set username=$2, email=$3` ) var ( diff --git a/store/postgres/users.go b/store/postgres/users.go index ca2379c3..2cf8ae50 100644 --- a/store/postgres/users.go +++ b/store/postgres/users.go @@ -22,7 +22,20 @@ func (p *pg) AddUser(ctx context.Context, u *types.User) error { if u.Id == "" { u.Id = uuid.New().String() } - _, err := p.conn.Exec(childCtx, queries.AddUser, u.Id, u.IsActive, u.Username, u.Name, u.Email, u.Password, t, t) + _, err := p.conn.Exec( + childCtx, + queries.AddUser, + u.Id, + u.IsActive, + u.Username, + u.Name, + u.Email, + u.Password, + u.Hireable, + u.HTMLURL, + t, + t, + ) if err != nil { return fmt.Errorf("error adding user to database: %w", err) } @@ -47,6 +60,7 @@ func (p *pg) AddOAuthUser(ctx context.Context, u *types.User) error { id.String(), u.Username, u.Email, + u.HTMLURL, t, t, u.Bio, @@ -160,6 +174,8 @@ func (p *pg) GetUserWithSession(ctx context.Context, sessionId string) (*types.U &user.Name, &user.Username, &user.Email, + &user.Hireable, + &user.HTMLURL, &user.CreatedAt, &user.UpdatedAt, ); err != nil { From 9620cce16bf49f63569844893f3145e97874aa9e Mon Sep 17 00:00:00 2001 From: jay-dee7 Date: Sat, 2 Apr 2022 21:00:57 +0530 Subject: [PATCH 2/3] Add: Skynet HEAD Retry Signed-off-by: jay-dee7 --- auth/github.go | 2 +- auth/jwt.go | 6 +- auth/jwt_middleware.go | 1 + auth/reset_password.go | 40 ++++++------ auth/signin.go | 9 +-- registry/v2/extensions/catalog_detail.go | 3 +- registry/v2/registry.go | 6 +- router/router.go | 8 +-- scripts/mock-images.sh | 80 ++++++++++++++++++++++++ services/email/createEmail.go | 10 +-- services/email/welcome_email.go | 2 +- skynet/skynet.go | 19 ++++-- store/postgres/container_image.go | 32 ++++++---- store/postgres/postgres.go | 2 +- store/postgres/queries/registry.go | 3 +- 15 files changed, 163 insertions(+), 60 deletions(-) create mode 100644 scripts/mock-images.sh diff --git a/auth/github.go b/auth/github.go index 8267ef15..be2e18e9 100644 --- a/auth/github.go +++ b/auth/github.go @@ -101,7 +101,7 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error { val := fmt.Sprintf("%s:%s", sessionId, oauthUser.Id) sessionCookie := a.createCookie("session_id", val, false, time.Now().Add(time.Hour*750)) - accessCookie := a.createCookie("access", accessToken, true, time.Now().Add(time.Hour)) + accessCookie := a.createCookie("access", accessToken, true, time.Now().Add(time.Hour*750)) refreshCookie := a.createCookie("refresh", refreshToken, true, time.Now().Add(time.Hour*750)) ctx.SetCookie(accessCookie) diff --git a/auth/jwt.go b/auth/jwt.go index 9448a7fa..ec2379be 100644 --- a/auth/jwt.go +++ b/auth/jwt.go @@ -156,7 +156,7 @@ func (a *auth) createOAuthClaims(u types.User, token *oauth2.Token) PlatformClai OauthPayload: token, StandardClaims: jwt.StandardClaims{ Audience: a.c.Endpoint(), - ExpiresAt: time.Now().Add(time.Hour).Unix(), + ExpiresAt: time.Now().Add(time.Hour * 750).Unix(), Id: u.Id, IssuedAt: time.Now().Unix(), Issuer: a.c.Endpoint(), @@ -237,7 +237,9 @@ func (a *auth) createClaims(id, tokenType string, acl AccessList) Claims { tokenLife := time.Now().Add(time.Minute * 10).Unix() switch tokenType { case "access": - tokenLife = time.Now().Add(time.Hour).Unix() + // TODO (jay-dee7) + // token can live for month now, but must be addressed when we implement PASETO + tokenLife = time.Now().Add(time.Hour * 750).Unix() case "refresh": tokenLife = time.Now().Add(time.Hour * 750).Unix() case "service": diff --git a/auth/jwt_middleware.go b/auth/jwt_middleware.go index eb8cb5fd..2c6ba3ff 100644 --- a/auth/jwt_middleware.go +++ b/auth/jwt_middleware.go @@ -15,6 +15,7 @@ import ( const ( AccessCookieKey = "access" + RefreshCookKey = "refresh" QueryToken = "token" ) diff --git a/auth/reset_password.go b/auth/reset_password.go index dc21029f..43659eaf 100644 --- a/auth/reset_password.go +++ b/auth/reset_password.go @@ -48,18 +48,28 @@ func (a *auth) ResetPassword(ctx echo.Context) error { if err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - "msg": "request body could not be decoded", + "error": err.Error(), + "message": "request body could not be decoded", }) } _ = ctx.Request().Body.Close() + if err = verifyPassword(pwd.NewPassword); err != nil { + a.logger.Log(ctx, err) + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": `password must be alphanumeric, at least 8 chars long, must have at least one special character +and an uppercase letter`, + }) + } + if kind == "forgot_password_callback" { hashPassword, err := a.hashPassword(pwd.NewPassword) if err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "ERR_HASH_NEW_PASSWORD", }) } @@ -75,7 +85,8 @@ func (a *auth) ResetPassword(ctx echo.Context) error { if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "error updating new password", }) } @@ -84,19 +95,11 @@ func (a *auth) ResetPassword(ctx echo.Context) error { }) } - if !a.verifyPassword(user.Password, pwd.OldPassword) { - err = fmt.Errorf("passwords do not match") - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - }) - } - if pwd.OldPassword == pwd.NewPassword { err = fmt.Errorf("new password can not be same as old password") a.logger.Log(ctx, err) return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), + "message": err.Error(), }) } @@ -104,21 +107,22 @@ func (a *auth) ResetPassword(ctx echo.Context) error { if err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + "error": err.Error(), + "message": "ERR_HASH_NEW_PASSWORD", }) } if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, newHashedPwd); err != nil { a.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), - "msg": "error updating user in db", + "error": err.Error(), + "message": "error updating new password", }) } a.logger.Log(ctx, nil) return ctx.JSON(http.StatusAccepted, echo.Map{ - "msg": "success", + "message": "password changed successfully", }) } @@ -152,7 +156,7 @@ func (a *auth) ForgotPassword(ctx echo.Context) error { }) } - token, err := a.newWebLoginToken(user.Id, user.Username, "service") + token, err := a.newWebLoginToken(user.Id, user.Username, "short-lived") if err != nil { return ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), diff --git a/auth/signin.go b/auth/signin.go index b2eb9edd..24e86186 100644 --- a/auth/signin.go +++ b/auth/signin.go @@ -95,18 +95,19 @@ func (a *auth) SignIn(ctx echo.Context) error { "message": "ERR_CREATING_SESSION", }) } + sessionId := fmt.Sprintf("%s:%s", id, userFromDb.Id) sessionCookie := a.createCookie("session_id", sessionId, false, time.Now().Add(time.Hour*750)) - accessCookie := a.createCookie("access", access, true, time.Now().Add(time.Hour)) + accessCookie := a.createCookie("access", access, true, time.Now().Add(time.Hour*750)) refreshCookie := a.createCookie("refresh", refresh, true, time.Now().Add(time.Hour*750)) - a.logger.Log(ctx, err) - ctx.SetCookie(accessCookie) ctx.SetCookie(refreshCookie) ctx.SetCookie(sessionCookie) - return ctx.JSON(http.StatusOK, echo.Map{ + err = ctx.JSON(http.StatusOK, echo.Map{ "token": access, "refresh": refresh, }) + a.logger.Log(ctx, err) + return err } diff --git a/registry/v2/extensions/catalog_detail.go b/registry/v2/extensions/catalog_detail.go index b37e76af..1bcbbff4 100644 --- a/registry/v2/extensions/catalog_detail.go +++ b/registry/v2/extensions/catalog_detail.go @@ -61,7 +61,7 @@ func (ext *extension) CatalogDetail(ctx echo.Context) error { offset = o } - total, err := ext.store.GetCatalogCount(ctx.Request().Context()) + total, err := ext.store.GetCatalogCount(ctx.Request().Context(), namespace) if err != nil { ext.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()}) @@ -90,6 +90,7 @@ func (ext *extension) CatalogDetail(ctx echo.Context) error { }) } + ext.logger.Log(ctx, err) return ctx.JSON(http.StatusOK, echo.Map{ "repositories": catalogWithDetail, "total": total, diff --git a/registry/v2/registry.go b/registry/v2/registry.go index 4982aad3..6f891e1b 100644 --- a/registry/v2/registry.go +++ b/registry/v2/registry.go @@ -146,7 +146,8 @@ func (r *registry) Catalog(ctx echo.Context) error { "error": err.Error(), }) } - total, err := r.store.GetCatalogCount(ctx.Request().Context()) + // empty namespace to pull the full catalog list + total, err := r.store.GetCatalogCount(ctx.Request().Context(), "") if err != nil { r.logger.Log(ctx, err) return ctx.JSON(http.StatusInternalServerError, echo.Map{ @@ -852,7 +853,8 @@ func (r *registry) GetImageNamespace(ctx echo.Context) error { }) } - total, err := r.store.GetCatalogCount(ctx.Request().Context()) + // empty namespace to pull full catalog list + total, err := r.store.GetCatalogCount(ctx.Request().Context(), "") if err != nil { return ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), diff --git a/router/router.go b/router/router.go index 1156e4ac..f50098bc 100644 --- a/router/router.go +++ b/router/router.go @@ -61,7 +61,7 @@ func Register( RegisterNSRoutes(nsRouter, reg) RegisterAuthRoutes(authRouter, authSvc) - Extensions(v2Router, reg, ext) + Extensions(v2Router, reg, ext, authSvc.JWT()) } // RegisterNSRoutes is one of the helper functions to Register @@ -118,13 +118,13 @@ func RegisterNSRoutes(nsRouter *echo.Group, reg registry.Registry) { } // Extensions for teh OCI dist spec -func Extensions(group *echo.Group, reg registry.Registry, ext extensions.Extenion) { +func Extensions(group *echo.Group, reg registry.Registry, ext extensions.Extenion, middlewares ...echo.MiddlewareFunc) { // GET /v2/_catalog group.Add(http.MethodGet, Catalog, reg.Catalog) // Auto-complete image search group.Add(http.MethodGet, Search, reg.GetImageNamespace) - group.Add(http.MethodGet, CatalogDetail, ext.CatalogDetail) - group.Add(http.MethodGet, RepositoryDetail, ext.RepositoryDetail) + group.Add(http.MethodGet, CatalogDetail, ext.CatalogDetail, middlewares...) + group.Add(http.MethodGet, RepositoryDetail, ext.RepositoryDetail, middlewares...) } diff --git a/scripts/mock-images.sh b/scripts/mock-images.sh new file mode 100644 index 00000000..4cedbe35 --- /dev/null +++ b/scripts/mock-images.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +docker pull ubuntu +docker pull alpine +docker pull postgres +docker pull httpd +docker pull nginx +docker pull busybox +docker pull golang +docker pull traefik + +docker tag ubuntu 0.0.0.0:5000/johndoe/ubuntu +docker tag alpine 0.0.0.0:5000/johndoe/alpine +docker tag postgres 0.0.0.0:5000/johndoe/postgres +docker tag httpd 0.0.0.0:5000/johndoe/httpd +docker tag ubuntu 0.0.0.0:5000/johndoe/ubuntu:beta +docker tag alpine 0.0.0.0:5000/johndoe/alpine:beta +docker tag postgres 0.0.0.0:5000/johndoe/postgres:beta +docker tag httpd 0.0.0.0:5000/johndoe/httpd:beta +docker tag ubuntu 0.0.0.0:5000/johndoe/ubuntu:alpha +docker tag alpine 0.0.0.0:5000/johndoe/alpine:alpha +docker tag postgres 0.0.0.0:5000/johndoe/postgres:alpha +docker tag httpd 0.0.0.0:5000/johndoe/httpd:alpha +docker tag ubuntu 0.0.0.0:5000/johndoe/ubuntu:theta +docker tag alpine 0.0.0.0:5000/johndoe/alpine:theta +docker tag postgres 0.0.0.0:5000/johndoe/postgres:theta +docker tag httpd 0.0.0.0:5000/johndoe/httpd:theta + +docker tag nginx 0.0.0.0:5000/chucknorris/ubuntu +docker tag busybox 0.0.0.0:5000/chucknorris/busybox +docker tag golang 0.0.0.0:5000/chucknorris/go +docker tag traefik 0.0.0.0:5000/chucknorris/traefik +docker tag nginx 0.0.0.0:5000/chucknorris/ubuntu:gamma +docker tag busybox 0.0.0.0:5000/chucknorris/busybox:gamma +docker tag golang 0.0.0.0:5000/chucknorris/go:gamma +docker tag traefik 0.0.0.0:5000/chucknorris/traefik:gamma +docker tag nginx 0.0.0.0:5000/chucknorris/ubuntu:hulk +docker tag busybox 0.0.0.0:5000/chucknorris/busybox:hulk +docker tag golang 0.0.0.0:5000/chucknorris/go:hulk +docker tag traefik 0.0.0.0:5000/chucknorris/traefik:hulk +docker tag nginx 0.0.0.0:5000/chucknorris/ubuntu:nimrod +docker tag busybox 0.0.0.0:5000/chucknorris/busybox:nimrod +docker tag golang 0.0.0.0:5000/chucknorris/go:nimrod +docker tag traefik 0.0.0.0:5000/chucknorris/traefik:nimrod + +echo "Qwerty@123" | docker login 0.0.0.0:5000 --username johndoe --password-stdin +docker push 0.0.0.0:5000/johndoe/ubuntu +docker push 0.0.0.0:5000/johndoe/alpine +docker push 0.0.0.0:5000/johndoe/postgres +docker push 0.0.0.0:5000/johndoe/httpd +docker push 0.0.0.0:5000/johndoe/ubuntu:beta +docker push 0.0.0.0:5000/johndoe/alpine:beta +docker push 0.0.0.0:5000/johndoe/postgres:beta +docker push 0.0.0.0:5000/johndoe/httpd:beta +docker push 0.0.0.0:5000/johndoe/ubuntu:alpha +docker push 0.0.0.0:5000/johndoe/alpine:alpha +docker push 0.0.0.0:5000/johndoe/postgres:alpha +docker push 0.0.0.0:5000/johndoe/httpd:alpha +docker push 0.0.0.0:5000/johndoe/ubuntu:theta +docker push 0.0.0.0:5000/johndoe/alpine:theta +docker push 0.0.0.0:5000/johndoe/postgres:theta +docker push 0.0.0.0:5000/johndoe/httpd:theta + +echo "Qwerty@123" | docker login 0.0.0.0:5000 --username chucknorris --password-stdin +docker push 0.0.0.0:5000/chucknorris/ubuntu +docker push 0.0.0.0:5000/chucknorris/busybox +docker push 0.0.0.0:5000/chucknorris/golang +docker push 0.0.0.0:5000/chucknorris/traefik +docker push 0.0.0.0:5000/chucknorris/ubuntu:gamma +docker push 0.0.0.0:5000/chucknorris/busybox:gamma +docker push 0.0.0.0:5000/chucknorris/go:gamma +docker push 0.0.0.0:5000/chucknorris/traefik:gamma +docker push 0.0.0.0:5000/chucknorris/ubuntu:hulk +docker push 0.0.0.0:5000/chucknorris/busybox:hulk +docker push 0.0.0.0:5000/chucknorris/go:hulk +docker push 0.0.0.0:5000/chucknorris/traefik:hulk +docker push 0.0.0.0:5000/chucknorris/ubuntu:nimrod +docker push 0.0.0.0:5000/chucknorris/busybox:nimrod +docker push 0.0.0.0:5000/chucknorris/go:nimrod +docker push 0.0.0.0:5000/chucknorris/traefik:nimrod diff --git a/services/email/createEmail.go b/services/email/createEmail.go index ae3b05a8..f4bb9bc5 100644 --- a/services/email/createEmail.go +++ b/services/email/createEmail.go @@ -14,21 +14,15 @@ func (e *email) CreateEmail(u *types.User, kind EmailKind, token string) (*mail. mailReq.To = append(mailReq.To, u.Email) mailReq.Data.Username = u.Username - name := u.Username - if u.Name != "" { - name = u.Name - } - + mailReq.Name = "Team OpenRegistry" switch kind { case VerifyEmailKind: m.SetTemplateID(e.config.VerifyEmailTemplateId) - mailReq.Name = name mailReq.Subject = "Verify Email" mailReq.Data.Link = fmt.Sprintf("%s/auth/verify?token=%s", e.baseURL, token) case ResetPasswordEmailKind: m.SetTemplateID(e.config.ForgotPasswordTemplateId) - mailReq.Name = name mailReq.Subject = "Forgot Password" mailReq.Data.Link = fmt.Sprintf("%s/auth/forgot-password?token=%s", e.baseURL, token) @@ -41,7 +35,7 @@ func (e *email) CreateEmail(u *types.User, kind EmailKind, token string) (*mail. p := mail.NewPersonalization() tos := []*mail.Email{ - mail.NewEmail(mailReq.Name, mailReq.To[0]), + mail.NewEmail(u.Username, mailReq.To[0]), } p.AddTos(tos...) diff --git a/services/email/welcome_email.go b/services/email/welcome_email.go index 9dc346c7..df93a19a 100644 --- a/services/email/welcome_email.go +++ b/services/email/welcome_email.go @@ -16,7 +16,7 @@ func (e *email) WelcomeEmail(list []string) error { mailReq.Subject = "Welcome to OpenRegistry" mailReq.Data.Link = fmt.Sprintf("%s/send-email/welcome", e.baseURL) - email := mail.NewEmail(mailReq.Name, e.config.SendAs) + email := mail.NewEmail("Team OpenRegistry", e.config.SendAs) m.SetFrom(email) p := mail.NewPersonalization() diff --git a/skynet/skynet.go b/skynet/skynet.go index c3357007..3391d74e 100644 --- a/skynet/skynet.go +++ b/skynet/skynet.go @@ -101,12 +101,23 @@ func (c *Client) AddImage(ns string, mf, l map[string][]byte) (string, error) { } func (c *Client) Metadata(skylink string) (*skynet.Metadata, error) { - metadata, err := c.skynet.Metadata(skylink, skynet.DefaultMetadataOptions) - if err != nil { - return nil, fmt.Errorf("SKYNET_METADATA_ERR: %w", err) + retryCounter := 3 + + var err error + var metadata *skynet.Metadata + for i := retryCounter; retryCounter != 0; i-- { + metadata, err = c.skynet.Metadata(skylink, skynet.DefaultMetadataOptions) + if err != nil { + err = fmt.Errorf("SKYNET_METADATA_ERR: %w", err) + retryCounter-- + // cool off + time.Sleep(time.Second * 2) + continue + } + break } - return metadata, nil + return metadata, err } func NewHttpClientForSkynet() *http.Client { diff --git a/store/postgres/container_image.go b/store/postgres/container_image.go index d8a066c7..f696a2e1 100644 --- a/store/postgres/container_image.go +++ b/store/postgres/container_image.go @@ -253,16 +253,26 @@ func (p *pg) SetConfig(ctx context.Context, txn pgx.Tx, cfg types.ConfigV2) erro return nil } -func (p *pg) GetCatalogCount(ctx context.Context) (int64, error) { +func (p *pg) GetCatalogCount(ctx context.Context, ns string) (int64, error) { childCtx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() var count int64 - row := p.conn.QueryRow(childCtx, queries.GetCatalogCount) + if ns != "" { + row := p.conn.QueryRow(childCtx, queries.GetUserCatalogCount, ns+"/%") + if err := row.Scan(&count); err != nil { + return 0, fmt.Errorf("ERR_SCAN_CATALOG_COUNT: %w", err) + } + + return count, nil + } + + row := p.conn.QueryRow(childCtx, queries.GetCatalogCount) if err := row.Scan(&count); err != nil { return 0, fmt.Errorf("ERR_SCAN_CATALOG_COUNT: %w", err) } return count, nil + } func (p *pg) GetCatalog(ctx context.Context, ns string, pageSize, offset int64) ([]string, error) { @@ -322,27 +332,23 @@ func (p *pg) GetCatalogDetail( pageSize = ps } - q := fmt.Sprintf(queries.GetCatalogDetailWithPagination, sortBy) - if ns != "" { - q = fmt.Sprintf(queries.GetUserCatalogDetailWithPagination, sortBy) - } - - rows, err = p.conn.Query(childCtx, q, pageSize, offset) - if err != nil { - err = fmt.Errorf("ERR_CATALOG_WITH_PAGINATION: %w", err) - } - if ns != "" { + q := fmt.Sprintf(queries.GetUserCatalogDetailWithPagination, sortBy) rows, err = p.conn.Query(childCtx, q, ns+"/%", ps, offset) if err != nil { err = fmt.Errorf("ERR_USER_CATALOG: %w", err) } + } else { + q := fmt.Sprintf(queries.GetCatalogDetailWithPagination, sortBy) + rows, err = p.conn.Query(childCtx, q, pageSize, offset) + if err != nil { + err = fmt.Errorf("ERR_CATALOG_WITH_PAGINATION: %w", err) + } } if err != nil { return nil, err } - defer rows.Close() var catalog []*types.ImageManifestV2 diff --git a/store/postgres/postgres.go b/store/postgres/postgres.go index d9b90b3c..42fa7da6 100644 --- a/store/postgres/postgres.go +++ b/store/postgres/postgres.go @@ -57,7 +57,7 @@ type RegistryStore interface { ctx context.Context, namespace string, pageSize int64, offset int64, sortBy string, ) ([]*types.ImageManifestV2, error) GetRepoDetail(ctx context.Context, namespace string, pageSize int64, offset int64) (*types.Repository, error) - GetCatalogCount(ctx context.Context) (int64, error) + GetCatalogCount(ctx context.Context, ns string) (int64, error) GetImageNamespace(ctx context.Context, search string) ([]*types.ImageManifestV2, error) DeleteLayerV2(ctx context.Context, txn pgx.Tx, digest string) error DeleteBlobV2(ctx context.Context, txn pgx.Tx, digest string) error diff --git a/store/postgres/queries/registry.go b/store/postgres/queries/registry.go index 7aace810..b1724a42 100644 --- a/store/postgres/queries/registry.go +++ b/store/postgres/queries/registry.go @@ -29,7 +29,8 @@ var ( GetImageTags = `select reference from config where namespace=$1;` GetManifestByRef = `select * from config where namespace=$1 and reference=$2;` GetManifestByDig = `select * from config where namespace=$1 and digest=$2;` - GetCatalogCount = `select count(*) from image_manifest;` + GetCatalogCount = `select count(namespace) from image_manifest;` + GetUserCatalogCount = `select count(namespace) from image_manifest where namespace like $1;` GetCatalog = `select namespace from image_manifest;` GetCatalogWithPagination = `select namespace from image_manifest limit $1 offset $2;` GetUserCatalogWithPagination = `select namespace from image_manifest where namespace like $1 limit $2 offset $3;` From bb0651e454e4983bbe8cb06c44998c9f03e2ab77 Mon Sep 17 00:00:00 2001 From: guacamole Date: Sun, 3 Apr 2022 05:23:53 +0530 Subject: [PATCH 3/3] Add: enhanced logging Signed-off-by: guacamole --- auth/github.go | 58 +++++++---- auth/invites.go | 38 ++++--- auth/renew.go | 49 +++++---- auth/reset_password.go | 116 +++++++++++++-------- auth/sessions.go | 48 +++++---- auth/signin.go | 62 +++++++---- auth/signout.go | 25 +++-- auth/signup.go | 59 +++++++---- auth/token.go | 57 ++++++---- auth/user.go | 37 ++++--- auth/verify_email.go | 70 ++++++++----- registry/v2/blobs.go | 48 ++++++--- registry/v2/registry.go | 208 ++++++++++++++++++++++++------------- router/router.go | 5 + telemetry/consoleWriter.go | 15 +-- 15 files changed, 566 insertions(+), 329 deletions(-) diff --git a/auth/github.go b/auth/github.go index be2e18e9..b0c5c5c8 100644 --- a/auth/github.go +++ b/auth/github.go @@ -30,10 +30,13 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error { stateToken := ctx.FormValue("state") _, ok := a.oauthStateStore[stateToken] if !ok { - a.logger.Log(ctx, fmt.Errorf("missing or invalid state token")) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": "missing or invalid state token", + err := fmt.Errorf("INVALID_STATE_TOKEN") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "missing or invalid state token", }) + a.logger.Log(ctx, err) + return echoErr } // no need to compare the stateToken from QueryParam \w stateToken from a.oauthStateStore // the key is the actual token :p @@ -42,31 +45,37 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error { code := ctx.FormValue("code") token, err := a.github.Exchange(context.Background(), code) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - "code": "GITHUB_EXCHANGE_ERR", + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "github exchange error", + "code": "GITHUB_EXCHANGE_ERR", }) + a.logger.Log(ctx, err) + return echoErr } req, err := a.ghClient.NewRequest(http.MethodGet, "/user", nil) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusPreconditionFailed, echo.Map{ - "error": err.Error(), - "code": "GH_CLIENT_REQ_FAILED", + echoErr := ctx.JSON(http.StatusPreconditionFailed, echo.Map{ + "error": err.Error(), + "message": "github client request failed", + "code": "GH_CLIENT_REQ_FAILED", }) + a.logger.Log(ctx, err) + return echoErr } req.Header.Set("Authorization", "token "+token.AccessToken) var oauthUser types.User _, err = a.ghClient.Do(ctx.Request().Context(), req, &oauthUser) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), - "code": "GH_CLIENT_REQ_EXEC_FAILED", + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "github client request execution failed", + "code": "GH_CLIENT_REQ_EXEC_FAILED", }) + a.logger.Log(ctx, err) + return echoErr } oauthUser.Username = oauthUser.Login @@ -74,29 +83,32 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error { accessToken, refreshToken, err := a.SignOAuthToken(oauthUser, token) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "cause": "JWT_SIGNING", }) + a.logger.Log(ctx, err) + return echoErr } oauthUser.Password = refreshToken if err = a.pgStore.AddOAuthUser(ctx.Request().Context(), &oauthUser); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "code": "GH_OAUTH_STORE_OAUTH_USER", }) + a.logger.Log(ctx, err) + return echoErr } sessionId := uuid.NewString() if err = a.pgStore.AddSession(ctx.Request().Context(), sessionId, refreshToken, oauthUser.Username); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "ERR_CREATING_SESSION", }) + a.logger.Log(ctx, err) + return echoErr } val := fmt.Sprintf("%s:%s", sessionId, oauthUser.Id) @@ -107,8 +119,10 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error { ctx.SetCookie(accessCookie) ctx.SetCookie(refreshCookie) ctx.SetCookie(sessionCookie) + + err = ctx.Redirect(http.StatusTemporaryRedirect, a.c.WebAppRedirectURL) a.logger.Log(ctx, nil) - return ctx.Redirect(http.StatusTemporaryRedirect, a.c.WebAppRedirectURL) + return err } const ( diff --git a/auth/invites.go b/auth/invites.go index bb04f67c..30fa80a3 100644 --- a/auth/invites.go +++ b/auth/invites.go @@ -18,42 +18,50 @@ func (a *auth) Invites(ctx echo.Context) error { var list List err := json.NewDecoder(ctx.Request().Body).Decode(&list) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "msg": "error decode body, expecting and array of emails", }) + a.logger.Log(ctx, err) + return echoErr } if list.Emails == "" { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": "cannot send empty list", + err := fmt.Errorf("ERR_EMPTY_LIST") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err, + "message": "cannot send empty list", }) + a.logger.Log(ctx, err) + return echoErr } emails := strings.Split(list.Emails, ",") if err = a.validateEmailList(emails); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "error validating email list", }) + a.logger.Log(ctx, err) + return echoErr } err = a.emailClient.WelcomeEmail(emails) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), - "msg": "err sending invites", + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "err sending invites", }) + a.logger.Log(ctx, err) + return echoErr } - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusAccepted, echo.Map{ - "msg": "success", + err = ctx.JSON(http.StatusAccepted, echo.Map{ + "message": "invites sent successfully", }) + a.logger.Log(ctx, err) + return err } func (a *auth) validateEmailList(emails []string) error { diff --git a/auth/renew.go b/auth/renew.go index 11a8afe5..afe0ae66 100644 --- a/auth/renew.go +++ b/auth/renew.go @@ -16,17 +16,19 @@ func (a *auth) RenewAccessToken(ctx echo.Context) error { c, err := ctx.Cookie("refresh") if err != nil { if err == http.ErrNoCookie { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusUnauthorized, echo.Map{ + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ "error": err.Error(), "message": "Unauthorised", }) + a.logger.Log(ctx, err) + return echoErr } - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "error getting refresh cookie", }) + a.logger.Log(ctx, err) + return echoErr } refreshCookie := c.Value var claims Claims @@ -35,45 +37,56 @@ func (a *auth) RenewAccessToken(ctx echo.Context) error { }) if err != nil { if err == jwt.ErrSignatureInvalid { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusUnauthorized, echo.Map{ + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ "error": err.Error(), - "message": "Signature error, unauthorised", + "message": "signature error, unauthorised", }) + a.logger.Log(ctx, err) + return echoErr } - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), + + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "error parsing claims", }) + a.logger.Log(ctx, err) + return echoErr } if !tkn.Valid { - a.logger.Log(ctx, fmt.Errorf("invalid token, Unauthorised")) - return ctx.JSON(http.StatusUnauthorized, echo.Map{ - "error": "invalid token, unauthorised", + err := fmt.Errorf("invalid token, Unauthorised") + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ + "error": err.Error(), + "message": "invalid token, unauthorised", }) + a.logger.Log(ctx, err) + return echoErr } userId := claims.Id user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, false) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusUnauthorized, echo.Map{ + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ "error": err.Error(), "message": "user not found in database, unauthorised", }) + a.logger.Log(ctx, err) + return echoErr } tokenString, err := a.newWebLoginToken(userId, user.Username, "access") if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "error creating new web token", }) + a.logger.Log(ctx, err) + return echoErr } accessCookie := a.createCookie("access", tokenString, true, time.Now().Add(time.Hour)) ctx.SetCookie(accessCookie) - return ctx.NoContent(http.StatusNoContent) + err = ctx.NoContent(http.StatusNoContent) + a.logger.Log(ctx, err) + return err } diff --git a/auth/reset_password.go b/auth/reset_password.go index 43659eaf..8bd9126e 100644 --- a/auth/reset_password.go +++ b/auth/reset_password.go @@ -16,29 +16,35 @@ import ( func (a *auth) ResetPassword(ctx echo.Context) error { token, ok := ctx.Get("user").(*jwt.Token) if !ok { - err := fmt.Errorf("JWT token can not be empty") - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusUnauthorized, echo.Map{ - "error": err.Error(), + err := fmt.Errorf("ERR_EMPTY_TOKEN") + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ + "error": err.Error(), + "message": "JWT token can not be empty", }) + a.logger.Log(ctx, err) + return echoErr } c, ok := token.Claims.(*Claims) if !ok { - err := fmt.Errorf("invalid claims in JWT") - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + err := fmt.Errorf("ERR_INVALID_CLAIMS") + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "invalid claims in JWT", }) + a.logger.Log(ctx, err) + return echoErr } userId := c.Id user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, true) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusNotFound, echo.Map{ - "error": err.Error(), + echoErr := ctx.JSON(http.StatusNotFound, echo.Map{ + "error": err.Error(), + "message": "error getting user by ID from DB", }) + a.logger.Log(ctx, err) + return echoErr } var pwd *types.Password @@ -46,108 +52,127 @@ func (a *auth) ResetPassword(ctx echo.Context) error { err = json.NewDecoder(ctx.Request().Body).Decode(&pwd) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "request body could not be decoded", }) + a.logger.Log(ctx, err) + return echoErr } _ = ctx.Request().Body.Close() if err = verifyPassword(pwd.NewPassword); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": `password must be alphanumeric, at least 8 chars long, must have at least one special character and an uppercase letter`, }) + a.logger.Log(ctx, err) + return echoErr } if kind == "forgot_password_callback" { hashPassword, err := a.hashPassword(pwd.NewPassword) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "ERR_HASH_NEW_PASSWORD", }) + a.logger.Log(ctx, err) + return echoErr } if user.Password == hashPassword { err = fmt.Errorf("new password can not be same as old password") - a.logger.Log(ctx, err) // error is already user friendly - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), "message": err.Error(), }) + a.logger.Log(ctx, err) + return echoErr } if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "error updating new password", }) + a.logger.Log(ctx, err) + return echoErr } - return ctx.JSON(http.StatusAccepted, echo.Map{ + err = ctx.JSON(http.StatusAccepted, echo.Map{ "message": "password changed successfully", }) + a.logger.Log(ctx, err) + return err } if pwd.OldPassword == pwd.NewPassword { - err = fmt.Errorf("new password can not be same as old password") - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "message": err.Error(), + err = fmt.Errorf("OLD_NEW_PWD_SAME") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "new password can not be same as old password", }) + a.logger.Log(ctx, err) + return echoErr } newHashedPwd, err := a.hashPassword(pwd.NewPassword) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "ERR_HASH_NEW_PASSWORD", }) + a.logger.Log(ctx, err) + return echoErr } if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, newHashedPwd); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "error updating new password", }) + a.logger.Log(ctx, err) + return echoErr } - a.logger.Log(ctx, nil) - return ctx.JSON(http.StatusAccepted, echo.Map{ + err = ctx.JSON(http.StatusAccepted, echo.Map{ "message": "password changed successfully", }) + a.logger.Log(ctx, nil) + return err } func (a *auth) ForgotPassword(ctx echo.Context) error { userEmail := ctx.QueryParam("email") if err := a.verifyEmail(userEmail); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "email is invalid", }) + a.logger.Log(ctx, err) + return echoErr } user, err := a.pgStore.GetUser(ctx.Request().Context(), userEmail, false) if err != nil { if errors.Unwrap(err) == pgx.ErrNoRows { - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "user does not exist with this email", }) + a.logger.Log(ctx, err) + return echoErr } - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "message": err.Error(), + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "error get user from DB with this email", }) + a.logger.Log(ctx, err) + return echoErr } if !user.IsActive { @@ -158,23 +183,26 @@ func (a *auth) ForgotPassword(ctx echo.Context) error { token, err := a.newWebLoginToken(user.Id, user.Username, "short-lived") if err != nil { - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), - "message": "ERR_GENERATE_RESET_PASSWORD_TOKEN", + "message": "error generating reset password token", }) + a.logger.Log(ctx, err) + return echoErr } if err = a.emailClient.SendEmail(user, token, email.ResetPasswordEmailKind); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "error sending password reset link", }) + a.logger.Log(ctx, err) + return echoErr } - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusAccepted, echo.Map{ + err = ctx.JSON(http.StatusAccepted, echo.Map{ "message": "a password reset link has been sent to your email", }) - + a.logger.Log(ctx, err) + return err } diff --git a/auth/sessions.go b/auth/sessions.go index 4e80cf46..ec51c13d 100644 --- a/auth/sessions.go +++ b/auth/sessions.go @@ -1,6 +1,7 @@ package auth import ( + "fmt" "net/http" "strconv" "strings" @@ -17,18 +18,22 @@ func (a *auth) ExpireSessions(ctx echo.Context) error { cookie, err := ctx.Cookie("session_id") if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": err.Error(), - "msg": "error while getting cookie", + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "error while getting cookie", }) + a.logger.Log(ctx, err) + return echoErr } parts := strings.Split(cookie.Value, ":") if len(parts) != 2 { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": "invalid cookie", + err := fmt.Errorf("ERR_INVALID_COOKIE") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "invalid cookie", }) + a.logger.Log(ctx, err) + return echoErr } sessionID := parts[0] @@ -39,29 +44,32 @@ func (a *auth) ExpireSessions(ctx echo.Context) error { if queryParamDeleteAll != "" { deleteAllSessions, err = strconv.ParseBool(queryParamDeleteAll) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "delete_all must be a boolean", }) + a.logger.Log(ctx, err) + return echoErr } _, err := uuid.Parse(userId) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "invalid user id", }) + a.logger.Log(ctx, err) + return echoErr } if deleteAllSessions { err := a.pgStore.DeleteAllSessions(ctx.Request().Context(), userId) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "could not delete all sessions", }) + a.logger.Log(ctx, err) + return echoErr } } @@ -70,21 +78,25 @@ func (a *auth) ExpireSessions(ctx echo.Context) error { if sessionID != "" { _, err := uuid.Parse(sessionID) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "invalid session id", }) + a.logger.Log(ctx, err) + return echoErr } err = a.pgStore.DeleteSession(ctx.Request().Context(), sessionID, userId) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "could not delete session", }) + a.logger.Log(ctx, err) + return echoErr } } - return nil + err = ctx.NoContent(http.StatusAccepted) + a.logger.Log(ctx, err) + return err } diff --git a/auth/signin.go b/auth/signin.go index 24e86186..449b7c3b 100644 --- a/auth/signin.go +++ b/auth/signin.go @@ -18,20 +18,23 @@ func (a *auth) SignIn(ctx echo.Context) error { var user types.User if err := json.NewDecoder(ctx.Request().Body).Decode(&user); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "invalid JSON object", }) + a.logger.Log(ctx, err) + return echoErr } - if err := user.Validate(); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + err := user.Validate() + if err != nil { + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "invalid data provided for user login", "code": "INVALID_CREDENTIALS", }) + a.logger.Log(ctx, err) + return echoErr } key := user.Email @@ -41,59 +44,72 @@ func (a *auth) SignIn(ctx echo.Context) error { userFromDb, err := a.pgStore.GetUser(ctx.Request().Context(), key, true) if err != nil { - a.logger.Log(ctx, err) if errors.Unwrap(err) == pgx.ErrNoRows { - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "user not found", }) + a.logger.Log(ctx, err) + return echoErr } - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "message": err.Error(), + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "database error, failed to get user", }) + a.logger.Log(ctx, err) + return echoErr } if !userFromDb.IsActive { - return ctx.JSON(http.StatusUnauthorized, echo.Map{ - "message": "account is inactive, please check your email and verify your account", + err = fmt.Errorf("account is inactive, please check your email and verify your account") + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ + "error": "ERR_USER_INACTIVE", + "message": err.Error(), }) + a.logger.Log(ctx, err) + return echoErr } if !a.verifyPassword(userFromDb.Password, user.Password) { - err = fmt.Errorf("password is wrong") - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusUnauthorized, echo.Map{ + err = fmt.Errorf("password is incorrect") + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ + "error": "ERR_INCORRECT_PASSWORD", "message": err.Error(), }) + a.logger.Log(ctx, err) + return echoErr } access, err := a.newWebLoginToken(userFromDb.Id, userFromDb.Username, "access") if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), - "message": "ERR_WEB_LOGIN_TOKEN", + "message": "error creating web login token", }) + a.logger.Log(ctx, err) + return echoErr } refresh, err := a.newWebLoginToken(userFromDb.Id, userFromDb.Username, "refresh") if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), - "message": "ERR_WEB_LOGIN_REFRESH_TOKEN", + "message": "error creating refresh token", }) + a.logger.Log(ctx, err) + return echoErr } id := uuid.NewString() if err = a.pgStore.AddSession(ctx.Request().Context(), id, refresh, userFromDb.Username); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), - "message": "ERR_CREATING_SESSION", + "message": "error creating session", }) + a.logger.Log(ctx, err) + return echoErr } sessionId := fmt.Sprintf("%s:%s", id, userFromDb.Id) diff --git a/auth/signout.go b/auth/signout.go index 5060ed66..9cc573eb 100644 --- a/auth/signout.go +++ b/auth/signout.go @@ -15,35 +15,42 @@ func (a *auth) SignOut(ctx echo.Context) error { sessionCookie, err := ctx.Cookie("session_id") if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), - "message": "ERROR_GETTING_SESSION_ID_FOR_SIGN_OUT", + "message": "error getting session ID fro sign-out user", }) + a.logger.Log(ctx, err) + return echoErr } parts := strings.Split(sessionCookie.Value, ":") if len(parts) != 2 { - a.logger.Log(ctx, fmt.Errorf("invalid session id")) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": "INVALID_SESSION_ID", + err := fmt.Errorf("invalid session id") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": "INVALID_SESSION_ID", + "message": err, }) + a.logger.Log(ctx, err) + return echoErr } sessionId := parts[0] userId := parts[1] if err := a.pgStore.DeleteSession(ctx.Request().Context(), sessionId, userId); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "could not delete sessions", }) + a.logger.Log(ctx, err) + return echoErr } ctx.SetCookie(a.createCookie("access", "", true, time.Now().Add(-time.Hour))) ctx.SetCookie(a.createCookie("refresh", "", true, time.Now().Add(-time.Hour))) ctx.SetCookie(a.createCookie("session_id", "", true, time.Now().Add(-time.Hour))) - return ctx.JSON(http.StatusAccepted, echo.Map{ + err = ctx.JSON(http.StatusAccepted, echo.Map{ "message": "session deleted successfully", }) + a.logger.Log(ctx, err) + return err } diff --git a/auth/signup.go b/auth/signup.go index 8a7e1907..6e553be3 100644 --- a/auth/signup.go +++ b/auth/signup.go @@ -22,37 +22,42 @@ func (a *auth) SignUp(ctx echo.Context) error { var u types.User if err := json.NewDecoder(ctx.Request().Body).Decode(&u); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "error decoding request body in sign-up", }) + a.logger.Log(ctx, err) + return echoErr } _ = ctx.Request().Body.Close() if err := u.Validate(); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "invalid request for user sign up", }) + a.logger.Log(ctx, err) + return echoErr } if err := verifyPassword(u.Password); err != nil { - a.logger.Log(ctx, err) // err.Error() is already user friendly - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), "message": err.Error(), }) + a.logger.Log(ctx, err) + return echoErr } passwordHash, err := a.hashPassword(u.Password) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "internal server error: could not hash the password", }) + a.logger.Log(ctx, err) + return echoErr } u.Password = passwordHash @@ -72,57 +77,67 @@ func (a *auth) SignUp(ctx echo.Context) error { err = a.pgStore.AddUser(ctx.Request().Context(), newUser) if err != nil { - a.logger.Log(ctx, err) if strings.Contains(err.Error(), postgres.ErrDuplicateConstraintUsername) { - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "username already exists", }) + a.logger.Log(ctx, err) + return echoErr } if strings.Contains(err.Error(), postgres.ErrDuplicateConstraintEmail) { - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "this email already taken, try sign in?", }) + a.logger.Log(ctx, err) + return echoErr } - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "could not persist the user", }) + a.logger.Log(ctx, err) + return echoErr } // in case of CI setup, no need to send verification emails if a.c.Environment == config.CI { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusCreated, echo.Map{ - "message": "user successfully created", + msg := fmt.Errorf("user successfully created") + echoErr := ctx.JSON(http.StatusCreated, echo.Map{ + "message": msg, }) + a.logger.Log(ctx, msg) + return echoErr } token := uuid.NewString() err = a.pgStore.AddVerifyEmail(ctx.Request().Context(), token, newUser.Id) if err != nil { - ctx.Set(types.HttpEndpointErrorKey, err.Error()) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), - "message": "ERR_ADDING_VERIFY_EMAIL", + "message": "error adding verify email", }) + a.logger.Log(ctx, err) + return echoErr } if err = a.emailClient.SendEmail(newUser, token, email.VerifyEmailKind); err != nil { - ctx.Set(types.HttpEndpointErrorKey, err.Error()) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "could not send verify link, please reach out to OpenRegistry Team", }) + a.logger.Log(ctx, err) + return echoErr } - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusCreated, echo.Map{ + echoErr := ctx.JSON(http.StatusCreated, echo.Map{ "message": "signup was successful, please check your email to activate your account", }) + a.logger.Log(ctx, echoErr) + return echoErr } // nolint diff --git a/auth/token.go b/auth/token.go index 0d217817..44bbb71c 100644 --- a/auth/token.go +++ b/auth/token.go @@ -23,54 +23,64 @@ func (a *auth) Token(ctx echo.Context) error { if authHeader != "" { username, password, err := a.getCredsFromHeader(ctx.Request()) if err != nil { + echoErr := ctx.NoContent(http.StatusUnauthorized) a.logger.Log(ctx, err) - return ctx.NoContent(http.StatusUnauthorized) + return echoErr } if strings.HasPrefix(password, "gho_") || strings.HasPrefix(password, "ghp_") { user, err := a.getUserWithGithubOauthToken(ctx.Request().Context(), password) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusUnauthorized, echo.Map{ - "error": err.Error(), + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ + "error": err.Error(), + "message": "invalid github token", }) + a.logger.Log(ctx, err) + return echoErr } token, err := a.newServiceToken(*user) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), "message": "failed to get new service token", }) + a.logger.Log(ctx, err) + return echoErr } - a.logger.Log(ctx, nil) - return ctx.JSON(http.StatusOK, echo.Map{ + + err = ctx.JSON(http.StatusOK, echo.Map{ "token": token, "expires_in": time.Now().Add(time.Hour).Unix(), // look at auth/jwt.go:251 "issued_at": time.Now(), }) + a.logger.Log(ctx, err) + return err } creds, err := a.validateUser(username, password) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusUnauthorized, echo.Map{ - "error": err.Error(), + echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{ + "error": err.Error(), + "message": "error validating user, unauthorised", }) + a.logger.Log(ctx, err) + return echoErr } - return ctx.JSON(http.StatusOK, creds) + err = ctx.JSON(http.StatusOK, creds) + a.logger.Log(ctx, err) + return err } scope, err := a.getScopeFromQueryParams(ctx.QueryParam("scope")) if err != nil { - errMsg := echo.Map{ - "error": err.Error(), - "msg": "invalid scope provided", - } - a.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSON(http.StatusBadRequest, errMsg) + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "invalid scope provided", + }) + a.logger.Log(ctx, err) + return echoErr } // issue a free-public token to pull any repository @@ -78,16 +88,21 @@ func (a *auth) Token(ctx echo.Context) error { if len(scope.Actions) == 1 && scope.Actions["pull"] { token, err := a.newPublicPullToken() if err != nil { + echoErr := ctx.NoContent(http.StatusInternalServerError) a.logger.Log(ctx, err) - return ctx.NoContent(http.StatusInternalServerError) + return echoErr } - return ctx.JSON(http.StatusOK, echo.Map{ + echoErr := ctx.JSON(http.StatusOK, echo.Map{ "token": token, }) + a.logger.Log(ctx, err) + return echoErr } - return ctx.NoContent(http.StatusUnauthorized) + err = ctx.NoContent(http.StatusUnauthorized) + a.logger.Log(ctx, err) + return err } func (a *auth) getCredsFromHeader(r *http.Request) (string, string, error) { diff --git a/auth/user.go b/auth/user.go index d51284c0..c8fee193 100644 --- a/auth/user.go +++ b/auth/user.go @@ -15,35 +15,46 @@ func (a *auth) ReadUserWithSession(ctx echo.Context) error { session, err := ctx.Cookie("session_id") if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), - "message": "ERROR_GETTING_SESSION_ID", + "message": "error getting session id", }) + a.logger.Log(ctx, err) + return echoErr } if session.Value == "" { - a.logger.Log(ctx, fmt.Errorf("error getting cookies")) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "msg": "error is cookie", + err := fmt.Errorf("ERR_GETTING_COOKIE") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "error getting cookie", }) + a.logger.Log(ctx, err) + return echoErr } parts := strings.Split(session.Value, ":") if len(parts) != 2 { - a.logger.Log(ctx, fmt.Errorf("invalid session id")) - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": "invalid session id", + err := fmt.Errorf("INVALID_SESSION_ID") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "invalid session id", }) + a.logger.Log(ctx, err) + return echoErr } sessionId := parts[0] user, err := a.pgStore.GetUserWithSession(ctx.Request().Context(), sessionId) if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), - "message": "ERROR_FETCHING_USER_WITH_SESSION", + "message": "error getting user with session", }) + a.logger.Log(ctx, err) + return echoErr } - return ctx.JSON(http.StatusOK, user) + + err = ctx.JSON(http.StatusOK, user) + a.logger.Log(ctx, err) + return err } diff --git a/auth/verify_email.go b/auth/verify_email.go index 25693b1d..f935ca2f 100644 --- a/auth/verify_email.go +++ b/auth/verify_email.go @@ -12,79 +12,96 @@ import ( func (a *auth) VerifyEmail(ctx echo.Context) error { ctx.Set(types.HandlerStartTime, time.Now()) + token := ctx.QueryParam("token") if token == "" { - return ctx.JSON(http.StatusBadRequest, echo.Map{ - "error": "token can not be empty", + err := fmt.Errorf("EMPTY_TOKEN") + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ + "error": err.Error(), + "message": "token can not be empty", }) + a.logger.Log(ctx, err) + return echoErr } if _, err := uuid.Parse(token); err != nil { - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), - "message": "ERR_PARSE_TOKEN", + "message": "error parsing token", }) + a.logger.Log(ctx, err) + return echoErr } userId, err := a.pgStore.GetVerifyEmail(ctx.Request().Context(), token) if err != nil { - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "invalid token", }) + a.logger.Log(ctx, err) + return echoErr } user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, false) if err != nil { - ctx.Set(types.HttpEndpointErrorKey, err.Error()) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), - "msg": "USER_NOT_FOUND", + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "user not found", }) + a.logger.Log(ctx, err) + return echoErr } user.IsActive = true err = a.pgStore.UpdateUser(ctx.Request().Context(), userId, user) if err != nil { - ctx.Set(types.HttpEndpointErrorKey, err.Error()) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), - "msg": "ERROR_UPDATE_USER", + "msg": "error updating user", }) + a.logger.Log(ctx, err) + return echoErr } err = a.pgStore.DeleteVerifyEmail(ctx.Request().Context(), token) if err != nil { - ctx.Set(types.HttpEndpointErrorKey, err.Error()) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), - "msg": "ERROR_DELETE_VERIFY_EMAIL", + "msg": "error while deleting verify email", }) + a.logger.Log(ctx, err) + return echoErr } access, err := a.newWebLoginToken(userId, user.Username, "access") if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "error getting access token", }) + a.logger.Log(ctx, err) + return echoErr } refresh, err := a.newWebLoginToken(userId, user.Username, "refresh") if err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ - "error": err.Error(), + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ + "error": err.Error(), + "message": "error getting refresh token", }) + a.logger.Log(ctx, err) + return echoErr } id := uuid.NewString() if err = a.pgStore.AddSession(ctx.Request().Context(), id, refresh, user.Username); err != nil { - a.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), - "message": "ERR_CREATING_SESSION", + "message": "error creating session", }) + a.logger.Log(ctx, err) + return echoErr } sessionId := fmt.Sprintf("%s:%s", id, userId) @@ -92,12 +109,13 @@ func (a *auth) VerifyEmail(ctx echo.Context) error { accessCookie := a.createCookie("access", access, true, time.Now().Add(time.Hour)) refreshCookie := a.createCookie("refresh", refresh, true, time.Now().Add(time.Hour*750)) - a.logger.Log(ctx, err) ctx.SetCookie(accessCookie) ctx.SetCookie(refreshCookie) ctx.SetCookie(sessionCookie) - return ctx.JSON(http.StatusOK, echo.Map{ + err = ctx.JSON(http.StatusOK, echo.Map{ "message": "user profile activated successfully", }) + a.logger.Log(ctx, err) + return err } diff --git a/registry/v2/blobs.go b/registry/v2/blobs.go index 2b8fe2d0..4dd6a515 100644 --- a/registry/v2/blobs.go +++ b/registry/v2/blobs.go @@ -39,28 +39,32 @@ func (b *blobs) HEAD(ctx echo.Context) error { layerRef, err := b.registry.store.GetLayer(ctx.Request().Context(), digest) if err != nil { details := echo.Map{ - "skynet": "layer not found", + "error": err.Error(), + "message": "skynet: layer not found", } errMsg := b.errorResponse(RegistryErrorCodeManifestBlobUnknown, err.Error(), details) + err = ctx.JSONBlob(http.StatusNotFound, errMsg) b.registry.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return err } metadata, err := b.registry.skynet.Metadata(layerRef.SkynetLink) if err != nil { details := echo.Map{ - "skynet": "skynet link not found", - "error": err.Error(), + "error": err.Error(), + "message": "skynet link not found", } errMsg := b.errorResponse(RegistryErrorCodeManifestBlobUnknown, "Manifest does not exist", details) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) b.registry.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } ctx.Response().Header().Set("Content-Length", fmt.Sprintf("%d", metadata.ContentLength)) ctx.Response().Header().Set("Docker-Content-Digest", digest) + err = ctx.String(http.StatusOK, "OK") b.registry.logger.Log(ctx, nil) - return ctx.String(http.StatusOK, "OK") + return err } /* @@ -83,17 +87,19 @@ func (b *blobs) UploadBlob(ctx echo.Context) error { "stream upload after first write are not allowed", nil, ) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) b.registry.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } buf := &bytes.Buffer{} if _, err := io.Copy(buf, ctx.Request().Body); err != nil { - b.registry.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "error copying request body in upload blob", }) + b.registry.logger.Log(ctx, err) + return echoErr } _ = ctx.Request().Body.Close() @@ -105,33 +111,38 @@ func (b *blobs) UploadBlob(ctx echo.Context) error { err.Error(), nil, ) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) b.registry.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } locationHeader := fmt.Sprintf("/v2/%s/blobs/uploads/%s", namespace, uuid) ctx.Response().Header().Set("Location", locationHeader) ctx.Response().Header().Set("Range", fmt.Sprintf("0-%d", len(buf.Bytes())-1)) + err := ctx.NoContent(http.StatusAccepted) b.registry.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusAccepted) + return err } start, end := 0, 0 // 0-90 if _, err := fmt.Sscanf(contentRange, "%d-%d", &start, &end); err != nil { details := map[string]interface{}{ - "error": "content range is invalid", + "error": err.Error(), + "message": "content range is invalid", "contentRange": contentRange, } errMsg := b.errorResponse(RegistryErrorCodeBlobUploadUnknown, err.Error(), details) + echoErr := ctx.JSONBlob(http.StatusRequestedRangeNotSatisfiable, errMsg) b.registry.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusRequestedRangeNotSatisfiable, errMsg) + return echoErr } if start != len(b.uploads[uuid]) { errMsg := b.errorResponse(RegistryErrorCodeBlobUploadUnknown, "content range mismatch", nil) + echoErr := ctx.JSONBlob(http.StatusRequestedRangeNotSatisfiable, errMsg) b.registry.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusRequestedRangeNotSatisfiable, errMsg) + return echoErr } buf := bytes.NewBuffer(b.uploads[uuid]) // 90 @@ -142,8 +153,9 @@ func (b *blobs) UploadBlob(ctx echo.Context) error { "error while creating new buffer from existing blobs", nil, ) + echoErr := ctx.JSONBlob(http.StatusInternalServerError, errMsg) b.registry.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusInternalServerError, errMsg) + return echoErr } // 10 ctx.Request().Body.Close() @@ -154,14 +166,16 @@ func (b *blobs) UploadBlob(ctx echo.Context) error { err.Error(), nil, ) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) b.registry.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } locationHeader := fmt.Sprintf("/v2/%s/blobs/uploads/%s", namespace, uuid) ctx.Response().Header().Set("Location", locationHeader) ctx.Response().Header().Set("Range", fmt.Sprintf("0-%d", buf.Len()-1)) + echoErr := ctx.NoContent(http.StatusAccepted) b.registry.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusAccepted) + return echoErr } func (b *blobs) blobTransaction(ctx echo.Context, bz []byte, uuid string) error { diff --git a/registry/v2/registry.go b/registry/v2/registry.go index 6f891e1b..31cb8470 100644 --- a/registry/v2/registry.go +++ b/registry/v2/registry.go @@ -66,13 +66,14 @@ func (r *registry) ManifestExists(ctx echo.Context) error { if err != nil { details := echo.Map{ - "skynet": "manifest not found", - "error": err.Error(), + "error": err.Error(), + "message": "skynet - manifest not found", } errMsg := r.errorResponse(RegistryErrorCodeManifestBlobUnknown, err.Error(), details) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } metadata, err := r.skynet.Metadata(manifest.Skylink) @@ -83,9 +84,9 @@ func (r *registry) ManifestExists(ctx echo.Context) error { } errMsg := r.errorResponse(RegistryErrorCodeManifestBlobUnknown, "Manifest does not exist", detail) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } if manifest.Reference != ref && manifest.Digest != ref { @@ -94,16 +95,17 @@ func (r *registry) ManifestExists(ctx echo.Context) error { "clientDigest": ref, } errMsg := r.errorResponse(RegistryErrorCodeManifestInvalid, "manifest digest does not match", details) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } ctx.Response().Header().Set("Content-Type", "application/json") ctx.Response().Header().Set("Content-Length", fmt.Sprintf("%d", metadata.ContentLength)) ctx.Response().Header().Set("Docker-Content-Digest", manifest.Digest) + echoErr := ctx.NoContent(http.StatusOK) r.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusOK) + return echoErr } // Catalog - The list of available repositories is made available through the catalog. @@ -120,10 +122,11 @@ func (r *registry) Catalog(ctx echo.Context) error { if queryParamPageSize != "" { ps, err := strconv.ParseInt(ctx.QueryParam("n"), 10, 64) if err != nil { - r.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), }) + r.logger.Log(ctx, err) + return echoErr } pageSize = ps } @@ -131,34 +134,38 @@ func (r *registry) Catalog(ctx echo.Context) error { if queryParamOffset != "" { o, err := strconv.ParseInt(ctx.QueryParam("last"), 10, 64) if err != nil { - r.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), }) + r.logger.Log(ctx, err) + return echoErr } offset = o } catalogList, err := r.store.GetCatalog(ctx.Request().Context(), namespace, pageSize, offset) if err != nil { - r.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), }) + r.logger.Log(ctx, err) + return echoErr } // empty namespace to pull the full catalog list total, err := r.store.GetCatalogCount(ctx.Request().Context(), "") if err != nil { - r.logger.Log(ctx, err) - return ctx.JSON(http.StatusInternalServerError, echo.Map{ + echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{ "error": err.Error(), }) + r.logger.Log(ctx, err) + return echoErr } - r.logger.Log(ctx, nil) - return ctx.JSON(http.StatusOK, echo.Map{ + echoErr := ctx.JSON(http.StatusOK, echo.Map{ "repositories": catalogList, "total": total, }) + r.logger.Log(ctx, nil) + return echoErr } @@ -174,16 +181,18 @@ func (r *registry) ListTags(ctx echo.Context) error { tags, err := r.store.GetImageTags(ctx.Request().Context(), namespace) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeTagInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } if limit != "" { n, err := strconv.ParseInt(limit, 10, 32) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeTagInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } if n > 0 { tags = tags[0:n] @@ -193,11 +202,12 @@ func (r *registry) ListTags(ctx echo.Context) error { } } - r.logger.Log(ctx, nil) - return ctx.JSON(http.StatusOK, echo.Map{ + echoErr := ctx.JSON(http.StatusOK, echo.Map{ "name": namespace, "tags": tags, }) + r.logger.Log(ctx, nil) + return echoErr } func (r *registry) List(ctx echo.Context) error { return fmt.Errorf("error") @@ -215,29 +225,33 @@ func (r *registry) PullManifest(ctx echo.Context) error { manifest, err := r.store.GetManifestByReference(ctx.Request().Context(), namespace, ref) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeManifestUnknown, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } resp, err := r.skynet.Download(manifest.Skylink) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeManifestInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } bz, err := io.ReadAll(resp) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeManifestInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } _ = resp.Close() ctx.Response().Header().Set("Docker-Content-Digest", manifest.Digest) ctx.Response().Header().Set("X-Docker-Content-ID", manifest.Skylink) ctx.Response().Header().Set("Content-Type", manifest.MediaType) ctx.Response().Header().Set("Content-Length", fmt.Sprintf("%d", len(bz))) + echoErr := ctx.JSONBlob(http.StatusOK, bz) r.logger.Log(ctx, nil) - return ctx.JSONBlob(http.StatusOK, bz) + return echoErr } // PullLayer @@ -252,8 +266,9 @@ func (r *registry) PullLayer(ctx echo.Context) error { layer, err := r.store.GetLayer(ctx.Request().Context(), clientDigest) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeBlobUnknown, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } if layer.SkynetLink == "" { @@ -262,8 +277,9 @@ func (r *registry) PullLayer(ctx echo.Context) error { } e := fmt.Errorf("skylink is empty").Error() errMsg := r.errorResponse(RegistryErrorCodeBlobUnknown, e, detail) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } resp, err := r.skynet.Download(layer.SkynetLink) @@ -273,14 +289,16 @@ func (r *registry) PullLayer(ctx echo.Context) error { "skylink": layer.SkynetLink, } errMsg := r.errorResponse(RegistryErrorCodeBlobUnknown, err.Error(), detail) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } buf := &bytes.Buffer{} if _, err := io.Copy(buf, resp); err != nil { errMsg := r.errorResponse(RegistryErrorCodeBlobUploadInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusInternalServerError, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusInternalServerError, errMsg) + return echoErr } _ = resp.Close() @@ -295,14 +313,16 @@ func (r *registry) PullLayer(ctx echo.Context) error { "client digest is different than computed digest", details, ) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } ctx.Response().Header().Set("Content-Length", fmt.Sprintf("%d", len(buf.Bytes()))) ctx.Response().Header().Set("Docker-Content-Digest", dig) + echoErr := ctx.Blob(http.StatusOK, "application/octet-stream", buf.Bytes()) r.logger.Log(ctx, nil) - return ctx.Blob(http.StatusOK, "application/octet-stream", buf.Bytes()) + return echoErr } // MonolithicUpload @@ -319,17 +339,19 @@ func (r *registry) MonolithicUpload(ctx echo.Context) error { "error in monolithic upload", nil, ) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } buf := &bytes.Buffer{} if _, err := io.Copy(buf, ctx.Request().Body); err != nil { - r.logger.Log(ctx, err) - return ctx.JSON(http.StatusBadRequest, echo.Map{ + echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{ "error": err.Error(), "message": "error copying request body in monolithic upload blob", }) + r.logger.Log(ctx, err) + return echoErr } _ = ctx.Request().Body.Close() @@ -341,15 +363,17 @@ func (r *registry) MonolithicUpload(ctx echo.Context) error { err.Error(), nil, ) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } locationHeader := fmt.Sprintf("/v2/%s/blobs/uploads/%s", namespace, uuid) ctx.Response().Header().Set("Location", locationHeader) + echoErr := ctx.NoContent(http.StatusCreated) r.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusCreated) + return echoErr } // ChunkedUpload @@ -382,8 +406,9 @@ func (r *registry) StartUpload(ctx echo.Context) error { "error while reading request body", details, ) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } _ = ctx.Request().Body.Close() // why defer? body is already read :) dig := digest(buf.Bytes()) @@ -398,15 +423,17 @@ func (r *registry) StartUpload(ctx echo.Context) error { "client digest does not meet computed digest", details, ) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", fmt.Errorf("%s", errMsg))) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } skylink, err := r.skynet.Upload(namespace, dig, buf.Bytes(), true) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeBlobUploadInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusRequestedRangeNotSatisfiable, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusRequestedRangeNotSatisfiable, errMsg) + return echoErr } layerV2 := &types.LayerV2{ @@ -423,25 +450,29 @@ func (r *registry) StartUpload(ctx echo.Context) error { txnOp, err := r.store.NewTxn(ctx.Request().Context()) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeUnknown, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusInternalServerError, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusInternalServerError, errMsg) + return echoErr } if err := r.store.SetLayer(ctx.Request().Context(), txnOp, layerV2); err != nil { errMsg := r.errorResponse(RegistryErrorCodeBlobUploadInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } if err := r.store.Commit(ctx.Request().Context(), txnOp); err != nil { errMsg := r.errorResponse(RegistryErrorCodeBlobUploadInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } link := r.getHttpUrlFromSkylink(skylink) ctx.Response().Header().Set("Location", link) + echoErr := ctx.NoContent(http.StatusCreated) r.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusCreated) + return echoErr } id := uuid.New() @@ -453,8 +484,9 @@ func (r *registry) StartUpload(ctx echo.Context) error { err.Error(), nil, ) + echoErr := ctx.JSONBlob(http.StatusInternalServerError, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusInternalServerError, errMsg) + return echoErr } r.txnMap[id.String()] = TxnStore{ txn: txn, @@ -465,8 +497,9 @@ func (r *registry) StartUpload(ctx echo.Context) error { ctx.Response().Header().Set("Content-Length", "0") ctx.Response().Header().Set("Docker-Upload-UUID", id.String()) ctx.Response().Header().Set("Range", fmt.Sprintf("0-%d", 0)) + echoErr := ctx.NoContent(http.StatusAccepted) r.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusAccepted) + return echoErr } //UploadProgress TODO @@ -479,15 +512,17 @@ func (r *registry) UploadProgress(ctx echo.Context) error { skylink, err := r.store.GetContentHashById(ctx.Request().Context(), uuid) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeBlobUnknown, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } if skylink == "" { err = fmt.Errorf("skylink is empty") errMsg := r.errorResponse(RegistryErrorCodeBlobUnknown, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } metadata, err := r.skynet.Metadata(skylink) @@ -496,16 +531,18 @@ func (r *registry) UploadProgress(ctx echo.Context) error { ctx.Response().Header().Set("Location", locationHeader) ctx.Response().Header().Set("Range", "bytes=0-0") ctx.Response().Header().Set("Docker-Upload-UUID", uuid) + echoErr := ctx.NoContent(http.StatusNoContent) r.logger.Log(ctx, err) - return ctx.NoContent(http.StatusNoContent) + return echoErr } locationHeader := fmt.Sprintf("/v2/%s/blobs/uploads/%s", namespace, uuid) ctx.Response().Header().Set("Location", locationHeader) ctx.Response().Header().Set("Range", fmt.Sprintf("bytes=0-%d", metadata.ContentLength)) ctx.Response().Header().Set("Docker-Upload-UUID", uuid) + echoErr := ctx.NoContent(http.StatusNoContent) r.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusNoContent) + return echoErr } // CompleteUpload @@ -525,8 +562,9 @@ func (r *registry) CompleteUpload(ctx echo.Context) error { buf := &bytes.Buffer{} if _, err := io.Copy(buf, ctx.Request().Body); err != nil { errMsg := r.errorResponse(RegistryErrorCodeDigestInvalid, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } _ = ctx.Request().Body.Close() // insert if bz is not nil @@ -540,8 +578,9 @@ func (r *registry) CompleteUpload(ctx echo.Context) error { "headerDigest": dig, "serverSideDigest": ourHash, "bodyDigest": digest(buf.Bytes()), } errMsg := r.errorResponse(RegistryErrorCodeDigestInvalid, "digest mismatch", details) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } blobNamespace := fmt.Sprintf("%s/blobs", namespace) @@ -552,15 +591,17 @@ func (r *registry) CompleteUpload(ctx echo.Context) error { "error": err.Error(), }) + echoErr := ctx.JSONBlob(http.StatusRequestedRangeNotSatisfiable, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusRequestedRangeNotSatisfiable, errMsg) + return echoErr } txnOp, ok := r.txnMap[id] if !ok { errMsg := r.errorResponse(RegistryErrorCodeUnknown, "transaction does not exist for uuid -"+id, nil) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } layer := &types.LayerV2{ @@ -575,24 +616,27 @@ func (r *registry) CompleteUpload(ctx echo.Context) error { } if !ok { errMsg := r.errorResponse(RegistryErrorCodeUnknown, "transaction does not exist for uuid -"+id, nil) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } if err := r.store.SetLayer(ctx.Request().Context(), txnOp.txn, layer); err != nil { errMsg := r.errorResponse(RegistryErrorCodeUnknown, err.Error(), echo.Map{ "error_detail": "set layer issues", }) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } if err := r.store.Commit(ctx.Request().Context(), txnOp.txn); err != nil { errMsg := r.errorResponse(RegistryErrorCodeUnknown, err.Error(), echo.Map{ "error_detail": "commitment issue", }) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } delete(r.txnMap, id) @@ -600,8 +644,9 @@ func (r *registry) CompleteUpload(ctx echo.Context) error { ctx.Response().Header().Set("Content-Length", "0") ctx.Response().Header().Set("Docker-Content-Digest", ourHash) ctx.Response().Header().Set("Location", locationHeader) + echoErr := ctx.NoContent(http.StatusCreated) r.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusCreated) + return echoErr } //BlobMount to be implemented by guacamole at a later stage @@ -636,8 +681,9 @@ func (r *registry) PushManifest(ctx echo.Context) error { err = json.Unmarshal(buf.Bytes(), &manifest) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeBlobUnknown, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + return echoErr } dig := digest(buf.Bytes()) @@ -645,8 +691,9 @@ func (r *registry) PushManifest(ctx echo.Context) error { skylink, err := r.skynet.Upload(mfNamespace, dig, buf.Bytes(), true) if err != nil { errMsg := r.errorResponse(RegistryErrorCodeManifestBlobUnknown, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } var layerIDs []string @@ -682,40 +729,45 @@ func (r *registry) PushManifest(ctx echo.Context) error { errMsg := r.errorResponse(RegistryErrorCodeUnknown, err.Error(), echo.Map{ "reason": "PG_ERR_CREATE_NEW_TXN", }) - r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) _ = r.store.Abort(ctx.Request().Context(), txnOp) - return ctx.JSONBlob(http.StatusInternalServerError, errMsg) + echoErr := ctx.JSONBlob(http.StatusInternalServerError, errMsg) + r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) + return echoErr } if err = r.store.SetManifest(ctx.Request().Context(), txnOp, val); err != nil { errMsg := r.errorResponse(RegistryErrorCodeUnknown, err.Error(), nil) - r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) _ = r.store.Abort(ctx.Request().Context(), txnOp) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) + r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) + return echoErr } if err = r.store.SetConfig(ctx.Request().Context(), txnOp, mfc); err != nil { errMsg := r.errorResponse(RegistryErrorCodeUnknown, err.Error(), nil) - r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) _ = r.store.Abort(ctx.Request().Context(), txnOp) - return ctx.JSONBlob(http.StatusBadRequest, errMsg) + echoErr := ctx.JSONBlob(http.StatusBadRequest, errMsg) + r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) + return echoErr } if err = r.store.Commit(ctx.Request().Context(), txnOp); err != nil { errMsg := r.errorResponse(RegistryErrorCodeUnknown, err.Error(), echo.Map{ "reason": "ERR_PG_COMMIT_TXN", }) - r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) _ = r.store.Abort(ctx.Request().Context(), txnOp) - return ctx.JSONBlob(http.StatusInternalServerError, errMsg) + echoErr := ctx.JSONBlob(http.StatusInternalServerError, errMsg) + r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) + return echoErr } locationHeader := r.getHttpUrlFromSkylink(skylink) ctx.Response().Header().Set("Location", locationHeader) ctx.Response().Header().Set("Docker-Content-Digest", dig) ctx.Response().Header().Set("X-Docker-Content-ID", skylink) + echoErr := ctx.String(http.StatusCreated, "Created") r.logger.Log(ctx, nil) - return ctx.String(http.StatusCreated, "Created") + return echoErr } // PushLayer @@ -731,8 +783,9 @@ func (r *registry) PushLayer(ctx echo.Context) error { // Must have a path of form /v2/{name}/blobs/{upload,sha256:} if len(elem) < 4 { errMsg := r.errorResponse(RegistryErrorCodeNameInvalid, "blobs must be attached to a repo", nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } id := uuid.New() @@ -741,8 +794,9 @@ func (r *registry) PushLayer(ctx echo.Context) error { ctx.Response().Header().Set("Location", locationHeader) ctx.Response().Header().Set("Docker-Upload-UUID", id.String()) ctx.Response().Header().Set("Range", "bytes=0-0") + echoErr := ctx.NoContent(http.StatusAccepted) r.logger.Log(ctx, nil) - return ctx.NoContent(http.StatusAccepted) + return echoErr } func (r *registry) CancelUpload(ctx echo.Context) error { @@ -770,13 +824,15 @@ func (r *registry) DeleteTagOrManifest(ctx echo.Context) error { "digest": ref, } errMsg := r.errorResponse(RegistryErrorCodeManifestUnknown, err.Error(), details) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } err := r.store.Commit(ctx.Request().Context(), txnOp) + echoErr := ctx.NoContent(http.StatusAccepted) r.logger.Log(ctx, err) - return ctx.NoContent(http.StatusAccepted) + return echoErr } func (r *registry) DeleteLayer(ctx echo.Context) error { @@ -787,8 +843,9 @@ func (r *registry) DeleteLayer(ctx echo.Context) error { if err != nil { errMsg := r.errorResponse(RegistryErrorCodeBlobUnknown, err.Error(), nil) + echoErr := ctx.JSONBlob(http.StatusNotFound, errMsg) r.logger.Log(ctx, fmt.Errorf("%s", errMsg)) - return ctx.JSONBlob(http.StatusNotFound, errMsg) + return echoErr } blobs := layer.BlobDigests @@ -826,8 +883,9 @@ func (r *registry) DeleteLayer(ctx echo.Context) error { } } err = r.store.Commit(ctx.Request().Context(), txnOp) + echoErr := ctx.NoContent(http.StatusAccepted) r.logger.Log(ctx, err) - return ctx.NoContent(http.StatusAccepted) + return echoErr } // Should also look into 401 Code diff --git a/router/router.go b/router/router.go index f50098bc..80ba2f41 100644 --- a/router/router.go +++ b/router/router.go @@ -62,6 +62,11 @@ func Register( RegisterNSRoutes(nsRouter, reg) RegisterAuthRoutes(authRouter, authSvc) Extensions(v2Router, reg, ext, authSvc.JWT()) + + //catch-all will redirect user back to web interface + e.Add(http.MethodGet, "/", func(ctx echo.Context) error { + return ctx.Redirect(http.StatusTemporaryRedirect, cfg.WebAppEndpoint) + }) } // RegisterNSRoutes is one of the helper functions to Register diff --git a/telemetry/consoleWriter.go b/telemetry/consoleWriter.go index 0b09cb9c..965c8f0d 100644 --- a/telemetry/consoleWriter.go +++ b/telemetry/consoleWriter.go @@ -23,24 +23,27 @@ func (l logger) consoleWriter(ctx echo.Context, errMsg error) { req := ctx.Request() res := ctx.Response() + var e error + + _, err := buf.WriteString(req.Method + " ") + e = multierror.Append(e, err) status := res.Status level := zerolog.InfoLevel switch { case status >= 500: + _, err = buf.WriteString(color.RedString("%d ", res.Status)) level = zerolog.ErrorLevel case status >= 400: + _, err = buf.WriteString(color.RedString("%d ", res.Status)) level = zerolog.WarnLevel case status >= 300: + _, err = buf.WriteString(color.YellowString("%d ", res.Status)) level = zerolog.ErrorLevel + default: + _, err = buf.WriteString(color.GreenString("%d ", res.Status)) } - var e error - - _, err := buf.WriteString(req.Method + " ") - e = multierror.Append(e, err) - - _, err = buf.WriteString(color.GreenString("%d ", res.Status)) e = multierror.Append(e, err) if level == zerolog.ErrorLevel {