Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Merge remote-tracking branch 'origin/audit-login-logout/kira-pilot' i…
…nto audit-logout/kira-pilot
  • Loading branch information
Kira-Pilot committed Feb 3, 2023
commit 038138d0e73cfde3c8ce2670cbcbc9365af0af2b
17 changes: 16 additions & 1 deletion coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,18 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
// @Success 200 {object} codersdk.Response
// @Router /users/logout [post]
func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var (
ctx = r.Context()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.APIKey](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionLogout,
})
)
defer commitAudit()

// Get a blank token cookie.
cookie := &http.Cookie{
// MaxAge < 0 means to delete the cookie now.
Expand All @@ -145,6 +156,8 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {

// Delete the session token from database.
apiKey := httpmw.APIKey(r)
aReq.Old = apiKey

err := api.Database.DeleteAPIKeyByID(ctx, apiKey.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Expand Down Expand Up @@ -198,6 +211,8 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
}
}

aReq.New = database.APIKey{}

httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
Message: "Logged out!",
})
Expand Down
211 changes: 0 additions & 211 deletions coderd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -983,217 +983,6 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques
httpapi.Write(ctx, rw, http.StatusOK, convertOrganization(organization))
}

// Authenticates the user with an email and password.
//
// @Summary Log in user
// @ID log-in-user
// @Accept json
// @Produce json
// @Tags Authorization
// @Param request body codersdk.LoginWithPasswordRequest true "Login request"
// @Success 201 {object} codersdk.LoginWithPasswordResponse
// @Router /users/login [post]
func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.APIKey](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionLogin,
})
)
aReq.Old = database.APIKey{}
defer commitAudit()

var loginWithPassword codersdk.LoginWithPasswordRequest
if !httpapi.Read(ctx, rw, r, &loginWithPassword) {
// We pass a disposable user ID just to force an audit diff
// and generate a log for a failed login
aReq.New = database.APIKey{UserID: uuid.New()}
return
}

user, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
Email: loginWithPassword.Email,
})
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error.",
})
// We pass a disposable user ID just to force an audit diff
// and generate a log for a failed login
aReq.New = database.APIKey{UserID: uuid.New()}
return
}

aReq.UserID = user.ID

// If the user doesn't exist, it will be a default struct.
equal, err := userpassword.Compare(string(user.HashedPassword), loginWithPassword.Password)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error.",
})
// We pass a disposable user ID just to force an audit diff
// and generate a log for a failed login
aReq.New = database.APIKey{UserID: uuid.New()}
return
}
if !equal {
// This message is the same as above to remove ease in detecting whether
// users are registered or not. Attackers still could with a timing attack.
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
Message: "Incorrect email or password.",
})
// We pass a disposable user ID just to force an audit diff
// and generate a log for a failed login
aReq.New = database.APIKey{UserID: uuid.New()}
return
}

if user.LoginType != database.LoginTypePassword {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Incorrect login type, attempting to use %q but user is of login type %q", database.LoginTypePassword, user.LoginType),
})
// We pass a disposable user ID just to force an audit diff
// and generate a log for a failed login
aReq.New = database.APIKey{UserID: uuid.New()}
return
}

// If the user logged into a suspended account, reject the login request.
if user.Status != database.UserStatusActive {
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
Message: "Your account is suspended. Contact an admin to reactivate your account.",
})
// We pass a disposable user ID just to force an audit diff
// and generate a log for a failed login
aReq.New = database.APIKey{UserID: uuid.New()}
return
}

cookie, key, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: user.ID,
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to create API key.",
Detail: err.Error(),
})
// We pass a disposable user ID just to force an audit diff
// and generate a log for a failed login
aReq.New = database.APIKey{UserID: uuid.New()}
return
}

aReq.New = *key

http.SetCookie(rw, cookie)

httpapi.Write(ctx, rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{
SessionToken: cookie.Value,
})
}

// Clear the user's session cookie.
//
// @Summary Log out user
// @ID log-out-user
// @Security CoderSessionToken
// @Produce json
// @Tags Users
// @Success 200 {object} codersdk.Response
// @Router /users/logout [post]
func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.APIKey](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionLogout,
})
)
defer commitAudit()

// Get a blank token cookie.
cookie := &http.Cookie{
// MaxAge < 0 means to delete the cookie now.
MaxAge: -1,
Name: codersdk.SessionTokenCookie,
Path: "/",
}
http.SetCookie(rw, cookie)

// Delete the session token from database.
apiKey := httpmw.APIKey(r)
aReq.Old = apiKey

err := api.Database.DeleteAPIKeyByID(ctx, apiKey.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error deleting API key.",
Detail: err.Error(),
})
return
}

// Deployments should not host app tokens on the same domain as the
// primary deployment. But in the case they are, we should also delete this
// token.
if appCookie, _ := r.Cookie(httpmw.DevURLSessionTokenCookie); appCookie != nil {
appCookieRemove := &http.Cookie{
// MaxAge < 0 means to delete the cookie now.
MaxAge: -1,
Name: httpmw.DevURLSessionTokenCookie,
Path: "/",
Domain: "." + api.AccessURL.Hostname(),
}
http.SetCookie(rw, appCookieRemove)

id, _, err := httpmw.SplitAPIToken(appCookie.Value)
if err == nil {
err = api.Database.DeleteAPIKeyByID(ctx, id)
if err != nil {
// Don't block logout, just log any errors.
api.Logger.Warn(r.Context(), "failed to delete devurl token on logout",
slog.Error(err),
slog.F("id", id),
)
}
}
}

// This code should be removed after Jan 1 2023.
// This code logs out of the old session cookie before we renamed it
// if it is a valid coder token. Otherwise, this old cookie hangs around
// and we never log out of the user.
oldCookie, err := r.Cookie("session_token")
if err == nil && oldCookie != nil {
_, _, err := httpmw.SplitAPIToken(oldCookie.Value)
if err == nil {
cookie := &http.Cookie{
// MaxAge < 0 means to delete the cookie now.
MaxAge: -1,
Name: "session_token",
Path: "/",
}
http.SetCookie(rw, cookie)
}
}

aReq.New = database.APIKey{}

httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
Message: "Logged out!",
})
}

type CreateUserRequest struct {
codersdk.CreateUserRequest
LoginType database.LoginType
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.