zitadel/internal/api/grpc/user/v2/user.go
Livio Spring e4a4b7cfbe
feat(api): add user creation to user service (#5745)
* chore(proto): update versions

* change protoc plugin

* some cleanups

* define api for setting emails in new api

* implement user.SetEmail

* move SetEmail buisiness logic into command

* resuse newCryptoCode

* command: add ChangeEmail unit tests

Not complete, was not able to mock the generator.

* Revert "resuse newCryptoCode"

This reverts commit c89e90ae35.

* undo change to crypto code generators

* command: use a generator so we can test properly

* command: reorganise ChangeEmail

improve test coverage

* implement VerifyEmail

including unit tests

* add URL template tests

* begin user creation

* change protos

* implement metadata and move context

* merge commands

* proto: change context to object

* remove old auth option

* remove old auth option

* fix linting errors

run gci on modified files

* add permission checks and fix some errors

* comments

* comments

* update email requests

* rename proto requests

* cleanup and docs

* simplify

* simplify

* fix setup

* remove unused proto messages / fields

---------

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
2023-04-26 07:47:57 +02:00

108 lines
3.5 KiB
Go

package user
import (
"context"
"io"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/pkg/grpc/user/v2alpha"
)
func (s *Server) AddHumanUser(ctx context.Context, req *user.AddHumanUserRequest) (_ *user.AddHumanUserResponse, err error) {
human, err := addUserRequestToAddHuman(req)
if err != nil {
return nil, err
}
err = s.command.AddHuman(ctx, req.GetOrganisation().GetOrgId(), human, false)
if err != nil {
return nil, err
}
return &user.AddHumanUserResponse{
UserId: human.ID,
Details: object.DomainToDetailsPb(human.Details),
EmailCode: human.EmailCode,
}, nil
}
func addUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman, error) {
username := req.GetUsername()
if username == "" {
username = req.GetEmail().GetEmail()
}
var urlTemplate string
if req.GetEmail().GetSendCode() != nil {
urlTemplate = req.GetEmail().GetSendCode().GetUrlTemplate()
// test the template execution so the async notification will not fail because of it and the user won't realize
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTemplate, req.GetUserId(), "code", "orgID"); err != nil {
return nil, err
}
}
bcryptedPassword, err := hashedPasswordToCommand(req.GetHashedPassword())
if err != nil {
return nil, err
}
passwordChangeRequired := req.GetPassword().GetChangeRequired() || req.GetHashedPassword().GetChangeRequired()
metadata := make([]*command.AddMetadataEntry, len(req.Metadata))
for i, metadataEntry := range req.Metadata {
metadata[i] = &command.AddMetadataEntry{
Key: metadataEntry.GetKey(),
Value: metadataEntry.GetValue(),
}
}
return &command.AddHuman{
ID: req.GetUserId(),
Username: username,
FirstName: req.GetProfile().GetFirstName(),
LastName: req.GetProfile().GetLastName(),
NickName: req.GetProfile().GetNickName(),
DisplayName: req.GetProfile().GetDisplayName(),
Email: command.Email{
Address: domain.EmailAddress(req.GetEmail().GetEmail()),
Verified: req.GetEmail().GetIsVerified(),
ReturnCode: req.GetEmail().GetReturnCode() != nil,
URLTemplate: urlTemplate,
},
PreferredLanguage: language.Make(req.GetProfile().GetPreferredLanguage()),
Gender: genderToDomain(req.GetProfile().GetGender()),
Phone: command.Phone{}, // TODO: add as soon as possible
Password: req.GetPassword().GetPassword(),
BcryptedPassword: bcryptedPassword,
PasswordChangeRequired: passwordChangeRequired,
Passwordless: false,
ExternalIDP: false,
Register: false,
Metadata: metadata,
}, nil
}
func genderToDomain(gender user.Gender) domain.Gender {
switch gender {
case user.Gender_GENDER_UNSPECIFIED:
return domain.GenderUnspecified
case user.Gender_GENDER_FEMALE:
return domain.GenderFemale
case user.Gender_GENDER_MALE:
return domain.GenderMale
case user.Gender_GENDER_DIVERSE:
return domain.GenderDiverse
default:
return domain.GenderUnspecified
}
}
func hashedPasswordToCommand(hashed *user.HashedPassword) (string, error) {
if hashed == nil {
return "", nil
}
// we currently only handle bcrypt
if hashed.GetAlgorithm() != "bcrypt" {
return "", errors.ThrowInvalidArgument(nil, "USER-JDk4t", "Errors.InvalidArgument")
}
return hashed.GetHash(), nil
}