mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
fix: use unique ID for linked accounts (#3441)
- move OAuth-related fields off of api_keys into a new user_links table - restrict users to single form of login - process updates to user email/usernames for OIDC - added a login_type column to users
This commit is contained in:
@ -77,10 +77,13 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, organizationID, err := api.createUser(r.Context(), codersdk.CreateUserRequest{
|
||||
Email: createUser.Email,
|
||||
Username: createUser.Username,
|
||||
Password: createUser.Password,
|
||||
user, organizationID, err := api.createUser(r.Context(), createUserRequest{
|
||||
CreateUserRequest: codersdk.CreateUserRequest{
|
||||
Email: createUser.Email,
|
||||
Username: createUser.Username,
|
||||
Password: createUser.Password,
|
||||
},
|
||||
LoginType: database.LoginTypePassword,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
@ -196,14 +199,14 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var createUser codersdk.CreateUserRequest
|
||||
if !httpapi.Read(rw, r, &createUser) {
|
||||
var req codersdk.CreateUserRequest
|
||||
if !httpapi.Read(rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
// Create the organization member in the org.
|
||||
if !api.Authorize(r, rbac.ActionCreate,
|
||||
rbac.ResourceOrganizationMember.InOrg(createUser.OrganizationID)) {
|
||||
rbac.ResourceOrganizationMember.InOrg(req.OrganizationID)) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
@ -211,8 +214,8 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
// TODO: @emyrk Authorize the organization create if the createUser will do that.
|
||||
|
||||
_, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
|
||||
Username: createUser.Username,
|
||||
Email: createUser.Email,
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
})
|
||||
if err == nil {
|
||||
httpapi.Write(rw, http.StatusConflict, codersdk.Response{
|
||||
@ -228,10 +231,10 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = api.Database.GetOrganizationByID(r.Context(), createUser.OrganizationID)
|
||||
_, err = api.Database.GetOrganizationByID(r.Context(), req.OrganizationID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: fmt.Sprintf("Organization does not exist with the provided id %q.", createUser.OrganizationID),
|
||||
Message: fmt.Sprintf("Organization does not exist with the provided id %q.", req.OrganizationID),
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -243,7 +246,10 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, _, err := api.createUser(r.Context(), createUser)
|
||||
user, _, err := api.createUser(r.Context(), createUserRequest{
|
||||
CreateUserRequest: req,
|
||||
LoginType: database.LoginTypePassword,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error creating user.",
|
||||
@ -257,7 +263,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
Users: []telemetry.User{telemetry.ConvertUser(user)},
|
||||
})
|
||||
|
||||
httpapi.Write(rw, http.StatusCreated, convertUser(user, []uuid.UUID{createUser.OrganizationID}))
|
||||
httpapi.Write(rw, http.StatusCreated, convertUser(user, []uuid.UUID{req.OrganizationID}))
|
||||
}
|
||||
|
||||
// Returns the parameterized user requested. All validation
|
||||
@ -701,6 +707,13 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if user.LoginType != database.LoginTypePassword {
|
||||
httpapi.Write(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),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// If the user logged into a suspended account, reject the login request.
|
||||
if user.Status != database.UserStatusActive {
|
||||
httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{
|
||||
@ -709,7 +722,7 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
sessionToken, created := api.createAPIKey(rw, r, database.InsertAPIKeyParams{
|
||||
sessionToken, created := api.createAPIKey(rw, r, createAPIKeyParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypePassword,
|
||||
})
|
||||
@ -732,7 +745,7 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
lifeTime := time.Hour * 24 * 7
|
||||
sessionToken, created := api.createAPIKey(rw, r, database.InsertAPIKeyParams{
|
||||
sessionToken, created := api.createAPIKey(rw, r, createAPIKeyParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypePassword,
|
||||
// All api generated keys will last 1 week. Browser login tokens have
|
||||
@ -818,7 +831,16 @@ func generateAPIKeyIDSecret() (id string, secret string, err error) {
|
||||
return id, secret, nil
|
||||
}
|
||||
|
||||
func (api *API) createAPIKey(rw http.ResponseWriter, r *http.Request, params database.InsertAPIKeyParams) (string, bool) {
|
||||
type createAPIKeyParams struct {
|
||||
UserID uuid.UUID
|
||||
LoginType database.LoginType
|
||||
|
||||
// Optional.
|
||||
ExpiresAt time.Time
|
||||
LifetimeSeconds int64
|
||||
}
|
||||
|
||||
func (api *API) createAPIKey(rw http.ResponseWriter, r *http.Request, params createAPIKeyParams) (string, bool) {
|
||||
keyID, keySecret, err := generateAPIKeyIDSecret()
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
@ -856,15 +878,11 @@ func (api *API) createAPIKey(rw http.ResponseWriter, r *http.Request, params dat
|
||||
Valid: true,
|
||||
},
|
||||
// Make sure in UTC time for common time zone
|
||||
ExpiresAt: params.ExpiresAt.UTC(),
|
||||
CreatedAt: database.Now(),
|
||||
UpdatedAt: database.Now(),
|
||||
HashedSecret: hashed[:],
|
||||
LoginType: params.LoginType,
|
||||
OAuthAccessToken: params.OAuthAccessToken,
|
||||
OAuthRefreshToken: params.OAuthRefreshToken,
|
||||
OAuthIDToken: params.OAuthIDToken,
|
||||
OAuthExpiry: params.OAuthExpiry,
|
||||
ExpiresAt: params.ExpiresAt.UTC(),
|
||||
CreatedAt: database.Now(),
|
||||
UpdatedAt: database.Now(),
|
||||
HashedSecret: hashed[:],
|
||||
LoginType: params.LoginType,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
@ -891,7 +909,12 @@ func (api *API) createAPIKey(rw http.ResponseWriter, r *http.Request, params dat
|
||||
return sessionToken, true
|
||||
}
|
||||
|
||||
func (api *API) createUser(ctx context.Context, req codersdk.CreateUserRequest) (database.User, uuid.UUID, error) {
|
||||
type createUserRequest struct {
|
||||
codersdk.CreateUserRequest
|
||||
LoginType database.LoginType
|
||||
}
|
||||
|
||||
func (api *API) createUser(ctx context.Context, req createUserRequest) (database.User, uuid.UUID, error) {
|
||||
var user database.User
|
||||
return user, req.OrganizationID, api.Database.InTx(func(db database.Store) error {
|
||||
orgRoles := make([]string, 0)
|
||||
@ -918,6 +941,7 @@ func (api *API) createUser(ctx context.Context, req codersdk.CreateUserRequest)
|
||||
UpdatedAt: database.Now(),
|
||||
// All new users are defaulted to members of the site.
|
||||
RBACRoles: []string{},
|
||||
LoginType: req.LoginType,
|
||||
}
|
||||
// If a user signs up with OAuth, they can have no password!
|
||||
if req.Password != "" {
|
||||
|
Reference in New Issue
Block a user