mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-07 07:16:54 +00:00
feat: Add CreateInstance endpoint (#9452)
This commit is contained in:
15
internal/api/grpc/authn/v2beta/converter.go
Normal file
15
internal/api/grpc/authn/v2beta/converter.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package authn
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
authn "github.com/zitadel/zitadel/pkg/grpc/authn/v2beta"
|
||||
)
|
||||
|
||||
func KeyTypeToDomain(t authn.KeyType) domain.AuthNKeyType {
|
||||
switch t {
|
||||
case authn.KeyType_KEY_TYPE_JSON:
|
||||
return domain.AuthNKeyTypeJSON
|
||||
default:
|
||||
return domain.AuthNKeyTypeNONE
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/zitadel/cmd/build"
|
||||
authn "github.com/zitadel/zitadel/internal/api/grpc/authn/v2beta"
|
||||
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
z_oidc "github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func InstancesToPb(instances []*query.Instance) []*instance.Instance {
|
||||
@@ -109,3 +117,126 @@ func instanceQueryToModel(searchQuery *instance.Query) (query.SearchQuery, error
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INST-3m0se", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func CreateInstancePbToSetupInstance(req *instance.CreateInstanceRequest, defaultInstance command.InstanceSetup, externalDomain string) *command.InstanceSetup {
|
||||
instance := defaultInstance
|
||||
if req.InstanceName != "" {
|
||||
instance.InstanceName = req.InstanceName
|
||||
instance.Org.Name = req.InstanceName
|
||||
}
|
||||
if req.CustomDomain != "" {
|
||||
instance.CustomDomain = req.CustomDomain
|
||||
}
|
||||
if req.FirstOrgName != "" {
|
||||
instance.Org.Name = req.FirstOrgName
|
||||
}
|
||||
|
||||
if user := req.GetMachine(); user != nil {
|
||||
defaultMachine := instance.Org.Machine
|
||||
if defaultMachine == nil {
|
||||
defaultMachine = new(command.AddMachine)
|
||||
}
|
||||
|
||||
instance.Org.Machine = createInstancePbToAddMachine(user, *defaultMachine)
|
||||
instance.Org.Human = nil
|
||||
} else if user := req.GetHuman(); user != nil {
|
||||
defaultHuman := instance.Org.Human
|
||||
if instance.Org.Human != nil {
|
||||
defaultHuman = new(command.AddHuman)
|
||||
}
|
||||
|
||||
instance.Org.Human = createInstancePbToAddHuman(user, *defaultHuman, instance.DomainPolicy.UserLoginMustBeDomain, instance.Org.Name, externalDomain)
|
||||
instance.Org.Machine = nil
|
||||
}
|
||||
|
||||
if lang := language.Make(req.DefaultLanguage); !lang.IsRoot() {
|
||||
instance.DefaultLanguage = lang
|
||||
}
|
||||
|
||||
return &instance
|
||||
}
|
||||
|
||||
func createInstancePbToAddHuman(req *instance.CreateInstanceRequest_Human, defaultHuman command.AddHuman, userLoginMustBeDomain bool, org, externalDomain string) *command.AddHuman {
|
||||
user := defaultHuman
|
||||
if req.Email != nil {
|
||||
user.Email.Address = domain.EmailAddress(req.Email.Email)
|
||||
user.Email.Verified = req.Email.IsEmailVerified
|
||||
}
|
||||
if req.Profile != nil {
|
||||
if req.Profile.FirstName != "" {
|
||||
user.FirstName = req.Profile.FirstName
|
||||
}
|
||||
if req.Profile.LastName != "" {
|
||||
user.LastName = req.Profile.LastName
|
||||
}
|
||||
if req.Profile.PreferredLanguage != "" {
|
||||
lang, err := language.Parse(req.Profile.PreferredLanguage)
|
||||
if err == nil {
|
||||
user.PreferredLanguage = lang
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if default username is email style or else append @<orgname>.<custom-domain>
|
||||
// this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
||||
if !userLoginMustBeDomain && !strings.Contains(user.Username, "@") {
|
||||
orgDomain, _ := domain.NewIAMDomainName(org, externalDomain)
|
||||
user.Username = user.Username + "@" + orgDomain
|
||||
}
|
||||
if req.UserName != "" {
|
||||
user.Username = req.UserName
|
||||
}
|
||||
|
||||
if req.Password != nil {
|
||||
user.Password = req.Password.Password
|
||||
user.PasswordChangeRequired = req.Password.PasswordChangeRequired
|
||||
}
|
||||
return &user
|
||||
}
|
||||
|
||||
func createInstancePbToAddMachine(req *instance.CreateInstanceRequest_Machine, defaultMachine command.AddMachine) (machine *command.AddMachine) {
|
||||
machine = new(command.AddMachine)
|
||||
if defaultMachine.Machine != nil {
|
||||
machineCopy := *defaultMachine.Machine
|
||||
machine.Machine = &machineCopy
|
||||
} else {
|
||||
machine.Machine = new(command.Machine)
|
||||
}
|
||||
|
||||
if req.UserName != "" {
|
||||
machine.Machine.Username = req.UserName
|
||||
}
|
||||
if req.Name != "" {
|
||||
machine.Machine.Name = req.Name
|
||||
}
|
||||
|
||||
if defaultMachine.Pat != nil || req.PersonalAccessToken != nil {
|
||||
pat := command.AddPat{
|
||||
// Scopes are currently static and can not be overwritten
|
||||
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner},
|
||||
}
|
||||
if req.GetPersonalAccessToken().GetExpirationDate().IsValid() {
|
||||
pat.ExpirationDate = req.PersonalAccessToken.ExpirationDate.AsTime()
|
||||
} else if defaultMachine.Pat != nil && !defaultMachine.Pat.ExpirationDate.IsZero() {
|
||||
pat.ExpirationDate = defaultMachine.Pat.ExpirationDate
|
||||
}
|
||||
machine.Pat = &pat
|
||||
}
|
||||
|
||||
if defaultMachine.MachineKey != nil || req.MachineKey != nil {
|
||||
machineKey := command.AddMachineKey{}
|
||||
if defaultMachine.MachineKey != nil {
|
||||
machineKey = *defaultMachine.MachineKey
|
||||
}
|
||||
if req.MachineKey != nil {
|
||||
if req.MachineKey.Type != 0 {
|
||||
machineKey.Type = authn.KeyTypeToDomain(req.MachineKey.Type)
|
||||
}
|
||||
if req.MachineKey.ExpirationDate.IsValid() {
|
||||
machineKey.ExpirationDate = req.MachineKey.ExpirationDate.AsTime()
|
||||
}
|
||||
}
|
||||
machine.MachineKey = &machineKey
|
||||
}
|
||||
|
||||
return machine
|
||||
}
|
||||
|
||||
@@ -9,6 +9,28 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance/v2"
|
||||
)
|
||||
|
||||
func (s *Server) CreateInstance(ctx context.Context, req *instance.CreateInstanceRequest) (*instance.CreateInstanceResponse, error) {
|
||||
id, pat, key, details, err := s.command.SetUpInstance(ctx, CreateInstancePbToSetupInstance(req, s.defaultInstance, s.externalDomain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var machineKey []byte
|
||||
if key != nil {
|
||||
machineKey, err = key.Detail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &instance.CreateInstanceResponse{
|
||||
Pat: pat,
|
||||
MachineKey: machineKey,
|
||||
InstanceId: id,
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteInstance(ctx context.Context, request *instance.DeleteInstanceRequest) (*instance.DeleteInstanceResponse, error) {
|
||||
instanceID := strings.TrimSpace(request.GetInstanceId())
|
||||
if err := validateParam(instanceID, "instance_id"); err != nil {
|
||||
|
||||
@@ -20,6 +20,8 @@ type Server struct {
|
||||
query *query.Queries
|
||||
checkPermission domain.PermissionCheck
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
defaultInstance command.InstanceSetup
|
||||
externalDomain string
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
@@ -29,12 +31,16 @@ func CreateServer(
|
||||
query *query.Queries,
|
||||
checkPermission domain.PermissionCheck,
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
defaultInstance command.InstanceSetup,
|
||||
externalDomain string,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
checkPermission: checkPermission,
|
||||
systemDefaults: systemDefaults,
|
||||
defaultInstance: defaultInstance,
|
||||
externalDomain: externalDomain,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
35
proto/zitadel/authn/v2beta/authn.proto
Normal file
35
proto/zitadel/authn/v2beta/authn.proto
Normal file
@@ -0,0 +1,35 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "zitadel/object/v2/object.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
|
||||
package zitadel.authn.v2beta;
|
||||
|
||||
option go_package ="github.com/zitadel/zitadel/pkg/grpc/authn/v2beta;authn";
|
||||
|
||||
message Key {
|
||||
string id = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"69629023906488334\"";
|
||||
}
|
||||
];
|
||||
zitadel.object.v2.Details details = 2;
|
||||
KeyType type = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"KEY_TYPE_JSON\"";
|
||||
description: "the file type of the key";
|
||||
}
|
||||
];
|
||||
google.protobuf.Timestamp expiration_date = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "the date a key will expire";
|
||||
example: "\"3019-04-01T08:45:00.000000Z\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
enum KeyType {
|
||||
KEY_TYPE_UNSPECIFIED = 0;
|
||||
KEY_TYPE_JSON = 1;
|
||||
}
|
||||
@@ -6,10 +6,12 @@ import "validate/validate.proto";
|
||||
import "zitadel/object/v2/object.proto";
|
||||
import "zitadel/instance/v2/instance.proto";
|
||||
import "zitadel/filter/v2beta/filter.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "zitadel/authn/v2beta/authn.proto";
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "google/api/field_behavior.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||
|
||||
option go_package = "github.com/zitadel/zitadel/pkg/grpc/instance/v2;instance";
|
||||
@@ -102,6 +104,72 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
}
|
||||
};
|
||||
|
||||
message CreateInstanceRequest {
|
||||
message Profile {
|
||||
string first_name = 1 [(validate.rules).string = {max_len: 200}];
|
||||
string last_name = 2 [(validate.rules).string = {max_len: 200}];
|
||||
string preferred_language = 3 [(validate.rules).string = {max_len: 10}];
|
||||
}
|
||||
message Email {
|
||||
string email = 1[(validate.rules).string = {min_len: 1, max_len: 200, email: true}];
|
||||
bool is_email_verified = 2;
|
||||
}
|
||||
message Password {
|
||||
string password = 1 [(validate.rules).string = {max_len: 200}];
|
||||
bool password_change_required = 2;
|
||||
}
|
||||
message Human {
|
||||
string user_name = 1 [(validate.rules).string = {max_len: 200}];
|
||||
Email email = 2 [(validate.rules).message.required = true];
|
||||
Profile profile = 3 [(validate.rules).message.required = false];
|
||||
Password password = 4 [(validate.rules).message.required = false];
|
||||
}
|
||||
message PersonalAccessToken {
|
||||
google.protobuf.Timestamp expiration_date = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2519-04-01T08:45:00.000000Z\"";
|
||||
description: "The date the token will expire and no logins will be possible";
|
||||
}
|
||||
];
|
||||
}
|
||||
message MachineKey {
|
||||
zitadel.authn.v2beta.KeyType type = 1 [(validate.rules).enum = {defined_only: true, not_in: [0]}];
|
||||
google.protobuf.Timestamp expiration_date = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2519-04-01T08:45:00.000000Z\"";
|
||||
description: "The date the key will expire and no logins will be possible";
|
||||
}
|
||||
];
|
||||
}
|
||||
message Machine {
|
||||
string user_name = 1 [(validate.rules).string = {max_len: 200}];
|
||||
string name = 2 [(validate.rules).string = {max_len: 200}];
|
||||
PersonalAccessToken personal_access_token = 3;
|
||||
MachineKey machine_key = 4;
|
||||
}
|
||||
|
||||
string instance_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string first_org_name = 2 [(validate.rules).string = {max_len: 200}];
|
||||
string custom_domain = 3 [(validate.rules).string = {max_len: 200}];
|
||||
|
||||
oneof owner {
|
||||
option (validate.required) = true;
|
||||
|
||||
// oneof field for the user managing the instance
|
||||
Human human = 4;
|
||||
Machine machine = 5;
|
||||
}
|
||||
|
||||
string default_language = 6 [(validate.rules).string = {max_len: 10}];
|
||||
}
|
||||
|
||||
message CreateInstanceResponse {
|
||||
string instance_id = 1;
|
||||
zitadel.object.v2.Details details = 2;
|
||||
string pat = 3;
|
||||
bytes machine_key = 4;
|
||||
}
|
||||
|
||||
message DeleteInstanceRequest {
|
||||
string instance_id = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
@@ -164,6 +232,36 @@ message ListInstancesResponse {
|
||||
|
||||
service InstanceService {
|
||||
|
||||
// CreateInstance creates a new instance
|
||||
rpc CreateInstance(CreateInstanceRequest) returns (CreateInstanceResponse) {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
description: "Creates a new instance";
|
||||
tags: "Instance";
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "The created instance.";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "#/definitions/CreateInstanceResponse";
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
option (google.api.http) = {
|
||||
post: "/v2/instances"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "system.instance.write"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// DeleteInstance deletes an instance with the given ID.
|
||||
rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse) {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
|
||||
Reference in New Issue
Block a user